django-controlcenter

django-controlcenter



皆さん、こんにちは、私の小さな開発-django-controlcenterを共有したいと思います。 これは、djangoプロジェクトのダッシュボードを作成するためのアプリケーションです。



目的



Django-adminは優れたCRUDの例であり、非常に便利なアプリケーションです。 モデルを接続すると、データベース内のすべてのエントリを含むプレートが表示されます。 次に、2番目を取り込み、3番目を取り込みます。 時間が経つにつれて、注文、コメント、リクエスト、レビューなどのこれらの兆候が多く発生し、1日に数回それらすべての間を行き来し始めます。 また、時にはあらゆる種類のグラフが必要な場合があります。



Django-controlcenterが登場したのは、いくつかのモデルで定期的に新しいレコードをチェックし、それらを無視するか、変更または削除してグラフのダイナミクスを確認する必要があるときだけでした。



免責事項



現在のバージョンはajaxを使用せず、実際にはCRUDでさえなく、 Readのみですが、高度な機能を備えています。



簡単な例



小さな例から始めましょう:



# project/dashboard.py from controlcenter import Dashboard, widgets from project.app.models import Model class ModelItemList(widgets.ItemList): model = Model list_display = ['pk', 'field'] class MyDashboard(Dashboard): widgets = ( ModelItemList, ) # project/settings.py CONTROLCENTER_DASHBOARDS = [ 'project.dashboards.MyDashboard' ]
      
      





このウィジェットは、最後の10個の値を持つ2つの列にラベルを表示します(デフォルトでItemList



、ページが破損しないように、 ItemList



出力ItemList



制限されています)。



アイテムリスト



使い慣れた用語を使用しました。 一般に、ウィジェットは、メソッドと属性の命名の観点から、 ViewsModelAdminの混合物であり、それらの動作が異なります。



 class ModelItemList(widgets.ItemList): model = Model queryset = model.active_objects.all() list_display = ('pk', 'field', 'get_foo') list_display_links = ('field', 'get_foo') template_name = 'my_custom_template.html' def get_foo(self, obj): return 'foo' get_foo.allow_tags = True get_foo.short_description = 'Foo!'
      
      





ご覧のとおり、新しいものはありません。 まだ



免責事項



ドキュメントは実際にはさらに進むので、サンプルを解析したい場合は、それらに直接進んでください。



ウィジェット



主なウィジェットは、 Widget



ItemList



Chart



3つだけです。 まだGroup



がありますが、これはウィジェットではなくラッパーです。 彼から始めましょう。



団体



ウィジェットはグループにまとめることができ、タイトルをクリックして切り替えることができます。 グループ化の場合、ウィジェットはリスト/カートリッジで示されるか、特別なラッパーが使用されます- Group







 class MyDashboard(Dashboard): widgets = ( Foo, (Bar, Baz), Group((Egg, Spam), width=widgets.LARGE, height=300, attrs={'class': 'my_class', 'data-foo': 'foo'}), )
      
      





Group



は、 width



height



attrs



3つのオプション引数を取ります。

重要なポイント:このような「複合」ウィジェットは、グループ内の「最高」の高さを取得します。これは、設計が適応性があり、 石積みを使用しているためです。



Group.width



ダッシュボードグリッドは適応性があります。最大768px



ウィジェットが幅全体を50%



、その後50%



または100%



ます。 1000px



からは、6列のグリッドが使用されます。 便宜上、値はwidgets



モジュールに保存されます。



 # controlcenter/widgets.py MEDIUM = 2 # 33%  [x] + [x] + [x] LARGE = 3 # 50%  [ x ] + [ x ] LARGER = 4 # 66%  [ x ] + [x] LARGEST = 6 # 100%  [ x ]
      
      





中間値は特に有用ではありませんが、それらを使用することを禁止する人はいません。



Group.height



最初はNone



ですが、インタガーを受け取ると、ウィジェットをmax-height



としてこの値に公開し、オプションのスクロールが表示されます。

ウィジェットにはwidth



height



もあり、これらの値がGroup



で指定されていない場合、このグループのウィジェットの最大値が取得されます。



Group.attrs



html



属性としてウィジェットに入力するすべてのもの。 id



設定することもできます。



ウィジェット



基本的なウィジェット。 ほとんど何もできません。 ただし、ユーティリティが1つあります。作成時に、 values



メソッド(およびチャートのseries



labels



legend



)をcached_property記述子にラップします。 したがって、属性にアクセスするとき(呼び出しなし)に値を使用でき、データがキャッシュされます。 これらの方法に頻繁に頼らなければならないので、これは少し便利です。 たとえば、チャートの場合、次のようなことが行われます。



 def labels(self): return [x for x, y in self.values] def series(self): return [y for x, y in self.values] def values(self): return self.get_queryset().values_list('label', 'series')
      
      





これはテンプレートで12回要求されますので、すぐにすべてをキャッシュすることをお勧めします。



Widget.title



ウィジェットのタイトル。 指定しない場合、クラス名から形成されます。



Widget.widthおよびWidget.height



動作はGroup



似ています(上記を参照)。



Widget.model



django.db.models.Modelを受け入れます。



Widget.get_queryset



django.generic.viewsと同様の動作:





Widget.valuesおよびWidget.limit_to



get_queryset



呼び出しget_queryset





したがって、「どこかに」データがある場合、このメソッドを書き換えてget_queryset



を忘れます。 少なくともファイルから読み取ります。 また、self.get_queryset self.get_queryset()[:self.limit_to]



ように、 self.get_queryset()[:self.limit_to]



None



でない場合、limit_toに制限します。



Widget.template_name_prefix



テンプレートのあるディレクトリ。



Widget.template_name



テンプレートの名前。



Widget.get_template_name



Widget.template_name_prefix



+ Widget.template_name



返します。



アイテムリスト



これは最も複雑なウィジェットであると同時に最も簡単なウィジェットです。 単純です。モデル、辞書、シート、 namedtupleなど問題なくすべてを噛むことができます-反復に適しているか、キー/属性でアクセスできるすべてです。 ただし、機能があります。



 class ModelItemList(widgets.ItemList): model = Model queryset = model.objects.all() list_display = ['pk', 'field']
      
      





ItemList.list_display



テンプレートのレンダリング中、要素からvalues



へのvalues



list_display



(モデル、辞書、namedtupleの場合)のキーによって取得され、シーケンスの場合、キーインデックスは大まかに言えばzip(list_display, values)



の値インデックスに等しくなりzip(list_display, values)







行番号


#



list_display



追加し、行番号を取得します。 settings.CONTROLCENTER_SHARP



値として設定することにより、「ポンド」を別の文字に置き換えることもできsettings.CONTROLCENTER_SHARP







ItemList.list_display_links



動作は、djangoのlist_display_linksに似ています。



オブジェクトを編集するためのリンク



ItemList



、管理パネルにオブジェクトの編集ページへのリンクを配置しようとしています。これを行うには、オブジェクトクラスと主キーが必要です。 したがって、ウィジェットはどこでもこのデータを検索しvalues



がモデルインスタンスを返す場合、ウィジェットはすべてを取り出します。 values



がディクショナリ、リストまたはnamedtupleを返す場合、もちろんどこも存在しないため、 ItemList.model



を指定する必要があります。 すべての場合において、ウィジェットはそれ自身でpk



またはid



を見つけようとしますが、シーケンスの場合、これは機能しません。そのため、ウィジェットはインデックスをシーケンス値のインデックスと比較することlist_display



でこれらのキーをlist_display



ます。

ところで、ウィジェットはdeferred



モデルを理解するので、次のように書くことができます: queryset = Model.obejcts.defer('field')





この機能を機能させるには、モデルをdjango-adminに登録する必要があります。



モデルチェンジリストへのリンク



時々、最後の10個の値を見てモデルページに移動するだけでは不十分です。 ModelAdmin



は、そのようなパスを独自に構築します。 ただし、ウィジェットではqueryset



内の任意のものに置き換えることができるため、支援する必要があります。 いくつかのオプションがあります:



 class ModelItemList(widgets.ItemList): model = Model #    changelist_url = model #   ,      changelist_url = model, {'status__exact': 0, 'o': '-7.-1'} #      changelist_url = model, 'status__exact=0&o=-7.-1' #   changelist_url = '/admin/model/' changelist_url = 'http://www.yandex.ru'
      
      





この機能を機能させるには、モデルをdjango-adminに登録する必要があります。



ItemList.sortable



プレートを並べ替えるには、sortable sortable=True



を指定するだけで十分ですが、jungaはデータベースで並べ替えられ、ウィジェットはクライアント側にあるため、たとえば列の日付がdd.mm



の形式である場合にインシデントが発生する可能性がありますdd.mm



ライブラリはsortable.jsです。



ItemList.method.allow_tagsおよびItemList.method.short_description



動作はDjangianのallow_tagsおよびshort_descriptionに似ています。



ItemList.empty_message



values



が空のリストを返す場合、この値を出力します。



ItemList.limit_to



デフォルト値は10



ため、自分で脚を撃たないようにします。



チャート



Chartistはグラフに使用されます-これは、独自の特性を持つ小さなライブラリです。 彼女は非常に速く、ただただ、私は通り過ぎることができませんでした。



グラフには、 LINE



BAR



PIE



3種類があります。 および対応するクラス: LineChart



BarChart



PieChart



。 加えて、いくつかの追加のもの、それについては後で詳しく説明します。



Chart



は、 legend



lables



series



3つの追加メソッドを定義します。これらもキャッシュされます。 3つのメソッドはすべて、ジェネレーターを含まないjsonシリアライズ可能オブジェクトを返す必要があります。



 class MyChart(widgets.Chart): def legend(self): return [] def labels(self): return [] def series(self): return []
      
      





Chart.legend



チャーティストはそのままでは凡例を表示できませんが、 チャーティストはチャートに値を描画しないため、凡例は表示されません(そういう瞬間があります)。 このような場合には、凡例が役立ちます。



Chart.labels



x



軸の値。 シーケンスを返す必要があります。決してジェネレーターを渡さないでください。



Chart.series



y



軸の値。 プロットには複数のデータを含めることができるため、リストのリストを返す必要があります。 繰り返しますが、ジェネレータはありません。 ここには小さな「落とし穴」があります。1つのタイプの値を持つBAR



タイプの場合、「フラット」リストが送信されます。 入れ子ではなく、これはchartartの追加オプションを設定します。 SingleBarChart



を使用する最も簡単な方法は、すべてが設定されていることです。



Chart.Chartist



Chart



は、ジャングル内のMeta



またはMedia



方法で追加のChartist



クラスを持つウィジェットです。



 class MyChart(Chart): class Chartist: klass = widgets.LINE point_lables = True options = { 'reverseData': True, 'axisY': { 'onlyInteger': True, }, 'fullWidth': True, }
      
      





唯一の違いは、 Chartist



を使用する場合、親クラスを継承する必要がないChartist



です。 古典的なpython継承とはclass Chartist(Parent.Chartist):



ます。 class Chartist(Parent.Chartist):



ではなくclass Chartist(Parent.Chartist):



class Chartist:



を作成しますclass Chartist(Parent.Chartist):



フィールドは自動的に継承されます。 継承クラスでは、親に接着されているoptions



を除き、すべてのフィールドが書き込まれoptions



Parent.Chartist.options.copy().update({'foo': 'bar'})



ではなく、新しいキー/値のペアのみを子クラスに書き込むことができます。 もちろん、この方法には欠点があります。デフォルト値は、必要に応じて書き換える必要があります。



重要! LineChart



'reverseData': True



に設定され、クライアントのlabels



series



値を反転します。 ほとんどの場合、このタイプのチャートは最新のデータを表示するために使用されるため、最初のチャートごとに手動でこれを行う必要はありません。このオプションはデフォルトで有効になっています。



Chart.Chartist.klass


チャートのタイプを決定します: widgets.LINE



widgets.BAR



widgets.PIE







Chart.Chartist.point_lables


プラグインはChartist



に接続され、 Chartist



は値をチャートに配置します。 これは奇妙ですが、デフォルトのチャート作成者はチャート自体の値なしで実行します。 残念ながら、このことはwidgets.LINE



のみ機能しwidgets.LINE



。 他の場合には、 legend



メソッドが役立ちます。



Chart.Chartist.options


完全にjsonに送信され、チャーティスト構造に送信される辞書。 すべてのオプションはサイトで説明されています



追加のクラス



widgets



モジュールでは、さらにいくつかの補助クラスがSingleLineChart



ます: SingleLineChart



SingleBarChart



SinglePieChart



単純なユーザーの場合。



 class BlogsChart(widgets.SingleBarChart): model = Blog values_list = ('name', 'score')
      
      





まあ、sobsno、それがすべてです。 name



の値はx



軸に移動し、 y



軸にscore



します。



ダッシュボード



アプリケーションは、最大10個の「パネル」をサポートします。これらは、 /admin/dashboards/[pk]/



pk



settings.CONTROLCENTER_DASHBOARDS



リストのインデックスです。



Dashboard.widgets



ウィジェットのリストを受け入れます。



Dashboard.title



カスタムヘッダー。 指定しない場合、クラス名から形成されます。



Dashboard.Media



jungaのメディアクラス。



設定



 #   CONTROLCENTER_DASHBOARDS = [] #      `ItemList` CONTROLCENTER_SHARP = '#' #   .    `Chartist`, #        `Material Design`, #  `material`. CONTROLCENTER_CHARTIST_COLORS = 'default'
      
      





例!



スクリーンショットと同じことをしましょう。

プロジェクトを作成し、それをpizzeria



と呼び、それにpizza



アプリケーションを追加します。



pizzeria.pizza.models



 from __future__ import unicode_literals from django.db import models class Pizza(models.Model): name = models.CharField(max_length=100, unique=True) def __str__(self): return self.name class Restaurant(models.Model): name = models.CharField(max_length=100, unique=True) menu = models.ManyToManyField(Pizza, related_name='restaurants') def __str__(self): return self.name class Order(models.Model): created = models.DateTimeField(auto_now_add=True) restaurant = models.ForeignKey(Restaurant, related_name='orders') pizza = models.ForeignKey(Pizza, related_name='orders')
      
      





設置



 pip install django-controlcenter
      
      





アプリをpizzeria.settings



ます



 INSTALLED_APPS = ( ... 'controlcenter', 'pizza', ) #   CONTROLCENTER_DASHBOARDS = ( 'pizzeria.dashboards.MyDashboard' )
      
      





pizzeria.urls



pizzeria.urls



追加しpizzeria.urls







 from django.conf.urls import url from django.contrib import admin from controlcenter.views import controlcenter urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^admin/dashboard/', controlcenter.urls), ]
      
      





ウィジェット



pizzeria.dashboards



ファイルでpizzeria.dashboards



ウィジェットを作成します。



 import datetime from collections import defaultdict from controlcenter import app_settings, Dashboard, widgets from controlcenter.widgets.core import WidgetMeta from django.db.models import Count from django.utils import timezone from django.utils.timesince import timesince from .pizza.models import Order, Pizza, Restaurant class MenuWidget(widgets.ItemList): #     ,   #    .     #  ,      . model = Pizza list_display = ['name', 'ocount'] list_display_links = ['name'] # -,  ItemList  , #          . limit_to = None #     300,   height = 300 def get_queryset(self): #         restaurant = super(MenuWidget, self).get_queryset().get() today = timezone.now().date() return (restaurant.menu .filter(orders__created__gte=today) .order_by('-ocount') .annotate(ocount=Count('orders'))) class LatestOrdersWidget(widgets.ItemList): #    20  #    model = Order queryset = (model.objects .select_related('pizza') .filter(created__gte=timezone.now().date()) .order_by('pk')) #  `#`    list_display = [app_settings.SHARP, 'pk', 'pizza', 'ago'] list_display_links = ['pk'] #        sortable = True #   20 limit_to = 20 #     height = 300 #   def ago(self, obj): return timesince(obj.created) RESTAURANTS = [ 'Mama', 'Ciao', 'Sicilia', ] #  -,   . # , ,       . # ,    : #  ,  ,  menu_widgets = [WidgetMeta('{}MenuWidget'.format(name), (MenuWidget,), {'queryset': Restaurant.objects.filter(name=name), #   'title': name + ' menu', #   `changelist`   GET  'changelist_url': ( Pizza, {'restaurants__name__exact': name})}) for name in RESTAURANTS] latest_orders_widget = [WidgetMeta( '{}LatestOrders'.format(name), (LatestOrdersWidget,), {'queryset': (LatestOrdersWidget .queryset .filter(restaurant__name=name)), 'title': name + ' orders', 'changelist_url': ( Order, {'restaurant__name__exact': name})}) for name in RESTAURANTS] class RestaurantSingleBarChart(widgets.SingleBarChart): #  -    title = 'Most popular restaurant' model = Restaurant class Chartist: options = { # -, Chartist   # float   ,     'onlyInteger': True, #    --  'chartPadding': { 'top': 24, 'right': 0, 'bottom': 0, 'left': 0, } } def legend(self): #      `y`, # , Chartist       return self.series def values(self): queryset = self.get_queryset() return (queryset.values_list('name') .annotate(baked=Count('orders')) .order_by('-baked')[:self.limit_to]) class PizzaSingleBarChart(RestaurantSingleBarChart): #   , , #     ,    model = Pizza limit_to = 3 title = 'Most popular pizza' class Chartist: #    klass = widgets.PIE class OrderLineChart(widgets.LineChart): #      #   7  title = 'Orders this week' model = Order limit_to = 7 #    width = widgets.LARGER class Chartist: #   --  options = { 'axisX': { 'labelOffset': { 'x': -24, 'y': 0 }, }, 'chartPadding': { 'top': 24, 'right': 24, } } def legend(self): #      return RESTAURANTS def labels(self): #   `x`  today = timezone.now().date() labels = [(today - datetime.timedelta(days=x)).strftime('%d.%m') for x in range(self.limit_to)] return labels def series(self): #     `labels`,    ,    #   , ,  -   #       series = [] for restaurant in self.legend: #   ,     #   ,    0 item = self.values.get(restaurant, {}) series.append([item.get(label, 0) for label in self.labels]) return series def values(self): #      limit_to = self.limit_to * len(self.legend) queryset = self.get_queryset() #       `GROUP BY`   : #    . # Order.created  datetime,     , #   `DATE` (sqlite3)  . #  , ORM   ,   #       queryset = (queryset.extra({'baked': 'DATE(created)'}) .select_related('restaurant') .values_list('restaurant__name', 'baked') .order_by('-baked') .annotate(ocount=Count('pk'))[:limit_to]) #  -- ,  --  :_ values = defaultdict(dict) for restaurant, date, count in queryset: # DATE  Sqlite3   YYYY-MM-DD #       DD-MM day_month = '{2}.{1}'.format(*date.split('-')) values[restaurant][day_month] = count return values
      
      





デシャード



django-controlcenterは、最大10個のカードをサポートします。 しかし、 pizzeria.dashboards



作成します



 class SimpleDashboard(Dashboard): widgets = ( menu_widgets, latest_orders_widget, RestaurantSingleBarChart, PizzaSingleBarChart, OrderLineChart, )
      
      





それがすべて、open /admin/dashboard/0/



です。



互換性



テストはpython 2.7.9、3.4.3、3.5.0およびdjango 1.8、1.9で実施されました。



 Name Stmts Miss Cover ---------------------------------------------------------------------- controlcenter/__init__.py 1 0 100% controlcenter/app_settings.py 27 0 100% controlcenter/base.py 10 0 100% controlcenter/dashboards.py 27 0 100% controlcenter/templatetags/__init__.py 0 0 100% controlcenter/templatetags/controlcenter_tags.py 109 0 100% controlcenter/utils.py 16 0 100% controlcenter/views.py 39 0 100% controlcenter/widgets/__init__.py 2 0 100% controlcenter/widgets/charts.py 67 0 100% controlcenter/widgets/core.py 93 0 100% ---------------------------------------------------------------------- TOTAL 391 0 100% _______________________________ summary ______________________________ py27-django18: commands succeeded py27-django19: commands succeeded py34-django18: commands succeeded py34-django19: commands succeeded py35-django18: commands succeeded py35-django19: commands succeeded
      
      





このアプリはdjango-grappelliとの素晴らしい友達でもあります。



ドキュメント



この記事はそのように考えることができます。スフィンクスに対処したらすぐに、私は近い将来に不器用な英語を翻訳します。



PS最初にOSPに参加することを決めました。コード自体よりも配布手順に多くの時間を費やしましたが、すべてが正しく行われたかどうかは完全にはわかりませんので、フィードバックに感謝します。



PPSヘッダーをテキストと区別できず、インラインコードが目立たないという事実をhabrデザイナーに感謝します。 記事を読むことができないので、できるだけ早くドックを書くようにします。




All Articles