Many2Many 多对多字段

为了能给 Bangumi 打标签,我们还需要一个 Tag 模型,同样的在 models 下新建 tag.py,并且定义 Tag 模型。

class Tag(models.Model):
    _name = 'bangumi.tag'
    _description = 'Bangumi tag'

    name = fields.Char(string='Name', required=True)
    user_id = fields.Many2one(
        'res.users', string='User', required=True,
        default=lambda self: self.env.uid
    )

别忘了将在 models/__init__.py 中加入 from . import tag

这次的 user_id 的默认值我们使用 lambda 函数来简化我们的代码。

由于 Bangumi (番剧) 与 Tag (标签) 是多对多的关系,所以需要用到 Many2many 字段。

我们分别在两个模型上都加上,Many2many (多对多) 字段。

class Bangumi(models.Model):
    _name = 'bangumi.bangumi'
    _description = 'Bangumi'

    name = fields.Char(string='Name', required=True)
    total = fields.Integer(string='Total', required=True)
    already_seen = fields.Integer(string='Already seen', default=0)
    score = fields.Float(string='Score', required=True, default=0.0)

    category_id = fields.Many2one(
        'bangumi.category', string='Category', required=False
    )

    tag_ids = fields.Many2many(
        'bangumi.tag', 'bangumi_bangumi_tag_rel',
        'bangumi_id', 'tag_id',
        string='Bangumi Tags'
    )
class Tag(models.Model):
    _name = 'bangumi.tag'
    _description = 'Bangumi tag'

    name = fields.Char(string='Name', required=True)
    user_id = fields.Many2one(
        'res.users', string='User', required=True,
        default=lambda self: self.env.uid
    )

    bangumi_ids = fields.Many2many(
        'bangumi.bangumi', 'bangumi_bangumi_tag_rel',
        'tag_id', 'bangumi_id',
        string='Tag Bangumi Set'
    )

⚠️ Many2many 的字段命名规则与 One2many 命名规则相同,名称都是以 _ids结尾。

Many2many 主要由以下几个属性组成:

  • 目标模型

    建立多对多关系的目标模型。

  • 多对多关系名称

    与目标模型建立的多对多关系名称,这个名称会在数据库中生成对应的表,所以命名时要注意以模块名称 + _下划线开头。

  • 关系中自身代表的字段

    这个字段名称会在关系数据表中生成,代表当前模型数据的 id。

  • 关系中代表目标的字段

    这个字段名称会在关系数据表中生成,代表目标模型数据的 id。

接下来还是进入到命令行中验证我们定义的 Many2many 字段,首先创建一些数据:

In [2]: tags = env['bangumi.tag'].create([{'name': 'warm blood'}, {'name': 'science fiction'}, {'name': 'fight'}])
In [3]: tags                                                                    
Out[3]: bangumi.tag(1, 2, 3)

接下来我们将数据写入:

In [2]: tags = env['bangumi.tag'].create([{'name': 'warm blood'}, {'name': 'science fiction'}, {'name': 'fight'}])
In [3]: tags             
Out[3]: bangumi.tag(1, 2, 3)
In [4]: bangumi_set = env['bangumi.bangumi'].search([])
In [5]: bangumi_set                                                             
Out[5]: bangumi.bangumi(1, 2)
In [6]: bangumi_set.write({'tag_ids': tags.ids})                                  
Out[6]: True
In [7]: bangumi_set.ids                                                          
Out[7]: [1, 2]
In [8]: env.cr.commit()
In [9]: bangumi_set[0].tag_ids                                                 
Out[9]: bangumi.tag()
In [10]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)                         
Out[10]: []

⚠️ 注意这种写法在 Odoo 10 会直接报错,Odoo 12 并没有抛出异常,但是数据依然无法写入。

可以发现数据根本没有写进去,这是为什么呢?查看以下官方对 Many2many 字段的描述:

One2many and Many2many use a special “commands” format to manipulate the set of records stored in/associated with the field.

This format is a list of triplets executed sequentially, where each triplet is a command to execute on the set of records. Not all commands apply in all situations. Possible commands are:

(0, _, values) adds a new record created from the provided value dict.
(1, id, values)
updates an existing record of id id with the values in values. Can not be used in create().
(2, id, _)
removes the record of id id from the set, then deletes it (from the database). Can not be used in create().
(3, id, _)
removes the record of id id from the set, but does not delete it. Can not be used on One2many. Can not be used in create().
(4, id, _)
adds an existing record of id id to the set. Can not be used on One2many.
(5, , )
removes all records from the set, equivalent to using the command 3 on every record explicitly. Can not be used on One2many. Can not be used in create().
(6, _, ids)
replaces all existing records in the set by the ids list, equivalent to using the command 5 followed by a command 4 for each id in ids.
Values marked as _ in the list above are ignored and can be anything, generally 0 or False.

从文档中的描述中可以看出,不止是 Many2many 字段,包括 One2many 字段在写入操作时都比较特殊。总的来说一共有7总格式:

  • (0, _, values)

    增加一条未创建的数据到 X2many 字段中,并且以 value 字典中的值作为这个新数据的值。其中下划线的值可以为 0 或 False。

      In [11]: bangumi_set[0].write({'tag_ids': [(0, 0, {'name': 'magic'})]})           
      Out[11]: True
      In [12]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)
      Out[12]: ['magic']
    
  • (1, id, values)

    更新一个已经存在的并且关联的值的数据,不能用在 create 方法中。

      In [13]:bangumi_set[0].tag_ids.id                                               
      Out[13]: 4
      In [14]: bangumi_set[0].write({'tag_ids': [(1, 4, {'name': 'Magic'})]})           
      Out[14]: True
      In [15]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)
      Out[15]: ['Magic']
    
  • (2, id, _)

    移除一个已经关联的数据,并且将这条数据从数据库中删除,不能用在 create 方法中。

      In [16]:  bangumi_set[0].write({'tag_ids': [(2, 4, 0)]})                         
      2019-01-14 16:27:30,601 62971 INFO odoo odoo.models.unlink: User #1 deleted bangumi.tag records with IDs: [4]
      Out[16]: True
      In [17]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)                          
      Out[17]: []
    
  • (3, id, _)

    移除一个已经关联的数据,但不将这条数据从数据库中删除,不能用在 create 方法中。

      In [18]: bangumi_set[0].write({'tag_ids': [(0, 0, {'name': 'magic'})]})           
      Out[18]: True
      In [19]: bangumi_set[0].tag_ids.id                                                
      Out[19]: 5
      In [20]: bangumi_set[0].write({'tag_ids': [(3, 5, 0)]})                           
      Out[20]: True
      In [21]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)                          
      Out[21]: []
    
  • (4, id, _)

    增加一个已经创建的数据到 Many2many 字段中,One2Many 不可用。

      In [22]: tags = env['bangumi.tag'].search([])
      In [23]: tags[0]                                                                  
      Out[23]: bangumi.tag(1,)
      In [24]: bangumi_set[0].write({'tag_ids': [(4, 1, 0)]})                           
      Out[24]: True
      In [25]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)                          
      Out[25]: ['warm blood']
    
  • (5, _, _)

    移除 Many2many 中的所有关联字段,但是并不从数据库中删除它们,不能用于 One2many 中。

      In [26]: tags = env['bangumi.tag'].search([])
      In [27]: for tag in tags: 
          ...:     bangumi_set[0].write({'tag_ids': [(4, tag.id, 0)]}) 
          ...:
      In [28]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)                          
      Out[28]: ['warm blood', 'science fiction', 'fight', 'magic']
      In [29]: bangumi_set[0].write({'tag_ids': [(5, 0, 0)]})                           
      Out[29]: True
      In [30]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)                          
      Out[30]: []
    
  • (6, 0, ids)

    用新的集合替换已经关联的 Many2many 集合,但不删除旧的数据。

      In [31]: bangumi_set[0].write({'tag_ids': [(4, 1, tags[0].id)]})                   
      Out[31]: True
      In [32]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)                           
      Out[32]: ['warm blood']
      In [33]: bangumi_set[0].write({'tag_ids': [(6, 0, tags[1:].ids)]})                 
      Out[33]: True
      In [34]: bangumi_set[0].tag_ids.mapped(lambda r: r.name)                          
      Out[34]: ['science fiction', 'fight']
    

Odoo 的 X2many 字段的写入设计的比较奇特,但是正是这样的设计,使得一对多和多对多字段能够批量进行修改,并且非常的灵活。

results matching ""

    No results matching ""