建立一個Odoo Module (四)- Computed fields、Model constriants

Computed fields and default values

目前爲止,fields 都是直接將數據寫到數據庫或者從數據庫讀取數據。Fields 中同樣可以通過調用方法獲取動態計算值,而不是從數據庫中取數據。
創建 computed field 的方式就是給一個 field 設置 compute 屬性,並將其值 = method name。那麼這個method 就會對 self 這個model的所有record自動生效。

注意
self 不僅可以像普通 python class 定義中一樣,調用方法,在 Odoo 中 self 同時代表 recordset,是一個有序的自己 model record 的集合,支持普通python 方法, len(self) and iter(self), 同時也可以將兩個recordset 相加。 recs1 + recs2
迭代 self, 將會把 records 中的record 一個一個返回,同時返回的每一個 record 也是一個 recordset,只不過 size == 1。也可以通過 點 來調用 單個record的屬性。 record.name

import random
from openerp 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))

Dependencies

computed field 的值通常是根據其他 field 的 value 計算出來的。Odoo 的 ORM 就希望開發者能夠明確指出需要依賴的 field 是哪些,所以提供了一個裝飾器 depend()。無論在哪,只要當被依賴的 fields 變動時,Odoo就會根據這個自動重新計算 computed field 的值。

from openerp 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

練習 4-1

  • 在 session model 中添加座位使用的 百分比
  • 將這個新增 field 添加到 tree form view 中
  • 將這個field 以 process bar的形式展現出來

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

                                <field name="start_date"/>
                                <field name="duration"/>
                                <field name="seats"/>
                                <!-- 新增 -->
                                <field name="taken_seats" widget="progressbar"/>
                                <!-- 結束 -->
                            </group>
                        </group>
                        <label for="attendee_ids"/>
                <tree string="Session Tree">
                    <field name="name"/>
                    <field name="course_id"/>
                    <!-- 新增 -->
                    <field name="taken_seats" widget="progressbar"/>
                    <!-- 結束 -->
                </tree>
            </field>
        </record>

Default values

任何類型的 field 都可以設置 default value,只需要在定義時,添加 default=x 。這個 x 可以是python 中的 boolean, string, int, float 等。也可以接受一個函數,以recordset 作爲參數,返回一個值

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

Note:
self.env 提供了一系列的藉口,用以處理一些東西

  • self.env.cr 或者 self._cr 是database的corsor, 可以通過這個,直接調用SQL
  • self.env.uid or self._uid 時當前登錄用戶的數據表 id
  • self.env.user 當前登錄用戶的 record
  • self.env.context or self._context 當前的 context directory
  • self.env.ref(xml_id) 通過 xml_id 返回 對應的 record
  • self.env[model_name] 返回 model_name 對應的 空 record

練習 4-2

  • 給 start_date 設置默認值, 爲 當前時間
  • session 中添加 active 字段

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

                                <field name="course_id"/>
                                <field name="name"/>
                                <field name="instructor_id"/>
                                <field name="active"/> <!-- 新增 -->
                            </group>
                            <group string="Schedule">
                                <field name="start_date"/>

注意:
Odoo 有一個內建機制, 將會自動的隱藏掉 active 字段值爲 False 的record


Onchange

Odoo onchange 機制主要用於在 client 中的 form view。當某個字段改變時,自動 update form view,不會主動存在database 中。注意,這個主要作用於 客戶端
比如:某個model 擁有三個字段 amout, unit_price and price。我們希望在form中修改其它兩個字段時, price 的值會自動的改變。爲此,提供了一個裝飾器 onchange() 來實現這個功能,

<!-- content of form view -->
<field name="amount"/>
<field name="unit_price"/>
<field name="price" readonly="1"/>
# 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",
        }
    }

練習 4-3
添加一個針對 字段變化的 onchange, 當form中字段值填寫不合法的時候,彈出警告

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",
                },
            }
    # 結束

Model constraints

Odoo提供了兩種方式來設置自動驗證 invariants: Python constraints and SQL constraints
Python constraints 就像普通method一樣,只不過需要使用裝飾器 constrains(), 不過也是需要指明 record中的那些字段需要使用 constraint,如果不滿足要求,method應該 raise an exception

from openerp.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)

練習 4-4
添加一個限制條件,用以明確,instructor not in the attendees in his/her own session
openacademy/models.py

# -*- coding: utf-8 -*-

from openerp import models, fields, api, exceptions # 修改

class 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 constraints 是通過給 model 設置屬性 _sql_constraints, 這個也和domain一樣是由一堆 三個元素組成的 tuple 的 list集合。(name, sql_definition, message),name 就是這個 SQL constraint 的名字,sql_definition 就是 table_constraint, message 就是 error message。


練習 4-5

  • CHECK course 的title 和 description 是否一樣
  • 明確 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'

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章