如何从计算函数内部更新其他字段或其他模型?
How do I update other fields or another models from inside compute function?
有3个类,sync.test.subject.a
与sync.test.subject.b
有many2many关系,被sync.test.subject.c
继承。
sync.test.subject.b
的 separated_chars
字段通过名为 _compute_separated_chars
的计算函数填充,该函数由 sync.test.subject.b
的 chars
字段的更改触发.
sync.test.subject.c
的作用基本上就是通过自己name
设置chars
,从而触发_compute_separated_chars
问题是我无法从计算函数中删除与 Many2many 字段相关的剩余记录(即 sync.test.subject.a
剩余记录),因为在执行该函数之前,该字段已被清空系统所以我无法获得ID。我什至不能使用临时字段来存储 sync.test.subject.a
id,因为任何与 separated_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.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()
#DELETE LEFTOVER RECORDS FROM a_model
for character in self.chars:
self.separated_chars += a_model.create({'name': character})
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>
我认为这种行为是由于 Odoo 延迟实现来处理链式计算字段触发器而不是正确处理触发器(基于依赖顺序)它们只是更新每个计算字段每次有对每个其他字段的更改.正因为如此,它们限制了计算函数内部对任何其他字段的任何更新。因为如果他们不这样做,递归计算函数调用就会爆炸。
因为这个问题很有趣并且处理新 Odoo 的行为 API 我花时间玩了一下 compute
方法。尽管有几个过早的陈述,但您在问题中所说的并非完全错误。
为了演示 Odoo 的行为,我使用以下设计创建了简单的 Books 应用程序。
有两种型号 - 'books.book' 和 'books.author'。它们中的每一个都与另一个具有 Many2many
关系 - 这比正常情况更正常,因为每本书都可能由一个或多个作者撰写,并且每个作者都应该写过一本或多本书。
这里要说的是,用这样的compute
方法来处理Many2many
相关对象有点奇怪。那是因为 Many2many
条记录存在并且彼此独立地拥有自己的生命。使用 One2many
关系就大不相同了。
但是无论如何,为了重现您在示例中向我们展示的行为,我将 author.books
字段设为 computed - 它的值由 _get_books()
方法哦author
class。
为了表明不同的计算域可以很好地独立工作,我创建了另一个计算域 - name
,它是通过 author
[=93] 的方法 _get_full_name()
计算的=].
现在谈谈 _get_books()
方法。基于 books_list
文本字段,此方法在 books_list
的每一行生成一本书。
创建图书时,该方法首先验证是否已存在同名图书。如果是这种情况,则将本书链接到作者。否则,将创建一本新书并将其链接到作者。
现在您最感兴趣的问题 - 在创建新书之前,与该作者相关的现有书籍已 删除 。为此,该方法使用 低级别 SQL 查询 。这样我们就解决了 compute
方法中没有相关对象列表的问题。
在处理依赖于另一个字段的计算字段时,您必须牢记以下几点:
- 它们是在它们所依赖的字段发生更改时计算的(这是个好消息)
- 每次您尝试访问它们的值时,都会评估是否需要重新计算它们。所以需要注意避免无休止的递归。
关于更改计算方法中其他字段的值。阅读 documentation 的以下部分:
Note
onchange methods work on virtual records assignment on these records
is not written to the database, just used to know which value to send
back to the client
这对 compute
方法也有效。那是什么意思?这意味着如果您为模型的另一个字段赋值,则该值不会写入数据库。但在保存表单时,该值将返回到用户界面并写入数据库。
在粘贴我的示例代码之前,我再次建议您更改应用程序的设计,不要以这种方式处理计算方法内部的多对多关系。创建新对象效果很好,但删除和修改现有对象很棘手,一点也不愉快。
这是 books.py
文件:
from openerp import models, fields, api
class book(models.Model):
_name = 'books.book'
_description = 'Some book'
name = fields.Char('Name')
authors = fields.Many2many('books.author', string='Author',
relation='books_to_authors_relation',
column1='book_id', column2='author_id')
book()
class author(models.Model):
_name = 'books.author'
_description = 'Author'
first_name = fields.Char('First Name')
second_name = fields.Char('Second Name')
name = fields.Char('Name', compute='_get_full_name', store=True)
books_list = fields.Text('List of books')
notes = fields.Text('Notes')
books = fields.Many2many('books.book', string='Books',
relation='books_to_authors_relation',
column1='author_id', column2='book_id',
compute='_get_books', store=True)
@api.one
@api.depends('first_name', 'second_name')
def _get_full_name(self):
import pdb; pdb.set_trace()
if not self.first_name or not self.second_name:
return
self.name = self.first_name + ' ' + self.second_name
@api.depends('books_list')
def _get_books(self):
if not self.books_list:
return
books = self.books_list.split('\n')
# Update another field of this object
# Please note that in this step we update just the
# fiedl in the web form. The real field of the object
# will be updated when saving the form
self.notes = self.books_list
# Empty the many2many relation
self.books = None
# And delete the related records
if isinstance(self.id, int):
sql = """
DELETE FROM books_to_authors_relation
WHERE author_id = %s
"""
self.env.cr.execute(sql, (self.id, ))
sql = """
DELETE FROM books_book
WHERE
name not in %s
AND id NOT in (
SELECT id from books_book as book
INNER JOIN books_to_authors_relation
as relation
ON book.id = relation.book_id
WHERE relation.author_id != %s)
"""
self.env.cr.execute(sql, (tuple(books), self.id, ))
### As per the documentation, we have to invalidate the caches after
### low level sql changes to the database
##self.env.invalidate_all()
# Create book records dinamically according to
# the Text field content
book_repository = self.env['books.book']
for book_name in books:
book = book_repository.search([('name', '=', book_name)])
if book:
self.books += book
else:
self.books += book_repository.create({'name': book_name, })
return
author()
和用户界面:
<openerp>
<data>
<menuitem id="books" name="Books App" sequence="0" />
<menuitem id="books.library" name="Library"
parent="books" sequence="0" />
<record model="ir.ui.view" id="books.book_form">
<field name="name">books.book.form</field>
<field name="model">books.book</field>
<field name="type">form</field>
<field name="arch" type="xml">
<group col="2">
<field name="name" />
</group>
<field name="authors" string="Authors" />
</field>
</record>
<record model="ir.ui.view" id="books.book_tree">
<field name="name">books.book.tree</field>
<field name="model">books.book</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<field name="name" />
<field name="authors" string="Authors" />
</field>
</record>
<record id="books.book_action" model="ir.actions.act_window">
<field name="name">Books</field>
<field name="res_model">books.book</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="books.books_menu" name="Books"
parent="books.library" sequence="10"
action="books.book_action"/>
<record model="ir.ui.view" id="books.author_tree">
<field name="name">books.author.tree</field>
<field name="model">books.author</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<field name="name" />
<field name="books_list" />
<field name="notes" />
<field name="books" string="Books" />
</field>
</record>
<record model="ir.ui.view" id="books.author_form">
<field name="name">books.author.form</field>
<field name="model">books.author</field>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="name" />
<group col="4">
<field name="first_name" />
<field name="second_name" />
</group>
<group col="6">
<field name="books_list" />
<field name="notes" string="Notes"/>
<field name="books" string="Books" />
</group>
</field>
</record>
<record id="books.author_action" model="ir.actions.act_window">
<field name="name">Authors</field>
<field name="res_model">books.author</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="books.authors" name="Authors"
parent="books.library" sequence="5"
action="books.author_action"/>
</data>
编辑
例如,如果您想子class 作者 class,则不要从 Many2many 字段中删除 relation
、column1
和 column2
属性定义 。他将保留默认关系 table 名称。
现在你可以在每个子class中定义一个这样的方法:
def _get_relation_table(self):
return 'books_author_books_book_rel'
并在 SQL 查询构造中使用此方法,当您要从此关系 table 中删除记录时。
有3个类,sync.test.subject.a
与sync.test.subject.b
有many2many关系,被sync.test.subject.c
继承。
sync.test.subject.b
的 separated_chars
字段通过名为 _compute_separated_chars
的计算函数填充,该函数由 sync.test.subject.b
的 chars
字段的更改触发.
sync.test.subject.c
的作用基本上就是通过自己name
设置chars
,从而触发_compute_separated_chars
问题是我无法从计算函数中删除与 Many2many 字段相关的剩余记录(即 sync.test.subject.a
剩余记录),因为在执行该函数之前,该字段已被清空系统所以我无法获得ID。我什至不能使用临时字段来存储 sync.test.subject.a
id,因为任何与 separated_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.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()
#DELETE LEFTOVER RECORDS FROM a_model
for character in self.chars:
self.separated_chars += a_model.create({'name': character})
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>
我认为这种行为是由于 Odoo 延迟实现来处理链式计算字段触发器而不是正确处理触发器(基于依赖顺序)它们只是更新每个计算字段每次有对每个其他字段的更改.正因为如此,它们限制了计算函数内部对任何其他字段的任何更新。因为如果他们不这样做,递归计算函数调用就会爆炸。
因为这个问题很有趣并且处理新 Odoo 的行为 API 我花时间玩了一下 compute
方法。尽管有几个过早的陈述,但您在问题中所说的并非完全错误。
为了演示 Odoo 的行为,我使用以下设计创建了简单的 Books 应用程序。
有两种型号 - 'books.book' 和 'books.author'。它们中的每一个都与另一个具有 Many2many
关系 - 这比正常情况更正常,因为每本书都可能由一个或多个作者撰写,并且每个作者都应该写过一本或多本书。
这里要说的是,用这样的compute
方法来处理Many2many
相关对象有点奇怪。那是因为 Many2many
条记录存在并且彼此独立地拥有自己的生命。使用 One2many
关系就大不相同了。
但是无论如何,为了重现您在示例中向我们展示的行为,我将 author.books
字段设为 computed - 它的值由 _get_books()
方法哦author
class。
为了表明不同的计算域可以很好地独立工作,我创建了另一个计算域 - name
,它是通过 author
[=93] 的方法 _get_full_name()
计算的=].
现在谈谈 _get_books()
方法。基于 books_list
文本字段,此方法在 books_list
的每一行生成一本书。
创建图书时,该方法首先验证是否已存在同名图书。如果是这种情况,则将本书链接到作者。否则,将创建一本新书并将其链接到作者。
现在您最感兴趣的问题 - 在创建新书之前,与该作者相关的现有书籍已 删除 。为此,该方法使用 低级别 SQL 查询 。这样我们就解决了 compute
方法中没有相关对象列表的问题。
在处理依赖于另一个字段的计算字段时,您必须牢记以下几点:
- 它们是在它们所依赖的字段发生更改时计算的(这是个好消息)
- 每次您尝试访问它们的值时,都会评估是否需要重新计算它们。所以需要注意避免无休止的递归。
关于更改计算方法中其他字段的值。阅读 documentation 的以下部分:
Note
onchange methods work on virtual records assignment on these records is not written to the database, just used to know which value to send back to the client
这对 compute
方法也有效。那是什么意思?这意味着如果您为模型的另一个字段赋值,则该值不会写入数据库。但在保存表单时,该值将返回到用户界面并写入数据库。
在粘贴我的示例代码之前,我再次建议您更改应用程序的设计,不要以这种方式处理计算方法内部的多对多关系。创建新对象效果很好,但删除和修改现有对象很棘手,一点也不愉快。
这是 books.py
文件:
from openerp import models, fields, api
class book(models.Model):
_name = 'books.book'
_description = 'Some book'
name = fields.Char('Name')
authors = fields.Many2many('books.author', string='Author',
relation='books_to_authors_relation',
column1='book_id', column2='author_id')
book()
class author(models.Model):
_name = 'books.author'
_description = 'Author'
first_name = fields.Char('First Name')
second_name = fields.Char('Second Name')
name = fields.Char('Name', compute='_get_full_name', store=True)
books_list = fields.Text('List of books')
notes = fields.Text('Notes')
books = fields.Many2many('books.book', string='Books',
relation='books_to_authors_relation',
column1='author_id', column2='book_id',
compute='_get_books', store=True)
@api.one
@api.depends('first_name', 'second_name')
def _get_full_name(self):
import pdb; pdb.set_trace()
if not self.first_name or not self.second_name:
return
self.name = self.first_name + ' ' + self.second_name
@api.depends('books_list')
def _get_books(self):
if not self.books_list:
return
books = self.books_list.split('\n')
# Update another field of this object
# Please note that in this step we update just the
# fiedl in the web form. The real field of the object
# will be updated when saving the form
self.notes = self.books_list
# Empty the many2many relation
self.books = None
# And delete the related records
if isinstance(self.id, int):
sql = """
DELETE FROM books_to_authors_relation
WHERE author_id = %s
"""
self.env.cr.execute(sql, (self.id, ))
sql = """
DELETE FROM books_book
WHERE
name not in %s
AND id NOT in (
SELECT id from books_book as book
INNER JOIN books_to_authors_relation
as relation
ON book.id = relation.book_id
WHERE relation.author_id != %s)
"""
self.env.cr.execute(sql, (tuple(books), self.id, ))
### As per the documentation, we have to invalidate the caches after
### low level sql changes to the database
##self.env.invalidate_all()
# Create book records dinamically according to
# the Text field content
book_repository = self.env['books.book']
for book_name in books:
book = book_repository.search([('name', '=', book_name)])
if book:
self.books += book
else:
self.books += book_repository.create({'name': book_name, })
return
author()
和用户界面:
<openerp>
<data>
<menuitem id="books" name="Books App" sequence="0" />
<menuitem id="books.library" name="Library"
parent="books" sequence="0" />
<record model="ir.ui.view" id="books.book_form">
<field name="name">books.book.form</field>
<field name="model">books.book</field>
<field name="type">form</field>
<field name="arch" type="xml">
<group col="2">
<field name="name" />
</group>
<field name="authors" string="Authors" />
</field>
</record>
<record model="ir.ui.view" id="books.book_tree">
<field name="name">books.book.tree</field>
<field name="model">books.book</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<field name="name" />
<field name="authors" string="Authors" />
</field>
</record>
<record id="books.book_action" model="ir.actions.act_window">
<field name="name">Books</field>
<field name="res_model">books.book</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="books.books_menu" name="Books"
parent="books.library" sequence="10"
action="books.book_action"/>
<record model="ir.ui.view" id="books.author_tree">
<field name="name">books.author.tree</field>
<field name="model">books.author</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<field name="name" />
<field name="books_list" />
<field name="notes" />
<field name="books" string="Books" />
</field>
</record>
<record model="ir.ui.view" id="books.author_form">
<field name="name">books.author.form</field>
<field name="model">books.author</field>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="name" />
<group col="4">
<field name="first_name" />
<field name="second_name" />
</group>
<group col="6">
<field name="books_list" />
<field name="notes" string="Notes"/>
<field name="books" string="Books" />
</group>
</field>
</record>
<record id="books.author_action" model="ir.actions.act_window">
<field name="name">Authors</field>
<field name="res_model">books.author</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="books.authors" name="Authors"
parent="books.library" sequence="5"
action="books.author_action"/>
</data>
编辑
例如,如果您想子class 作者 class,则不要从 Many2many 字段中删除 relation
、column1
和 column2
属性定义 。他将保留默认关系 table 名称。
现在你可以在每个子class中定义一个这样的方法:
def _get_relation_table(self):
return 'books_author_books_book_rel'
并在 SQL 查询构造中使用此方法,当您要从此关系 table 中删除记录时。