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

模型之间的关系

来自模型的记录可能与来自另一模型的记录相关。例如,一个销售订单记录和一个包含客户数据的客户记录相关;同时也和销售订单线记录相关。

练习

创建会话模型

对于模块Open Academy,我们考虑一个会话模型:会话是在给定时间为给定听众授课的课程。

为会话创建模型。一个会话有一个名字,一个开始日期,一个持续时间和一些座位。 添加一个动作和菜单项来显示它们。通过菜单项使新模型可见。

  1. openacademy/models/models.py中创建类Session 
  2. openacademy/view/openacademy.xml中添加对会话对象的访问

openacademy/models.py

    name = fields.Char(string="Title", required=True)
    description = fields.Text()


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
            openacademy.session
            
                
Sessions openacademy.session form tree,form

digits=(6, 2) 指定浮点数的精度:6是数字的总数,而2是逗号后的位数。注意逗号之前的数字的位数最大为4

关系字段

关系字段链接记录,即相同模型(层次结构)或不同模型之间的记录。

关系字段类型为:

Many2one(other_model, ondelete='set null')

与其他对象的简单链接:

print foo.other_id.name

请参阅

foreign keys

One2many(other_model, related_field)

一个虚拟关系,一个Many2one的逆关系。One2many 表现为一个记录容器,访问它导致一组(可能是空的)记录集合:

for other in foo.other_ids:
    print other.name

危险

因为One2many是一个虚拟关系,所以必须在other_model中有一个Many2one字段,它的名字必须是related_field

Many2many(other_model)

双向多重关系,任何一方的记录都可以与另一方的任何数量的记录相关。作为记录容器,访问它也会导致一个可能的空记录集:

for other in foo.other_ids:
    print other.name

练习

多对一关系

使用many2one,修改Course 和Session模型,以反映它们与其他模型的关系:

  • course有一个responsible用户;那个字段的值是内置模型res.users的记录
  • session有一个instructor;该字段的值是一个内置的模型res.partner记录
  • session和course是相关的;其字段的值是模型openacademy.course的记录,也是必须的
  • 适配视图
  1. 将相关的Many2one字段添加到模型中,以及
  2. 在视图中添加它们

openacademy/models.py

    name = fields.Char(string="Title", required=True)
    description = fields.Text()

    responsible_id = fields.Many2one('res.users',
        ondelete='set null', string="Responsible", index=True)


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

    instructor_id = fields.Many2one('res.partner', string="Instructor")
    course_id = fields.Many2one('openacademy.course',
        ondelete='cascade', string="Course", required=True)

openacademy/views/openacademy.xml

                    
                        
                            
                            
                        
                        
                            
            
        

        
        
            course.tree
            openacademy.course
            
                
                    
                    
                
            
        

        
        
        
            session.tree
            openacademy.session
            
                
                    
                    
                
            
        

        
            Sessions
            openacademy.session

练习

逆一对多关系

使用逆关系字段one2many,修改模型以反映courses 与sessions之间的关系

  1. 修改 Course 类,并且
  2. course 表单视图中添加字段

openacademy/models.py

    responsible_id = fields.Many2one('res.users',
        ondelete='set null', string="Responsible", index=True)
    session_ids = fields.One2many(
        'openacademy.session', 'course_id', string="Sessions")


class Session(models.Model):

openacademy/views/openacademy.xml

                            
                                
                            
                            
                                
                                    
                                        
                                        
                                    
                                
                            
                        
                    

练习

多重多对多关系

使用关系字段many2many,修改Session模型以将每个session 与一组attendees联系起来。Attendees 将由partner 记录表示,因此我们将关联内置的模型res.partner。相应地调整视图。

  1. 修改 Session 类,并且
  2. 在表单视图中添加字段

openacademy/models.py

    instructor_id = fields.Many2one('res.partner', string="Instructor")
    course_id = fields.Many2one('openacademy.course',
        ondelete='cascade', string="Course", required=True)
    attendee_ids = fields.Many2many('res.partner', string="Attendees")

openacademy/views/openacademy.xml

                                
                            
                        
                        

继承

模型继承

Odoo提供了两种继承机制,以模块化方式扩展现有模型。

第一个继承机制允许一个模块修改另一个模块中定义一个模型的行为:

  • 将字段添加到模型中
  • 重写模型上字段的定义
  • 向模型添加约束
  • 将方法添加到模型中
  • 重写模型上的现有方法

第二继承机制(委托)允许将模型的每个记录链接到父模型中的记录,并提供对父记录的字段的透明访问。

Odoo10教程---模块化二:模型间关系,继承,计算字段等_第1张图片

请参阅

  • _inherit
  • _inherits

视图继承

代替修改现有的视图(通过重写它们),Odoo提供的视图继承让子视图“扩展”的视图应用在顶部的根视图,并可以从父视图中添加或删除内容。

扩展视图使用inherit_id字段引用它的父类,而不是单个视图,其arch字段由任意数量的xpath元素组成,它们选择和改变父视图的内容:



    id.category.list2
    idea.category
    
    
        
        
          
        
    

expr

在父视图中XPath表达式选择单个元素。如果不匹配元素或多于一个,则会引发错误

position

应用于匹配元素的操作:

inside

在匹配元素的末尾追加xpath的body

replace

替换使用xpath的body的匹配元素,替换任何 $0 节点发生在使用原始元素的新的body中

before

在匹配元素之前插入xpat的body作为兄弟

after

匹配元素之后插入xpat的body作为兄弟

attributes

使用在xpath的body中的特殊的attribute元素改变匹配元素的属性

提示

当匹配一个元素,position 属性可以直接设置在元素上以便被发现。下面的两个继承将给出相同的结果


    



    

练习

更改现有内容

  • 使用模型继承,修改现有的Partner 模型以添加instructor布尔字段,以及对应于session-partner关系的many2many 字段
  • 使用视图继承,在partner表单视图中显示此字段

这是引入开发者模式来检查视图、找到其外部ID和放置新字段的位置的机会

  1. 创建文件 openacademy/models/partner.py 并在 __init__.py中导入
  2. 创建文件 openacademy/views/partner.xml 并在__manifest__.py中添加

openacademy/__init__.py

# -*- coding: utf-8 -*-
from . import controllers
from . import models
from . import partner

openacademy/__manifest__.py

        # 'security/ir.model.access.csv',
        'templates.xml',
        'views/openacademy.xml',
        'views/partner.xml',
    ],
    # only loaded in demonstration mode
    'demo': [

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


 
    
        
        
            partner.instructor
            res.partner
            
            
                
                    
                        
                            
                            
                        
                    
                
            
        

        
            Contacts
            res.partner
            tree,form
        
        
        
    

在Odoo中,域是对记录条件进行编码的值。一个域是一个列表,用于选择一个模型的记录子集的标准。每个标准是一个三元组,有一个字段名、一个运算符和一个值。

例如,当使用Product 模型下列域选择所有是服务且单价超过1000的产品:

[('product_type', '=', 'service'), ('unit_price', '>', 1000)]

默认情况下,标准使用隐式的AND联合。逻辑运算符 & (AND), | (OR) 和! (NOT)可用来显示的联合标准。它们在前缀位置中使用(操作符在其参数之前插入而不是在它们之间)。例如,选择合适的产品“其是服务或有单价不是在1000和2000之间”:

['|',
    ('product_type', '=', 'service'),
    '!', '&',
        ('unit_price', '>=', 1000),
        ('unit_price', '<', 2000)]

当尝试在客户端界面中选择记录时,可以将domain 参数添加到关系字段中,以限制关系的有效记录

练习

关系字段上的域

当为Session选择instructor时,仅instructors (partners 的instructor 设置为 True)应该可见。

openacademy/models.py

    duration = fields.Float(digits=(6, 2), help="Duration in days")
    seats = fields.Integer(string="Number of seats")

    instructor_id = fields.Many2one('res.partner', string="Instructor",
        domain=[('instructor', '=', True)])
    course_id = fields.Many2one('openacademy.course',
        ondelete='cascade', string="Course", required=True)
    attendee_ids = fields.Many2many('res.partner', string="Attendees")

声明为文字列表的域被评估为服务器端,不能引用右侧的动态值,声明为字符串的域被评估为客户端,并允许右侧的字段名

练习

更多的复杂域

创建新的partner categories Teacher / Level 1 and Teacher / Level 2. 对于一个session的instructor可以是 instructor或teacher(任何级别)

  1. 修改Session 模型的域
  2. 修改openacademy/view/partner.xml 以访问Partner categories

openacademy/models.py

    seats = fields.Integer(string="Number of seats")

    instructor_id = fields.Many2one('res.partner', string="Instructor",
        domain=['|', ('instructor', '=', True),
                     ('category_id.name', 'ilike', "Teacher")])
    course_id = fields.Many2one('openacademy.course',
        ondelete='cascade', string="Course", required=True)
    attendee_ids = fields.Many2many('res.partner', string="Attendees")

openacademy/views/partner.xml

        

        
            Contact Tags
            res.partner.category
            tree,form
        
        

        
            Teacher / Level 1
        
        
            Teacher / Level 2
        
    

计算字段和默认值

到目前为止,字段已经直接存储在数据库中并直接从数据库中检索。也可以计算字段。在这种情况下,字段的值不是从数据库中检索的,而是通过调用模型的方法即时计算的。

若要创建计算字段,请创建字段并将其属性compute 设置为方法的名称。计算方法应该简单地设置self中每个记录的字段值。

危险

self 是一个收藏

self是一个记录集对象,即记录的有序集合。它支持集合上的标准Python操作,比如len(self)iter(self),再加上 recs1 + recs2之类的额外设置操作。

self 上迭代给出一个接一个的记录,其中每个记录本身是一个大小为1的集合。您可以使用点标记来访问/分配单个记录上的字段, 像record.name.

import random
from odoo import models, fields, api

class ComputedModel(models.Model):
    _name = 'test.computed'

    name = fields.Char(compute='_compute_name')

    @api.multi
    def _compute_name(self):
        for record in self:
            record.name = str(random.randint(1, 1e6))

依赖关系

计算字段的值通常取决于计算记录上其他字段的值。ORM期望开发者使用装饰器depends()来指定计算方法上的依赖关系。给定的依赖项被ORM用来触发字段的重新计算,每当其依赖性被修改时:

from odoo import models, fields, api

class ComputedModel(models.Model):
    _name = 'test.computed'

    name = fields.Char(compute='_compute_name')
    value = fields.Integer()

    @api.depends('value')
    def _compute_name(self):
        for record in self:
            record.name = "Record with value %s" % record.value

练习

计算字段

  • 将所占席位的百分比添加到Session模型中
  • 在树和表单视图中显示字段
  • 将字段显示为进度
  1. Session添加计算字段
  2. Session视图中显示字段 view:

openacademy/models.py

    course_id = fields.Many2one('openacademy.course',
        ondelete='cascade', string="Course", required=True)
    attendee_ids = fields.Many2many('res.partner', string="Attendees")

    taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')

    @api.depends('seats', 'attendee_ids')
    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

openacademy/views/openacademy.xml

                                
                                
                                
                                
                            
                        
                        
                
                    
                    
                    
                
            
        

默认值

任何字段都可以得到默认值。在字段定义中,添加选项default=X,其中X是Python文字值(布尔、整数、浮点、字符串),或者是一个记录集并返回一个值的函数:

name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)

对象self.env可以访问请求参数和其他有用的东西:

  • self.env.cr 或 self._cr 是数据库游标对象;它用于查询数据库
  • self.env.uid 或 self._uid 是当前用户的数据库id
  • self.env.user 是当前用户的记录
  • self.env.context 或 self._context 是上下文字典
  • self.env.ref(xml_id) 返回对应于XML id的记录
  • self.env[model_name] 返回给定模型的实例

练习

活动对象-默认值

  • 定义start_date字段默认值为今天(参见 Date)
  • Session类中添加字段 active , 并且默认情况下设置sessions为活动的

openacademy/models.py

    _name = 'openacademy.session'

    name = fields.Char(required=True)
    start_date = fields.Date(default=fields.Date.today)
    duration = fields.Float(digits=(6, 2), help="Duration in days")
    seats = fields.Integer(string="Number of seats")
    active = fields.Boolean(default=True)

    instructor_id = fields.Many2one('res.partner', string="Instructor",
        domain=['|', ('instructor', '=', True),

openacademy/views/openacademy.xml

                                
                                
                                
                                
                            
                            
                                

Odoo 有内置的规则,使active 字段设置为 False不可见

Onchange

“Onchange”机制为客户界面提供了一种方法,只要用户在字段中填入一个值,而无需保存任何数据,就可以更新表单。

例如,假设一个模型有三个字段amountunit_price 和price,并且当任何其他字段被修改时,您希望更新表单上的价格。为了实现这一点,定义一种方法,其中self 表示表单视图中的记录,并用onchange()来装饰它,以指定它必须触发哪个字段。你在self上做的任何更改都会反映在表单上。





# onchange handler
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
    # set auto-changing field
    self.price = self.amount * self.unit_price
    # Can optionally return a warning and domains
    return {
        'warning': {
            'title': "Something bad happened",
            'message': "It was very bad indeed",
        }
    }

对于计算字段,值onchange 行为是内置的,如可以通过播放Session 表单看到的:更改座位或参与者的数量,自动更新taken_seats进度条。

练习

警告

添加一个明确的onchange 来警告无效值,比如负数的座位,或参与者比座位更多

openacademy/models.py

                r.taken_seats = 0.0
            else:
                r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats

    @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",
                },
            }

 

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

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

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

 

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

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

你可能感兴趣的:(odoo,python,架构及框架)