Source code for xadmin.plugins.chart

# coding=utf-8
"""
图表插件
=========

功能
----

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

截图
----

.. 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 datetime
import decimal
import calendar

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

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, force_unicode, 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_unicode(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_instance=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_unicode(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')