Odoo中文社区可以通过以下三个域名访问:shine-it.net , odoocn.org,odoo.net.cn

原论坛用户的基本信息和发帖这里都予以保留,请注意:原论坛用户无需重新注册新用户,但是您的密码需要重置

开发人员可以登录gitter讨论组: http://gitter.im/odoo-china/Talk, 需要github账号

如果您登录系统碰到问题,请在微信公众号留言:

自制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
    [code]
    {
        "name": "Graph Views",
        "category" : "Hidden",
        "description":"""Graph Views for Web Client

    * Parse a <graph> view but allows changing dynamically the presentation
    * Graph Types: pie, lines, areas, bars, radar
    * Stacked / Not Stacked for areas and bars
    * Legends: top, inside (top/left), hidden
    * Features: download as PNG or CSV, browse data grid, switch orientation
    * Unlimited "Group By" levels (not stacked), two cross level analysis (stacked)
    """,
        "version": "3.0",
        "depends": ['web'],
        "js": [
            "static/lib/highchart/js/highcharts.js",
            "static/src/js/graph.js"
        ],
        "css": [
            "static/src/css/.css",
        ],
        'qweb' : [
            "static/src/xml/
    .xml",
        ],
        "auto_install": True
    }

    [/code]


    下面研究highcharts.
    观察highcharts的示例(http://www.highcharts.com/demo/),折线图是这样运行的:

    [code]
    $(function () {
        var chart;
        $(document).ready(function() {
            chart = new Highcharts.Chart({
                chart: {
                    renderTo: 'container',
                    type: 'line',
                    marginRight: 130,
                    marginBottom: 25
                },
                title: {
                    text: 'Monthly Average Temperature',
                    x: -20 //center
                },
                subtitle: {
                    text: 'Source: WorldClimate.com',
                    x: -20
                },
                xAxis: {
                    categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                        'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
                },
                yAxis: {
                    title: {
                        text: 'Temperature (°C)'
                    },
                    plotLines: [{
                        value: 0,
                        width: 1,
                        color: '#808080'
                    }]
                },
                tooltip: {
                    formatter: function() {
                            return '<b>'+ this.series.name +'</b><br/>'+
                            this.x +': '+ this.y +'°C';
                    }
                },
                legend: {
                    layout: 'vertical',
                    align: 'right',
                    verticalAlign: 'top',
                    x: -10,
                    y: 100,
                    borderWidth: 0
                },
                series: [{
                    name: 'Tokyo',
                    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]
                }, {
                    name: 'New York',
                    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]
                }, {
                    name: 'Berlin',
                    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]
                }, {
                    name: 'London',
                    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]
                }]
            });
        });
       
    });
    [/code]


    第二步,研究一下服务端。
    服务端代码集中在graph.py里。
    GraphView类是一个标准的View, 是客户端图表数据的来源,也定义了图表显示方式。如果要修改图表,几乎是要动这个类。
    我们来简单的看。首先修改客户端。
    只要把服务端返回的数据变成这种格式就可以了。动手:
    [code]
    # -- coding: utf-8 --

    import tools
    from tools import safe_eval

    try:
        # embedded
        import openerp.addons.web.common.http as openerpweb
        from openerp.addons.web.controllers.main import View
    except ImportError:
        # standalone
        import web.common.http as openerpweb
        from web.controllers.main import View

    from lxml import etree

    class GraphView(View):
        _cp_path = '/web_graph/graph'
       
        @tools.cache(timeout=3600)
        def from_db(self, obj, chart_type, title, fields, domain, group_by, context):
            result = {}
            if len(fields)<2:
                return result
           
            field_x = fields[1]
            field_y = fields[2]
            field_z = (len(fields)==4) and fields[3] or ''

            ids = obj.search(domain)
               
            if ids:
                records = obj.read(ids)
               
                #field_x
                categories = []
                #field_z
                groups = []
                series = []
               
                if field_z:
                    data_set = {}
                    for r in records:
                        #get categories.
                        if r[field_x] not in categories:
                            categories.append(r[field_x])
                           
                        if r[field_z] not in groups:
                            groups.append(r[field_z])
                       
                        data_set[r[field_x]+r[field_z]] = r[field_y]
                   
                    #transform data
                    # series

                    for g in groups:
                        s = {'name':g, 'data':[]}
                        for cate in categories:
                            s['data'].append(data_set.get(cate+g, 0))
                        series.append(s)

                else:
                    data = []
                    for r in records:
                        if r[field_x] not in categories:
                            categories.append(r[field_x])
                        data.append(r[field_y])
                   
                    series.append({'data':data})

            return categories, series
       
        @openerpweb.jsonrequest
        def data_get(self, req, model=None, domain=[], group_by=[], view_id=False, context={}, *kwargs):

            obj = req.session.model(model)
            xml = obj.fields_view_get(view_id, 'graph')
            graph_xml = etree.fromstring(xml['arch'])
           
            chart_type = graph_xml.attrib.get('type') or 'line'
            chart_title = graph_xml.attrib.get('string') or '图表'
            fields = [ element.attrib.get('name') for element in graph_xml.iter() ]
           
            data = self.from_db(obj, chart_type, chart_title, fields, domain, group_by, context)

            result = {
                'title':chart_title,
                'categories':data[0],
                'series':data[1],
                'chart_type':chart_type,
            }
           
            return result

    [/code]

    很简单, 我只处理这样的Graph定义:
    [code]
    <record id="view_sale_order_report_monthly_tree" model="ir.ui.view">
            <field eval="1" name="priority"/>
            <field name="name">sale.order.report.monthly.tree</field>
            <field name="model">sale.order.report.monthly</field>
            <field name="type">tree</field>
            <field name="arch" type="xml">
                <tree string="每月销售统计">
                    <field name="date" />
                    <field name="amount" />
                    <field name="source" />
                </tree>
            </field>
        </record>
    [/code]

    第一个field,作为x轴,第二个,作为y轴。第三个,group成多个系列。 这样的处理就是简单化,并不会考虑openerp原来考虑的事情,所以不是一个通用方法。
    (另,如果要是想进一步扩展graph,则需要修改addons/base/rng/view.rng的规则。)

    下面就是让highcharts显示出来了。
    观察web_graph的xml模板和graph.js
    我记得原来的xml模板element_id上有一些bug(6.1),我修改了一下:
    [code]
    <template>
        <div t-name="GraphView" t-att-id="element_id+'-chart-'+chart_id"
            style="height:300px;position:relative;"/>
    </template>
    [/code]

    这是highcharts显示的容器。
    重头戏在graph.js里,这里需要处理很多东西,但是也简单。按照原本的客户端views写法:

    [code]

    /
    ---------------------------------------------------------
    * OpenERP web_graph
    ---------------------------------------------------------/

    openerp.web_graph = function (openerp) {

    var QWeb = openerp.web.qweb,
        _lt = openerp.web._lt;
    openerp.web.views.add('graph', 'openerp.web_graph.GraphView');
    openerp.web_graph.GraphView = openerp.web.View.extend({
        display_name: _lt('Graph'),
       
        init: function(parent, dataset, view_id, options) {
            this._super(parent);
            this.dataset = dataset;
            this.view_id = view_id;
            this.set_default_options(options);
            this.fields_view = {};
           
            this.model = dataset.model;
            this.chart_id = Math.floor((Math.random()100)+1);
        },
       
        start: function() {
            this._super();
           
            this.$element.html(QWeb.render("GraphView", {
                "chart_id": this.chart_id,
                'element_id': this.widget_parent.element_id
            }));
        },
        stop: function() {
            this._super();
        },

        /

        * get data here.
        */
        do_search: function(domain, context, group_by) {
           
            this.rpc(
                      '/web_graph/graph/data_get',
                      {
                          'model': this.model,
                          'domain': domain,
                          'group_by': group_by,
                          'view_id': this.view_id,
                          'context': context
                      }, this.on_search
                    );

        },
       
        on_search: function(result){
            container = this.widget_parent.element_id+"-chart-"+this.chart_id;
           
            var chart = new Highcharts.Chart({
                chart: {
                    renderTo: container,
                    height: 300
                },
                title: {
                    text: result.title
                },
                xAxis: {
                    categories: result.categories
                },
                series: result.series
            });
        },
       
        do_show: function() {
            this.do_push_state({});
            return this.super();
        }
    });
    };
    // vim:et fdc=0 fdl=0:

    [/code]

    能看出,主要是三个方法(标准的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里规则的修改。


    心情不好,写的很笼统,如果有疑问,可以回帖,或者新浪微博 @杨振宇
     
    谨以此文,纪念我远在天堂的儿子。 愿天父的慈爱永远呵护你。



  • 注意:
    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
    [code]
    {
        "name": "Graph Views",
        "category" : "Hidden",
        "description":"""Graph Views for Web Client

    * Parse a <graph> view but allows changing dynamically the presentation
    * Graph Types: pie, lines, areas, bars, radar
    * Stacked / Not Stacked for areas and bars
    * Legends: top, inside (top/left), hidden
    * Features: download as PNG or CSV, browse data grid, switch orientation
    * Unlimited "Group By" levels (not stacked), two cross level analysis (stacked)
    """,
        "version": "3.0",
        "depends": ['web'],
        "js": [
            "static/lib/highchart/js/highcharts.js",
            "static/src/js/graph.js"
        ],
        "css": [
            "static/src/css/.css",
        ],
        'qweb' : [
            "static/src/xml/
    .xml",
        ],
        "auto_install": True
    }

    [/code]


    下面研究highcharts.
    观察highcharts的示例(http://www.highcharts.com/demo/),折线图是这样运行的:

    [code]
    $(function () {
        var chart;
        $(document).ready(function() {
            chart = new Highcharts.Chart({
                chart: {
                    renderTo: 'container',
                    type: 'line',
                    marginRight: 130,
                    marginBottom: 25
                },
                title: {
                    text: 'Monthly Average Temperature',
                    x: -20 //center
                },
                subtitle: {
                    text: 'Source: WorldClimate.com',
                    x: -20
                },
                xAxis: {
                    categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                        'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
                },
                yAxis: {
                    title: {
                        text: 'Temperature (°C)'
                    },
                    plotLines: [{
                        value: 0,
                        width: 1,
                        color: '#808080'
                    }]
                },
                tooltip: {
                    formatter: function() {
                            return '<b>'+ this.series.name +'</b><br/>'+
                            this.x +': '+ this.y +'°C';
                    }
                },
                legend: {
                    layout: 'vertical',
                    align: 'right',
                    verticalAlign: 'top',
                    x: -10,
                    y: 100,
                    borderWidth: 0
                },
                series: [{
                    name: 'Tokyo',
                    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]
                }, {
                    name: 'New York',
                    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]
                }, {
                    name: 'Berlin',
                    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]
                }, {
                    name: 'London',
                    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]
                }]
            });
        });
       
    });
    [/code]


    第二步,研究一下服务端。
    服务端代码集中在graph.py里。
    GraphView类是一个标准的View, 是客户端图表数据的来源,也定义了图表显示方式。如果要修改图表,几乎是要动这个类。
    我们来简单的看。首先修改客户端。
    只要把服务端返回的数据变成这种格式就可以了。动手:
    [code]
    # -- coding: utf-8 --

    import tools
    from tools import safe_eval

    try:
        # embedded
        import openerp.addons.web.common.http as openerpweb
        from openerp.addons.web.controllers.main import View
    except ImportError:
        # standalone
        import web.common.http as openerpweb
        from web.controllers.main import View

    from lxml import etree

    class GraphView(View):
        _cp_path = '/web_graph/graph'
       
        @tools.cache(timeout=3600)
        def from_db(self, obj, chart_type, title, fields, domain, group_by, context):
            result = {}
            if len(fields)<2:
                return result
           
            field_x = fields[1]
            field_y = fields[2]
            field_z = (len(fields)==4) and fields[3] or ''

            ids = obj.search(domain)
               
            if ids:
                records = obj.read(ids)
               
                #field_x
                categories = []
                #field_z
                groups = []
                series = []
               
                if field_z:
                    data_set = {}
                    for r in records:
                        #get categories.
                        if r[field_x] not in categories:
                            categories.append(r[field_x])
                           
                        if r[field_z] not in groups:
                            groups.append(r[field_z])
                       
                        data_set[r[field_x]+r[field_z]] = r[field_y]
                   
                    #transform data
                    # series

                    for g in groups:
                        s = {'name':g, 'data':[]}
                        for cate in categories:
                            s['data'].append(data_set.get(cate+g, 0))
                        series.append(s)

                else:
                    data = []
                    for r in records:
                        if r[field_x] not in categories:
                            categories.append(r[field_x])
                        data.append(r[field_y])
                   
                    series.append({'data':data})

            return categories, series
       
        @openerpweb.jsonrequest
        def data_get(self, req, model=None, domain=[], group_by=[], view_id=False, context={}, *kwargs):

            obj = req.session.model(model)
            xml = obj.fields_view_get(view_id, 'graph')
            graph_xml = etree.fromstring(xml['arch'])
           
            chart_type = graph_xml.attrib.get('type') or 'line'
            chart_title = graph_xml.attrib.get('string') or '图表'
            fields = [ element.attrib.get('name') for element in graph_xml.iter() ]
           
            data = self.from_db(obj, chart_type, chart_title, fields, domain, group_by, context)

            result = {
                'title':chart_title,
                'categories':data[0],
                'series':data[1],
                'chart_type':chart_type,
            }
           
            return result

    [/code]

    很简单, 我只处理这样的Graph定义:
    [code]
    <record id="view_sale_order_report_monthly_tree" model="ir.ui.view">
            <field eval="1" name="priority"/>
            <field name="name">sale.order.report.monthly.tree</field>
            <field name="model">sale.order.report.monthly</field>
            <field name="type">tree</field>
            <field name="arch" type="xml">
                <tree string="每月销售统计">
                    <field name="date" />
                    <field name="amount" />
                    <field name="source" />
                </tree>
            </field>
        </record>
    [/code]

    第一个field,作为x轴,第二个,作为y轴。第三个,group成多个系列。 这样的处理就是简单化,并不会考虑openerp原来考虑的事情,所以不是一个通用方法。
    (另,如果要是想进一步扩展graph,则需要修改addons/base/rng/view.rng的规则。)

    下面就是让highcharts显示出来了。
    观察web_graph的xml模板和graph.js
    我记得原来的xml模板element_id上有一些bug(6.1),我修改了一下:
    [code]
    <template>
        <div t-name="GraphView" t-att-id="element_id+'-chart-'+chart_id"
            style="height:300px;position:relative;"/>
    </template>
    [/code]

    这是highcharts显示的容器。
    重头戏在graph.js里,这里需要处理很多东西,但是也简单。按照原本的客户端views写法:

    [code]

    /
    ---------------------------------------------------------
    * OpenERP web_graph
    ---------------------------------------------------------/

    openerp.web_graph = function (openerp) {

    var QWeb = openerp.web.qweb,
        _lt = openerp.web._lt;
    openerp.web.views.add('graph', 'openerp.web_graph.GraphView');
    openerp.web_graph.GraphView = openerp.web.View.extend({
        display_name: _lt('Graph'),
       
        init: function(parent, dataset, view_id, options) {
            this._super(parent);
            this.dataset = dataset;
            this.view_id = view_id;
            this.set_default_options(options);
            this.fields_view = {};
           
            this.model = dataset.model;
            this.chart_id = Math.floor((Math.random()100)+1);
        },
       
        start: function() {
            this._super();
           
            this.$element.html(QWeb.render("GraphView", {
                "chart_id": this.chart_id,
                'element_id': this.widget_parent.element_id
            }));
        },
        stop: function() {
            this._super();
        },

        /

        * get data here.
        */
        do_search: function(domain, context, group_by) {
           
            this.rpc(
                      '/web_graph/graph/data_get',
                      {
                          'model': this.model,
                          'domain': domain,
                          'group_by': group_by,
                          'view_id': this.view_id,
                          'context': context
                      }, this.on_search
                    );

        },
       
        on_search: function(result){
            container = this.widget_parent.element_id+"-chart-"+this.chart_id;
           
            var chart = new Highcharts.Chart({
                chart: {
                    renderTo: container,
                    height: 300
                },
                title: {
                    text: result.title
                },
                xAxis: {
                    categories: result.categories
                },
                series: result.series
            });
        },
       
        do_show: function() {
            this.do_push_state({});
            return this.super();
        }
    });
    };
    // vim:et fdc=0 fdl=0:

    [/code]

    能看出,主要是三个方法(标准的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 开发的最佳实践,
    and 节哀。



  • 阿门



  • 谢谢楼主无私贡献 ~ 拜读之后很受启发!

    现在看到这篇文章,相信您已经度过最艰难的时刻。

    逝者长已矣,来者犹可追。

    默哀并祝福楼主 ~


  • 管理员

    坛子里对OpenERP Web端的技术讨论不多,兄弟的帖子让我们受益匪浅。
    6.1对OpenERP的web client做了完全的重写,变成了OpenERP应用服务器的一个addon了,并且可以象开发服务器端的功能模块一样开发web端的模块:[url=http://doc.openerp.com/trunk/developers/web/addons/]http://doc.openerp.com/trunk/developers/web/addons/[/url]
    兄弟如果能将其做成一个addon模块,就是一个经典的学习案例了。

    兄弟教会了我什么是坚强,祝福你和你至爱的人。



  • 感谢各位兄弟。

    说说为什么谈论web addon的少。
    要想从技术上搞懂openerp这套体系,不仅仅需要python的基础,还需要很强的javascript的技术。
    这在其他的领域,是那种前台后台通吃的人。
    我恰好都做过一些。所以动手改的话,比较熟悉一些。

    我本人是在一家比较大的制造型企业里,做市场和品牌运作的工作。平时只有在闲的时候才能写一些东西。

    至于各位兄弟说的一个单独的chart addon, 过了这几天,我会考虑单独做一个出来。

    社区里这位“辛巴达”的兄弟: 我帮不了忙,因为我平时的事情太多,是在是不好意思。


登录后回复
 

与 Odoo 中文社区 的连接断开,我们正在尝试重连,请耐心等待