自制Openerp图表
- 
注意: 
 1. 本文介绍一种简单的,非通用的改进openerp的思路。并非一定要取代原有方式。
 2. 本文会修改web_graph模块,如果在你的项目里使用了这个模块,请避免修改,以防止异常。
 3. 本文基于openerp 6.1
 通过本文,你可以知道:
 1. web_graph的运行机制。
 2. 如何动手修改这个模块。
 看看这个模块的结构:
 [attachimg=1]
 客户端采用的是highchart(http://www.highcharts.com/),当然,如果你喜欢其他的lib,都是没问题的。
 第一步,把highcharts给包含到模块来,这样openerp才能把这个库合并输出。
 把highcharts放置在适合的位置。
 修改__openerp__.py<br />{<br />    "name": "Graph Views",<br />    "category" : "Hidden",<br />    "description":"""Graph Views for Web Client<br /><br />* Parse a <graph> view but allows changing dynamically the presentation<br />* Graph Types: pie, lines, areas, bars, radar<br />* Stacked / Not Stacked for areas and bars<br />* Legends: top, inside (top/left), hidden<br />* Features: download as PNG or CSV, browse data grid, switch orientation<br />* Unlimited "Group By" levels (not stacked), two cross level analysis (stacked)<br />""",<br />    "version": "3.0",<br />    "depends": ['web'],<br />    "js": [<br />        "static/lib/highchart/js/highcharts.js",<br />        "static/src/js/graph.js"<br />    ],<br />    "css": [<br />        "static/src/css/*.css",<br />    ],<br />    'qweb' : [<br />        "static/src/xml/*.xml",<br />    ],<br />    "auto_install": True<br />}<br /><br />
 下面研究highcharts.
 观察highcharts的示例(http://www.highcharts.com/demo/),折线图是这样运行的:<br />$(function () {<br />    var chart;<br />    $(document).ready(function() {<br />        chart = new Highcharts.Chart({<br />            chart: {<br />                renderTo: 'container',<br />                type: 'line',<br />                marginRight: 130,<br />                marginBottom: 25<br />            },<br />            title: {<br />                text: 'Monthly Average Temperature',<br />                x: -20 //center<br />            },<br />            subtitle: {<br />                text: 'Source: WorldClimate.com',<br />                x: -20<br />            },<br />            xAxis: {<br />                categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',<br />                    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']<br />            },<br />            yAxis: {<br />                title: {<br />                    text: 'Temperature (°C)'<br />                },<br />                plotLines: [{<br />                    value: 0,<br />                    width: 1,<br />                    color: '#808080'<br />                }]<br />            },<br />            tooltip: {<br />                formatter: function() {<br />                        return '<b>'+ this.series.name +'</b><br/>'+<br />                        this.x +': '+ this.y +'°C';<br />                }<br />            },<br />            legend: {<br />                layout: 'vertical',<br />                align: 'right',<br />                verticalAlign: 'top',<br />                x: -10,<br />                y: 100,<br />                borderWidth: 0<br />            },<br />            series: [{<br />                name: 'Tokyo',<br />                data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6]<br />            }, {<br />                name: 'New York',<br />                data: [-0.2, 0.8, 5.7, 11.3, 17.0, 22.0, 24.8, 24.1, 20.1, 14.1, 8.6, 2.5]<br />            }, {<br />                name: 'Berlin',<br />                data: [-0.9, 0.6, 3.5, 8.4, 13.5, 17.0, 18.6, 17.9, 14.3, 9.0, 3.9, 1.0]<br />            }, {<br />                name: 'London',<br />                data: [3.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8]<br />            }]<br />        });<br />    });<br />    <br />});<br />
 第二步,研究一下服务端。
 服务端代码集中在graph.py里。
 GraphView类是一个标准的View, 是客户端图表数据的来源,也定义了图表显示方式。如果要修改图表,几乎是要动这个类。
 我们来简单的看。首先修改客户端。
 只要把服务端返回的数据变成这种格式就可以了。动手:<br /># -*- coding: utf-8 -*-<br /><br />import tools<br />from tools import safe_eval<br /><br />try:<br />    # embedded<br />    import openerp.addons.web.common.http as openerpweb<br />    from openerp.addons.web.controllers.main import View<br />except ImportError:<br />    # standalone<br />    import web.common.http as openerpweb<br />    from web.controllers.main import View<br /><br />from lxml import etree<br /><br />class GraphView(View):<br />    _cp_path = '/web_graph/graph'<br />    <br />    @tools.cache(timeout=3600)<br />    def from_db(self, obj, chart_type, title, fields, domain, group_by, context):<br />        result = {}<br />        if len(fields)<2:<br />            return result<br />        <br />        field_x = fields[1]<br />        field_y = fields[2]<br />        field_z = (len(fields)==4) and fields[3] or ''<br /><br />        ids = obj.search(domain)<br />            <br />        if ids:<br />            records = obj.read(ids)<br />            <br />            #field_x<br />            categories = []<br />            #field_z<br />            groups = []<br />            series = []<br />            <br />            if field_z:<br />                data_set = {}<br />                for r in records:<br />                    #get categories.<br />                    if r[field_x] not in categories:<br />                        categories.append(r[field_x])<br />                        <br />                    if r[field_z] not in groups:<br />                        groups.append(r[field_z])<br />                    <br />                    data_set[r[field_x]+r[field_z]] = r[field_y]<br />                <br />                #transform data<br />                # series<br /><br />                for g in groups:<br />                    s = {'name':g, 'data':[]}<br />                    for cate in categories:<br />                        s['data'].append(data_set.get(cate+g, 0))<br />                    series.append(s)<br /><br />            else:<br />                data = []<br />                for r in records:<br />                    if r[field_x] not in categories:<br />                        categories.append(r[field_x])<br />                    data.append(r[field_y])<br />                <br />                series.append({'data':data})<br /><br />        return categories, series<br />    <br />    @openerpweb.jsonrequest<br />    def data_get(self, req, model=None, domain=[], group_by=[], view_id=False, context={}, **kwargs):<br /><br />        obj = req.session.model(model)<br />        xml = obj.fields_view_get(view_id, 'graph')<br />        graph_xml = etree.fromstring(xml['arch'])<br />        <br />        chart_type = graph_xml.attrib.get('type') or 'line'<br />        chart_title = graph_xml.attrib.get('string') or '图表'<br />        fields = [ element.attrib.get('name') for element in graph_xml.iter() ]<br />        <br />        data = self.from_db(obj, chart_type, chart_title, fields, domain, group_by, context)<br /><br />        result = {<br />            'title':chart_title,<br />            'categories':data[0],<br />            'series':data[1],<br />            'chart_type':chart_type,<br />        }<br />        <br />        return result<br /><br />
 很简单, 我只处理这样的Graph定义:<br /><record id="view_sale_order_report_monthly_tree" model="ir.ui.view"><br />        <field eval="1" name="priority"/><br />        <field name="name">sale.order.report.monthly.tree</field><br />        <field name="model">sale.order.report.monthly</field><br />        <field name="type">tree</field><br />        <field name="arch" type="xml"><br />            <tree string="每月销售统计"><br />                <field name="date" /><br />                <field name="amount" /><br />                <field name="source" /><br />            </tree><br />        </field><br />    </record><br />
 第一个field,作为x轴,第二个,作为y轴。第三个,group成多个系列。 这样的处理就是简单化,并不会考虑openerp原来考虑的事情,所以不是一个通用方法。
 (另,如果要是想进一步扩展graph,则需要修改addons/base/rng/view.rng的规则。)
 下面就是让highcharts显示出来了。
 观察web_graph的xml模板和graph.js
 我记得原来的xml模板element_id上有一些bug(6.1),我修改了一下:<br /><template><br />    <div t-name="GraphView" t-att-id="element_id+'-chart-'+chart_id"<br />        style="height:300px;position:relative;"/><br /></template><br />
 这是highcharts显示的容器。
 重头戏在graph.js里,这里需要处理很多东西,但是也简单。按照原本的客户端views写法:<br /><br />/*---------------------------------------------------------<br /> * OpenERP web_graph<br /> *---------------------------------------------------------*/<br /><br />openerp.web_graph = function (openerp) {<br /><br />var QWeb = openerp.web.qweb,<br />    _lt = openerp.web._lt;<br />openerp.web.views.add('graph', 'openerp.web_graph.GraphView');<br />openerp.web_graph.GraphView = openerp.web.View.extend({<br />    display_name: _lt('Graph'),<br />    <br />    init: function(parent, dataset, view_id, options) {<br />        this._super(parent);<br />        this.dataset = dataset;<br />        this.view_id = view_id;<br />        this.set_default_options(options);<br />        this.fields_view = {};<br />        <br />        this.model = dataset.model;<br />        this.chart_id = Math.floor((Math.random()*100)+1);<br />    },<br />    <br />    start: function() {<br />        this._super();<br />        <br />        this.$element.html(QWeb.render("GraphView", {<br />            "chart_id": this.chart_id,<br />            'element_id': this.widget_parent.element_id<br />        }));<br />    },<br />    stop: function() {<br />        this._super();<br />    },<br /><br />    /*<br />    * get data here.<br />    */<br />    do_search: function(domain, context, group_by) {<br />        <br />        this.rpc(<br />                  '/web_graph/graph/data_get',<br />                  {<br />                      'model': this.model,<br />                      'domain': domain,<br />                      'group_by': group_by,<br />                      'view_id': this.view_id,<br />                      'context': context<br />                  }, this.on_search<br />                );<br /><br />    },<br />    <br />    on_search: function(result){<br />        container = this.widget_parent.element_id+"-chart-"+this.chart_id;<br />        <br />        var chart = new Highcharts.Chart({<br />            chart: {<br />                renderTo: container,<br />                height: 300<br />            },<br />            title: {<br />                text: result.title<br />            },<br />            xAxis: {<br />                categories: result.categories<br />            },<br />            series: result.series<br />        });<br />    },<br />    <br />    do_show: function() {<br />        this.do_push_state({});<br />        return this._super();<br />    }<br />});<br />};<br />// vim:et fdc=0 fdl=0:<br /><br />
 能看出,主要是三个方法(标准的api,参考openerp客户端的文档):
 start, do_search, on_search
 start是指本widget启动的时候要做的事情。
 do_search, 是启动以后,获取数据。
 on_search, 服务端返回数据,根据数据来设置highchart, 显示数据。
 需要注意的是,模板里的element_id( t-att-id="element_id+'-chart-'+chart_id" ) 要和 graph.js里的
 "container = this.widget_parent.element_id+"-chart-"+this.chart_id; " 一致。
 最终,我们得到一个更好看的chart.
 [attachimg=2]
 继续:
 1. 可以写一个更通用的服务端。
 2. 可以扩展这个chart,主要对view.rng里规则的修改。
 心情不好,写的很笼统,如果有疑问,可以回帖,或者新浪微博 @杨振宇_
 谨以此文,纪念我远在天堂的儿子。 愿天父的慈爱永远呵护你。
- 
坛子里对OpenERP Web端的技术讨论不多,兄弟的帖子让我们受益匪浅。 
 6.1对OpenERP的web client做了完全的重写,变成了OpenERP应用服务器的一个addon了,并且可以象开发服务器端的功能模块一样开发web端的模块: [检测到链接无效,已移除]
 兄弟如果能将其做成一个addon模块,就是一个经典的学习案例了。
 兄弟教会了我什么是坚强,祝福你和你至爱的人。


