【问题标题】:Compute method is called multiple times?计算方法被多次调用?
【发布时间】:2015-09-09 09:17:47
【问题描述】:

我发现这可能不是并发问题,因为 就在我尝试更新sync.test.subject.bseparated_chars 字段时(在方法的末尾)调用方法。所以 我无法通过线程锁定来解决这个问题,因为该方法实际上等待自己再次被调用。我不明白这是一个完全奇怪的行为。

我在更新计算字段时发现了一个奇怪的行为。在这种情况下,代码胜于文字:

型号:

from openerp import models, fields, api, _

class sync_test_subject_a(models.Model):

    _name           = "sync.test.subject.a"

    name            = fields.Char('Name')

sync_test_subject_a()

class sync_test_subject_b(models.Model):

    _name           = "sync.test.subject.b"

    chars           = fields.Char('Characters')
    separated_chars = fields.Many2many('sync.test.subject.a',string='Separated Name', store=True, compute='_compute_separated_chars')

    @api.depends('chars')
    def _compute_separated_chars(self):
        print "CHAR SEPARATION BEGIN"
        sync_test_subject_a_pool = self.env['sync.test.subject.a']

        print "SEPARATE CHARS"
        # SEPARATE CHARS
        characters = []
        if self.chars:
            for character in self.chars:
                characters.append(character)

        print "DELETE CURRENT CHARS"
        # DELETE CURRENT MANY2MANY LINK
        self.separated_chars.unlink()

        print "DELETE EXISTING sync_test_subject_a THAT ARE RELATED TO CURRENT SELF"
        # DELETE EXISTING sync_test_subject_a THAT ARE RELATED TO CURRENT SELF
        deleted_separated_char_ids = []
        for separated_char in self.separated_chars:
            deleted_separated_char_ids.append(separated_char.sync_test_subject_a_id.id)

        sync_test_subject_a_pool.browse(deleted_separated_char_ids).unlink()

        print "INSERT NEW CHAR RECORDS"
        #INSERT NEW CHAR RECORDS        
        separated_char_ids = []
        for character in characters:
            separated_char_ids.append(sync_test_subject_a_pool.create({'name':character}).id)

        print "UPDATE self.separated_chars WITH CHAR IDS"
        #UPDATE self.separated_chars WITH CHAR IDS
        self.separated_chars = separated_char_ids
        print "CHAR SEPARATION END"

sync_test_subject_b()

class sync_test_subject_c(models.Model):

    _name           = "sync.test.subject.c"
    _inherit        = "sync.test.subject.b"

    name            = fields.Char('Name')

    @api.one
    def action_set_char(self):
        self.chars = self.name

sync_test_subject_c()

观看次数:

<?xml version="1.0" encoding="UTF-8"?>
<openerp>
    <data>
        <!-- Top menu item -->
        <menuitem name="Testing Module"
            id="testing_module_menu"
            sequence="1"/>

        <menuitem id="sync_test_menu" name="Synchronization Test" parent="testing_module_menu" sequence="1"/>

        <!--Expense Preset View-->
        <record model="ir.ui.view" id="sync_test_subject_c_form_view">
            <field name="name">sync.test.subject.c.form.view</field>
            <field name="model">sync.test.subject.c</field>
            <field name="type">form</field>
            <field name="arch" type="xml">
                <form string="Sync Test" version="7.0">
                    <header>
                    <div class="header_bar">
                        <button name="action_set_char" string="Set Name To Chars" type="object" class="oe_highlight"/>
                    </div>
                    </header>
                    <sheet>
                        <group>
                            <field string="Name" name="name" class="oe_inline"/>
                            <field string="Chars" name="chars" class="oe_inline"/>
                            <field string="Separated Chars" name="separated_chars" class="oe_inline"/>
                        </group>
                    </sheet>
                </form>
            </field>
        </record>

        <record model="ir.ui.view" id="sync_test_subject_c_tree_view">
            <field name="name">sync.test.subject.c.tree.view</field>
            <field name="model">sync.test.subject.c</field>
            <field name="type">tree</field>
            <field name="arch" type="xml">
                <tree string="Class">
                    <field string="Name" name="name"/>
                </tree>
            </field>
        </record>

        <record model="ir.ui.view" id="sync_test_subject_c_search">
            <field name="name">sync.test.subject.c.search</field>
            <field name="model">sync.test.subject.c</field>
            <field name="type">search</field>
            <field name="arch" type="xml">
                <search string="Sync Test Search">
                    <field string="Name" name="name"/>
                </search>
            </field>
        </record>

        <record id="sync_test_subject_c_action" model="ir.actions.act_window">
            <field name="name">Sync Test</field>
            <field name="res_model">sync.test.subject.c</field>
            <field name="view_type">form</field>
            <field name="domain">[]</field>
            <field name="context">{}</field>
            <field name="view_id" eval="sync_test_subject_c_tree_view"/>
            <field name="search_view_id" ref="sync_test_subject_c_search"/>
            <field name="target">current</field>
            <field name="help">Synchronization Test</field>
        </record>

        <menuitem action="sync_test_subject_c_action" icon="STOCK_JUSTIFY_FILL" sequence="1"
            id="sync_test_subject_c_action_menu"  parent="testing_module.sync_test_menu"
        />
    </data>
</openerp>

有3个类,sync.test.subject.async.test.subject.b有many2many关系,sync.test.subject.c继承。

sync.test.subject.bseparated_chars 字段是通过名为_compute_separated_chars 的计算函数填充的,该函数由sync.test.subject.bchars 字段的更改触发。

sync.test.subject.c的作用基本上就是通过自己的name设置chars,从而触发_compute_separated_chars

执行此操作会触发“错误”:

  • 创建一个新的sync.test.subject.c 输入任何名称,例如ABCDEFG
  • 保存新的sync.test.subject.c
  • Set Name To Chars 按钮呼叫action_set_char

你会看到函数被触发了两次。

执行结果如下:

CHAR SEPARATION BEGIN
SEPARATE CHARS
DELETE CURRENT CHARS
DELETE EXISTING sync_test_subject_a THAT ARE RELATED TO CURRENT SELF
INSERT NEW CHAR RECORDS
UPDATE self.separated_chars WITH CHAR IDS
CHAR SEPARATION BEGIN
SEPARATE CHARS
DELETE CURRENT CHARS
DELETE EXISTING sync_test_subject_a THAT ARE RELATED TO CURRENT SELF
INSERT NEW CHAR RECORDS
UPDATE self.separated_chars WITH CHAR IDS
CHAR SEPARATION END
CHAR SEPARATION END

结果,原本应该是A,B,C,D,E,F,G 的记录翻倍变成了A,B,C,D,E,F,G,A,B,C,D,E,F,G。这是一种非常危险的行为,因为这会导致数据崩溃。

这是一个详细的分步布局(基于打印输出):

M1 = first call to the method
M2 = second call to the method

For example in this case

chars = ABCDEFG > trigger the method

M1 - CHAR SEPARATION BEGIN
M1 - SEPARATE CHARS
     M1 tries to separate the chars into list [A,B,C,D,E,F,G]

M1 - DELETE CURRENT CHARS
     This is needed to REPLACE current character records with the new ones.
     since the `separated_chars` is currently empty nothing will be deleted

M1 - DELETE EXISTING sync_test_subject_a THAT ARE RELATED TO CURRENT SELF
     The method will try to get all of the `sync.test.subject.a` ids that is related 
     to self by getting them from `separated_chars` so that they can be unlinked 
     (This happens before M1 - DELETE CURRENT CHARS).
     Since nothing has been added to the `separated_chars`, this will not do anything

M1 - INSERT NEW CHAR RECORDS
     Adding [A,B,C,D,E,F,G] `to sync.test.subject.a`, so `sync.test.subject.a` will have
     [A,B,C,D,E,F,G]

M1 - UPDATE self.separated_chars WITH CHAR IDS
     CURRENTLY THIS IS NOT YET EXECUTED, so no `sync.test.subject.a` ids has been assigned 
     to self. it will wait till M2 finish executing.

M2 - CHAR SEPARATION BEGIN
M2 - SEPARATE CHARS
     M1 tries to separate the chars into list [A,B,C,D,E,F,G]

M2 - DELETE CURRENT CHARS
     Because the `separated_chars` IS STILL EMPTY nothing happens.

M2 - DELETE EXISTING sync_test_subject_a THAT ARE RELATED TO CURRENT SELF
     See, currently the `separated_chars` field IS STILL EMPTY THOUGH the 
     `sync.test.subject.a` is filled with [A,B,C,D,E,F,G] the method can't
     get the ids because the `separated_chars` is empty.

M2 - INSERT NEW CHAR RECORDS
     Now the method adds [A,B,C,D,E,F,G] `to sync.test.subject.a`, so 
     `sync.test.subject.a` will have [A,B,C,D,E,F,G,A,B,C,D,E,F,G]

M2 - UPDATE self.separated_chars WITH CHAR IDS
     This will link `sync.test.subject.a` ids to self so now self has
     [A,B,C,D,E,F,G]

M2 - CHAR SEPARATION END
     Now after this M1 will continue linking the `sync.test.subject.a` ids to self
     that means self will now have [A,B,C,D,E,F,G,A,B,C,D,E,F,G]

M1 - CHAR SEPARATION END

See the problem isn't how many times the method is executed but it's how the method calls itself when it tries to update the field. Which is nonsense.

问题:

  • 为什么_compute_separated_chars 在 同一时刻?
  • _compute_separated_chars 的触发器应该是 成为chars 的更新,而chars 仅更新一次,为什么 该方法是否被调用了两次?

源文件:Source

【问题讨论】:

    标签: python openerp odoo odoo-8 openerp-8


    【解决方案1】:

    我认为您的问题出在其他地方。不要介意 Odoo 调用您的 _compute_separated_chars 方法多少次,您必须正确返回 separated_chars 字段的值。我的问题在哪里?

    separated_chars 字段的值是在您的 _compute_separated_chars 方法中计算的。所以,在调用这个方法的时候,这个字段的值是undefined!你不能依赖它。但是你这样做了——你使用这个值来删除现有的记录。

    如果您进一步调试,您会看到在执行该方法时,separated_chars 的值可能为空。这样,您什么都不会删除。如果您计算数据库表sync_test_subject_a 中的行数,您可能会发现它们随着您的_compute... 方法的每次执行而增加。

    尝试为您的 many2many 关系字段计算详细说明一些不同的逻辑。

    以后我会给你一个更简洁的方法,但你的问题仍然存在。 separator_chars 未取消链接,因为在调用 self.separated_chars 时为空!

    @api.one
    @api.depends('chars')
    def _compute_separated_chars(self):
        a_model = self.env['sync.test.subject.a']
        if not self.chars:
            return
        self.separated_chars.unlink()
        for character in self.chars:
            self.separated_chars += \
                    a_model.create({'name': character})
    

    【讨论】:

    • Andrei 感谢您抽出宝贵时间回答我的问题。这里的问题不在于方法执行多少次,如果方法按顺序执行就不会出现问题。问题是当我尝试更新字段时方法尝试调用自身,这很疯狂,因为它会重叠自己的方法。该方法的目的是更新该字段,但是当我尝试更新该字段时,它再次调用自己,但是为了什么?我已经更新了我的答案,以便您可以逐步了解它是如何工作的。
    • 如果我能以某种方式替换所有 many2many 记录并同时删除与这些记录相关的所有记录,我可能不需要在插入这些新记录之前进行清理。
    • @William,让我再说一遍。方法_compute_separated_chars 不是自己调用的——它是由Odoo 创建新对象的机制调用的。如果您真的很好奇为什么会发生这种情况,我可以为您调查。但是您的问题在其他地方-您尝试使用尚未定义的东西!你不能使用self.separated_chars,因为它没有被定义。它将在您的方法返回后定义。 “未定义”是指它没有价值!您是否按照我的建议检查了数据库表 sync_test_subject_a 中的行数?
    • 我从来没有说过它一开始是自己调用的,它是在方法执行结束时自己调用的,就在我更新字段之前。我不仅检查了行数,我什至还在我的描述中写了结果行。我知道这张桌子一直在增长,我已经在我的描述中解释了为什么它一直在增长。我知道 self.separated_chars 尚未定义,并且我已经解释了为什么我在描述中做了我所做的事情。如果它不调用自己(不重叠)你如何解释执行结束时的双打印“CHAR SEPARATION END”?
    • 你需要了解我在方法中调用self.separated_chars的原因。
    【解决方案2】:

    好吧,总结一下。这个问题已经走到了尽头。如果不编辑 Odoo 的源代码,我将无法解决此问题。

    Odoo 做的不合理的事情如下:

    1. 系统奇怪地再次从内部调用计算方法 如果您将 id 分配给计算字段(通过 道路)。 (有关分配不会触发此问题的 many2many 值的替代方法,请参阅 Andrei 的答案)
    2. 系统限制对任何其他字段或模型的任何更改,除了 计算方法中与计算方法相关的字段。
    3. 每次有变化时系统都会更新计算域 到任何其他领域(即使是那些与 计算字段)。

    Andrei Boyanov 对该方法的替代实现解决了我的一些问题。但是还有一个问题,我把问题移到Another Question Thread

    【讨论】:

      【解决方案3】:

      这种行为实际上是每次我尝试更新separated_chars 字段时再次调用自身的方法引起的。导致此行为的原因未知,因为该方法的触发器是 chars 字段的更改,如以下代码行中指定的:@api.depends('chars')

      在更新 separated_chars 字段之前,我使用设置为 True 的锁定字段(布尔值)对此进行了修补,这样当再次调用 _compute_separated_chars 时,它不会做任何事情。

      代码:

      class sync_test_subject_b(models.Model):
      
          _name           = "sync.test.subject.b"
      
          chars           = fields.Char('Characters')
          separated_chars = fields.Many2many('sync.test.subject.a',string='Separated Name', store=True, compute='_compute_separated_chars')
          lock            = fields.Boolean('Lock')
      
          @api.depends('chars')
          def _compute_separated_chars(self):
              if not self.lock:            
      
                  print "CHAR SEPARATION BEGIN"
                  sync_test_subject_a_pool = self.env['sync.test.subject.a']
      
                  print "SEPARATE CHARS"
                  # SEPARATE CHARS
                  characters = []
                  if self.chars:
                      for character in self.chars:
                          characters.append(character)
      
                  print "DELETE CURRENT CHARS"
                  # DELETE CURRENT MANY2MANY LINK
                  self.separated_chars.unlink()
      
                  print "DELETE EXISTING sync_test_subject_a THAT ARE RELATED TO CURRENT SELF"
                  # DELETE EXISTING sync_test_subject_a THAT ARE RELATED TO CURRENT SELF
                  deleted_separated_char_ids = []
                  for separated_char in self.separated_chars:
                      deleted_separated_char_ids.append(separated_char.sync_test_subject_a_id.id)
      
                  sync_test_subject_a_pool.browse(deleted_separated_char_ids).unlink()
      
                  print "INSERT NEW CHAR RECORDS"
                  #INSERT NEW CHAR RECORDS        
                  separated_char_ids = []
                  for character in characters:
                      separated_char_ids.append(sync_test_subject_a_pool.create({'name':character}).id)
      
                  self.lock = True
      
                  print "UPDATE self.separated_chars WITH CHAR IDS"
                  #UPDATE self.separated_chars WITH CHAR IDS
                  self.separated_chars = separated_char_ids
      
                  self.lock = False
                  print "CHAR SEPARATION END"
      
      sync_test_subject_b()
      

      如果有人知道更好的解决方案,请随时在此线程中发布。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-28
        • 1970-01-01
        • 1970-01-01
        • 2016-06-02
        • 1970-01-01
        • 1970-01-01
        • 2018-03-23
        • 2010-11-14
        相关资源
        最近更新 更多