-----------------
更新時間
11:17 2016-09-18 星期日
11:00 2016-03-13 星期日
09:10 2016-03-03 星期四
11:46 2016-02-25 星期四
10:06 2016-02-24 星期三
14:51 2016-02-23 星期二
18:07 2016-02-19 星期五
17:44 2016-02-17 星期三
-----------------
*模型
模型是業務對象的呈現
* 系統已定義的模型查看
設置->技術->數據結構->模型
現在定義的有700多個
* 版本演變
from openerp.osv import fields, osv
->
from openerp import models, fields
-------------
osv.osv ---> orm.Model ---> models.Model
osv.TransientModel ---> orm.TransientModel ---> models.TransientModel
------------
class MyModel(osv.osv):
pass...
-------------
class MyModel(osv.Model):
pass...
------------
class MyModel(models.Model):
pass...
-------------
上面這些在后台8.0版本都可以運行,在后面的版本不敢確定了
確切的查看
openerp/models.py 文件
openerp/osv/osv.py 這個文件內容純是兼容性寫法
except_osv = except_orm
osv = Model
osv_memory = TransientModel
osv_abstract = AbstractModel
左邊是早期寫法,很多技術文檔,包含里面的官方模塊也還有很多老的api
新的,盡量用新的api來寫模塊
openerp/osv/orm.py
把openerp/models.py綜合進來了
系統里大部分還是老的寫法,不過自己盡量用新的寫法,會簡潔一些
差別最大是搜索:
v7 search() 得到是ids 要得到recordset, 還得用 browse() 或 read()
V8 search() 得到是recordset
-----------
def _get_language(self, cr, uid, context):
lang_model = self.pool.get('res.lang')
lang_ids = lang_model.search(cr, uid, [('translatable', '=', True)], context=context)
lang_data = lang_model.read(cr, uid, lang_ids, ['code', 'name'], context=context)
return [(d['code'], d['name']) for d in lang_data]
-----------
def _get_language(self):
lang_model = self.env.get('res.lang')
或
# lang_model = self.env['res.lang']
lang_data = lang_model.search_read([('translatable', '=', True)],['code', 'name'])
return [(d['code'], d['name']) for d in lang_data]
-----------
@ v7 版
* 常用模型 models.model, models.TransientModel, models.AbstractModel
model:是基本模型,繼續了它,模型就具有官方的ORM功能了
TransientModel:常用於向導模型,數據是存入數據庫,但不是永久的
AbstractModel:常用於混合繼承
* 創建模型:
from openerp import models, fields
class Stage(models.Model):
_name = 'todo.task.stage'
_order = 'sequence,name'
_rec_name = 'name'
_table = 'todo_task_stage'
field1 = fields.Char()
_columns = {
'name': fields.char('State', size=64, required=True, translate=True),
'sequence': fields.integer('Sequence', help="Used to order states."),
'state_color': fields.selection(STATE_COLOR_SELECTION, 'State Color'),
'team': fields.selection(STATE_SCOPE_TEAM, 'Scope Team'),
}
_defaults = {
'sequence': 1,
}
哪些關鍵詞可以定義,可查看 /openerp/models.py 中的class BaseModel(object): 下面的定義
# _name 模型的標識符,用於引用,全局唯一,一般格式 "ModuleName.ClassName"
# _rec_name 覆蓋默認的name字段,重新定義,一般會加其它合成方法生成名稱,
# _table 映射到數據庫的表名,可自定義,默認是模型標識符把"."換成"_" 連啟來
# _columns 設置字段名 字典{字段名->字段聲明}
# _defaults 設置字段的默認值
# _auto 是否創建對象對應的table 缺省為True
# _inherit 繼承對象當不再定義 _name時,就是用父對象,若再定義_name 就生成了新的對象
# _inherits 繼承對象,且創建新的對象,不擁有父對象的字段,但可以操作父對象的字段
# _constraints 對象約束
def _check_date(self,cr,uid,ids)
for rec in self.browse(cr,uid,ids):
search_ids =self.search(cr,uid,[('date_from','<=', rec.date_to),('date_to','>=',rec.date_from),
('employee_id','=', rec.employee_id.id),('id','<>',rec.id)])
if search_ids:
return False
return True
_constraints=[_check_date,u'你在相同的時間段不允許創建多張請假條!',[u'起始日期',u'結束日期']]
# _sql_constraints 數據庫約束,最底層級別的約束
(標識,條件,顯示字符)
_sql_constraints =[
('date_check',"CHECK(date_from <= date_to)",u"開始日期必須小於結束日期"),
('days_check',"CHECK(days>0)",u"請假天數必須大於0")
]
# _log_access 是否自動在對應的數據表中增加 create_uid,create_date,write_uid,write_date 四個字段
可用於對象的方法(perm_read)讀取,權限控制
缺省是True
# _order 定義search()和read()方法是排序,缺省是 id 升序
# _parent_store
# _parent_name
# _parent_order
得到模型
self.env['todo.task.stage'] 或 self.env.get('todo.task.stage')
舊api 是 self.pool['todo.task.stage']或self.pool.get('todo.task.stage')
Transient 和 Abstract
查看系統已注冊的模型
技術->數據結構->模型
* 三種常用繼承 (在model中操作)
_inherit 沒重定義_name 這樣數據庫不會新建對象表,用被繼承的表
_inherit 重定義_name 這樣數據庫會創建新的對象表存儲
_inherits 是復合繼承,有模型對象,有字段對象
示例:
class MyModelExtended(Model):
_inherit = 'a.model' # direct heritage
_inherit = ['a.model, 'a.other.model'] # direct heritage
_inherits = {'a.model': 'field_name'} # polymorphic heritage
* 基本字段類型:
具有哪些字段類型可以查看 openerp/fields.py 中定義的字段類
例子:
name = fields.Char('Name',40,translate=True)
desc = fields.Text('Description')
state = fields.Selection( [('draft','New'), ('open','Started'),('done','Closed')],'State')
docs = fields.Html('Documentation')
sequence = fields.Integer('Sequence')
perc_complete = fields.Float('% Complete',(3,2))
date_effective = fields.Date('Effective Date')
date_changed = fields.Datetime('Last Changed')
fold = fields.Boolean('Folded')
image = fields.Binary('Image')
用方法設置默認值:
a_field = fields.Char(default=compute_default_value)
def compute_default_value(self):
return self.get_value()
注:參數self,現在python2.7.x 只是類中的方法,第一個參數就是self
但在調用這個方法的時候不必為這個參數賦值
例子說明:創建了一個類MyClass,實例化MyClass得到了MyObject這個對象,
然后調用這個對象的方法MyObject.method(arg1,arg2) ,這個過程中,
Python會自動轉為Myclass.mehod(MyObject,arg1,arg2)
# Char 字符串
size:字符串長度
translate 字符串可翻譯
# Text 文本 ,沒有長度限制
translate 字符串可翻譯
# Selection 下接選擇列表
下接選擇列表
例子:
aselection = fields.Selection([('a', 'A')])
aselection = fields.Selection(selection=[('a', 'A')])
aselection = fields.Selection(selection='a_function_name')
class SomeModel(models.Model):
_inherits = 'some.model'
type = fields.Selection(selection_add=[('b', 'B'), ('c', 'C')]) #增加選項
# Html html文本
translate 字符串可翻譯
# Integer 整數
# Float 浮點數
digits 設定 整數部分的長度,和小數部分的精度位 afloat = fields.Float(digits=(32, 32))
精度也可以用方法去計算 afloat = fields.Float(digits_compute=fun()) fun是自定義方法,返回
整數部分的長度,和小數部分的精度位 的元組
# Date 日期
context_tody 得到今天的日期
today 得到系統的日期
from_string 從字符串轉轉成日期對象
to_string 從日期對象轉字符串
例子:
>>> from openerp import fields
>>> adate = fields.Date()
>>> fields.Date.today()
'2014-06-15'
>>> fields.Date.context_today(self)
'2014-06-15'
>>> fields.Date.context_today(self, timestamp=datetime.datetime.now())
'2014-06-15'
>>> fields.Date.from_string(fields.Date.today())
datetime.datetime(2014, 6, 15, 19, 32, 17)
>>> fields.Date.to_string(datetime.datetime.today())
'2014-06-15'
日期格式化 DATE_FORMAT='%Y-%m-%d'
字段賦當前日期 fields.date.context_today, fields.date.context_today(self,cr,uid,context=context),
fields.date.today()
官方建議寫法 fields.date.context_today
字符串轉日期 datetime.datetime.strptime(sale.date,DATE_FORMAT)
日期轉字符串 datetime.datetime.strftime(datetme.date.today(),DATE_FORMAT)
python獲取當前日期 datetime.date.today()
# Datetime 時間
context_timestamp 得到今天的時間
now 得到系統的時間
from_string 從字符串轉轉成日期對象
to_string 從日期對象轉字符串
例子:
>>> fields.Datetime.context_timestamp(self, timestamp=datetime.datetime.now())
datetime.datetime(2014, 6, 15, 21, 26, 1, 248354, tzinfo=<DstTzInfo 'Europe/Brussels' CEST+2:00:00 DST>)
>>> fields.Datetime.now()
'2014-06-15 19:26:13'
>>> fields.Datetime.from_string(fields.Datetime.now())
datetime.datetime(2014, 6, 15, 19, 32, 17)
>>> fields.Datetime.to_string(datetime.datetime.now())
'2014-06-15 19:26:13'
return { 'value': {'shop_accept_datetime': time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time())) }}
時間格式化 DATETIME_FORMAT="%Y-%m-%d %H:%M:%S"
包含毫秒 DATETIME_FORMAT="%Y-%m-%d %H:%M:%S.%f"
字段賦當前時間 fields.datetime.now(), fields.datetime.context_timestamp(self,cr,uid,datetime.now(),context=context)
官方建議寫法 fields.datetime.now()
python 獲取當前時間 datetime.datetime.now()
--------
'datetime': fields.datetime('Historization Time')
_defaults = {
'datetime': fields.datetime.now, #賦當前時間
}
# Boolean 布爾值(true, false) 若default=True 復選框默認選上
# Bninary 二進制
儲存文件采用 base64編碼
# function: 函數型,該類型的字段,字段值由函數計算而得,不存儲在數據表中
其定義格式為:
fields.function(fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float',
fnct_search=None, obj=None, method=False, store=True)
@ fcnt 是函數或方法,用於計算字段值。如果method = true, 表示fcnt是對象的方法,其格式如下:
def fnct(self, cr, uid, ids, field_name, args, context),否則,其格式如下:
def fnct(cr, table, ids, field_name, args, context)。ids是系統傳進來的當前
存取的record id。field_name是本字段名,當一個函數用於多個函數字段類型時,
本參數可區分字段。args是'arg=None'傳進來的參數。 返回字典
@ arg 是傳給fcnt的實際參數
@ method 為True表示本字段的函數是對象的一個方法,為False表示是全局函數,不是對象的方法。
如果method=True,obj指定method的對象。
@ fcnt_inv 是用於寫本字段的函數或方法(沒有提供則本字佰只讀)。如果method = true, 其格式是:
def fcnt_inv(self, cr, uid, ids, field_name, field_value, args, context),否則格式為:
def fcnt_inv(cr, table, ids, field_name, field_value, args, context)
@ fnct_inv_arg 傳給寫本字佰方法的實際參數
@ type 是函數返回值的類型。
@ fcnt_search 定義該字段的搜索行為(不提供不返回任何結果)。如果method = true, 其格式為:
def fcnt_search(self, cr, uid, obj, field_name, args),否則格式為:def fcnt_search(cr, uid, obj, field_name, args)
@ store 表示是否希望在數據庫中存儲本字段值,缺省值為False。true表示存入 若采用增強形式,則會重新計算字段再存入
不過store還有一個增強形式,格式為 store={'object_name':(function_name,['field_name1','field_name2'],priority)} ,
其含義是,如果對象'object_name'的字段['field_name1','field_name2']發生任何改變,系統將調用函數function_name,
函數的返回結果將作為參數(arg)傳送給本字段的主函數,即fnct。
# dummy:虛擬型 fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one')
string:顯示字符
relation:關聯對象
type:關聯到對象的類型,這里是many2one
# many2one: 多對一關系,格式為:fields.many2one(關聯對象Name, 字段顯示名, ... )。可選參數有:
ondelete,可選值為"cascade"和"null",缺省值為"null",表示one端的record被刪除后,many端的record是否級聯刪除。
# one2many: 一對多關系,格式為:fields.one2many(關聯對象Name, 關聯字段, 字段顯示名, ... ),例:
'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts')。
# many2many: 多對多關系。例如: 'category_id':fields.many2many('res.partner.category','res_partner_category_rel',
'partner_id','category_id','Categories'), 表示以多對多關系關聯到對象res.partner.category,
關聯表為'res_partner_category_rel',關聯字段為 'partner_id'和'category_id'。當定義上述字段時,
OpenERP會自動創建關聯表為 'res_partner_category_rel',它含有關聯字段'partner_id'和'category_id'
# reference: 引用型,
格式為:fields.reference(字段名, selection, size, ... )。
其中selection是:
1)返回tuple列表的函數,或者 2)表征該字段引用哪個對象(or model)的tuples列表。
reference字段在數據庫表中的存儲形式是(對象名,ID),如(product.product,3)
3)表示引用對象product.product(數據表product_product)中id=3的數據。reference的例子:
def _links_get(self, cr, uid):
cr.execute('select object,name from res_request_link order by priority')
return cr.fetchall()
...
'ref':fields.reference('Document Ref 2', selection=_links_get, size=128),
...
上例表示,字段ref可以引用哪些對象類型的resource,可引用的對象類型從下拉框選擇。
下拉框的選項由函數_links_get返回,是(object,name)對的列表,如[("product.product","Product"),
("account.invoice","Invoice"), ("stock.production.lot","Production Lot")] 。
# related: 關聯字段,表示本字段引用關聯表中的某字段。特別用於一對多,
比如一個移庫單包含多個庫存移動單,要在一個移庫單緊着羅列出關聯的所有庫存移動單
這時采用 related 定義字段特別有用。
格式為:fields.related(關系字段,引用字段,type, relation, string, ...),
關系字段是本對象的某字段(通常是one2many or many2many),
引用字段是通過關系字段關聯的數據表的字段,
type是引用字段的類型,若關聯的字段是many2one 或 many2many 類型,那就要明確指定
如果type是many2one or many2many, relation指明關聯表。例子如下:
'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'),
'city':fields.related('address','city',type='char', string='City'),
'country':fields.related('address','country_id',type='many2one', relation='res.country', string='Country'),
這里,city引用address的city字段,country引用address的country對象。在address的關聯對象res.partner.address中,
country_id是many2one類型的字段,所以type='many2one', relation='res.country'。
# property: 屬性字段,下面以具體例子解說property字段類型。
'property_product_pricelist': fields.property('product.pricelist', type='many2one',
relation='product.pricelist',string="Sale Pricelist", method=True, view_load=True, group_name="Pricelists Properties")
這個例子表示,本對象通過字段'property_product_pricelist'多對一(type='many2one')
關聯到對象 product.pricelist(relation='product.pricelist')。和many2one字段類型不同的是,
many2one字段會在本對象中創建數據表字段'property_product_pricelist',property字段類型不會
創建數據表字段'property_product_pricelist'。property字段類型會從數據表ir.property中查找
name='property_product_pricelist'(即字段定義中的'product.pricelist'加上前綴 property,
並將"."替換成"_"作為name)且company_id和本對象相同的記錄,從該記錄的value字段(value字段
類型為 reference)查得關聯記錄,如(product.pricelist,1),表示本對象的resource多對一關聯
到對象 product.pricelist的id=1的記錄。也就是說,property字段類型通過ir.property間接多對一關聯到別的對象。
property字段類型基本上和many2one字段類型相同,但是有兩種情況優於many2one字段。其一是,例如,
當有多條記錄通過 ir.property的name='property_product_pricelist'的記錄關聯到記錄 (product.pricelist,1),
此時,如果希望將所有關聯關系都改成關聯到記錄(product.pricelist,2)。如果是 many2one類型,不寫代碼,
很難完成此任務,是property字段的話,只要將ir.property中的value值 (product.pricelist,1)
改成(product.pricelist,2),則所有關聯關系都變了。修改ir.property的 value值可以在系統管理下的
菜單Configuration --> Properties中修改。其二是,例如,同一業務伙伴,但希望A公司的用戶進來看到
的該業務伙伴價格表為pricelistA,B公司的用戶進來看到的該業務伙伴價格表為pricelistB,
則many2one類型達不到該效果。property類型通過ir.property中的記錄關聯時加上了 company_id的條件,
因此可以使得不同公司的員工進來看到不同的關聯記錄。
由於property類型通過ir.property關聯,因此,每個property類型的字段都必須在ir.property中有一條關聯記錄。
這可以在安裝時導入該條記錄,參考代碼如下:
<record model="ir.property" id="property_product_pricelist">
<field name="name">property_product_pricelist</field>
<field name="fields_id" search="[('model','=','res.partner'), ('name','=','property_product_pricelist')]"/>
<field name="value" eval="'product.pricelist,'+str(list0)"/>
</record>
* 字段使用精度
import openerp.addons.decimal_precision as dp 這個模塊主要處理字段的精度
#設置->技術->數據結構->小數精度 這里面定義各情況的小數精度
....
'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Product Price'),
銷售價格,采用系統 Product Price的小數精度保留
* 字段集中寫法
_columns = {
'name':fields.char( u'課程名',size=64,select=True),
'date_start':fields.date(u'開始日期',select=True),
'total_day':fields.float(u'總天數',digits=(16,1)),
'teacher':fields.many2one('res.users',u'授課老師'),
'students':fields.many2many('res.partner',string=u'學生'),
'price':fields.float(u'價格',digits=(16,2)),
}
采用_columns 關鍵詞來描述
* 普通字段屬性
在 openerp/fields.py 中 _slots 有定義
# string 字段顯示名label,任意字符串
# help='str' 用於懸掛幫助信息提示
# readonly=True 本字段是否只讀,缺省值:False
# required=True 本字段是否必須的,缺省值:False。
# index = True 設置字段創建索引
# select=True 相關聯字段下拉列表
# default 設置字段的默認值
name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self:self.env.user)
# states 定義特定state才生效的屬性,格式為:{'name_of_the_state': list_of_attributes},
其中list_of_attributes是形如[('name_of_attribute', value), ...]的tuples列表。
例子(參見account.transfer): 'partner_id': fields.many2one('res.partner', 'Partner',
states={'posted':[('readonly',True)]}),
# groups 設置權限組訪問可見,值為列表形式,元素是組的xml ids(也就是外部標識符)
# copy=False 設置字段不能被復制,普通字段是True, 在 one2many是默認是False
# oldname='field' 當一個字段在新版本重命名了,指定老的字段,老的字段的數據會自動拷貝到新的字段中
# compute 給定一個方法名計算出該字段
# inverse 給定一個方法名反轉該字段 (為了在計算字段設置值)
document = fields.Char(compute='_get_document', inverse='_set_document')
def _get_document(self):
for record in self:
with open(record.get_document_path) as f:
record.document = f.read()
def _set_document(self):
for record in self:
if not record.document: continue
with open(record.get_document_path()) as f:
f.write(record.document)
# search 給定一個方法名搜索該字段 (用在計算字段)
upper_name = field.Char(compute='_compute_upper', search='_search_upper')
def _search_upper(self, operator, value):
if operator == 'like':
operator = 'ilike'
return [('name', operator, value)]
# store 設定該字段是否存在數據表中,在用了compute時為False ,其它默認是True
# compute_sudo 設定是否要超級用戶來計算該字段,默認是False
upper = fields.Char(compute='_compute_upper',
inverse='_inverse_upper',
search='_search_upper')
@api.depends('name')
def _compute_upper(self):
for rec in self:
rec.upper = rec.name.upper() if rec.name else False
def _inverse_upper(self):
for rec in self:
rec.name = rec.upper.lower() if rec.upper else False
def _search_upper(self, operator, value):
if operator == 'like':
operator = 'ilike'
return [('name', operator, value)]
--------
from openerp import api
total = fields.Float(compute='_compute_total')
@api.depends('value', 'tax')
def _compute_total(self):
for record in self:
record.total = record.value + record.value * record.tax
# related 序列的字段名,用於關聯字段,默認不存數據表中,可以store=True來存,
nickname = fields.Char(related='user_id.partner_id.name', store=True)
這里看出前面定義了user_id字段,這里是接着關聯到 user_id這個字段把所要的信息提取出來
# company_dependent 定義該字段是不是公司依賴
# change_default 別的字段的缺省值是否可依賴於本字段,缺省值為:False。
例子(參見res.partner.address) 'zip': fields.char('Zip', change_default=True, size=24),
這個例子中,可以根據zip的值設定其它字段的缺省值,例如,可以通過程序代碼,
如果zip為200000則city設為“上海”,如果zip為100000則city為“北京”。
# domain: 域條件,缺省值:[]。在many2many和many2one類型中,字段值是關聯表的id,
域條件用於過濾關聯表的record。例子: 'default_credit_account_id': fields.many2one('account.account',
'Default Credit Account', domain="[('type','!=','view')]"),
本例表示,本字段關聯到對象('account.account')中的,type不是'view'的record。
# context 上下文
# invisible: 本字段是否可見,即是否在界面上顯示本字段,缺省值True。
# selection: 只用於reference字段類型,參見前文reference的說明。
# size 只用於Char 設置可最大接收字符數
# translate=true 用於Text Char Html 本字段值(不是字段的顯示名)是否可翻譯,缺省值:False。
# ondelete 用於關聯字段,定義刪除方式
# manual
# deprecated=True 當用這個字段,日志會記錄警告,主要是版本升級一些過渡字段
# password ="True" 密碼星號顯示
# nolabel ="1" 隱藏標簽
# attr屬性 可以定義多條件這段只讀顯示
# digits 格式化浮點數
<field digits="(14,3)" name="volume" attrs="{readonly:[('type','=','service')]}"
# default_focus 新開窗口光標位置
# Widget 多種部件顯示格式 widget="one2many_list"
one2one_list, one2many_list, many2one_list, many2many,Url,Email,Image,float_time,reference
不同的字段類型具有不同的屬性,上面是公用的居多,基它的可以在
openerp/fields.py 和 openerp/osv/fields.py 中找,主要看 __slots__的列表值
*保留字段名字
# id 數據庫自動生成做為主鍵
# create_uid 創建記錄時的用戶
# create_date 創建記錄時的日期和時間
# write_uid 修改記錄時的最后用戶
# write_date 修改記錄時最后日期和時間
*內置專用字段名
# name (Char) 記錄的默認名,但可以用 _rec_name 去覆蓋
# active (Boolean) 標識記錄的有效性 常用於domain domain是元組表達式
# sequence (Integer) 用在列表視圖記錄排序
# state (Selection) 顯示記錄的生命周期
# parent_id, parent_left, parent_right 用於記錄的上下級
* 部件字段類型(Widgets)
# image(binary)
# monetary(float)
# duration (float)
# relative(datetime)
# contact(res.partner many2one)
* 字段條件約束
_constraints=[(check_phone,u'手機號碼和電話號碼不能同時為空',['mobile_number','phone_number']),
(check_estimated_ship_date,u'期望發貨日期必須比當前日期要大',['estimated_ship_date'])]
def check_phone(self, cr, uid, ids, context=None):
for apply_delivery in self.browse(cr,uid,ids,context=context):
if apply_delivery.mobile_number or apply_delivery.phone_number:
return True
return False
def check_estimated_ship_date(self, cr, uid, ids, context=None):
apply_delivery =self.browse(cr,uid,ids,context=context)
estimated_ship=time.strptime(apply_delivery[0].estimated_ship_date,'%Y-%m-%d %H:%M:%S')
if time.mktime(estimated_ship)
return False
return True
_sql_constraints = {
('internal_code_uniq', 'unique(internal_code)',
'Internal Code mast be unique!')
}
internal_code 必須是唯一
新api
from openerp.exceptions import ValidationError
@api.constrains('age')
def _check_something(self): # self是一個記錄集,要遍歷,便也可用@api.one 來輔助
for record in self:
if record.age > 20:
raise ValidationError("Your record is too old: %s" % record.age)
* 模型的關系
例子:
class TodoTask(models.Model):
_inherit = 'todo.task'
stage_id = fields.Many2one('todo.task.stage','Stage')
tage_ids =fields.Many2many('todo.task.tag',string='Tages')
model 和 string 所有關系都接受屬性,
# 多對一 Many2one
意思就是到對方的對象上選擇一個用來關聯本記錄
parent_id = fields.Many2one(string='Parent Tag', comodel_name='product.tag',
select=True, ondelete='cascade')
string :顯示標簽名稱
comodel_name:引用的目標模型名
select=True 字段加上索引
domain
context 是上下文
ondelete 默認值為set null 表示關聯記錄刪除 本字段設為空
restrict 關聯對方記錄刪除,本關聯字段不讓其刪除,要刪除本關聯
字段的記錄,才能刪除關聯對方的記錄
cascade 關聯對方記錄刪除,對於本關聯字段對應的記錄全部跟着刪除
auto_join=False 允許用sql 語句來關聯
delegate=False
# 一對多 One2Many
接受的屬性 和 Many2one 一樣
可以很簡單地從one得到many方的對應的所有記錄
class Stage(models.Model):
_name = 'todo.task.stage'
Stage class relation with Tasks:
tasks = fields.One2many(
'todo.task', # 關聯對象
'stage_id', # 本對象提供給關聯對象關聯的字段,這里是主鍵
這個字段只要是唯一就可以
'Tasks in this stage')
parent_id = fields.Many2one(string='Parent Tag', comodel_name='product.tag',
select=True, ondelete='cascade')
child_ids = fields.One2many(string='Child Tags', comodel_name='product.tag',
inverse_name='parent_id')
這里parent_id會存入數據表,child_ids就不會了
string :顯示標簽名稱
comodel_name:引用的目標模型名
inverse_name 返向關聯的字段 對應Many2one
domain:
context:
auto_join=False 允許用sql 語句來關聯
limit='1000' 定義讀時出多少個記錄
copy=False
# 正反關聯定義:
Many2one 和 one2many 是成對出現的
Many2one 找關聯對象
One2many 提供給關聯對象的關聯字段
@ 正:
class TodoTask(models.Model):
_inherit = 'todo.task'
stage_id = fields.Many2one('todo.task.stage','Stage')
@ 反:
class Stage(models.Model):
_name = 'todo.task.stage'
tasks = fields.One2many(
'todo.task',
'stage_id',
'Tasks in this stage')
# 多對多 Many2many
會轉換為Many2one Many2one
這個one,自然就是境加的中間關聯表,一般表名后綴為 _rel
@ class 為 TodoTask
tag_ids = fields.Many2many(
'todo.task.tag', # related model
'todo_task_tag_rel', # relation table name
'task_id', # field for "this" record
'tag_id', # field for "other" record
string='Tasks')
也可以用長表單鍵表示
tag_ids = fields.Many2many(
comodel_name='todo.task.tag', # related model
relation='todo_task_tag_rel', # relation table name
column1='task_id', # field for "this" record
column2='tag_id', # field for "other" record
string='Tasks')
string:顯示標簽名稱
comodel_name:引用的目標模型名
relation:存多對多關系的關系表名
column1;存目標模型關系字段在關系表中
column1;存本模型關系字段在關系表中
domain
context 是上下文
limit='1000' 定義讀時出多少個記錄
對應對方關聯對象也要加一條反向關聯,這個相對簡單
@ class為 Tag
task_ids = fields.Many2many(
'todo.task',# related model
string='Tasks' )
# 父子分層
可以用Many2one 表示子到父, 用One2many 表示父到子
class Tags(models.Model):
_name = 'todo.task.tag'
_parent_store = True
# _parent_name = 'parent_id'
name = fields.Char('Name')
parent_id = fields.Many2one(
'todo.task.tag', 'Parent Tag', ondelete='restrict')
parent_left = fields.Integer('Parent Left', index=True)
parent_right = fields.Integer('Parent Right', index=True)
為了方便還會加一個字段
child_ids = fields.One2many('todo.task.tag', 'parent_id', 'Child Tags')
# One2Many 和 Many2many 的 eval 賦值
<field name=”tag_ids”
eval=”[(6,0,
[ref(’vehicle_tag_leasing’),
ref(’fleet.vehicle_tag_compact’),
ref(’fleet.vehicle_tag_senior’)]
)]” />
(0,_ ,{’field’: value}) 這將創建一個新的記錄並連接它
(1,id,{’field’: value}): 這是更新一個已經連接了的記錄的值
(2,id,_) 這是刪除或取消連接某個已經連接了的記錄
(3,id,_) 這是取消連接但不刪除一個已經連接了的記錄
(4,id,_) 連接一個已經存在的記錄
(5,_,_) 取消連接但不刪除所有已經連接了的記錄
(6,_,[ids]) 用給出的列表替換掉已經連接了的記錄
這里的下划線一般是0或False
# 引用字段動態關系
Refers to
class TodoTask(models.Model):
refers_to = fields.Reference(
[('res.user', 'User'), ('res.partner', 'Partner')],
'Refers to')
可用保用任何注冊模型
from openerp.addons.base.res import res_request
def referencable_models(self):
return res_request.referencable_models(
self, self.env.cr, self.env.uid, context=self.env.context)
class TodoTask(models.Model):
refers_to = fields.Reference(
referencable_models, 'Refers to')
這樣就不會只局限在用戶和合作伙伴兩個模型了
* 字段設置
不是所有定義都會存到數據表中
首先_auto 要為true 缺省也是true
#普通定義
name = fields.Char('Tag Name', required=True, translate=True)
#顯示名稱取代通過計算
_rec_name='display_name'
display_name = field.Char('Full Name' compute="_compute_display_name")
#這個是不會存數據表中的
@api.one
@api.depends('name', 'parent_id.name')
def _compute_display_name(self):
""" Return the tags' display name, including their direct parent. """
if self.parent_id:
self.display_name = self.parent_id.display_name + ' / ' + self.name
else:
self.display_name = self.name
#
* 字段操作
# 計算
class TodoTask(models.Model):
stage_fold = fields.Boolean(
'Stage Folded?',
compute='_compute_stage_fold')
@api.one
@api.depends('stage_id.fold')
def _compute_stage_fold(self):
self.stage_fold = self.stage_id.fold
-----
from openerp import api
total = fields.Float(compute='_compute_total')
@api.depends('value', 'tax')
def _compute_total(self):
for record in self:
record.total = record.value + record.value * record.tax
可以采用@api.one 讓它自動循環
@api.one
@api.depends('value', 'tax') #定義依賴的字段,這樣定義的字段值變化,對應會重新計算
def _compute_total(self):
self.total = self.value + self.value * self.tax
# 關聯字段
participant_nick = fields.Char(string='Nick name',
related='partner_id.name')
合作伙伴名字改,該對象的昵稱跟着變化
# 搜索和寫入
class TodoTask(models.Model):
stage_fold = fields.Boolean(
'Stage Folded?',
compute='_compute_stage_fold',
# store=False) # the default
search='_search_stage_fold',
inverse='_write_stage_fold')
@api.one
@api.depends('stage_id.fold')
def _compute_stage_fold(self):
self.stage_fold = self.stage_id.fold
def _search_stage_fold(self, operator, value):
return [('stage_id.fold', operator, value)]
def _write_stage_fold(self):
self.stage_id.fold = self.stage_fold
# 為了搜索加字段
stage_state = fields.Selection(
related='stage_id.state',
string='Stage State')
# 模型約束(SQL 和 Python)
#一個用戶在一時間只有一個活動的任務,加復合主鍵一樣
class TodoTask(models.Model):
_sql_constraints = [
('todo_task_name_uniq',
UNIQUE (name, user_id, active)',
'Task title must be unique!')]
#檢查名稱少於5個字符
class TodoTask(models.Model):
@api.one
@api.constrains('name')
def _check_name_size(self):
if len(self.name) < 5:
raise ValidationError('Must have 5 chars!')
* 向導對象
向導是一個動態與用戶進行互動對話的模型
TransientModel 擴展 openerp/models.py中的Model類
#向導的數據不是固定的,系統會定期清除數據
#向導沒有任何權限限制,用戶可以隨意創建
兼容osv_memory = TransientModel
如: class procurement_compute(models.TransientModel)
class procurement_compute(osv.osv_memory) 舊
class procurement_compute(osv.TransientModel) 舊
# 向導是通過 "ir.actions.act_window" 觸發的
<record model="ir.actions.act_window" id="act_make_procurement">
<field name="name">Procurement Request</field>
<field name="res_model">make.procurement</field>
<field name="src_model">product.product</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="key2">client_action_multi</field>
</record>
可以簡寫:
<act_window name="Procurement Request"
res_model="make.procurement"
src_model="product.product"
view_mode="form"
target="new"
key2="client_action_multi"
id="act_make_procurement"/>
id 不可重名
name 菜單名稱
src_model 上級對象
res_model 向導對象
view_mode 視圖類型
target 開打方式 current 當前窗口,new 新建窗口,inline 行內編輯,inlineview 內嵌視圖
key2 菜單出現位置
定義向導視圖
一般兩個確認事件,選擇取消后窗口自動消失
<button name="make_procurement" string="Ask New Products" type="object" class="oe_highlight" />
or
<button string="Cancel" class="oe_link" special="cancel" />
* 記錄集
一個模型的實例同時也是一個記錄集的實例
一個記錄集是同一個模型的一組記錄
class AModel(Model):
# ...
def a_fun(self):
self.do_something() # self 在這里就是一個記錄集,會包含很多記錄
record_set = self
record_set.do_something()
def do_something(self):
for record in self:
print record
如果用了@api.one 來修飾,self 就會當前一條記錄,不是記錄集了
*記錄集支持的操作
rs1 | rs2 求並集 是指兩個集合的所有元素構成的集合
rs1 + rs2 求兩個集直接連起來,不管重復
rs1 & rs2 求交集 是指兩個集合元素相同的部分構成的集合
rs1 - rs2 求差集 是指其中一個集合中除去另一個集合相同元素以后剩余的元素構成的集合
rs1.copy() 淺拷貝
#其它記錄集操作
record in recordset 檢測一個記錄是否在一個記錄集中
record not in recordset 檢測一個記錄是否不在一個記錄集中
recordset.ids 記錄ID集
recordset.ensure_one() 檢測是一個記錄
recordset.exists() 若存在返回一個備份
recordset.filtered(func) 過濾記錄集
recordset.mapped(func)
recordset.sorted(func) 排序后
# filtered()
def filtered(self, func):
recset.filtered(lambda record: record.company_id == user.company_id)
只保留是當前用戶公司的記錄
recset.filtered("product_id.can_be_sold")
保留可銷售的產品
# sorted()
def sorted(self, key=None, reverse=False):
recset.sorted(key=lambda r: r.name)
按記錄集中的名稱字段升序排序
#先按照安partner_id 排序,然后用name排序
from operator import attrgetter
recset.sorted(key=attrgetter('partner_id', 'name'))
# mapped()
def mapped(self, func):
recset.mapped(lambda record: record.price_unit - record.cost_price)
# 從記錄集抽出 name 這一列再組成記錄集
recset.mapped('name')
# 返回合作伙伴記錄集
recset.mapped('invoice_id.partner_id')
* 特殊記錄集ids屬性
* 一些記錄的操作
# 得到記錄的顯示名稱
name_get() 方法 對應的字段是 display_name
* 環境操作
# self.env 得到環境 這是一個很有用的東西
def afun(self):
self.env
# or
model.env
#修改上下文
def with_context(self, *args, **kwargs)
self.env['res.partner'].with_context(tz=x).create(vals)
不能用這個函數來修改當前的環境
# 替換整個環境
def with_env(self, env)
with_env()
# 切換用戶
def sudo(self, user=SUPERUSER_ID):
self.sudo(user.id)
self.sudo() # 會切換到超級用戶
# or
self.env['res.partner'].sudo().create(vals)
用普通用戶去搜索合作伙伴
public = env.ref('base.public_user')
env['res.partner'].sudo(public).search([])
# 得到用戶
self.env.user 得到當前用戶記錄
self.env.uid 或 self._uid 得到當前用戶在數據表中的id
# 得到游標(這是操作數據表的基礎)
self.env.cr 或 self._cr
# 得到上下文(得到狀態數據)
self.env.context 或 self._context
# 得到給定模型的實例(重點業務操作)
self.env[model_name]
model_name ,像 res.users res.partner等可以到后台查到
# 得到xml的記錄
self.env.ref('base.main_company')
# 清理環境緩存
self.env.invalidate_all()
* 模型中的常用操作(v7,v8都可以調用,采用的調用形式不一樣,返回的結果不一樣)
這些是模弄預定義的方法
可以 openerp/models.py 中的 BaseModel類中的定義的方法
其中有一段 :
_cr = property(lambda self: self.env.cr)
_uid = property(lambda self: self.env.uid)
_context = property(lambda self: self.env.context)
這樣繼承模型,就可以用 self._cr self._uid self._context
# 基本方法create, search, search_count, read, search_read, browse, write, unlink,copy
-----
create:創建一條記錄
@api.model
@api.returns('self', lambda value: value.id)
def create(self, vals):
pass...
vals 是一個字典數據類型 {'key':'value'}
v8調用 create(vals) 返回創建的記錄
v7調用 create(cr, uid, vals, context=none)返回創建記錄的id
例子:
v8:self.env.get('res.users').create({'name': 'New name'})
v7:self.pool['stock.transfer_details'].create(cr, uid, {'picking_id': len(picking) and picking[0] or False}, context)
---------
write:寫入記錄
@api.multi
def write(self, vals):
def _write(self, cr, user, ids, vals, context=None)
vals:是一個字典數據類型 {'key':'value'}
v8調用 write(vals) 返回True
v7調用 write(cr, uid, vals, context=none)
例子:
v8:self.write({'x': 1, 'y': 2, 'z': 4})
v7:self.write(cr, uid, processed_ids, {'processed': 'true'}, context=context)
-----------
search:指定條件搜索記錄
@api.returns('self',
upgrade=lambda self, value, args, offset=0, limit=None, order=None, count=False: value if count else self.browse(value),
downgrade=lambda self, value, args, offset=0, limit=None, order=None, count=False: value if count else value.ids)
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
pass...
args 是搜索條件 是domain表達式
offset 記錄第幾個 (int)
limit 返回幾個(int)
order 指定排序字段(str)
count 為真,則只返回記錄集總數 (bool)
v8調用 search(args[, offset=0][, limit=None][, order=None][, count=False]) 返回記錄集
v7調用 search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False) 返回記錄集ids
例子:
v8: self.env.get('res.users').search([('is_company', '=', True)])
v7:self.pool.get('stock.config.settings').search(cr, uid, [], limit=1, order='id DESC', context=context)
-----------
search_count:指定條件搜索記錄數
def search_count(self, cr, user, args, context=None):
pass...
args 是搜索條件 是domain表達式
v8調用 search(args)
v7調用 search_count(self, cr, user, args, context=None)
例子:
v8: self.env.get('res.users').search_count([('is_company', '=', True)])
v7:self.pool('account.move.line').search_count(cr, uid, [('asset_id', '=', asset_id)], context=context)
-------
read:讀取記錄集中指定字段 返回字典列表(記錄)
@api.v7
def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
pass...
若有多個返回記錄的字典列表,若一個就返回一個記錄,沒有就返回false
@api.v8
def read(self, fields=None, load='_classic_read'):
pass...
若有多個返回記錄的字典列表,沒有就返回空列表[]
fields: 字段列表,默認是全部字段(list)
v8調用 read(fields)
v7調用 read(self, cr, user, ids, fields=None, context=None)
例子:
v8:self.env['res.users'].read([], ['name'])
v7:self.read(cr, uid, ids, ['product_qty'])
----------------
search_read:搜索並讀取指定字段,合了search()和read()方法
resolve_o2m_commands_to_record_dicts = resolve_2many_commands
def search_read(self, cr, uid, domain=None, fields=None, offset=0, limit=None, order=None, context=None):
pass...
domain:條件表達式
fields: 字段列表,默認是全部字段(list)
offset 記錄第幾個 (int)
limit 返回幾個(int)
order 指定排序字段(str)
v8調用 self.search_read(domain=None, fields=None)
v7調用 self.search_read(self, cr, uid, domain=None, fields=None, offset=0, limit=None, order=None, context=None)
例子:
v8: self.env['res.users'].search_read([], ['name'])
v7: self.pool.get('stock.picking.type').search_read(cr, uid, [], ['sequence'], order='sequence desc')
----------------
browse: 指定ids搜索記錄集
@api.v7
def browse(self, cr, uid, arg=None, context=None):
@api.v8
def browse(self, arg=None):
args 記錄的ids (list)
v8調用 self.browse(args)
v7調用 self.browse(self, cr, uid, arg=None, context=None)
例子:
v8: self.env['res.users'].self.browse([1, 2, 3])
v7: self.pool.get('stock.quant').browse(cr, uid, quant_ids, context=context)
---------------
unlink:記錄刪除
def unlink(self, cr, uid, ids, context=None):
ids:記錄ID集 (list)
v8調用 self.unlink(ids) 返回一個新記錄
v7調用 self.unlink(self, cr, uid, ids, context=None) 返回一個新記錄id
例子:
v8: self.unlink([operation_id])
v7: self.unlink(cr, uid, [operation_id], context=context)
--------------
copy:復制記錄
分為記錄拷貝和記錄集拷貝
記錄拷貝:
@api.one
記錄集拷貝
@api.multi
@api.returns('self', lambda value: value.id)
def copy(self, cr, uid, id, default=None, context=None):
v8調用 self.copy(args) 返回一個新記錄
v7調用 self.copy(self, cr, uid, id, default=None, context=None 返回一個新記錄id
例子:
v8: self.copy(move.id)
v7: self.copy(cr, uid, move.id, default_val)
# 得到指定字段默認值:
@api.model
def default_get(self, fields_list):
返回指定字段默認的字典列表
v8調用 default_get(fields_list)
v7調用 default_get(self, cr, uid, fields, context=None)
例子:
v8: self.default_get({'reception_steps', 'delivery_steps'})
v7: self.default_get(cr, uid, {'reception_steps', 'delivery_steps'})
# 得到記錄的顯示名:
@api.multi
def name_get(self):
pass...
這個方法會覆蓋:
https://github.com/odoo/odoo/blob/8.0/addons/event/event.py#L194
v8調用 self.name_get()
v7調用 self.name_get(cr, user, ids, context=null) 一般重寫了,加入了ids
例子:
v8:
v7:
# 創建只有display_name的記錄
@api.model
def name_create(self, name):
返回創建好的記錄的 [(id,name)]
v8調用 self.name_create()
v7調用 self.name_create(cr, user, name, context=null) 一般重寫了,加入了ids
# 根據display_name來搜索
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
pass...
name:給定比較的name (str)
args:domain:條件表達式
operator: name用來的比較操作符
limit:限定返回記錄數目
v8調用 self.name_search(name[,args][,operator][,limit])
v7調用 self.name_search(cr, user,name[,args][,operator][,limit], context=null)
# 得到指定字段特定語言的字符串
def read_string(self, cr, uid, id, langs, fields=None, context=None):
# 寫入批定字段特定語言的字符串
def write_string(self, cr, uid, id, langs, vals, context=None):
# 清除記錄的緩存
def clear_caches(self):
# 按分組列表記錄
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):
cr: 數據游標
uid: 當前用戶id
domain: domain:條件表達式
fields: 列出來顯示的字段 (list)
groupby: 列出用來分組的字段(list)
offset: 結果集的起點,第幾條開始(int)
limit: 最多顯示幾條(int)
context: 上下文
orderby: 字段排序列表(list)
lazy: 延遲加載(bool)
返回字典列表
# 得到字段的權限
def check_field_access_rights(self, cr, user, operation, fields, context=None):
# 特殊字段操作方法:perm_read, perm_write
def perm_read(self, cr, uid, ids)
def perm_write(self, cr, uid, ids, fields)
#字段(fields)和視圖(views)操作方法:fields_get, distinct_field_get, fields_view_get
def fields_get(self, cr, user, allfields=None, context=None, write_access=True, attributes=None)
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False)
def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None)
# 記錄寫
@api.one
...
self.write({'key': value })
# or
record.write({'key': value})
寫入沒有返回值
@api.multi
def do_mass_update(self):
self.ensure_one()
if not (self.new_deadline or self.new_user_id):
raise exceptions.ValidationError('No data to update!')
# else:
_logger.debug('Mass update on Todo Tasks %s',
self.task_ids.ids)
if self.new_deadline:
self.task_ids.write({'date_deadline': self.new_deadline})
if self.new_user_id:
self.task_ids.write({'user_id': self.new_user_id.id})
return True
self.ensure_one() 檢測單例
# 檢查記錄對象存在 exists()
if not record.exists():
raise Exception("The record has been deleted")
# 外id引用
>>> env.ref('base.group_public')
res.groups(2)
#保證記錄單例 ensure_one
records.ensure_one()
# is equivalent to but clearer than:
assert len(records) == 1, "Expected singleton"
# 記錄集寫
@api.multi
...
self.write({'key': value })
# It will write on all record.
self.line_ids.write({'key': value })
# Many2many One2Many的行為
self.line_ids.create({'name': 'Tho'}) #
self.line_ids.create({'name': 'Tho', 'order_id': self.id}) #
self.line_ids.write({'name': 'Tho'}) # 保存所有的相關記錄
# 刪除
>>> rec = Partner.search([('name', '=', 'ACME')])
>>> rec.unlink()
# 拋出異常終止
raise Exception(
"You can not export the column ID of model %s, because the "
"table %s is not an ordinary table."
% (self._name, self._table))
這個方式也可用來調試代碼變量
* 使用數據庫操作游標
def my_fun(self):
cursor = self._cr
# or
self.env.cr
* 保用線程
with Environment.manage(): # class function
env = Environment(cr, uid, context)
* 打印時間處理,主要是時區
dt = datetime.datetime.strptime(str, '%Y-%m-%d %H:%M:%S')
dt = dt.replace(tzinfo=pytz.utc).astimezone(pytz.timezone('Asia/Shanghai'))
return dt.strftime(fmt.encode('utf-8')).decode('utf-8')
這個主要的是解決打印時間的顯示的和實際時間的差別,