• odoo模块的创建 openacademy学习笔记


    odoo -u academy -d academy
    

    openacademy官网完整教程

    • 练习创建模块
      使用上面的命令行创建一个空模块Open Academy,并将其安装在Odoo中。
    odoo scaffold openacademy myaddons
    
    • 练习定义模型,
      在openacademy模块中定义新的数据模型课程,每门课程包含两个字段,标题和描述,其中标题是必填字段。编辑文件openacademy/models/models.py以包含Course类。

    openacademy/models/models.py

    from odoo import models, fields, api
    
    class Course(models.Model):
        #定义了两个字段name,description
        _name = 'openacademy.course'
    
        name = fields.Char(string="Title", required=True)
        description = fields.Text()
    
    
    • 练习定义演示数据,
      添加演示数据以填充Course模型的数据,编辑文件openacademy/demo/demo.xml来添加演示数据

    openacademy/demo/demo.xml

    <odoo>
        <data>
            <record model="openacademy.course" id="course0">
                <field name="name">Course 0</field>
                <field name="description">Course 0's description
    
    Can have multiple lines
                </field>
            </record>
            <record model="openacademy.course" id="course1">
                <field name="name">Course 1</field>
                <!-- no description for this one -->
            </record>
            <record model="openacademy.course" id="course2">
                <field name="name">Course 2</field>
                <field name="description">Course 2's description</field>
            </record>
        </data>
    </odoo>
    
    
    

    demo.xml如何起作用?

    必须添加到data中

        'data': [
            # 'security/ir.model.access.csv',
            'views/views.xml',
            'views/templates.xml',
            'demo/demo.xml',
        ],
    

    添加到demo中不起作用

    • 练习定义新菜单项,
      在开放学院菜单项下定义新菜单项来访问课程。用户应该能够:

    显示所有课程的列表
    建立或编辑课程
    1.建立openacademy/views/openacademy.xml以创建操作和能够触发操作的菜单项。
    2.添加这个文件到openacademy/__manifest__.py下的data列表。

    编写菜单(menuitem)和行为(action)

    <?xml version="1.0" encoding="UTF-8"?>
    <odoo>
        <data>
            <!-- window action -->
            <!--
                The following tag is an action definition for a "window action",
                that is an action opening a view or a set of views
            -->
            <record model="ir.actions.act_window" id="course_list_action">
                <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>
                <field name="help" type="html">
                    <p class="oe_view_nocontent_create">Create the first course
                    </p>
                </field>
            </record>
    
            <!-- top level menu: no parent -->
            <menuitem id="main_openacademy_menu" name="Open Academy"/>
            <!-- A first level in the left side menu is needed
                 before using action= attribute -->
            <menuitem id="openacademy_menu" name="Open Academy"
                      parent="main_openacademy_menu"/>
            <!-- the following menuitem should appear *after*
                 its parent openacademy_menu and *after* its
                 action course_list_action -->
            <menuitem id="courses_menu" name="Courses" parent="openacademy_menu"
                      action="course_list_action"/>
            <!-- Full id location:
                 action="openacademy.course_list_action"
                 It is not required when it is the same module -->
        </data>
    </odoo>
    
    
    • 练习使用XML定制窗体视图

    建立课程对象的表单视图,显示课程的名称和描述字段。

    • 练习notebook结构元素

    在课程的表单视图中,将描述字段放在一个选项卡中,然后再添加选项卡放置其它字段。修改后的课程表单视图如下:

    openacademy/views/openacademy.xml

      <!--form视图 Course-->
            <record model="ir.ui.view" id="view_form_openacademy_course">
                <field name="name">course.form</field>
                <field name="model">openacademy.course</field>
                <field name="arch" type="xml">
                    <form string="Course Form">
                        <sheet>
                            <group>
                                <field name="name"/>
                                <field name="description"/>
                            </group>
                            <!--notebook标签页-->
                            <notebook>
                                <!--描述-->
                                <page string="Description">
                                    <field name="description"/>
                                </page>
    
                            </notebook>
                        </sheet>
                    </form>
                </field>
            </record>
    

    notebook效果:

    当然也可以用纯HTML页面写

    • 练习搜索课程

    通过标题和描述来搜索课程。

       <!--搜索视图 Course-->
            <record model="ir.ui.view" id="course_search_view">
                <field name="name">course.search</field>
                <field name="model">openacademy.course</field>
                <field name="arch" type="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>
    
    
    • 练习建立一个授课模型

    在开放学院模块中,我们考虑一个授课模型:一个授课是在给定的时间中对给定的受众教授指定的课程。为授课建立模型,授课包括名称、开始时间、持续时间和席位数。添加操作和菜单项来显示新的模型。

    openacademy/models.py

    class Session(models.Model):
        _name = 'openacademy.session'
    
        name = fields.Char(required=True)
        start_date = fields.Date()
        duration = fields.Float(digits=(6, 2), help="Duration in days")
        seats = fields.Integer(string="Number of seats")
    
    
    

    openacademy/views/openacademy.xml

    <!-- session form view -->
            <record model="ir.ui.view" id="session_form_view">
                <field name="name">session.form</field>
                <field name="model">openacademy.session</field>
                <field name="arch" type="xml">
                    <form string="Session Form">
                        <sheet>
                            <group>
                                <field name="name"/>
                                <field name="start_date"/>
                                <field name="duration"/>
                                <field name="seats"/>
                            </group>
                        </sheet>
                    </form>
                </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</field>
            </record>
    
            <menuitem id="session_menu" name="Sessions"
                      parent="openacademy_menu"
                      action="session_list_action"/>
    
    
    • 练习Many2one关联

    编辑Course和Session模型以反映他们与其它模型的关联:

    • 课程有一个负责的用户;该字段的值是内置模型res.users的记录
    #class Course(models.Model)
      responsible_id = fields.Many2one('res.users',
                                         ondelete='set null', string="Responsible", index=True)
    
    • 一个授课有一个教师;该字段的值是内置模型res.partner的记录
    #class Session(models.Model)
    instructor_id = fields.Many2one('res.partner', string="Instructor")
    
    • 授课与课程相关;该字段的值是openacademy.course模型的记录,并且是必填项
    #class Session(models.Model)
    course_id = fields.Many2one('openacademy.course',
                                    ondelete='cascade', string="Course", required=True)
    
    • 在模型中添加Many2one关联,并在视图显示
    #form视图
            <record model="ir.ui.view" id="view_form_openacademy_course">
                <field name="name">course.form</field>
                <field name="model">openacademy.course</field>
                <field name="arch" type="xml">
                    <form string="Course Form">
                        <sheet>
                            <group>
                                <field name="name"/>
                                <field name="description"/>
                                <field name="responsible_id"/>
                            </group>
                            <!--notebook标签页-->
                            <notebook>
                                <!--描述-->
                                <page string="Description">
                                    <field name="description"/>
                                </page>
    
                            </notebook>
                        </sheet>
                    </form>
                </field>
            </record>
    

     <record model="ir.ui.view" id="course_tree_view">
                <field name="name">course.tree</field>
                <field name="model">openacademy.course</field>
                <field name="arch" type="xml">
                    <tree string="Course Tree">
                        <field name="name"/>
                        <field name="description"/>
                        <field name="responsible_id"/>
                    </tree>
                </field>
            </record>
    

    多对一关系

    关联字段

    Many2one(other_model, ondelete='set null')

    多对一字段

    一个链接到其它对象的简单示例是这样的:

    print foo.other_id.name
    

    一对多模型,通过for循环遍历

    这是一个虚拟的关联,是Many2one的逆,One2many作为记录的容器,访问它将返回一个记录集(也可能是一个空记录集):

    for other in foo.other_ids:
        print other.name
    
    • 练习逆关联One2many
      使用逆关联字段one2many,编辑模型以反映课程和授课之间的关系。

    • 编辑Course类,并且加入字段到它的表单视图

    #class Course(models.Model)
        session_ids = fields.One2many(
            'openacademy.session', 'course_id', string="Sessions")
    
        <!--form视图 Course-->
                 <record model="ir.ui.view" id="course_form_view">
                <field name="name">course.form</field>
                <field name="model">openacademy.course</field>
                <field name="arch" type="xml">
                    <form string="Course Form">
                        <sheet>
                            <group>
                                <field name="name"/>
                                <field name="responsible_id"/>
                            </group>
                            <!--notebook标签页-->
                            <notebook>
                                <!--描述-->
                                <page string="Description">
                                    <field name="description"/>
                                </page>
                                <!--域-->
                                <page string="Sessions">
                                    <field name="session_ids">
                                        <tree string="Registered sessions">
                                            <field name="name"/>
                                            <!--instructor_id授课教师与课程的捆绑-->
                                            <field name="instructor_id"/>
                                        </tree>
                                    </field>
                                </page>
                            </notebook>
                        </sheet>
                    </form>
                </field>
            </record>
    

    notebook中添加了session

    • 练习多对多关联many2many
      在授课模型中添加关联字段many2many,将每次授课和参与的听众做关联,听众来自于内置模型res.partner。相应的调整对应的视图。

    • 修改Session类并且加入字段到它的表单视图中

    # class Session(models.Model)
    attendee_ids = fields.Many2many('res.partner', string="Attendees")
    

    视图

      <record model="ir.ui.view" id="session_form_view">
                <field name="name">session.form</field>
                <field name="model">openacademy.session</field>
                <field name="arch" type="xml">
                    <form string="Session Form">
                        <sheet>
                            <group>
                                <field name="name"/>
                                <field name="start_date"/>
                                <field name="duration"/>
                                <field name="seats"/>
                            </group>
                            <!--授课和参与的听众做关联,听众来自于内置模型res.partner-->
                            <label for="attendee_ids"/>
                            <field name="attendee_ids"/>
                        </sheet>
                    </form>
                </field>
            </record>
    
    

    • 练习更改现有内容

    • 使用模型继承,修改现有partner模型,添加instructor布尔字段,以及对应表示"授课-讲师"关联的many2many字段

    • 使用视图继承在partner的表单视图中显示这个字段

    注意,这里是通过开发人员模式来查找视图外部ID并放置新字段的。

    • 创建文件openacademy/models/partner.py并将其导入__init__.py
    • 创建文件openacademy/views/partner.xml并将其添加到__manifest__.py

    res.partner表的路径

    odooodoo10odooaddonsase es es_partner.py

    odooodoo10odooaddonsproductviews es_partner_views.xml

    openacademy/models/init.py

    from . import models
    from . import partner
    

    openacademy/manifest.py

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

    openacademy/partner.py

    # -*- coding: utf-8 -*-
    from odoo import fields, models
    
    class Partner(models.Model):
        _inherit = 'res.partner'
    
        # Add a new column to the res.partner model, by default partners are not
        # instructors
        instructor = fields.Boolean("Instructor", default=False)
    
        session_ids = fields.Many2many('openacademy.session',
            string="Attended Sessions", readonly=True)
    
    

    openacademy/views/partner.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <odoo>
        <data>
            <!-- Add instructor field to existing view -->
            <!--form视图-->
            <record model="ir.ui.view" id="partner_instructor_form_view">
                <field name="name">partner.instructor</field>
                <field name="model">res.partner</field>
                <field name="inherit_id" ref="base.view_partner_form"/>
                <field name="arch" type="xml">
                    <notebook position="inside">
                        <page string="Sessions">
                            <group>
                                <field name="instructor"/>
                                <field name="session_ids"/>
                            </group>
                        </page>
                    </notebook>
                </field>
            </record>
    
            <record model="ir.actions.act_window" id="contact_list_action">
                <field name="name">Contacts</field>
                <field name="res_model">res.partner</field>
                <field name="view_mode">tree,form</field>
            </record>
            <menuitem id="configuration_menu" name="Configuration"
                      parent="main_openacademy_menu"/>
            <menuitem id="contact_menu" name="Contacts"
                      parent="configuration_menu"
                      action="contact_list_action"/>
            <record model="ir.actions.act_window" id="contact_cat_list_action">
                <field name="name">Contact Tags</field>
                <field name="res_model">res.partner.category</field>
                <field name="view_mode">tree,form</field>
            </record>
            <menuitem id="contact_cat_menu" name="Contact Tags"
                      parent="configuration_menu"
                      action="contact_cat_list_action"/>
    
            <record model="res.partner.category" id="teacher1">
                <field name="name">Teacher / Level 1</field>
            </record>
            <record model="res.partner.category" id="teacher2">
                <field name="name">Teacher / Level 2</field>
            </record>
        </data>
    
    </odoo>
    
    

    <field name="inherit_id" ref="base.view_partner_form"/>视图继承
    odooodoo10odooaddonsproductviews es_partner_views.xml

    • 练习在关联字段上使用Domain,
      当为授课选取讲师时,只有instructor值为True的讲师会被显示出来。

    注意
    声明为文字列表的domain会在服务端进行计算,不会出现在右侧的动态列表中,而声明为字符串的domain是在客户端进行计算的,字段名将出现在右侧列表。

    #class Session(models.Model)
    instructor_id = fields.Many2one('res.partner', string="Instructor",
                                        domain=[('instructor', '=', True)])
    
    • 练习更复杂的domain,创建新的partner类别Techer/Level1和Techer/Level2.一个授课的教授人可以是讲师或者任意级别的教师。
           <!-- Add instructor field to existing view -->
            <!--form视图-->
            <record model="ir.ui.view" id="partner_instructor_form_view">
                <field name="name">partner.instructor</field>
                <field name="model">res.partner</field>
                <!--继承-->
                <field name="inherit_id" ref="base.view_partner_form"/>
                <field name="arch" type="xml">
                    <notebook position="inside">
                        <page string="Sessions">
                            <group>
                                <field name="instructor"/>
                                <field name="session_ids"/>
                            </group>
                        </page>
                    </notebook>
                </field>
            </record>
            <!--定义行为-->
            <record model="ir.actions.act_window" id="contact_list_action">
                <field name="name">Contacts</field>
                <field name="res_model">res.partner</field>
                <field name="view_mode">tree,form</field>
            </record>
            <!--菜单-->
            <menuitem id="configuration_menu" name="Configuration"
                      parent="main_openacademy_menu"/>
            <menuitem id="contact_menu" name="Contacts"
                      parent="configuration_menu"
                      action="contact_list_action"/>
    
    
            <record model="ir.actions.act_window" id="contact_cat_list_action">
                <field name="name">Contact Tags</field>
                <field name="res_model">res.partner.category</field>
                <field name="view_mode">tree,form</field>
            </record>
    
    
            <menuitem id="contact_cat_menu" name="Contact Tags"
                      parent="configuration_menu"
                      action="contact_cat_list_action"/>
            <!-- 定义分类-->
            <record model="res.partner.category" id="teacher1">
                <field name="name">Teacher / Level 1</field>
            </record>
            <record model="res.partner.category" id="teacher2">
                <field name="name">Teacher / Level 2</field>
            </record>
    

    视图继承路径odooodoo10odooaddonsproductviews es_partner_views.xml

    • 练习计算字段

    • 加入座席占用百分比字段到授课模型。

    • 在列表视图和表单视图中显示这个字段

    • 以进度条的方式显示这个字段

    #class Session(models.Model)
    
     taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')
    
        @api.depends('seats', 'attendee_ids')   #依赖的任一字段变化时(ORM or Form),触发该函数执行
        def _taken_seats(self):
            for r in self:
                if not r.seats:
                    r.taken_seats = 0.0
                else:
                    r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
    
    

    <field name="taken_seats" widget="progressbar"/>progresbar进度条

    • 练习默认值

    定义start_date默认值为今天
    在授课类添加字段active,并且设置其默认值为True

    class Session(models.Model):
        start_date = fields.Date(default=fields.Date.today)
        active = fields.Boolean(default=True)
    

    注意
    Odoo 有内置规则:active字段值为False时记录不可见

    • 练习
      通过"onchange"机制显示的验证无效值,例如座位数为负数或者座位数多与参与者。
    #class Session(models.Model)
    @api.onchange('seats', 'attendee_ids')
        def _verify_valid_seats(self):
            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",
                    },
                }
    
    

    • 练习添加Python约束,讲师不能在自己的授课出席人中
    import exceptions
    
    #class Session(models.Model):
    @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")
    

    • 练习添加重复项,因为我们为课程名称添加了唯一性约束,所以不能再使用"复制"功能(表单->复制)。重写"复制"方法,允许复制课程对象,将原始名称更改为"原始名称的副本"。
    #class Course(models.Model)
       @api.multi
        def copy(self, default=None):
            default = dict(default or {})
            print '--------------'
            print self.name
            print '--------------'
            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_name
            return super(Course, self).copy(default)
    
        <record model="ir.ui.view" id="session_form_view">
                <field name="name">session.form</field>
                <field name="model">openacademy.session</field>
                <field name="arch" type="xml">
                    <form string="Session Form">
                        <!--<sheet>-->
                            <!--<group>-->
                                <!--<field name="course_id"/>-->
                                <!--<field name="name"/>-->
                                <!--<field name="start_date"/>-->
                                <!--<field name="duration"/>-->
                                <!--<field name="seats"/>-->
                                <!--<field name="instructor_id"/>-->
                            <!--</group>-->
                            <!--&lt;!&ndash;授课和参与的听众做关联,听众来自于内置模型res.partner&ndash;&gt;-->
                            <!--<label for="attendee_ids"/>-->
                            <!--<field name="attendee_ids"/>-->
                        <!--</sheet>-->
                        <sheet>
                            <group>
                                <group string="General">
                                    <field name="course_id"/>
                                    <field name="name"/>
                                    <field name="instructor_id"/>
                                    <field name="active"/>
                                </group>
                                <group string="Schedule">
                                    <field name="start_date"/>
                                    <!--<field name="end_date"/>-->
                                    <field name="duration"/>
                                    <!--<field name="hours"/>-->
                                    <field name="seats"/>
                                    <field name="taken_seats" widget="progressbar"/>
                                </group>
                            </group>
                            <label for="attendee_ids"/>
                            <field name="attendee_ids"/>
                        </sheet>
                    </form>
                </field>
            </record>
    

    打印出来的name,default

    --------------
    语文
    --------------
    {'name': u'Copy of u8bedu6587'}
    
    



    高级视图

    • 练习列表着色
      编辑授课的树视图,使得持续时间少于5天的授课以蓝色显示,持续时间超过15天的授课以红色显示。编辑授课的树视图:
     <record model="ir.ui.view" id="session_tree_view">
                <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"/>
                        <field name="state"/>
                    </tree>
                </field>
            </record>
    
    

    • 练习日历视图
      给授课模型添加一个日历视图,使用户可以查看与开放学院相关联的事件。

    • 添加一个计算字段end_date,通过start_dateduration计算获得。

    • 反函数使字段可写,并允许在日历视图中移动授课(通过拖放操作)

    • 向授课模型添加日历视图

    • 添加日历视图到授课模型的动作中

    models.py

    from datetime import timedelta
    # class Session(models.Model)
    
    end_date = fields.Date(string="End Date", store=True,
                               compute='_get_end_date', inverse='_set_end_date')  #结束日期
    
    @api.depends('start_date', 'duration')  #计算end_date结束日期
        def _get_end_date(self):
            for r in self:
                if not (r.start_date and r.duration):
                    r.end_date = r.start_date
                    continue
    
                # Add duration to start_date, but: Monday + 5 days = Saturday, so
                # subtract one second to get on Friday instead
                start = fields.Datetime.from_string(r.start_date)
                duration = timedelta(days=r.duration, seconds=-1)
                r.end_date = start + duration
    
        def _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 instead
                start_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
    
    

    • 练习搜索视图

    • 在课程搜索视图中添加按钮,用以筛选当前用户负责的课程,并且作为默认选择。

    • 再添加一个分组按钮,用于对当前用户负责的课程进行分组。

      <!--搜索视图 Course-->
            <record model="ir.ui.view" id="course_search_view">
                <field name="name">course.search</field>
                <field name="model">openacademy.course</field>
                <field name="arch" type="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>
    
    
    <!--动作定义-->
           <record model="ir.actions.act_window" id="course_list_action">
                <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>
                <!--在课程搜索视图中添加按钮,用以筛选当前用户负责的课程,并且作为默认选择  默认选择的规则-->
                <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>
                </field>
            </record>
    

    • 练习甘特图

    添加甘特图使用户可以查看授课的日程排期,授课将按讲师分组。

    • 创建一个计算字段,表示以小时计算的授课持续时间
    • 添加甘特图,并且将甘特图添加到授课模型的action上。
       <!--甘特图-->
          <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.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>
    

    • 练习看板视图
      添加显示按课程分组的授课看板视图(列是课程)

    • 授课模型中添加整型字段color

    • 添加看板视图并更新action

    color = fields.Integer()
    
        <!--看板视图-->
              <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">
                                <div
                                        t-attf-class="oe_kanban_color_{{kanban_getcolor(record.color.raw_value)}}
                                                      oe_kanban_global_click_edit oe_semantic_html_override
                                                      oe_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>
    



    工作流和安全

    • 练习伪工作流

    在授课模型上添加一个字段state,用于定义一个工作流程。授课存在三个可能的状态:Draft(草稿,默认值)、Confirmed(已确认)、Done(已完成)。在授课的form视图中,添加一个只读字段用于显示课程状态,并可以通过按钮来改变状态。有效的状态值迁移包括:

    • Draft->Confirmed
    • Confirmed->Draft
    • Confirmed->Done
    • Done->Draft
    1. 添加一个新的字段state
    2. 添加状态迁移方法,这个方法可以被form表单的按钮所调用,用以更改授课的状态
    3. 将相关按钮添加到授课的form视图
    4. openacademy/models.py
    #class Session(models.Model):
     state = fields.Selection([
            ('draft', "Draft"),
            ('confirmed', "Confirmed"),
            ('done', "Done"),
        ], default='draft')   #工作流状态
    
    
    <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="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">
                <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>
    
            <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>
    
    • 练习工作流
      使用真正的授课工作流替换之前的伪工作流。修改授课的form视图,按钮将调用工作流而不是调用模型的方法。

    • 练习自动状态迁移
      当超过一半座席被保留时,自动将授课的状态从Draft迁移到Confirmed。

    当定义了工作流,需要卸载模块,否则原来定义好的数据无法进入工作流(可以直接修改数据库)

    <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 > 50</field>
            </record>
        </data>
    </odoo>
    
    
    
    • 练习服务器动作
      用服务器动作替换用于同步授课状态的Python方法。工作流和服务器动作都可以从UI创建。
    <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>
    
    
    

    • 练习
      通过Odoo界面添加访问控制权限
      建立一个新用户John Smit,然后建立OpenAcademy/Session Read组,并赋予这个组对授课模型的读权限。
    1. 建立一个新用户John Smit通过 设置->用户->用户
    2. 建立一个新组session_read通过 设置->用户->组,这个组拥有对授课模型的读权限
    3. 编辑John Smith用户,把他加入到session_read
    4. John Smith身份登录系统,检查权限是否正确。

    • 练习
      通过数据文件添加访问控制权限:

    • 建立一个组OpenAcademy / Manager,这个组对开放学院的所有模型都有完全权限。

    • 让Session和Course对所有用户可读

    1. 建立新的文件openacademy/security/security.xml用来定义OpenAcademy Manager组
    2. 编辑文件openacademy/security/ir.model.access.csv来添加对模型的访问权限
    3. 最后更新openacademy/manifest.py来添加新的数据文件

    openacademy/__manifest__.py

    注意security.xml顺序,优先加载security.xml 然后加载csv,否则报错

    Exception: Module loading openacademy failed: file openacademy/security/ir.model.access.csv could not be processed:
     在字段'Group'中没找到匹配的记录外部id 'group_manager'
    在字段'Group'中没找到匹配的记录外部id 'group_manager'
    在字段'Object'中没找到匹配的记录外部id 'model_scheduler_demo'
    在字段'Group'中没找到匹配的记录外部id 'group_manager'
    
    
      'data': [
            'security/security.xml',
            'security/ir.model.access.csv',
            'views/openacademy.xml',
            'views/templates.xml',
            'views/partner.xml',
            'views/session_workflow.xml',
            'demo/demo.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>
    
    
    

    只能可读,不可修改

    • 练习记录规则
      为授课模型和OpenAcademy / Manager组添加记录规则,这个记录规则限制只有课程负责人可以对课程进行write和unlink操作,如果课程还没有负责人,这个组的所有用户都可以编辑它。在openacademy/security/security.xml文件中创建新的规则:

    openacademy/security/security.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <odoo>
        <data>
            <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'))]"/>  #   (4,id,_) 连接一个已经存在的记录
                <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>
    

    只有添加到用户组里面才可以进行读写



    向导与国际化

    • 练习定义向导
      创建一个向导模型,这个向导模型通过many2one关联授课模型,并通过many2many关联合作伙伴模型。添加新文件openacademy/wizard.py

    openacademy/models/__init__.py

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

    openacademy/wizard.py

    # -*- coding: utf-8 -*-
    
    from odoo import models, fields, api
    
    class Wizard(models.TransientModel):
        _name = 'openacademy.wizard'
    
        session_ids = fields.Many2one('openacademy.session',
            string="Session", required=True)
        attendee_ids = fields.Many2many('res.partner', string="Attendees")
    
    
    • 练习启动向导
    1. 为向导定义一个form视图
    2. 在授课模型的上下文中添加action用于启动向导
    3. 给向导的session字段定义默认值;使用上下文参数self._context来获取当前授课

    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_ids = fields.Many2one('openacademy.session',
            string="Session", required=True, default=_default_session)
        attendee_ids = fields.Many2many('res.partner', string="Attendees")
    
    

    openacademy/views/openacademy.xml

    <!--定义向导视图-->
           <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_ids"/>
                            <field name="attendee_ids"/>
                        </group>
                        <!--底部-->
                        <footer>
                            <!--订阅按钮-->
                            <button name="subscribe" type="object"
                                    string="Subscribe" class="oe_highlight"/>
                            <!--取消按钮-->
                            <button special="cancel" string="Cancel"/>
                        </footer>
                    </form>
                </field>
            </record>
    
          <!--定义向导行为-->
          <!--关联session   定义wizard-->
            <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"/>
    

    • 练习注册与会者
      给向导添加按钮,并且实现相应的方法,将与会者添加到给定的授课。
     <footer>
                            <!--订阅按钮-->
                            <button name="subscribe" type="object"
                                    string="Subscribe" class="oe_highlight"/>
                            <!--取消按钮-->
                            <button special="cancel" string="Cancel"/>
                        </footer>
    
    • 练习与会者注册多个授课
      修改向导模型,以便与会者可以注册到多个授课
    class Wizard(models.TransientModel):
        _name = 'openacademy.wizard'
    
    
        def _default_sessions(self):
            return self.env['openacademy.session'].browse(self._context.get('active_ids'))
            print '------------------------'
            print dir(self)
            print '------------------------'
        #字段
        session_ids = fields.Many2one('openacademy.session',
            string="Session", required=True)
        attendee_ids = fields.Many2many('res.partner', string="Attendees")
    
    
        @api.multi
        def subscribe(self):
            for session in self.session_ids:
                session.attendee_ids |= self.attendee_ids
            return {}
    
    
    
    • 练习
      翻译一个模块
      为已经安装的Odoo模块选择第二语言。使用Odoo提供的功能对模块进行翻译。
    1. 创建目录openacademy/i18n/
    2. 安装任意一种你希望的语言 (设置->翻译->加载翻译
    3. 同步翻译术语(设置->翻译->应用程序术语->同步术语
    4. 导出不指定语言的翻译模板文件(设置->翻译->导入/导出->导出翻译),保存在openacademy/i18n/
    5. 导出指定语言的翻译文件(设置->翻译->导入/导出->导出翻译),保存在openacademy/i18n/
    6. 打开导出的翻译文件(使用任意一款文本编辑软件或者专用的PO文件编辑器,例如POEdit然后翻译其中的术语)
    7. models.py文件中,为odoo._方法添加一个导入声明,并且标记需要翻译的字符串
    8. 重复3-6的步骤

    实际上添加i18n这个文件夹,放入导出的po文件zh_cn.po文件,必须重新安装模块后,整个翻译才会生效

    openacademy/models.py

    下划线翻译相应的浏览器提示

    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_name
            return 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"))
    
    
    



    报表和WebService

    • 练习创建授课模型的报表
      对于每个授课,报表都将显示它的授课名称,开始和结束时间,以及课程出席人列表。
    <?xml version="1.0" encoding="utf-8" ?>
    <odoo>
        <data>
            <!--定义报表-->
             <report
            id="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>
    
    • 练习定义一个仪表盘
      定义一个仪表盘,这个仪表盘包含了已经建立的图形视图、授课日历视图、课程列表视图(可以选择form视图)。这个仪表盘可以通过菜单中的菜单项使用,并且当选择开放学院主菜单时自动显示在web客户端。
    'version': '0.1',
    
        # any module necessary for this one to work correctly
        'depends': ['base', 'board'],
    
        # always loaded
        'data': [
            'views/openacademy.xml',
            'views/partner.xml',
            'views/session_workflow.xml',
            'views/session_board.xml',
            'reports.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>
                                <action
                                    string="Attendees by course"
                                    name="%(act_session_graph)d"
                                    height="150"
                                    width="510"/>
                                <action
                                    string="Sessions"
                                    name="%(act_session_calendar)d"/>
                            </column>
                            <column>
                                <action
                                    string="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>
    
            <menuitem
                name="Session Dashboard" parent="base.menu_reporting_dashboard"
                action="open_board_session"
                sequence="1"
                id="menu_board_session" icon="terp-graph"/>
        </data>
    </odoo>
    
    
    

    • 练习在客户端添加新服务
      编写Python程序,用来发送XML-RPC请求到运行Odoo的PC。这个程序将显示全部授课,以及所对应的座位数。也可以为课程创建新的授课。
  • 相关阅读:
    jmeter之GUI运行原理
    jmeter之自定义java请求性能测试
    TestNG+ExtentReports生成超漂亮的测试报告(转)
    【测试设计】使用jenkins 插件Allure生成漂亮的自动化测试报告(转)
    Windows 清空剪切板
    android studio Warehouse(仓库配置)
    C/C++ string to int or int to string
    CNC FANUC define program
    CNC WEB
    CNC 法兰克加工中心CNC数控系统怎么修改程序
  • 原文地址:https://www.cnblogs.com/myt2000/p/9275891.html
Copyright © 2020-2023  润新知