1.多对一(一对一)关系:采购单与供应商之间的关系
'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
change_default=True, track_visibility='always'),
2.一对多(主细结构)关系:采购单主表与采购单明细表之间的关系
'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
对应的XML定义:
<page string="Purchase Order"> <field name="order_line"> <tree string="Purchase Order Lines" editable="bottom"> <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)"/> <field name="name"/> <field name="date_planned"/> <field name="company_id" groups="base.group_multi_company" widget="selection"/> <field name="account_analytic_id" groups="purchase.group_analytic_accounting" domain="[('type','not in',('view','template'))]"/> <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)"/> <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)"/> <field name="price_unit"/> <field name="taxes_id" widget="many2many_tags" domain="[('parent_id','=',False),('type_tax_use','!=','sale')]"/> <field name="price_subtotal"/> </tree> </field> <group class="oe_subtotal_footer oe_right"> <field name="amount_untaxed" widget="monetary" options="{'currency_field': 'currency_id'}"/> <field name="amount_tax" widget="monetary" options="{'currency_field': 'currency_id'}"/> <div class="oe_subtotal_footer_separator oe_inline"> <label for="amount_total"/> <button name="button_dummy" states="draft" string="(update)" type="object" class="oe_edit_only oe_link"/> </div> <field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/> </group> <div class="oe_clear"/> <field name="notes" class="oe_inline" placeholder="Terms and conditions..."/> </page>
反过来,采购单明细与采购单主表之间为多对一关系
'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
其中 ondelete='cascade' 表示开启级联删除,即删除采购单主表时自动删除采购单明细表。
3.多对多关系:客户与客户类型之间的关系,自动生成关联表 res_partner_res_partner_category_rel
'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'),
对应的XML定义:
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
对应的XML定义:
<page string="库位操作范围">
<field name="location_ids" nolabel="1" />
</page>
4.自关联关系:OpenERP客户与联系人之间的关系,res.partner 与 res.partner 自关联产生 many2one 关系(parent_id)和 one2many 关系(child_ids)
class res_partner(osv.osv, format_address):
_description = 'Partner'
_name = "res.partner"
# indirection to avoid passing a copy of the overridable method when declaring the function field
_commercial_partner_id = lambda self, *args, **kwargs: self._commercial_partner_compute(*args, **kwargs)
_order = "name"
_columns = {
'name': fields.char('Name', size=128, required=True, select=True),
'date': fields.date('Date', select=1),
'title': fields.many2one('res.partner.title', 'Title'),
'parent_id': fields.many2one('res.partner', 'Related Company'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts', domain=[('active','=',True)]), # force "active_test" domain to bypass _search() override
'ref': fields.char('Reference', size=64, select=1),
'lang': fields.selection(_lang_get, 'Language',
help="If the selected language is loaded in the system, all documents related to this contact will be printed in this language. If not, it will be English."),
'tz': fields.selection(_tz_get, 'Timezone', size=64,
help="The partner's timezone, used to output proper date and time values inside printed reports. "
"It is important to set a value for this field. You should use the same timezone "
"that is otherwise used to pick and render date and time values: your computer's timezone."),
'tz_offset': fields.function(_get_tz_offset, type='char', size=5, string='Timezone offset', invisible=True),
'user_id': fields.many2one('res.users', 'Salesperson', help='The internal user that is in charge of communicating with this contact if any.'),
'vat': fields.char('TIN', size=32, help="Tax Identification Number. Check the box if this contact is subjected to taxes. Used by the some of the legal statements."),
'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
'website': fields.char('Website', size=64, help="Website of Partner or Company"),
'comment': fields.text('Notes'),
'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'),
'credit_limit': fields.float(string='Credit Limit'),
'ean13': fields.char('EAN13', size=13),
'active': fields.boolean('Active'),
'customer': fields.boolean('Customer', help="Check this box if this contact is a customer."),
'supplier': fields.boolean('Supplier', help="Check this box if this contact is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."),
'employee': fields.boolean('Employee', help="Check this box if this contact is an Employee."),
'function': fields.char('Job Position', size=128),
'type': fields.selection([('default', 'Default'), ('invoice', 'Invoice'),
('delivery', 'Shipping'), ('contact', 'Contact'),
('other', 'Other')], 'Address Type',
help="Used to select automatically the right address according to the context in sales and purchases documents."),
'street': fields.char('Street', size=128),
'street2': fields.char('Street2', size=128),
'zip': fields.char('Zip', change_default=True, size=24),
'city': fields.char('City', size=128),
'state_id': fields.many2one("res.country.state", 'State'),
'country_id': fields.many2one('res.country', 'Country'),
'country': fields.related('country_id', type='many2one', relation='res.country', string='Country',
deprecated="This field will be removed as of OpenERP 7.1, use country_id instead"),
'email': fields.char('Email', size=240),
'phone': fields.char('Phone', size=64),
'fax': fields.char('Fax', size=64),
'mobile': fields.char('Mobile', size=64),
'birthdate': fields.char('Birthdate', size=64),
'is_company': fields.boolean('Is a Company', help="Check if the contact is a company, otherwise it is a person"),
'use_parent_address': fields.boolean('Use Company Address', help="Select this if you want to set company's address information for this contact"),
# image: all image fields are base64 encoded and PIL-supported
'image': fields.binary("Image",
help="This field holds the image used as avatar for this contact, limited to 1024x1024px"),
'image_medium': fields.function(_get_image, fnct_inv=_set_image,
string="Medium-sized image", type="binary", multi="_get_image",
store={
'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
},
help="Medium-sized image of this contact. It is automatically "\
"resized as a 128x128px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views."),
'image_small': fields.function(_get_image, fnct_inv=_set_image,
string="Small-sized image", type="binary", multi="_get_image",
store={
'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
},
help="Small-sized image of this contact. It is automatically "\
"resized as a 64x64px image, with aspect ratio preserved. "\
"Use this field anywhere a small image is required."),
'has_image': fields.function(_has_image, type="boolean"),
'company_id': fields.many2one('res.company', 'Company', select=1),
'color': fields.integer('Color Index'),
'user_ids': fields.one2many('res.users', 'partner_id', 'Users'),
'contact_address': fields.function(_address_display, type='char', string='Complete Address'),
# technical field used for managing commercial fields
'commercial_partner_id': fields.function(_commercial_partner_id, type='many2one', relation='res.partner', string='Commercial Entity'),
'create_uid': fields.many2one('res.users', u"创建用户", invisible=False, readonly=True), #需要在记录中读取该字段或者在视图、打印中显示该字段时,对象中必须包含
}
5.自关联关系(树状结构,无限级):产品类别与父级类别之间的关系,product.category 与 product.category 自关联产生 many2one 关系(parent_id)和 one2many 关系(child_id)
class product_category(osv.osv):
def name_get(self, cr, uid, ids, context=None):
if isinstance(ids, (list, tuple)) and not len(ids):
return []
if isinstance(ids, (long, int)):
ids = [ids]
reads = self.read(cr, uid, ids, ['name','parent_id'], context=context)
res = []
for record in reads:
name = record['name']
if record['parent_id']:
name = record['parent_id'][1]+' / '+name
res.append((record['id'], name))
return res
def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
res = self.name_get(cr, uid, ids, context=context)
return dict(res)
_name = "product.category"
_description = "Product Category"
_columns = {
'name': fields.char('Name', size=64, required=True, translate=True, select=True),
'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
'parent_id': fields.many2one('product.category','Parent Category', select=True, ondelete='cascade'),
'child_id': fields.one2many('product.category', 'parent_id', string='Child Categories'),
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of product categories."),
'type': fields.selection([('view','View'), ('normal','Normal')], 'Category Type', help="A category of the view type is a virtual category that can be used as the parent of another category to create a hierarchical structure."),
'parent_left': fields.integer('Left Parent', select=1),
'parent_right': fields.integer('Right Parent', select=1),
}
_defaults = {
'type' : lambda *a : 'normal',
}
_parent_name = "parent_id"
_parent_store = True
_parent_order = 'sequence, name'
_order = 'parent_left'
def _check_recursion(self, cr, uid, ids, context=None):
level = 100
while len(ids):
cr.execute('select distinct parent_id from product_category where id IN %s',(tuple(ids),))
ids = filter(None, map(lambda x:x[0], cr.fetchall()))
if not level:
return False
level -= 1
return True
_constraints = [
(_check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
]
def child_get(self, cr, uid, ids):
return [ids]
product_category()