皆さん、こんにちは、私の小さな開発-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
制限されています)。
使い慣れた用語を使用しました。 一般に、ウィジェットは、メソッドと属性の命名の観点から、 ViewsとModelAdminの混合物であり、それらの動作が異なります。
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と同様の動作:
-
queryset
がある場合、それを返します。 -
model
がある場合、デフォルトのマネージャーを返します。
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デザイナーに感謝します。 記事を読むことができないので、できるだけ早くドックを書くようにします。