Source code for xadmin.plugins.chart

"""
图表插件
=========

功能
----

在数据列表页面, 跟列表数据生成图表. 可以指定多个数据列, 生成多个图表.

截图
----

.. image:: /images/plugins/chart.png

使用
----

在 Model OptionClass 中设定 ``data_charts`` 属性, 该属性为 dict 类型, key 是图表的标示名称, value 是图表的具体设置属性. 使用示例::

    class RecordAdmin(object):
        data_charts = {
            "user_count": {'title': u"User Report", "x-field": "date", "y-field": ("user_count", "view_count"), "order": ('date',)},
            "avg_count": {'title': u"Avg Report", "x-field": "date", "y-field": ('avg_count',), "order": ('date',)}
        }

图表的主要属性为:

    ``title`` : 图表的显示名称

    ``x-field`` : 图表的 X 轴数据列, 一般是日期, 时间等

    ``y-field`` : 图表的 Y 轴数据列, 该项是一个 list, 可以同时设定多个列, 这样多个列的数据会在同一个图表中显示

    ``order`` : 排序信息, 如果不写则使用数据列表的排序

版本
----

暂无

API
---
.. autoclass:: ChartsPlugin
.. autoclass:: ChartsView

"""

import calendar
import datetime
import decimal

from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.http import HttpResponse, HttpResponseNotFound
from django.template import loader
from django.utils.http import urlencode
from django.utils.encoding import force_text, smart_text
from django.utils.translation import ugettext_lazy as _, ugettext

from xadmin.plugins.utils import get_context_dict
from xadmin.sites import site
from xadmin.views import BaseAdminPlugin, ListAdminView
from xadmin.views.dashboard import ModelBaseWidget, widget_manager
from xadmin.util import lookup_field, label_for_field, json


@widget_manager.register
class ChartWidget(ModelBaseWidget):
    widget_type = 'chart'
    description = _('Show models simple chart.')
    template = 'xadmin/widgets/chart.html'
    widget_icon = 'fa fa-bar-chart-o'

    def convert(self, data):
        self.list_params = data.pop('params', {})
        self.chart = data.pop('chart', None)

    def setup(self):
        super(ChartWidget, self).setup()

        self.charts = {}
        self.one_chart = False
        model_admin = self.admin_site._registry[self.model]
        chart = self.chart

        if hasattr(model_admin, 'data_charts'):
            if chart and chart in model_admin.data_charts:
                self.charts = {chart: model_admin.data_charts[chart]}
                self.one_chart = True
                if self.title is None:
                    self.title = model_admin.data_charts[chart].get('title')
            else:
                self.charts = model_admin.data_charts
                if self.title is None:
                    self.title = ugettext(
                        "%s Charts") % self.model._meta.verbose_name_plural

    def filte_choices_model(self, model, modeladmin):
        return bool(getattr(modeladmin, 'data_charts', None)) and \
               super(ChartWidget, self).filte_choices_model(model, modeladmin)

    def get_chart_url(self, name, v):
        return self.model_admin_url('chart', name) + "?" + urlencode(self.list_params)

    def context(self, context):
        context.update({
            'charts': [{"name": name, "title": v['title'], 'url': self.get_chart_url(name, v)} for name, v in
                       self.charts.items()],
        })

    # Media
    def media(self):
        return self.vendor('flot.js', 'xadmin.plugin.charts.js')


class JSONEncoder(DjangoJSONEncoder):
    def default(self, o):
        if isinstance(o, (datetime.date, datetime.datetime)):
            return calendar.timegm(o.timetuple()) * 1000
        elif isinstance(o, decimal.Decimal):
            return str(o)
        else:
            try:
                return super(JSONEncoder, self).default(o)
            except Exception:
                return smart_text(o)


[docs]class ChartsPlugin(BaseAdminPlugin): data_charts = {} def init_request(self, *args, **kwargs): return bool(self.data_charts) def get_chart_url(self, name, v): return self.admin_view.model_admin_url('chart', name) + self.admin_view.get_query_string() # Media def get_media(self, media): return media + self.vendor('flot.js', 'xadmin.plugin.charts.js') # Block Views def block_results_top(self, context, nodes): context.update({ 'charts': [{"name": name, "title": v['title'], 'url': self.get_chart_url(name, v)} for name, v in self.data_charts.items()], }) nodes.append(loader.render_to_string('xadmin/blocks/model_list.results_top.charts.html', context=get_context_dict(context)))
[docs]class ChartsView(ListAdminView): data_charts = {} def get_ordering(self): if 'order' in self.chart: return self.chart['order'] else: return super(ChartsView, self).get_ordering() def get(self, request, name): if name not in self.data_charts: return HttpResponseNotFound() self.chart = self.data_charts[name] self.x_field = self.chart['x-field'] y_fields = self.chart['y-field'] self.y_fields = ( y_fields,) if type(y_fields) not in (list, tuple) else y_fields datas = [{"data": [], "label": force_text(label_for_field( i, self.model, model_admin=self))} for i in self.y_fields] self.make_result_list() for obj in self.result_list: xf, attrs, value = lookup_field(self.x_field, obj, self) for i, yfname in enumerate(self.y_fields): yf, yattrs, yv = lookup_field(yfname, obj, self) datas[i]["data"].append((value, yv)) option = {'series': {'lines': {'show': True}, 'points': {'show': False}}, 'grid': {'hoverable': True, 'clickable': True}} try: xfield = self.opts.get_field(self.x_field) if type(xfield) in (models.DateTimeField, models.DateField, models.TimeField): option['xaxis'] = {'mode': "time", 'tickLength': 5} if type(xfield) is models.DateField: option['xaxis']['timeformat'] = "%y/%m/%d" elif type(xfield) is models.TimeField: option['xaxis']['timeformat'] = "%H:%M:%S" else: option['xaxis']['timeformat'] = "%y/%m/%d %H:%M:%S" except Exception: pass option.update(self.chart.get('option', {})) content = {'data': datas, 'option': option} result = json.dumps(content, cls=JSONEncoder, ensure_ascii=False) return HttpResponse(result)
site.register_plugin(ChartsPlugin, ListAdminView) site.register_modelview(r'^chart/(.+)/$', ChartsView, name='%s_%s_chart')