Odoo入门(六)——视图层

视图层-设计用户界面

这一章节我们会学习用来构建用户界面的视图层。

  • 视图跟控件
  • context跟domian

使用xml文件定义用户界面

  • 用户界面与业务记录一样,都是存储在我们Odoo的数据库中。我们设计用户界面其实本质上就是使用xml文件来把Odoo中的UI从数据库中拉出来显示给用户。
  • 构造一个新的 todo_ui 模块.编辑__mainfest__.py文件

    {
        'name': 'User interface improvements tp the To-Do app',
        'description': 'User friendly features.',
        'author': 'xer',
        'depends': ['todo_user'],
        'data': ['views/todo_view.xml',
             'views/todo_menu.xml',
             'security/ir.model.access.csv',
             ],
    }

菜单选项

  • 菜单选项被存贮在ir.ui.menu模型中.可以在Settings | Technical | User Interface | Menu Items中查看 我们在’todo_app’应用中已经创建了顶层菜单’To-Do app tasks’.现在来对这个菜单进行扩展 新建views/todo_menu.xml.添加如下代码

         <!--Modify the top menu-->
        <menuitem id="todo_app.menu_todo_task" name="To-Do"></menuitem>
        <!--app menu items-->
        <menuitem id="menu_todo_task_view"
              name="Tasks"
              parent="todo_app.menu_todo_task"
              sequence="10"
              action="todo_app.action_todo_task"/>
        <menuitem id="menu_todo_config"
              name="Configuration"
              parent="todo_app.menu_todo_task"
              sequence="100"
              groups="base.group_system"/>
        <menuitem id="menu_todo_task_stage"
              name="Stages"
              parent="menu_todo_config"
              sequence="10"
              action="action_todo_stage" />

    Odoo的缩写可以直接使用<menuitem>这个标签来代替<record mode='ir.ui.view'>

    • 第一行中我们直接使用了定义在’todo_app’中的菜单视图的外部xml id.直接对其进行改写
    • 第二行通过’parent’属性来定义二级菜单,使用action来调用菜单动作.
    • 第三行我们定义了一个设置菜单,规定了group为管理员才能获取.
    • 最后,我们添加了Stage菜单.绑定action用来显示任务的阶段.

窗口动作(actions)

  • 窗口动作通常与菜单以及按钮结合使用,它在GUI客户端中调度每个窗口。通过domian过滤器能显示记录中符合条件的子集,通过context可以传递参数。

        <act_window id="action_todo_stage"
            name="To-Do Task Stages"
            res_model="todo.task.stage"
            view_mode="tree,form"
            target="current"
            context="{'default_state': 'open'}"
            domain="[]"
            limit="80"
            />
        <act_window id="todo_app.action_todo_task"
            name="To-Do Tasks"
            res_model="todo.task"
            view_mode="tree,form,calendar,kanban,graph,pivot"
            target="current"
            context="{'search_default_filter_my_tasks': True}"
            />
        <act_window id="action_todo_task_stage"
            name="To-Do Task Stages"
            res_model="todo.task.stage"
            src_model="todo.task"
            multi="False"
            />
  • 我们在’views/todo_menu.xml’中添加窗口动作,注意要把这些元素放在menu元素之前.窗口动作被存贮在’ir.actions.act_window’模型中,可使用缩写<act_window> 上面代码第一个action我们打开了Task Stages模型.其中主要属性如下所列:

    • name: 在视图层显示的动作的名称.
    • res_model : 动作要调用的模型
    • view_mode: 视图显示模式。第一个模式是默认打开时的显示方式
    • target: new能打开新窗口,默认是current,即在当前页面打开
    • context : 在目标视图打开时,提供传递的参数(传递上下文)。通常用来设置默认值以及添加过滤
    • domain :domain过滤规则。
    • limit : 每页显示的记录数。
    • src_model :指定了在哪个模型中放置More菜单
    • multi : 这个属性默认为false,当设置为True时能够在list视图中使用(注意到我们的More菜单只在点开具体task记录时才可以使用)
  • 第二个action我们重新定义了todo_app中的动作,添加了多种视图表示模式

  • 第三个动作并没有绑定菜单.这个动作是注册到了我们视图层右上角的more选项中. 右上角的more

Context(上下文)跟domain

Context 数据

  • 上下文数据是一个保存着会话信息的字典能够同时在用户界面跟业务后台中传递数据。

    • 从用户界面角度:它能把前一个视图中的数据,例如记录ID传递到下一个视图。为下一个视图传递默认值
    • 从服务器角度:一些记录集字段的值依赖于Odoo的本地设置,也可以使用context传递。特别是lang的值,它是翻译的关键.举个例子:

      {'lang': 'en_US', 'tz': 'Europe/Brussels', 'uid': 1}
  • 当打开一个表单视图或者从按钮上获取一个链接,active_id被添加到context中.它代表了当前记录的id,如果是列表视图,则会有一个active_ids包含列表中所有的记录.

  • 如果要设置默认值,要通过default_或者default_search_前缀.举例: 设置默认user_id字段的值

    {'default_user_id' : uid}

    设置一个默认的过滤器:

    {'default_search_filter_my_tasks' : 1}

Domain表达式

  • domian表达式是用来过滤数据记录的。它本质是通过Odoo的ORM模型转换为SQL的WHERE子句。domain表达式的每个条件是一个三元素的元组格式:('field_name','operator','value')

  • field_name: 是要被过滤的字段名称。能够使用.操作符来表示关联模型的字段.

  • value : 必须能通过操作符跟字段进行比较。

  • operator : 操作符。常见的如<,>,<,>=,=,!=

    • =like:可以使用模式匹配,下划线“_”匹配一个字符,百分号“%”匹配零或者多个字符。
    • like: %value%模式,模糊搜索。ilikelike类似,忽略大小写.not like,通过%value%模式不匹配
    • child of:判断是否value的子记录,通过_parent_name实现。
    • in, not in:判断value是否在元素的列表里面。 domain表达式是一个列表,通常包含了许多个条件元组,这些条件元组直接默认的逻辑关系是AND。 条件间使用的逻辑前缀:
  • & :逻辑 AND,条件间的默认关系。2个参数(后2个条件或者条件组合) ,例如:

    [’&‘, (‘partner_id.coutnry_id.code’, ‘=’, ‘CN’), (‘partner_id.coutry_id.code’, ‘=’, ‘US’)]

    这里的 & 就是把后面的2个条件通过AND组合起来也就是 A AND B,但是注意到这里说的是还有“条件组合”的情况,所以还有可能是

    [’&‘, (‘partner_id.coutnry_id.code’, ‘=’, ‘CN’), ‘&‘,(‘partner_id.coutry_id.code’, ‘=’, ‘US’), (‘partner_id.coutry_id.code’, ‘=’, ‘GB’)]

    转换为一般的表示方法则是 A AND (B AND C),但是因为’&’是默认的逻辑关系,所以我们其实可以不用显式表示

    [(‘partner_id.coutnry_id.code’, ‘=’, ‘CN’),(‘partner_id.coutry_id.code’, ‘=’, ‘US’), (‘partner_id.coutry_id.code’, ‘=’, ‘GB’)]

    更多的Domain讨论可以在 Domain规则中看到


表单视图

  • form视图就像纸质文件一样,有着简单的布局。
  • 我们可以对它进行布局的设置,现在让我们通过继承来扩展在todo_app中定义过的form视图.

    处理相同类型的多个视图.

  • 同一个模型可能会有多个视图,通过添加action的属性view_id,可以指定需要使用的视图.

  • Odoo为我们的模型提供了最为基础的form视图,当我们进行视图的修改后,Odoo会通过优先值来调用新视图.默认的视图优先级为16.

    业务文档视图

  • 正常业务使用中,许多记录都被记录在纸质文件上,例如发票,库存单.因此构建一张类似纸质文件的视图是十分有必要的.在我们的To-Do Task中,创建 views/todo_views.xml 文件.

    <record id="view_form_todo_task_ui"
            model="ir.ui.view">
        <field name="model">todo.task</field>
        <field name="priority">15</field>
        <field name="arch" type="xml">
            <form>
                <header>
                <!-- To add buttons and status widget-->
                </header>
                <sheet>
                   <!-- To add form content-->
                </sheet>
                <div class="oe_chatter">
                    <field name="message_follower_ids"
                           widget="mail_followers"/>
                    <field name="message_ids" widget="mail_thread"/>
                </div>
            </form>
        </field>
    </record>

    业务文档视图通常使用3个主要区域。

    • header :状态条
    • sheet : 主要内容
    • chatter :底部防止留言板一类的界面显示 我们在 chatter 中使用了Odoo中mail模块的社交网络组件。
  • 通常我们在页面头部中放置动作按钮以及页面的生命周期状态。
  • 通常action是通过放置button按钮来实现的。通常使用`class=“oe_highlight”可以使其高亮显示
  • 文档记录的生命周期使用’statusbar’这个窗口组件.一般使用’State’名称的selection字段或者一个’Stage’名字的多对一字段来表示文件记录当前的状态.
  • 按照目前Odoo的趋势.逐渐使用Stage代替State来表示记录状态.
  • 在todo_view.xml中扩展我们的header

            <header>
                <field name="state" invisible="True"/>
                <button name='do_toggle_done' type="object"
                    attrs="{'invisible':[('state','in',['draft'])]}"
                    string="Toggle Done"
                    class="oe_highlight"/>
                <field name="stage_id"
                   widget="statusbar"
                   clickable="True"
                   options="{'fold_field':'fold'}"/>
            </header>
    • 我们使用字段前,首先需要把该字段导入到view中。
    • 上面的代码中。首先我们把记录的state字段设置为不可见,可以设置属性invisible="True"让字段在客户界面不显示.
    • 下面定义一个button按钮来触发以前定义过的do_toggle_done方法.属性”attrs”的定义是指当文档记录的state状态为draft时隐藏该按钮.
    • clickable属性允许通过点击status bar来改变文档记录的stage字段值.

Sheet

  • sheet 标签中主要是实际数据存放的地方,它被设计成跟纸质文档类似的格式。它的主要结构:
    • 居中的文档标题
    • 右上角的一个按钮盒
    • 其他文档头部字段
    • 一个用来记载额外字段的笔记本

标题跟副标题

  • <group>元素外的字段不会自动生成标注。所以我, 需要为这些标题元素添加label属性。Odoo的xml文件中可以使用html跟css语言。我们一般把标题放在<div class='oe_title'>标签下。

            <sheet>
                <div class="oe_title">
                <label for="name" class="oe_edit_only"/>
                <h1><field name="name"/></h1>
                <h3>
                    <span class="oe_read_only">By</span>
                    <label for="user_id" class="oe_edit_only"/>
                    <field name="user_id" class="oe_inline"/>
                </h3>
                </div>
            </sheet>

    <label>的用法.for属性指定了需要的字段.class="oe_edit_only"表示只在编辑模式时显示该字段.

智能按钮区域

  • 右上角有一个按钮存放的区域。我们可以在其中添加按钮.

    <div name="buttons" class="oe_right oe_button_box">
    <!-- smart buttons here ...-->
    </div>
  • 我们表单的主要内容应该放置在<group>标签中.一个字段值跟一个字段标签通常需要两列,而一个<group>标签会在页面布局上插入两列。所以我们使用2个group标签,这样就可以构造左右两个字段的布局格式。

                    <group name="group_top">
                        <group name="group_left">
                            <field name="date_deadline"/>
                            <!--<separator string="Reference"/>-->
                            <field name="refers_to"/>
                        </group>
                        <group name="group_right">
                            <field name="tag_ids" widget="many2many_tags"/>
                            <field name="stage_state"/>
                        </group>
                    </group>

    效果如下:

    group效果

  • 我们在定义group标签时,可以定义一个name属性,这有利于关联以及以后的扩展.

  • 在group标签中我们可以使用col,colspan属性来对布局进一步的控制.

    • col代表group分割的列数,默认为2.
    • colspan表示每列的尺寸大小

分页本

  • 另一种格式化内容的方式是使用notebook元素.包含多重分页节选。它能让不常用的数据在使用时才显示。 例子:

    <notebook>
    <page string="Whiteboard" name="whiteboard">
      <field name="docs"/>
    </page>
    <page>
    <!--second page content -->
    </page>
    </notebook>

视图语义组件

  • 刚才我们解释了如何结构化表单内容。现在我们来讲讲构造表单数据的部件。

Fields

  • 视图中字段同样有许多属性,我们可以通过定义这些属性来重写模型中已经定义的字段的属性。
    • name : 字段数据库名字的标识
    • string : 标签文本。在view中定义可以重写模型中已经定义的string名字
    • help :帮助文本,鼠标悬停时显示
    • placeholder : 在创建时字段输入框中默认显示的内容。
    • widget : 字段显示的窗口化部件。
    • options :一个JSON数据类型的可选参数,用来给窗口化部件传参。
    • class :CSS样式
    • nolabel=“True” :在groups视图中不显示字段的标签常用。
    • invisible=“True” : 字段不可见
    • readonly=“True” : 字段在视图中只读
    • required=“True” : 字段必须输入
    • password=“True” : 字段输入变为”*“这样的密码格式
    • filename : 二进制文件,上传后保存的名字
    • mode : 一对多字段使用,它指定了一对多字段在关联字段中显示的模式,默认是tree,可以为form,kanban,graph.

字段的标签

  • label元素通常用来控制字段在视图中的显示.

    <label for="name" class="oe_edit_only" />

    表示只有当处于编辑模式时才显示字段标签。 注意,在group元素内嵌中,我们还要设置nolabel="True".

关系字段

  • 关系字段可用的属性有 context,domain.这些已经介绍过.还有一个options属性.我们知道,在关系型字段上,我们能够使用快速创建功能来快速创建一个新的关联模型记录,通过

    options={'no_open': True, 'no_create' : True}

    可以关闭快速创建功能.

字段窗口组件

  • 字段根据自己的类型都有默认的窗口显示控件.当我们还可以自己来选择额外的窗口显示控件.

  • 对于字符型字段:

    • email : 用来添加一个”发送给…“的动作
    • url : 格式化文本为一个可以点击的URL地址
    • html : 一个可以转换text到HTML内容的控件
  • 对于数字字段:

    • handle : 对于排序字段,直接显示向上向下箭头
    • float_time : 单精度时间
    • monetary :价格(和精度位数相关)
    • progressbar :精度条,按照百分比。
  • 关系型字段:

    • many2many_tags : 展示一个类似按钮标签的列表
    • selection : 使用了selection 字段部件来展示many-to-one 字段
    • radio : 单选标签
    • kanban_state_selection :
    • priority : 优先级。通常就是个数字。

按钮

  • 按钮支持下面的属性:

    • icon : 定义图标按钮。只能在addons/web/static/src/img/icons中获取
    • string : 按钮的字符描述
    • type : 一共有3个值: 1. workflow : 触发工作流 2. object : 调用python方法 3. action : 执行窗口动作

    • name : 根据type的取值来设置.如果是工作流,就是一个工作流信号名称, object就是python方法名,action就是动作的外部id

    • args : 当type参数是object时用来传递参数

    • context : 添加上下文内容.

    • confirm : 确认按钮上显示的字符

    • special=“cancel” : 在向导中使用.用来取消向导表单或者关闭向导视图.

智能(Smart)按钮

  • 就是表单结构中右上角的按钮小盒子.

image.png

  • 在我们的app中,我们定义了了一个展示当前task总数的按钮,点击它会直接列出当前用户的task列表记录.

  • 首先在 todo_model.py 文件中添加统计task的计算字段,

        def compute_user_todo_count(self):
        for task in self:
            task.user_todo_count = task.search_count([('user_id', '=', task.user_id.id)])
    
        user_todo_count = fields.Integer('User To-Do Count', compute='compute_user_todo_count')
  • 然后在视图层添加button按钮

                <div name="buttons" class="oe_right oe_button_box">
                <button class="oe_stat_button"
                    type="action" icon="fa-tasks"
                    name="%(action_todo_task_button)d"
                    context="{'default_user_id': user_id}"
                    help="All to-dos for this user">
                    <field string="To-Dos" name="user_todo_count"
                       widget="statinfo"/>
                </button>

    添加 smart buttons 时,有下列属性可以使用:

    • class=“oe_stat_button” :
    • icon : 设置按钮使用的图标
    • typename : smart button中, type 通常就是 actionname 的记录则是XMLID, 这里我们就需要使用 %(action-external-id)d 来让外部id转为实际的数据库id.
    • string :添加文本标志.
    • context : 传递上下文,代码中我们传递了用户的id作为action打开下一个视图的默认值.
    • help : 帮助文本信息 button 元素可以被看做一个容器,我们是有statinfo 这个小组件来展示字段的统计信息。代码中我们显示了一个计算字段。另外还可以在其中放入静态文本字段 举例:

      <div> User's To-dos </div>

      下面来完善我们定义的action_todo_task_button 这个动作.它必须在button 之前定义

          <act_window id="action_todo_task_button"
              name="To-Do Tasks"
              res_model="todo.task"
              view_mode="tree,form,calendar,graph,pivot"
              domain="[('user_id','=',default_user_id)]"
          />

动态视图

  • 视图元素提供了一些属性同样允许视图根据字段的值的变化来动态的展示对应的视图。

on change 事件

  • Odoo 提供的on change 机制通过监测字段的值的改变来对目标字段的值进行改变。举个例子,价格字段随着产品字段的选择的改变而产生变化来显示具体产品的价格。
  • 从Odoo8.0版本以后,on change 可以直接在模型python代码中实现。通过@api.onchange(‘field1’, ‘field2’) 来实现.

动态属性

  • 动态属性能够更好的通过 on change 机制来对用户视图上元素的可视化进行控制.

    • group : 让一个元素是否可见依赖当前用户的分组.其实是通过权限控制实现.

    • states : 根据当前记录的 Stage 字段.

视图模式

  • List View(列表视图):

        <record id="view_tree_todo_task" model="ir.ui.view">
        <field name="name">To-do Task tree</field>
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <tree colors="red:is_done==True">
            <field name="name"/>
            <field name="is_done"/>
            </tree>
        </field>
        </record>

    对应属性:

    1. default_order : 默认的排序规则.
    2. create,delte,edit : 如果设置为 false ,让对应的视图上的动作失效.
    3. editable : 让记录能够在列表视图中直接被修改. 列表视图中,数字型字段能够通过使用 sum, avg, min, max 属性来对每列的数据进行一个统计操作。

      <field name="amount" sum="Total Amount"/>
  • 搜索视图

    搜索视图被定义在<search>标签中.

    ```xml
        <record id="todo_app.view_filter_todo_task"
            model="ir.ui.view">
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <search>
            <field name="name" />
            <field name="user_id"/>
            <filter name="filter_not_done" string="Not Done"
                   domain="[('is_done','=',False)]"/>
            <filter name="filter_done" string="Done"
                   domain="[('is_done','!=',False)]"/>
            <separator/>
            <filter name="group_user" string="By User"
                context="{'group_by':'user_id'}"/>
            </search>
        </field>
        </record>
    ```
    

    我们使用了 nameuser_id 字段进行搜索. 搜索视图属性:

    1. name : 标识了使用的字段.
    2. string : 字符标识
    3. operator : 操作方式,数字类型字段默认为=, 其他的是ilike.
    4. filter_domain : 过滤规则.提供一个可以选择的现成过滤器小方块显示在搜索栏下拉菜单中.
    5. groups : 根据使用者的分组决定搜索权限.

      对于过滤器元素.以下属性可以使用:

      1. name : 定义了过滤器的标识id.
      2. string : 过滤器在视图中的字符标识.
      3. domain : domain过滤规则.
      4. context : 上下文传递数据.
      5. groups : 根据当前用户分组权限决定是否显示本过滤器.
  • 日历视图

        <record id="view_calendar_todo_task" model="ir.ui.view">
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <calendar date_start="date_deadline" color="user_id"
                  display="[name], Stage [stage_id]">
            <field name="name"></field>
            <field name="stage_id"/>
            </calendar>
        </field>
        </record>

    日历视图属性:

    1. date_start : 开始日期。
    2. date_end : 结束日期.
    3. date_delay : 持续时间,可以用来代替date_end .很好理解,end - start = delay嘛。
    4. all_day : 一个bool字段来决定是否是全天持续事件.
    5. color : 根据分组来设置一个显示颜色
    6. display : 显示的格式. 代码中就是显示记录的名字跟阶段状态。
    7. mode :显示模式,默认是 day, week, month
  • 图表视图跟数据透视表

    图表视图为数据提供了图像可视化。 在todo_ui/models/todo_model.py中task模型中添加一个字段

    effort_estimate = fields.Integer('effort estimate')
    <record id="view_graph_todo_task" model="ir.ui.view">
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <graph type="bar">
                <field name="stage_id"/>
                <field name="effort_estimate" type="measure"/>
            </graph>
        </field>
    </record>

    图表的type 有3个可选 bar,pie,line

  • 添加数据透视表

    <record id="view_pivot_todo_task" model="ir.ui.view">
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <pivot>
                <field name="stage_id" type="col"/>
                <field name="user_id"/>
                <field name="date_deadline" interval="week"/>
                <field name="effort_estimate" type="measure"/>
            </pivot>
        </field>
    </record>