自制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模块,就是一个经典的学习案例了。
兄弟教会了我什么是坚强,祝福你和你至爱的人。