# 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' ]
ItemList
limited in output so as not to tear the page down to you). 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
and Chart
. There is also a Group
, but this is not a widget, but a wrapper. Let's start with it.Group
. class MyDashboard(Dashboard): widgets = ( Foo, (Bar, Baz), Group((Egg, Spam), width=widgets.LARGE, height=300, attrs={'class': 'my_class', 'data-foo': 'foo'}), )
Group
takes three optional arguments: width
, height
, attrs
.768px
widgets occupy the entire width, then 50%
or 100%
. From 1000px
used 6-column grid. For convenience, the values are stored in the widgets
module: # controlcenter/widgets.py MEDIUM = 2 # 33% [x] + [x] + [x] LARGE = 3 # 50% [ x ] + [ x ] LARGER = 4 # 66% [ x ] + [x] LARGEST = 6 # 100% [ x ]
None
, but having received an integer, set this value to the widget as max-height
and an optional scroll appears.width
and height
, in case these values are not specified in the Group
, the maximum value is taken from the widgets in this group.html
attribute. You can even set the id
.values
method (and series
, labels
, legend
for charts) in the cached_property descriptor. Accordingly, values are available as when accessing an attribute (without calling), and data is cached. This is just a small convenience, as you often need to refer to these methods. For example, the following is done for charts: 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')
Group
(see above).queryset
, it will return.model
, it will return its default manager.get_queryset
.get_queryset
. At least from the file read. Also limits the queriset to limit_to
if it is not None
, like this: self.get_queryset()[:self.limit_to]
.Widget.template_name_prefix
+ Widget.template_name
. class ModelItemList(widgets.ItemList): model = Model queryset = model.objects.all() list_display = ['pk', 'field']
values
are taken from the keys of list_display
(for models, dictionaries and namedtuple), for sequences the key index is equal to the value index, roughly speaking zip(list_display, values)
.#
to list_display
and get line numbering. Also, the "grid" can be replaced with another symbol by setting it as a value in settings.CONTROLCENTER_SHARP
.ItemList
tries to hang a link to the object editing page in the admin panel, for this it needs an object class and a primary key. Therefore, the widget will search for this data everywhere: if values
return the model instance, it will pull everything out of it. If values
returns a dictionary, a list or a namedtuple , then you will need to specify ItemList.model
, because, of course, no longer where. In all cases, the widget will try to find pk
or id
its own, but in the case of sequences, this will not work, so the widget will look for these keys in list_display
matching its index with the index of sequence values.deferred
models, so you can write like this: queryset = Model.obejcts.defer('field')
.ModelAdmin
builds such paths on its own. But in the widget you can substitute anything in the queryset
, so you have to help. There are several options: 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'
sortable=True
, but remember that janga sorts in the database, and the widget on the client side, so incidents can occur, for example, if the dates in the column are in the dd.mm
format. The sortable.js library is used .values
returns an empty list.10
, so you don't shoot yourself in the leg.LINE
, BAR
, PIE
; and their respective classes: LineChart
, BarChart
, PieChart
. Plus a few extras, more on that later.Chart
defines three additional methods: legend
, lables
, series
, which are also cached. All three methods must return a json-serializable object that does not include generators. class MyChart(widgets.Chart): def legend(self): return [] def labels(self): return [] def series(self): return []
x
axis. Should return a sequence, in no case do not pass the generator.y
axis. Must return a list of lists, since there may be multiple data in the graphs. Again, no generators. There is a small “Gotcha” here, for the type BAR
with one type of value, the “flat” list is transferred, i.e. not nested, this sets an additional option for the Chartist. The easiest way to use SingleBarChart
- everything is set up in it.Chart
is a widget with an additional Chartist
class inside in Meta
or Media
style in jang. class MyChart(Chart): class Chartist: klass = widgets.LINE point_lables = True options = { 'reverseData': True, 'axisY': { 'onlyInteger': True, }, 'fullWidth': True, }
Chartist
not necessary to inherit the parent class, i.e. this is not the classic python inheritance: you write class Chartist:
not class Chartist(Parent.Chartist):
- fields are inherited automatically. In the inheriting class, all fields are rewritten, except options
, which is glued to the parent, i.e. in the child class, you can write only new key / value pairs, not Parent.Chartist.options.copy().update({'foo': 'bar'})
. Of course, this method has a downside: the default values, if necessary, will have to be rewritten.LineChart
set to 'reverseData': True
, which reverses the values of labels
and series
on the client. Most often, this type of charts is used to display the latest data, and so that you do not have to do it manually in every first chart, this option is enabled by default.widgets.LINE
, widgets.BAR
, widgets.PIE
.Chartist
, which Chartist
the values on the chart. This is strange, but the default Chartist does without values on the chart itself. Unfortunately, this thing only works with widgets.LINE
. In other cases, the legend
method will help.widgets
module, several more auxiliary classes are SingleLineChart
: SingleLineChart
, SingleBarChart
, SinglePieChart
- for simple use cases. class BlogsChart(widgets.SingleBarChart): model = Blog values_list = ('name', 'score')
name
values will go to the x
axis, and the score
to the y
axis./admin/dashboards/[pk]/
- where pk
index in the list of settings.CONTROLCENTER_DASHBOARDS
. # CONTROLCENTER_DASHBOARDS = [] # `ItemList` CONTROLCENTER_SHARP = '#' # . `Chartist`, # `Material Design`, # `material`. CONTROLCENTER_CHARTIST_COLORS = 'default'
pizzeria
, add the pizza
application to it. 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
applications to pizzeria.settings
INSTALLED_APPS = ( ... 'controlcenter', 'pizza', ) # CONTROLCENTER_DASHBOARDS = ( 'pizzeria.dashboards.MyDashboard' )
pizzeria.urls
to 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
file pizzeria.dashboards
create widgets: 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
pizzeria.dashboards
class SimpleDashboard(Dashboard): widgets = ( menu_widgets, latest_orders_widget, RestaurantSingleBarChart, PizzaSingleBarChart, OrderLineChart, )
/admin/dashboard/0/
. 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
Source: https://habr.com/ru/post/278743/
All Articles