模型约束

Odoo提供了两种设置自动验证不变量的方法: Python constraints 和SQL constraints.

Python约束定义为用constrains()修饰的方法,并在记录集上调用。修饰符指定约束中涉及哪些字段,以便在修改其中一个约束时自动评估约束。如果不满足该不变量,则期望该方法引发异常:

from odoo.exceptions import ValidationError@api.constrains('age')
def _check_something(self):for record in self:if record.age > 20:raise ValidationError("Your record is too old: %s" % record.age)# all records passed the test, don't return anything

练习

添加Python约束

添加一个约束,该约束检查instructor在他/她自己的session的attendees中不存在。

openacademy/models.py

# -*- coding: utf-8 -*-from odoo import models, fields, api, exceptionsclass Course(models.Model):_name = 'openacademy.course'
                    'message': "Increase seats or remove excess attendees",},}@api.constrains('instructor_id', 'attendee_ids')def _check_instructor_not_in_attendees(self):for r in self:if r.instructor_id and r.instructor_id in r.attendee_ids:raise exceptions.ValidationError("A session's instructor can't be an attendee")

SQL约束是通过模型属性_sql_constraints定义的。后者被分配给字符串的三元组列表(name, sql_definition, message),其中name 是有效的SQL约束名称,sql_definition 是table_constraint表达式,message 是错误消息。

练习

添加SQL约束

在PostgreSQL's documentation的帮助下,添加以下约束:

  1. CHECK (检查)课程描述和课程名称是不同的
  2. 使Course的name UNIQUE(唯一的)

openacademy/models.py

    session_ids = fields.One2many('openacademy.session', 'course_id', string="Sessions")_sql_constraints = [('name_description_check','CHECK(name != description)',"The title of the course should not be the description"),('name_unique','UNIQUE(name)',"The course title must be unique"),]class Session(models.Model):_name = 'openacademy.session'

练习

练习6 - 添加重复选项

因为我们为课程名称的唯一性添加了一个约束,所以不再可能使用“复制”函数 (Form ‣ Duplicate)。

重新实现你自己的“复制”方法,允许复制课程对象,把原来的名字改成“[原始名字]的拷贝”。

openacademy/models.py

    session_ids = fields.One2many('openacademy.session', 'course_id', string="Sessions")@api.multidef copy(self, default=None):default = dict(default or {})copied_count = self.search_count([('name', '=like', u"Copy of {}%".format(self.name))])if not copied_count:new_name = u"Copy of {}".format(self.name)else:new_name = u"Copy of {} ({})".format(self.name, copied_count)default['name'] = new_namereturn super(Course, self).copy(default)_sql_constraints = [('name_description_check','CHECK(name != description)',

高级视图

树视图

树视图可以采取补充属性来进一步定制他们的行为:

decoration-{$name}

允许根据相应的记录属性更改行文本的样式。

值是Python表达式。对于每个记录,表达式以记录的属性作为上下文值来评估,如果为true,则将相应的样式应用于行。 其他上下文值是uid (当前用户的id) 和current_date (当前日期为yyyy-MM-dd形式的字符串)。

{$name} 可以是 bf (font-weight: bold), it (font-style: italic),或任何的bootstrap contextual color (danger,infomutedprimarysuccess or warning).

<tree string="Idea Categories" decoration-info="state=='draft'"decoration-danger="state=='trashed'"><field name="name"/><field name="state"/>
</tree>

editable

要么是 "top" 要么是 "bottom"。使树视图可就地编辑(而不是必须通过表单视图),该值是新行出现的位置

练习

列表颜色

修改Session树视图的方式是持续少于5天的会话是蓝色的,而持续超过15天的则是红色的。

修改Session树视图:

openacademy/views/openacademy.xml

            <field name="name">session.tree</field><field name="model">openacademy.session</field><field name="arch" type="xml"><tree string="Session Tree" decoration-info="duration&lt;5" decoration-danger="duration&gt;15"><field name="name"/><field name="course_id"/><field name="duration" invisible="1"/><field name="taken_seats" widget="progressbar"/></tree></field>

日历视图

将记录显示为日历事件。它们的根元素是<calendar>,它们最常见的属性是:

color

用于颜色分割的字段的名称。颜色被自动分配给事件,但是相同颜色段中的事件(具有与它们的@color字段相同的值的记录)将被赋予相同的颜色

date_start

记录的字段保存事件的开始日期/时间

date_stop (可选的)

记录字段保存事件的结束日期/时间

字段(定义每个日历事件的标签    )

<calendar string="Ideas" date_start="invent_date" color="inventor_id"><field name="name"/>
</calendar>

练习

日历视图

Session模型添加日历视图,使用户能够查看与Open Academy相关的事件。

  1. 添加从start_date和duration计算的AA字段end_date

    提示

    逆函数使字段可写,并允许在日历视图中移动会话(通过拖放)

  2. Session 模型添加日历视图
  3. 并将日历视图添加到Session 模型的动作中

openacademy/models.py

# -*- coding: utf-8 -*-from datetime import timedelta
from odoo import models, fields, api, exceptionsclass Course(models.Model):
    attendee_ids = fields.Many2many('res.partner', string="Attendees")taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')end_date = fields.Date(string="End Date", store=True,compute='_get_end_date', inverse='_set_end_date')@api.depends('seats', 'attendee_ids')def _taken_seats(self):
                },}@api.depends('start_date', 'duration')def _get_end_date(self):for r in self:if not (r.start_date and r.duration):r.end_date = r.start_datecontinue# Add duration to start_date, but: Monday + 5 days = Saturday, so# subtract one second to get on Friday insteadstart = fields.Datetime.from_string(r.start_date)duration = timedelta(days=r.duration, seconds=-1)r.end_date = start + durationdef _set_end_date(self):for r in self:if not (r.start_date and r.end_date):continue# Compute the difference between dates, but: Friday - Monday = 4 days,# so add one day to get 5 days insteadstart_date = fields.Datetime.from_string(r.start_date)end_date = fields.Datetime.from_string(r.end_date)r.duration = (end_date - start_date).days + 1@api.constrains('instructor_id', 'attendee_ids')def _check_instructor_not_in_attendees(self):for r in self:

openacademy/views/openacademy.xml

            </field></record><!-- calendar view --><record model="ir.ui.view" id="session_calendar_view"><field name="name">session.calendar</field><field name="model">openacademy.session</field><field name="arch" type="xml"><calendar string="Session Calendar" date_start="start_date"date_stop="end_date"color="instructor_id"><field name="name"/></calendar></field></record><record model="ir.actions.act_window" id="session_list_action"><field name="name">Sessions</field><field name="res_model">openacademy.session</field><field name="view_type">form</field><field name="view_mode">tree,form,calendar</field></record><menuitem id="session_menu" name="Sessions"

搜索视图

搜索视图<field>元素可以有一个@filter_domain,它重写在给定字段中搜索生成的域。在给定的域中,self 表示用户输入的值。在下面的示例中,它用于搜索字段name 和description

搜索视图还可以包含<filter>元素,这些元素充当预定义搜索的切换。过滤器必须具有以下属性之一:

domain

将给定域添加到当前搜索

context

在当前搜索中添加一些上下文;使用键group_by在给定字段名上分组结果

<search string="Ideas"><field name="name"/><field name="description" string="Name and description"filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/><field name="inventor_id"/><field name="country_id" widget="selection"/><filter name="my_ideas" string="My Ideas"domain="[('inventor_id', '=', uid)]"/><group string="Group By"><filter name="group_by_inventor" string="Inventor"context="{'group_by': 'inventor_id'}"/></group>
</search>

若要在操作中使用非默认搜索视图,则应使用动作记录的search_view_id字段链接。

该操作还可以通过context 字段设置搜索字段的默认值:表单search_default_field_name的上下文键将初始化所提供的值的field_name。搜索过滤器必须有一个可选的@name,具有默认值,并作为布尔值(默认情况下只能启用)。

练习

搜索视图

  1. 添加一个按钮来过滤当前用户在课程搜索视图中负责的课程。默认情况下选择它
  2. 添加一个按钮,由负责用户分组课程

openacademy/views/openacademy.xml

                <search><field name="name"/><field name="description"/><filter name="my_courses" string="My Courses"domain="[('responsible_id', '=', uid)]"/><group string="Group By"><filter name="by_responsible" string="Responsible"context="{'group_by': 'responsible_id'}"/></group></search></field></record>
            <field name="res_model">openacademy.course</field><field name="view_type">form</field><field name="view_mode">tree,form</field><field name="context" eval="{'search_default_my_courses': 1}"/><field name="help" type="html"><p class="oe_view_nocontent_create">Create the first course</p>

甘特图视图

警告

甘特图视图需要在企业版版本中存在的web_gantt模块。

水平柱状图通常用于显示项目规划和进度,它们的根元素是<gantt>

<gantt string="Ideas"date_start="invent_date"date_stop="date_finished"progress="progress"default_group_by="inventor_id" />

练习

甘特图

添加甘特图,使用户能够查看与Open Academy模块相关联的session调度。session应由instructor分组。

  1. 创建一个以小时为单位表示session持续时间的计算字
  2. 添加甘特图视图的定义,并将甘特图视图添加到Session模型的操作中

openacademy/models.py

    end_date = fields.Date(string="End Date", store=True,compute='_get_end_date', inverse='_set_end_date')hours = fields.Float(string="Duration in hours",compute='_get_hours', inverse='_set_hours')@api.depends('seats', 'attendee_ids')def _taken_seats(self):for r in self:
            end_date = fields.Datetime.from_string(r.end_date)r.duration = (end_date - start_date).days + 1@api.depends('duration')def _get_hours(self):for r in self:r.hours = r.duration * 24def _set_hours(self):for r in self:r.duration = r.hours / 24@api.constrains('instructor_id', 'attendee_ids')def _check_instructor_not_in_attendees(self):for r in self:

openacademy/views/openacademy.xml

            </field></record><record model="ir.ui.view" id="session_gantt_view"><field name="name">session.gantt</field><field name="model">openacademy.session</field><field name="arch" type="xml"><gantt string="Session Gantt" color="course_id"date_start="start_date" date_delay="hours"default_group_by='instructor_id'><field name="name"/></gantt></field></record><record model="ir.actions.act_window" id="session_list_action"><field name="name">Sessions</field><field name="res_model">openacademy.session</field><field name="view_type">form</field><field name="view_mode">tree,form,calendar,gantt</field></record><menuitem id="session_menu" name="Sessions"

图形视图

图形视图允许聚合的概述和模型的分析,它们的根元素是 <graph>

数据透视图(元素<pivot>)是一个多维表,它允许选择文件和维度来获得正确的聚合数据集,然后移动到更为图形化的概观。数据透视图共享与图视图相同的内容定义。

图形视图有4种显示模式,默认模式是使用@typ属性来选择的。

柱状图(默认的)

柱状图,第一维用于定义水平轴上的组,其他维度定义每个组内的聚合条。

默认情况下,柱状图是并排的,它们可以通过使用@stacked="True"在<graph>上进行堆叠

曲线图

二维线图

饼图

二维的饼图

图形视图包含具有强制@type属性的<field>值:

row (默认的)

默认情况下应聚合字段

measure

字段应该聚合而不是分组

<graph string="Total idea score by Inventor"><field name="inventor_id"/><field name="score" type="measure"/>
</graph>

警告

图形视图对数据库值执行聚合,它们不与非存储的计算字段一起使用

练习

图形视图

在Session对象中添加一个图形视图,它显示每个course中在柱形图表单下attendees的数量。

  1. 将attendees的数量作为存储的计算字段添加
  2. 然后添加相关视图

openacademy/models.py

    hours = fields.Float(string="Duration in hours",compute='_get_hours', inverse='_set_hours')attendees_count = fields.Integer(string="Attendees count", compute='_get_attendees_count', store=True)@api.depends('seats', 'attendee_ids')def _taken_seats(self):for r in self:
        for r in self:r.duration = r.hours / 24@api.depends('attendee_ids')def _get_attendees_count(self):for r in self:r.attendees_count = len(r.attendee_ids)@api.constrains('instructor_id', 'attendee_ids')def _check_instructor_not_in_attendees(self):for r in self:

openacademy/views/openacademy.xml

            </field></record><record model="ir.ui.view" id="openacademy_session_graph_view"><field name="name">openacademy.session.graph</field><field name="model">openacademy.session</field><field name="arch" type="xml"><graph string="Participations by Courses"><field name="course_id"/><field name="attendees_count" type="measure"/></graph></field></record><record model="ir.actions.act_window" id="session_list_action"><field name="name">Sessions</field><field name="res_model">openacademy.session</field><field name="view_type">form</field><field name="view_mode">tree,form,calendar,gantt,graph</field></record><menuitem id="session_menu" name="Sessions"

看板视图

用于组织任务、生产过程等。它们的根元素是 <kanban>

看板视图显示了一组可能列在列中的卡。每个卡代表一个记录,每个列代表聚合字段的值。

例如,项目任务可以按阶段(每个列是一个阶段),或由负责的(每个列是用户)来组织,等等。

看板视图将每个卡的结构定义为表单元素(包括基本HTML)和QWEB的混合。

练习

看板视图

添加一个看板视图,显示按course分组的sessions(列是course)。

  1. Session模型添加整数color字段
  2. 添加看板视图并更新动作

openacademy/models.py

    duration = fields.Float(digits=(6, 2), help="Duration in days")seats = fields.Integer(string="Number of seats")active = fields.Boolean(default=True)color = fields.Integer()instructor_id = fields.Many2one('res.partner', string="Instructor",domain=['|', ('instructor', '=', True),

openacademy/views/openacademy.xml

        </record><record model="ir.ui.view" id="view_openacad_session_kanban"><field name="name">openacad.session.kanban</field><field name="model">openacademy.session</field><field name="arch" type="xml"><kanban default_group_by="course_id"><field name="color"/><templates><t t-name="kanban-box"><divt-attf-class="oe_kanban_color_{{kanban_getcolor(record.color.raw_value)}}oe_kanban_global_click_edit oe_semantic_html_overrideoe_kanban_card {{record.group_fancy==1 ? 'oe_kanban_card_fancy' : ''}}"><div class="oe_dropdown_kanban"><!-- dropdown menu --><div class="oe_dropdown_toggle"><i class="fa fa-bars fa-lg"/><ul class="oe_dropdown_menu"><li><a type="delete">Delete</a></li><li><ul class="oe_kanban_colorpicker"data-field="color"/></li></ul></div><div class="oe_clear"></div></div><div t-attf-class="oe_kanban_content"><!-- title -->Session name:<field name="name"/><br/>Start date:<field name="start_date"/><br/>duration:<field name="duration"/></div></div></t></templates></kanban></field></record><record model="ir.actions.act_window" id="session_list_action"><field name="name">Sessions</field><field name="res_model">openacademy.session</field><field name="view_type">form</field><field name="view_mode">tree,form,calendar,gantt,graph,kanban</field></record><menuitem id="session_menu" name="Sessions"parent="openacademy_menu"

工作流

工作流是与描述其动态的业务对象相关联的模型。工作流也用于跟踪随着时间推移而演变的进程。

练习

工作流

添加一个state 字段到Session模型。它将被用来定义一个workflow-ish。

sesion可以有三种可能的状态:Draft (默认的), Confirmed 和Done。

在session表单中,添加一个(只读)字段来可视化状态,以及按钮来更改状态。有效的转换是:

  • Draft -> Confirmed
  • Confirmed -> Draft
  • Confirmed -> Done
  • Done -> Draft
  1. 添加一个新的 state 字段
  2. 添加状态转换方法,可以从视图按钮调用这些方法来改变记录的状态
  3. 并将相关按钮添加到会话的表单视图中

openacademy/models.py

    attendees_count = fields.Integer(string="Attendees count", compute='_get_attendees_count', store=True)state = fields.Selection([('draft', "Draft"),('confirmed', "Confirmed"),('done', "Done"),], default='draft')@api.multidef action_draft(self):self.state = 'draft'@api.multidef action_confirm(self):self.state = 'confirmed'@api.multidef action_done(self):self.state = 'done'@api.depends('seats', 'attendee_ids')def _taken_seats(self):for r in self:

openacademy/views/openacademy.xml

            <field name="model">openacademy.session</field><field name="arch" type="xml"><form string="Session Form"><header><button name="action_draft" type="object"string="Reset to draft"states="confirmed,done"/><button name="action_confirm" type="object"string="Confirm" states="draft"class="oe_highlight"/><button name="action_done" type="object"string="Mark as done" states="confirmed"class="oe_highlight"/><field name="state" widget="statusbar"/></header><sheet><group><group string="General">

工作流可以与Odoo中的任何对象相关联,并且是完全可定制的。工作流用于构造和管理业务对象和文档的生命周期,并用图形工具定义转换、触发器等。像往常一样,工作流、活动(节点或动作)和转换(条件)被声明为XML记录。在工作流中导航的令牌被称为工作项。

警告

与模型相关联的工作流仅在创建模型的记录时创建。因此,在工作流定义之前没有与工作流实例相关联的工作流实例

练习

工作流

通过实际工作流替换特设Session工作流。转换Session 表单视图,因此它的按钮调用工作流而不是模型的方法。

openacademy/__manifest__.py

        'templates.xml','views/openacademy.xml','views/partner.xml','views/session_workflow.xml',],# only loaded in demonstration mode'demo': [

openacademy/models.py

        ('draft', "Draft"),('confirmed', "Confirmed"),('done', "Done"),])@api.multidef action_draft(self):

openacademy/views/openacademy.xml

            <field name="arch" type="xml"><form string="Session Form"><header><button name="draft" type="workflow"string="Reset to draft"states="confirmed,done"/><button name="confirm" type="workflow"string="Confirm" states="draft"class="oe_highlight"/><button name="done" type="workflow"string="Mark as done" states="confirmed"class="oe_highlight"/><field name="state" widget="statusbar"/>

openacademy/views/session_workflow.xml

<odoo><data><record model="workflow" id="wkf_session"><field name="name">OpenAcademy sessions workflow</field><field name="osv">openacademy.session</field><field name="on_create">True</field></record><record model="workflow.activity" id="draft"><field name="name">Draft</field><field name="wkf_id" ref="wkf_session"/><field name="flow_start" eval="True"/><field name="kind">function</field><field name="action">action_draft()</field></record><record model="workflow.activity" id="confirmed"><field name="name">Confirmed</field><field name="wkf_id" ref="wkf_session"/><field name="kind">function</field><field name="action">action_confirm()</field></record><record model="workflow.activity" id="done"><field name="name">Done</field><field name="wkf_id" ref="wkf_session"/><field name="kind">function</field><field name="action">action_done()</field></record><record model="workflow.transition" id="session_draft_to_confirmed"><field name="act_from" ref="draft"/><field name="act_to" ref="confirmed"/><field name="signal">confirm</field></record><record model="workflow.transition" id="session_confirmed_to_draft"><field name="act_from" ref="confirmed"/><field name="act_to" ref="draft"/><field name="signal">draft</field></record><record model="workflow.transition" id="session_done_to_draft"><field name="act_from" ref="done"/><field name="act_to" ref="draft"/><field name="signal">draft</field></record><record model="workflow.transition" id="session_confirmed_to_done"><field name="act_from" ref="confirmed"/><field name="act_to" ref="done"/><field name="signal">done</field></record></data>
</odoo>

提示

为了检查工作流的实例是否与session一起正确创建,可以到设置‣ 技术‣ 工作流‣ 实例中查看

练习

自动转换

当超过一半的session席位被使用时,自动将session从Draft 转换为Confirmed 。

openacademy/views/session_workflow.xml

            <field name="act_to" ref="done"/><field name="signal">done</field></record><record model="workflow.transition" id="session_auto_confirm_half_filled"><field name="act_from" ref="draft"/><field name="act_to" ref="confirmed"/><field name="condition">taken_seats &gt; 50</field></record></data>
</odoo>

练习

服务器动作

替换Python方法,通过服务器操作同步session状态。

工作流和服务器操作都可以完全由UI创建。

openacademy/views/session_workflow.xml

            <field name="on_create">True</field></record><record model="ir.actions.server" id="set_session_to_draft"><field name="name">Set session to Draft</field><field name="model_id" ref="model_openacademy_session"/><field name="code">
model.search([('id', 'in', context['active_ids'])]).action_draft()</field></record><record model="workflow.activity" id="draft"><field name="name">Draft</field><field name="wkf_id" ref="wkf_session"/><field name="flow_start" eval="True"/><field name="kind">dummy</field><field name="action"></field><field name="action_id" ref="set_session_to_draft"/></record><record model="ir.actions.server" id="set_session_to_confirmed"><field name="name">Set session to Confirmed</field><field name="model_id" ref="model_openacademy_session"/><field name="code">
model.search([('id', 'in', context['active_ids'])]).action_confirm()</field></record><record model="workflow.activity" id="confirmed"><field name="name">Confirmed</field><field name="wkf_id" ref="wkf_session"/><field name="kind">dummy</field><field name="action"></field><field name="action_id" ref="set_session_to_confirmed"/></record><record model="ir.actions.server" id="set_session_to_done"><field name="name">Set session to Done</field><field name="model_id" ref="model_openacademy_session"/><field name="code">
model.search([('id', 'in', context['active_ids'])]).action_done()</field></record><record model="workflow.activity" id="done"><field name="name">Done</field><field name="wkf_id" ref="wkf_session"/><field name="kind">dummy</field><field name="action"></field><field name="action_id" ref="set_session_to_done"/></record><record model="workflow.transition" id="session_draft_to_confirmed">

安全性

访问控制机制必须被配置来实现一致的安全策略。

基于组的访问控制机制

在模型res.groups上创建组作为正常记录,并通过菜单定义授予菜单访问权限。然而,即使没有菜单,对象仍然可以被间接访问,因此必须为组定义实际的对象级权限(读、写、创建、断开)。它们通常通过CSV文件插入模块内部。还可以使用字段的组属性限制对视图或对象的特定字段的访问。

访问权限

访问权限被定义为ir.model.access模型的记录。每个访问权限与一个模型、一个组(或者没有全局访问组)以及一组权限相关:读、写、创建、解锁。这样的访问权限通常由以其模型ir.model.access.csv命名的CSV文件创建:

id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_idea_idea,idea.idea,model_idea_idea,base.group_user,1,1,1,0
access_idea_vote,idea.vote,model_idea_vote,base.group_user,1,1,1,0

练习

通过Odoo 接口添加访问控制

创建新用户"John Smith",然后创建一个组"OpenAcademy / Session Read",其具有对Session 模型的读访问权限。

  1. 通过设置 ‣ 用户 ‣ 用户 创建新用户John Smith
  2. 通过设置 ‣ 用户 ‣ 群组创建一个新的组session_read,它应该具有Session模型上的读访问权限
  3. 编辑John Smith使他们成为session_read成员
  4. 使用John Smith登录 检查访问权限是否正确

练习

通过模块中的数据文件添加访问控制

使用数据文件,

  • 创建一个完全访问所有OpenAcademy模型的OpenAcademy / Manager
  • 使Session和Course可由所有用户阅读
  1. 创建一个新文件openacademy/security/security.xml来保存OpenAcademy 管理器组
  2. 使用模型的访问权限编辑文件 openacademy/security/ir.model.access.csv
  3. 最后更新openacademy/__manifest__.py,将新的数据文件添加到它里面

openacademy/__manifest__.py

    # always loaded'data': ['security/security.xml','security/ir.model.access.csv','templates.xml','views/openacademy.xml','views/partner.xml',

openacademy/security/ir.model.access.csv

id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
course_manager,course manager,model_openacademy_course,group_manager,1,1,1,1
session_manager,session manager,model_openacademy_session,group_manager,1,1,1,1
course_read_all,course all,model_openacademy_course,,1,0,0,0
session_read_all,session all,model_openacademy_session,,1,0,0,0

openacademy/security/security.xml

<odoo><data><record id="group_manager" model="res.groups"><field name="name">OpenAcademy / Manager</field></record></data>
</odoo>

记录规则

记录规则限制对给定模型的记录子集的访问权限。规则是模型ir.rule的记录,与模型、多个组(many2many 字段)、限制应用的权限以及域相关联。指定访问权限受限的记录的域。

下面是一个防止删除不处于状态cancel的引线的规则的示例。请注意,字段groups 的值必须遵循与ORM方法write()相同的约定。

<record id="delete_cancelled_only" model="ir.rule"><field name="name">Only cancelled leads may be deleted</field><field name="model_id" ref="crm.model_crm_lead"/><field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/><field name="perm_read" eval="0"/><field name="perm_write" eval="0"/><field name="perm_create" eval="0"/><field name="perm_unlink" eval="1" /><field name="domain_force">[('state','=','cancel')]</field>
</record>

练习

记录规则

为模型Course和"OpenAcademy / Manager"组添加记录规则,该规则限制writeunlink访问course的responsible。如果course没有responsible,小组的所有用户必须能够修改它。

在openacademy/security/security.xml中创建新规则:

openacademy/security/security.xml

        <record id="group_manager" model="res.groups"><field name="name">OpenAcademy / Manager</field></record><record id="only_responsible_can_modify" model="ir.rule"><field name="name">Only Responsible can modify Course</field><field name="model_id" ref="model_openacademy_course"/><field name="groups" eval="[(4, ref('openacademy.group_manager'))]"/><field name="perm_read" eval="0"/><field name="perm_write" eval="1"/><field name="perm_create" eval="0"/><field name="perm_unlink" eval="1"/><field name="domain_force">['|', ('responsible_id','=',False),('responsible_id','=',user.id)]</field></record></data>
</odoo>

向导

向导通过动态表单描述与用户(或对话框)的交互会话。向导只是一个扩展类TransientModel而不是Model的模型。类TransientModel扩展了Model并重用其现有的机制,具有以下特殊性:

  • 向导记录不意味着持久性;它们在一定时间后自动从数据库中删除。这就是为什么它们被称为瞬变的原因
  • 向导模型不需要显式访问权限:用户拥有向导记录的所有权限。
  • 向导记录可以通过many2one字段引用常规记录或向导记录,但常规记录不能通过many2one字段引用向导记录

我们希望创建一个向导,允许用户为特定sessio创建attendees,或者同时为session列表创建向导。

练习

定义向导

创建一个与Session模型具有many2one关系的向导模型,以及与Partner模型具有many2many 的关系。

添加新文件 openacademy/wizard.py:

openacademy/__init__.py

from . import controllers
from . import models
from . import partner
from . import wizard

openacademy/wizard.py

# -*- coding: utf-8 -*-from odoo import models, fields, apiclass Wizard(models.TransientModel):_name = 'openacademy.wizard'session_id = fields.Many2one('openacademy.session',string="Session", required=True)attendee_ids = fields.Many2many('res.partner', string="Attendees")

启动向导

向导是由ir.actions.act_window记录发起的,其中字段target 设置为值new。后者将向导视图打开到弹出窗口中。动作可以由菜单项触发。

还有另一种方法来启动向导:使用像上面那样的ir.actions.act_window记录,但是有一个额外的字段src_model,它指定了在哪个模型的上下文中该动作是可用的。向导将出现在模型的上下文动作中,位于主视图的上方。由于ORM中的一些内部钩子,这样的动作用标签act_window声明为XML。

<act_window id="launch_the_wizard"name="Launch the Wizard"src_model="context.model.name"res_model="wizard.model.name"view_mode="form"target="new"key2="client_action_multi"/>

向导使用常规视图,它们的按钮可以使用属性special="cancel" 关闭向导窗口而不保存。

练习

启动向导

  1. 为向导定义表单视图
  2. 添加动作以在Session模型的上下文中启动它
  3. 在向导中定义session字段的默认值;使用上下文参数self._context检索当前session

openacademy/wizard.py

class Wizard(models.TransientModel):_name = 'openacademy.wizard'def _default_session(self):return self.env['openacademy.session'].browse(self._context.get('active_id'))session_id = fields.Many2one('openacademy.session',string="Session", required=True, default=_default_session)attendee_ids = fields.Many2many('res.partner', string="Attendees")

openacademy/views/openacademy.xml

        <menuitem id="session_menu" name="Sessions"parent="openacademy_menu"action="session_list_action"/><record model="ir.ui.view" id="wizard_form_view"><field name="name">wizard.form</field><field name="model">openacademy.wizard</field><field name="arch" type="xml"><form string="Add Attendees"><group><field name="session_id"/><field name="attendee_ids"/></group></form></field></record><act_window id="launch_session_wizard"name="Add Attendees"src_model="openacademy.session"res_model="openacademy.wizard"view_mode="form"target="new"key2="client_action_multi"/></data>
</odoo>

练习

注册attendees

向向导添加按钮,并实现将attendees 添加到给定session的相应方法

openacademy/views/openacademy.xml

                        <field name="attendee_ids"/></group><footer><button name="subscribe" type="object"string="Subscribe" class="oe_highlight"/>or<button special="cancel" string="Cancel"/></footer></form></field></record>

openacademy/wizard.py

    session_id = fields.Many2one('openacademy.session',string="Session", required=True, default=_default_session)attendee_ids = fields.Many2many('res.partner', string="Attendees")@api.multidef subscribe(self):self.session_id.attendee_ids |= self.attendee_idsreturn {}

练习

注册参会者多个会话

修改向导模型,以便参会者可以注册到多个会话

openacademy/views/openacademy.xml

            <field name="arch" type="xml"><form string="Add Attendees"><group><field name="session_ids"/><field name="attendee_ids"/></group><footer>

openacademy/wizard.py

class Wizard(models.TransientModel):_name = 'openacademy.wizard'def _default_sessions(self):return self.env['openacademy.session'].browse(self._context.get('active_ids'))session_ids = fields.Many2many('openacademy.session',string="Sessions", required=True, default=_default_sessions)attendee_ids = fields.Many2many('res.partner', string="Attendees")@api.multidef subscribe(self):for session in self.session_ids:session.attendee_ids |= self.attendee_idsreturn {}

国际化

每个模块都可以在i18n目录内提供自己的翻译,通过将文件命名为LANG.po,其中LANG是语言的区域代码,或者当它们不同时,语言和国家组合(例如pt.po或者pt_BR.po)。对于所有启用的语言,翻译将由Odoo自动加载。开发人员在创建模块时总是使用英语,然后使用Odoo的获取文本POT导出功能(设置‣ 翻译‣ 导入/导出‣ 导出翻译 不指定语言)导出模块术语,以创建模块模板POT文件,然后导出翻译的PO文件。许多IDE有用于编辑和合并PO/POT文件的插件或模型。

提示

由Odoo生成的轻便对象文件在Transifex上发布,使其易于翻译软件

|- idea/ # The module directory|- i18n/ # Translation files| - idea.pot # Translation Template (exported from Odoo)| - fr.po # French translation| - pt_BR.po # Brazilian Portuguese translation| (...)

提示

默认情况下,Odoo的POT导出只在Python代码中提取XML文件内的标签或字段定义,但是任何Python字符串可以通过将其与函数odoo._()(例如 _("Label"))包围起来来进行翻译

练习

翻译模块

为您的Odoo安装选择第二种语言。使用Odoo提供的工具翻译您的模块。(以下步骤需激活开发者模式)

  1. 新建目录 openacademy/i18n/
  2. 安装你想要的任何语言( 翻译‣ 加载翻译)
  3. 同步可译术语(翻译‣ 应用程序术语 ‣ 同步术语)
  4. 在不指定语言的情况下通过导出创建模板翻译文件(  翻译‣ 导入/导出 ‣ 导出翻译) ,请保存在openacademy/i18n/
  5. 通过导出和(翻译 ‣ 导入/导出 ‣ 导出翻译)指定语言来创建翻译文件。请保存到 openacademy/i18n/
  6. 用基本的文本编辑器或专用的PO-file编辑器打开导出的翻译文件,例如POEdit并翻译缺失的术语
  7. 在models.py中,为函数odoo._添加一个导入语句,并将缺少的字符串作为可翻译的标记
  8. 重复步骤3-6

openacademy/models.py

# -*- coding: utf-8 -*-from datetime import timedelta
from odoo import models, fields, api, exceptions, _class Course(models.Model):_name = 'openacademy.course'
        default = dict(default or {})copied_count = self.search_count([('name', '=like', _(u"Copy of {}%").format(self.name))])if not copied_count:new_name = _(u"Copy of {}").format(self.name)else:new_name = _(u"Copy of {} ({})").format(self.name, copied_count)default['name'] = new_namereturn super(Course, self).copy(default)
        if self.seats < 0:return {'warning': {'title': _("Incorrect 'seats' value"),'message': _("The number of available seats may not be negative"),},}if self.seats < len(self.attendee_ids):return {'warning': {'title': _("Too many attendees"),'message': _("Increase seats or remove excess attendees"),},}
    def _check_instructor_not_in_attendees(self):for r in self:if r.instructor_id and r.instructor_id in r.attendee_ids:raise exceptions.ValidationError(_("A session's instructor can't be an attendee"))

报表

打印报表

Odoo 8附带了一个基于QWeb、Twitter Bootstrap和Wkhtmltopdf的新报表引擎。

报表是两个要素的组合:

  • 一个ir.actions.report.xml,它提供了一个<report>快捷方式元素,它为报表建立了各种基本参数(默认类型,报告是否应该在生成后保存到数据库中,…)

    <reportid="account_invoices"model="account.invoice"string="Invoices"report_type="qweb-pdf"name="account.report_invoice"file="account.report_invoice"attachment_use="True"attachment="(object.state in ('open','paid')) and('INV'+(object.number or '').replace('/','')+'.pdf')"
    />
    
  • 实际报表的标准QWeb view:

    <t t-call="report.html_container"><t t-foreach="docs" t-as="o"><t t-call="report.external_layout"><div class="page"><h2>Report title</h2></div></t></t>
    </t>the standard rendering context provides a number of elements, the most
    important being:``docs``the records for which the report is printed
    ``user``the user printing the report
    

因为报表是标准网页,它们可以通过URL获得,并且可以通过该URL操作输出参数,例如,通过http://localhost:8069/report/html/account.report_invoice/1(如果安装了account)和通过http://localhost:8069/report/pdf/account.report_invoice/1的PDF版本,可以提供发票报表的HTML版本。

危险

如果您的PDF报表缺少样式(即文本出现,但样式/布局与HTML版本不同),可能您的wkhtmltopdf进程无法到达Web服务器下载。

如果在生成PDF报表时检查服务器日志并发现CSS样式没有被下载,则这肯定是问题所在。

wkhtmltopdf 进程将使用web.base.url系统参数作为所有链接文件的根路径,但是每次管理员登录时,该参数都会自动更新。如果您的服务器驻留在某种代理后面,则无法到达。您可以通过添加这些系统参数中的一个来修复:

  • report.url, 指向一个可从服务器访问的URL (可能是http://localhost:8069或类似的东西)。它只用于这个特殊目的
  • web.base.url.freeze, 设置为True, 将停止自动更新 web.base.url

练习

为session模型创建报表

对于每个会话,它应该显示会话的名称、它的开始和结束,并列出会话的参与者。

openacademy/__manifest__.py

    'version': '0.1',# any module necessary for this one to work correctly'depends': ['base', 'report'],# always loaded'data': [

openacademy/__manifest__.py

        'views/openacademy.xml','views/partner.xml','views/session_workflow.xml','reports.xml',],# only loaded in demonstration mode'demo': [

openacademy/reports.xml

<odoo>
<data><reportid="report_session"model="openacademy.session"string="Session Report"name="openacademy.report_session_view"file="openacademy.report_session"report_type="qweb-pdf" /><template id="report_session_view"><t t-call="report.html_container"><t t-foreach="docs" t-as="doc"><t t-call="report.external_layout"><div class="page"><h2 t-field="doc.name"/><p>From <span t-field="doc.start_date"/> to <span t-field="doc.end_date"/></p><h3>Attendees:</h3><ul><t t-foreach="doc.attendee_ids" t-as="attendee"><li><span t-field="attendee.name"/></li></t></ul></div></t></t></t></template>
</data>
</odoo>

仪表板

练习

定义仪表板

定义一个仪表板,其中包含您创建的图形视图、会话日历视图和课程列表视图(可切换到表单视图)。此仪表板应通过菜单中的菜单项提供,并在选择OpenAcademy主菜单时自动显示在Web客户端中

注:此模块需要您odoo已经安装了系统的仪表板模块,此练习是菜单附加在系统仪表板中。

  1. 新建文件 openacademy/views/session_board.xml。它应该包含板视图、在该视图中引用的动作、打开仪表板的动作以及重新定义主菜单项以添加仪表板动作

    可用的仪表板样式是 11-11-22-1 和1-1-1

  2. 更新openacademy/__manifest__.py 以引用新的数据文件

openacademy/__manifest__.py

    'version': '0.1',# any module necessary for this one to work correctly'depends': ['base', 'report', 'board'],# always loaded'data': [
        'views/openacademy.xml','views/partner.xml','views/session_workflow.xml','views/session_board.xml','reports.xml',],# only loaded in demonstration mode

openacademy/views/session_board.xml

<?xml version="1.0"?>
<odoo><data><record model="ir.actions.act_window" id="act_session_graph"><field name="name">Attendees by course</field><field name="res_model">openacademy.session</field><field name="view_type">form</field><field name="view_mode">graph</field><field name="view_id"ref="openacademy.openacademy_session_graph_view"/></record><record model="ir.actions.act_window" id="act_session_calendar"><field name="name">Sessions</field><field name="res_model">openacademy.session</field><field name="view_type">form</field><field name="view_mode">calendar</field><field name="view_id" ref="openacademy.session_calendar_view"/></record><record model="ir.actions.act_window" id="act_course_list"><field name="name">Courses</field><field name="res_model">openacademy.course</field><field name="view_type">form</field><field name="view_mode">tree,form</field></record><record model="ir.ui.view" id="board_session_form"><field name="name">Session Dashboard Form</field><field name="model">board.board</field><field name="type">form</field><field name="arch" type="xml"><form string="Session Dashboard"><board style="2-1"><column><actionstring="Attendees by course"name="%(act_session_graph)d"height="150"width="510"/><actionstring="Sessions"name="%(act_session_calendar)d"/></column><column><actionstring="Courses"name="%(act_course_list)d"/></column></board></form></field></record><record model="ir.actions.act_window" id="open_board_session"><field name="name">Session Dashboard</field><field name="res_model">board.board</field><field name="view_type">form</field><field name="view_mode">form</field><field name="usage">menu</field><field name="view_id" ref="board_session_form"/></record><menuitemname="Session Dashboard" parent="base.menu_reporting_dashboard"action="open_board_session"sequence="1"id="menu_board_session" icon="terp-graph"/></data>
</odoo>

WebServices

Web服务模块为所有Web服务提供一个公共接口:

  • XML-RPC
  • JSON-RPC

业务对象也可以通过分布式对象机制访问。它们都可以通过与上下文视图的客户端接口进行修改。

Odoo 可通过XML-RPC/JSON-RPC接口访问,其中库以多种语言存在。

XML-RPC库

下面的例子是一个Python程序,它与Odoo服务器与xmlrpclib库交互:

import xmlrpclibroot = 'http://%s:%d/xmlrpc/' % (HOST, PORT)uid = xmlrpclib.ServerProxy(root + 'common').login(DB, USER, PASS)
print "Logged in as %s (uid: %d)" % (USER, uid)# Create a new note
sock = xmlrpclib.ServerProxy(root + 'object')
args = {'color' : 8,'memo' : 'This is a note','create_uid': uid,
}
note_id = sock.execute(DB, uid, PASS, 'note.note', 'create', args)

练习

向客户端添加新服务

编写一个Python程序,可以发送XML-RPC/请求给运行Odoo的PC (你的,或者你的指导者的)。这个程序应该显示所有的sessions,以及它们对应的seats数目。它还应该为一个course创建一个新的session

import functools
import xmlrpclib
HOST = 'localhost'
PORT = 8069
DB = 'openacademy'
USER = 'admin'
PASS = 'admin'
ROOT = 'http://%s:%d/xmlrpc/' % (HOST,PORT)# 1. Login
uid = xmlrpclib.ServerProxy(ROOT + 'common').login(DB,USER,PASS)
print "Logged in as %s (uid:%d)" % (USER,uid)call = functools.partial(xmlrpclib.ServerProxy(ROOT + 'object').execute,DB, uid, PASS)# 2. Read the sessions
sessions = call('openacademy.session','search_read', [], ['name','seats'])
for session in sessions:print "Session %s (%s seats)" % (session['name'], session['seats'])
# 3.create a new session
session_id = call('openacademy.session', 'create', {'name' : 'My session','course_id' : 2,
})

代替使用硬编码的课程ID,代码可以按名称查找课程:

# 3.create a new session for the "Functional" course
course_id = call('openacademy.course', 'search', [('name','ilike','Functional')])[0]
session_id = call('openacademy.session', 'create', {'name' : 'My session','course_id' : course_id,
})

JSON-RPC库

下面的示例是一个Python程序,它与标准的Python库urllib2和json交互,与Odoo服务器交互:

import json
import random
import urllib2def json_rpc(url, method, params):data = {"jsonrpc": "2.0","method": method,"params": params,"id": random.randint(0, 1000000000),}req = urllib2.Request(url=url, data=json.dumps(data), headers={"Content-Type":"application/json",})reply = json.load(urllib2.urlopen(req))if reply.get("error"):raise Exception(reply["error"])return reply["result"]def call(url, service, method, *args):return json_rpc(url, "call", {"service": service, "method": method, "args": args})# log in the given database
url = "http://%s:%s/jsonrpc" % (HOST, PORT)
uid = call(url, "common", "login", DB, USER, PASS)# create a new note
args = {'color' : 8,'memo' : 'This is another note','create_uid': uid,
}
note_id = call(url, "object", "execute", DB, uid, PASS, 'note.note', 'create', args)

这里是同一个程序,使用jsonrpclib库:

import jsonrpclib# server proxy object
url = "http://%s:%s/jsonrpc" % (HOST, PORT)
server = jsonrpclib.Server(url)# log in the given database
uid = server.call(service="common", method="login", args=[DB, USER, PASS])# helper function for invoking model methods
def invoke(model, method, *args):args = [DB, uid, PASS, model, method] + list(args)return server.call(service="object", method="execute", args=args)# create a new note
args = {'color' : 8,'memo' : 'This is another note','create_uid': uid,
}
note_id = invoke('note.note', 'create', args)

实例可以很容易地从XML- RPC适应JSON-RPC。

在不显式地通过XML- RPC或JSON-RPC的情况下,在各种语言中有许多高级API来访问Odoo系统,例如:

  • https://github.com/akretion/ooor
  • https://github.com/syleam/openobject-library
  • https://github.com/nicolas-van/openerp-client-lib
  • http://pythonhosted.org/OdooRPC
  • https://github.com/abhishek-jaiswal/php-openerp-lib

上一篇:Odoo10教程---模块化二:模型间关系,继承,计算字段等

代码下载地址:https://download.csdn.net/download/mzl87/10405778

ps:有翻译不当之处,欢迎留言指正。

原文地址:https://www.odoo.com/documentation/10.0/howtos/backend.html

Odoo10教程---模块化三:模型约束,高级视图,工作流,安全性,向导,国际化和报表等相关推荐

  1. Odoo10教程---模块化一:新建一个模块及基本视图

    开启/关闭Odoo服务器 Odoo使用客户端/服务器体系结构,客户端是通过RPC访问Odoo 服务器的Web浏览器. 业务逻辑和扩展通常在服务器端执行,尽管支持客户端特征(例如像交互式地图的新数据表示 ...

  2. odoo10参考系列--视图三(其他高级视图)

    图表 图表视图用于在多个记录或记录组上可视化聚合视图.它的根元素是<graph> ,可以有以下属性: type 使用bar (默认的), pie 和line三个中的一个图表类型 stack ...

  3. Odoo10教程---模块化二:模型间关系,继承,计算字段等

    模型之间的关系 来自模型的记录可能与来自另一模型的记录相关.例如,一个销售订单记录和一个包含客户数据的客户记录相关:同时也和销售订单线记录相关. 练习 创建会话模型 对于模块Open Academy, ...

  4. 软考 程序员教程-第三章 数据库基础知识

    软考 程序员教程-第三章 数据库基础知识 第三章 数据库基础知识 3.1 基本概念 数据库系统(DataBase System,DBS)由数据库(DataBase,DB).硬件.软件和人员4大部分组成 ...

  5. 黑马lavarel教程---5、模型操作(AR模式)

    黑马lavarel教程---5.模型操作(AR模式) 一.总结 一句话总结: AR: ActiveRecord :Active Record(活动记录),是一种领域模型模式,特点是一个模型类对应关系型 ...

  6. Laravel大型项目系列教程(三)之发表文章

    Laravel大型项目系列教程(三)之发表文章 一.前言 上一节教程中完成了用户管理,这节教程将大概完成发表Markdown格式文章并展示的功能. 二.Let's go 1.数据库迁移 文章模块中我们 ...

  7. pve安装黑群晖直通硬盘_蜗牛星际装机教程篇三:手把手教你安装黑群晖NAS

    原文作者:范俩仟 蜗牛星际装机教程篇三:手把手教你安装黑群晖NAS 有很多评论说软路由没必要,我想说我就是把这东西当个玩意儿来消遣的,您喜欢看电影,我就喜欢玩电子产品.还有就是我没钱,所以想花最少的钱 ...

  8. v_rep教程----构建纯净模型

    v_rep教程----构建纯净模型 构建可见形状: 建立关节 模型定义 本教程将指导您逐步建立一个纯净的模拟模型,一个机器人,或任何其他项目.得到一个美观.快速显示.快速仿真和稳定的仿真模型,这是一个 ...

  9. UML教程1:模型图的构成和功能说明

    文章目录 大纲 一.前言 1.1 UML概述 1.1.1 UML简介 1.1.2 UML模型图的构成 1.2 UML事物 1.2.1 构件事物 1.2.2 行为事物 1.2.3 分组事物 1.2.4 ...

最新文章

  1. 数据库系统——B+树索引
  2. 配置windows驱动开发环境
  3. 静态代理设计与动态代理设计
  4. 销售流程图_工作流程图网络图模板,招聘销售合同库存,完整设计拿来就用
  5. ASP.NET Atlas简单控件介绍——Sys.Component基类与Sys.UI.Control基类
  6. 京东壕掷27亿买下一座酒店 官方回应:以办公为主!
  7. IAR执行到断点处不能单步运行解决方法
  8. 【报告分享】2020直播电商行业研究报告.pdf(附下载链接)
  9. php 保存错误日志,PHP中把错误日志保存在系统日志中_PHP教程
  10. bzoj 3504: [Cqoi2014]危桥(最大流)
  11. php基础案例 购物车,php购物车实例(1/5)_PHP教程
  12. 7-24 说反话-加强版 (20 分)
  13. Mysql中,int(10)和int(11)的区别
  14. VJC中机器人走多边形转向角度、速度、时间数值的计算
  15. 云计算机技术与应用学什么的,云计算技术与应用专业学什么 主要课程
  16. 南师大GIS考研数据库2019年第五题
  17. Tiny6410+K9GAG08U0E
  18. 搭建云服务器简单流程【华为云服务器】
  19. Leo写的前言 自由与规则—— 程序员羊皮卷 连载 8
  20. 【计算机架构】什么是实时操作系统、什么是非实时操作系统;实时操作系统和非实时操作系统的区别

热门文章

  1. 设计灵感|清爽直观的仪表盘设计,转化繁杂数据
  2. 圣诞节海报设计还没开始?感受下合适的节日感PSD模板
  3. python读取数据库中指定内容_python如何用正则表达式读取对应字段写入数据库中?...
  4. C++数据结构03--静态链式线性表的实现
  5. VisualStudio2019 DLL生成并使用教程(C++)最详细Demo教程
  6. qt 将相应字符写入txt文件
  7. 单链表的实现操作(C语言)
  8. WEB的发展历程、Tomcat服务器、与HTTP协议
  9. Kernel Crash kdump 使用指南
  10. 2021年五月下旬推荐文章