There are 3 classes, sync.test.subject.a
which has many2many relation with sync.test.subject.b
which is inherited by sync.test.subject.c
.
有3个类,sync.test.subject.a,它与sync.test.subject.b有很多关系,它由sync.test.subject.c继承。
sync.test.subject.b
's separated_chars
field is populated through a compute function called _compute_separated_chars
which is triggered by the change of sync.test.subject.b
's chars
field.
sync.test.subject.b的separated_chars字段通过名为_compute_separated_chars的计算函数填充,该函数由sync.test.subject.b的字符字段的更改触发。
The role of sync.test.subject.c
is basically to set chars
by its own name
so that _compute_separated_chars
is triggered.
sync.test.subject.c的作用基本上是通过自己的名称设置字符,以便触发_compute_separated_chars。
The problem is I can't delete leftover records that are related to a Many2many field (namely sync.test.subject.a
leftover records) from inside the compute function because BEFORE the function is executed the field is already emptied by the system so I can't get the ids. I can't even use temporary field to store sync.test.subject.a
ids because any changes that are not related to separated_chars
won't be committed by the system from inside the compute function (By any changes, I mean really ANY changes either to other fields from the same model or other changes to other models won't be committed). How do I solve this?
问题是我无法从计算函数内部删除与Many2many字段(即sync.test.subject.a剩余记录)相关的剩余记录,因为在执行该函数之前,该字段已被系统清空,因此我无法得到ids。我甚至无法使用临时字段来存储sync.test.subject.a id,因为系统不会从计算功能内部提交任何与separated_chars无关的更改(通过任何更改,我的意思是任何更改)从同一模型的其他字段或其他模型的其他更改将不会提交)。我该如何解决这个问题?
Models:
楷模:
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()
Views:
浏览次数:
<?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>
I think this behavior is caused by a lazy implementation by Odoo to handle chain computed field triggers instead of handling the triggers correctly (sequentially based on the dependencies) they just update EVERY computed fields EVERYTIME there are changes to EVERY OTHER FIELD. And because of that they restrict any update to any other field from inside the compute function. Because if they don't it will blow up with recursive compute function calling.
我认为这种行为是由Odoo处理链式计算字段触发器而不是正确处理触发器的延迟实现(依次依赖于依赖性)引起的,它们只是更新每个计算字段每次都有变化到每个其他字段。因此,它们限制对计算功能内部任何其他字段的任何更新。因为如果他们不这样做会使用递归计算函数调用。
1 个解决方案
#1
5
Because the question is interesting and deal with the behavior of the new Odoo API I took the time to play a little bit with the compute
methods. What you say in your question is not totally wrong although there are several premature statements.
因为这个问题很有意思并且处理了新的Odoo API的行为,所以我花了一些时间来使用计算方法。你在问题中所说的并不是完全错误的,尽管有几个过早的陈述。
To demonstrate the Odoo's behavior I created the simple Books application with the following design.
为了演示Odoo的行为,我使用以下设计创建了简单的Books应用程序。
There are two models - 'books.book' and 'books.author'. Each of them has a Many2many
relation with the other - that's mode than normal as every book may be written by one or more authors and every author is supposed to have written one or more books.
有两种模式 - 'books.book'和'books.author'。他们每个人都与另一个人有很多关系 - 这种模式比正常模式,因为每本书都可能由一位或多位作者撰写,每位作者都应该写一本或多本书。
Here is the place to say that is a little bit weired to deal with Many2many
related objects from such a compute
method as you want. That's because the Many2many
records exist and have their one life independently each of the other. With One2many
relation it's much different.
这里有一个地方可以说有点需要处理来自这种计算方法的Many2many相关对象。这是因为Many2many记录存在并且彼此独立地拥有一个生命。与One2many的关系,它有很大的不同。
But any way, to reproduce the behavior you show us in your example I made the author.books
field computed - it's value is computed by the _get_books()
method oh the author
class.
但无论如何,为了重现您在示例中向我们展示的行为,我创建了author.books字段 - 它的值是由作者类的_get_books()方法计算的。
Just to show that different computed fields work well and independently, I created another computed field - name
, which is computed be the method _get_full_name()
of the author
class.
为了表明不同的计算字段能够很好地独立工作,我创建了另一个计算字段 - 名称,它是作者类的方法_get_full_name()计算的。
Now some words about the _get_books()
method. Based on the books_list
Text field, this method generates one book per line of the books_list
.
现在关于_get_books()方法的一些话。基于books_list文本字段,此方法为books_list的每一行生成一本书。
When creating the book the method first verify if a book with this name already exists. If this is the case, this book is linked to the author. Else, a new book is created and linked to the author.
创建图书时,方法首先验证是否已存在具有此名称的图书。如果是这种情况,本书与作者相关联。此外,还创建了一本新书并与作者联系。
And now the question that mostly interests you - before the creation of the new books the existing books related to this author are deleted. To do that the method uses a low level SQL queries. This way we deal with the problem that we don't have the list of related objects inside the compute
method.
现在,您最感兴趣的问题是 - 在创建新书之前,与该作者相关的现有书籍将被删除。为此,该方法使用低级SQL查询。这样我们就可以解决在compute方法中没有相关对象列表的问题。
What you must have in mind when dealing with computed fields depending from another field is the following:
处理依赖于其他字段的计算字段时必须注意的事项如下:
- They are computed when the field they depend on is changed (thats the good news)
- 当他们依赖的字段发生变化时计算它们(这是好消息)
- The need to recompute them is evaluated every time when you try to access their value. So some care is needed to avoid endless recursion.
- 每次尝试访问它们的值时,都会评估重新计算它们的需要。因此需要注意避免无休止的递归。
About changing the values of another fields inside the compute method. Read the following part of the documentation:
关于更改compute方法中其他字段的值。阅读文档的以下部分:
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
onchange方法对这些记录上的虚拟记录赋值工作没有写入数据库,只是用来知道要将哪个值发送回客户端
Thats valid for the compute
methods too. What that means? It means that if you assign a value to another field of the model, this value won't be written in the database. But the value will be returned to the user interface and written to the database while saving the form.
这也适用于计算方法。那意味着什么?这意味着如果将值分配给模型的另一个字段,则该值将不会写入数据库中。但是,该值将返回到用户界面并在保存表单时写入数据库。
Before pasting my sample code, I suggest you again to change the design of your application and not to deal in this way with the many2many relations from inside the compute method. Creation of new objects works well but deletion and modification of existing ones is tricky and not pleasant at all.
在粘贴我的示例代码之前,我建议您再次更改应用程序的设计,而不是以这种方式处理来自compute方法内部的many2many关系。创建新对象效果很好,但删除和修改现有对象是棘手的,根本不愉快。
Here is the books.py
file:
这是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()
And the user interface:
和用户界面:
<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>
EDIT
编辑
If you want to subclass the author class for example, than remove the relation
, column1
and column2
attributes from the Many2many field definition . his will leave the default relation table names.
例如,如果要将作者类子类化,则从Many2many字段定义中删除关系,column1和column2属性。他将保留默认的关系表名称。
Now you can define in each subclass a method like this:
现在,您可以在每个子类中定义一个这样的方法:
def _get_relation_table(self):
return 'books_author_books_book_rel'
and use this method in the SQL query construction when you want to delete records from this relation table.
如果要从此关系表中删除记录,请在SQL查询构造中使用此方法。
#1
5
Because the question is interesting and deal with the behavior of the new Odoo API I took the time to play a little bit with the compute
methods. What you say in your question is not totally wrong although there are several premature statements.
因为这个问题很有意思并且处理了新的Odoo API的行为,所以我花了一些时间来使用计算方法。你在问题中所说的并不是完全错误的,尽管有几个过早的陈述。
To demonstrate the Odoo's behavior I created the simple Books application with the following design.
为了演示Odoo的行为,我使用以下设计创建了简单的Books应用程序。
There are two models - 'books.book' and 'books.author'. Each of them has a Many2many
relation with the other - that's mode than normal as every book may be written by one or more authors and every author is supposed to have written one or more books.
有两种模式 - 'books.book'和'books.author'。他们每个人都与另一个人有很多关系 - 这种模式比正常模式,因为每本书都可能由一位或多位作者撰写,每位作者都应该写一本或多本书。
Here is the place to say that is a little bit weired to deal with Many2many
related objects from such a compute
method as you want. That's because the Many2many
records exist and have their one life independently each of the other. With One2many
relation it's much different.
这里有一个地方可以说有点需要处理来自这种计算方法的Many2many相关对象。这是因为Many2many记录存在并且彼此独立地拥有一个生命。与One2many的关系,它有很大的不同。
But any way, to reproduce the behavior you show us in your example I made the author.books
field computed - it's value is computed by the _get_books()
method oh the author
class.
但无论如何,为了重现您在示例中向我们展示的行为,我创建了author.books字段 - 它的值是由作者类的_get_books()方法计算的。
Just to show that different computed fields work well and independently, I created another computed field - name
, which is computed be the method _get_full_name()
of the author
class.
为了表明不同的计算字段能够很好地独立工作,我创建了另一个计算字段 - 名称,它是作者类的方法_get_full_name()计算的。
Now some words about the _get_books()
method. Based on the books_list
Text field, this method generates one book per line of the books_list
.
现在关于_get_books()方法的一些话。基于books_list文本字段,此方法为books_list的每一行生成一本书。
When creating the book the method first verify if a book with this name already exists. If this is the case, this book is linked to the author. Else, a new book is created and linked to the author.
创建图书时,方法首先验证是否已存在具有此名称的图书。如果是这种情况,本书与作者相关联。此外,还创建了一本新书并与作者联系。
And now the question that mostly interests you - before the creation of the new books the existing books related to this author are deleted. To do that the method uses a low level SQL queries. This way we deal with the problem that we don't have the list of related objects inside the compute
method.
现在,您最感兴趣的问题是 - 在创建新书之前,与该作者相关的现有书籍将被删除。为此,该方法使用低级SQL查询。这样我们就可以解决在compute方法中没有相关对象列表的问题。
What you must have in mind when dealing with computed fields depending from another field is the following:
处理依赖于其他字段的计算字段时必须注意的事项如下:
- They are computed when the field they depend on is changed (thats the good news)
- 当他们依赖的字段发生变化时计算它们(这是好消息)
- The need to recompute them is evaluated every time when you try to access their value. So some care is needed to avoid endless recursion.
- 每次尝试访问它们的值时,都会评估重新计算它们的需要。因此需要注意避免无休止的递归。
About changing the values of another fields inside the compute method. Read the following part of the documentation:
关于更改compute方法中其他字段的值。阅读文档的以下部分:
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
onchange方法对这些记录上的虚拟记录赋值工作没有写入数据库,只是用来知道要将哪个值发送回客户端
Thats valid for the compute
methods too. What that means? It means that if you assign a value to another field of the model, this value won't be written in the database. But the value will be returned to the user interface and written to the database while saving the form.
这也适用于计算方法。那意味着什么?这意味着如果将值分配给模型的另一个字段,则该值将不会写入数据库中。但是,该值将返回到用户界面并在保存表单时写入数据库。
Before pasting my sample code, I suggest you again to change the design of your application and not to deal in this way with the many2many relations from inside the compute method. Creation of new objects works well but deletion and modification of existing ones is tricky and not pleasant at all.
在粘贴我的示例代码之前,我建议您再次更改应用程序的设计,而不是以这种方式处理来自compute方法内部的many2many关系。创建新对象效果很好,但删除和修改现有对象是棘手的,根本不愉快。
Here is the books.py
file:
这是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()
And the user interface:
和用户界面:
<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>
EDIT
编辑
If you want to subclass the author class for example, than remove the relation
, column1
and column2
attributes from the Many2many field definition . his will leave the default relation table names.
例如,如果要将作者类子类化,则从Many2many字段定义中删除关系,column1和column2属性。他将保留默认的关系表名称。
Now you can define in each subclass a method like this:
现在,您可以在每个子类中定义一个这样的方法:
def _get_relation_table(self):
return 'books_author_books_book_rel'
and use this method in the SQL query construction when you want to delete records from this relation table.
如果要从此关系表中删除记录,请在SQL查询构造中使用此方法。