[ADD] fields.py: New x2many command helper
We provide a new helper class to help redact x2many commands for create and write methods. To ensure best compatibility with the xmlrpc layer we do not change the protocole, the commands are still 3-elements tuples where the first element is still an integer in between 0 and 6. The helper class provide the cannonical constants and static methods to ease working with the commands. The new helper class is also available in QWeb. Developers are encouraged to transition their code so it uses this new helper class. Task: 2366606
This commit is contained in:
committed by
Raphael Collet
parent
90a7072f93
commit
eded14b4c4
@@ -220,6 +220,10 @@ Relational Fields
|
||||
|
||||
.. autoclass:: Many2many()
|
||||
|
||||
.. autoclass:: Command()
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
Pseudo-relational fields
|
||||
''''''''''''''''''''''''
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ from . import models
|
||||
from . import fields
|
||||
from . import api
|
||||
from odoo.tools.translate import _, _lt
|
||||
from odoo.fields import Command
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Other imports, which may require stuff from above
|
||||
|
||||
@@ -6,7 +6,7 @@ from werkzeug.wrappers import Response
|
||||
|
||||
from odoo.http import Controller, dispatch_rpc, request, route
|
||||
from odoo.service import wsgi_server
|
||||
from odoo.fields import Date, Datetime
|
||||
from odoo.fields import Date, Datetime, Command
|
||||
from odoo.tools import lazy
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ class OdooMarshaller(xmlrpc.client.Marshaller):
|
||||
return self.dispatch[type(v)](self, v, write)
|
||||
dispatch[lazy] = dump_lazy
|
||||
|
||||
dispatch[Command] = dispatch[int]
|
||||
|
||||
|
||||
# monkey-patch xmlrpc.client's marshaller
|
||||
xmlrpc.client.Marshaller = OdooMarshaller
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID, _
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID, _, Command
|
||||
from odoo.exceptions import MissingError, UserError, ValidationError, AccessError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.safe_eval import safe_eval, test_python_expr
|
||||
@@ -76,6 +76,7 @@ class IrActions(models.Model):
|
||||
'timezone': timezone,
|
||||
'b64encode': base64.b64encode,
|
||||
'b64decode': base64.b64decode,
|
||||
'Command': Command,
|
||||
}
|
||||
|
||||
@api.model
|
||||
@@ -376,6 +377,7 @@ class IrActionsServer(models.Model):
|
||||
# - time, datetime, dateutil, timezone: useful Python libraries
|
||||
# - log: log(message, level='info'): logging function to record debug information in ir.logging table
|
||||
# - UserError: Warning Exception to use with raise
|
||||
# - Command: x2Many commands namespace
|
||||
# To return an action, assign: action = {...}\n\n\n\n"""
|
||||
|
||||
@api.model
|
||||
|
||||
+179
-60
@@ -11,6 +11,7 @@ import itertools
|
||||
import logging
|
||||
import base64
|
||||
import binascii
|
||||
import enum
|
||||
import pytz
|
||||
import psycopg2
|
||||
|
||||
@@ -1015,10 +1016,11 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
defaults = record.default_get([self.name])
|
||||
if self.name in defaults:
|
||||
# The null value above is necessary to convert x2many field
|
||||
# values. For instance, converting [(4, id)] accesses the
|
||||
# field's current value, then adds the given id. Without an
|
||||
# initial value, the conversion ends up here to determine
|
||||
# the field's value, and generates an infinite recursion.
|
||||
# values. For instance, converting [(Command.LINK, id)]
|
||||
# accesses the field's current value, then adds the given
|
||||
# id. Without an initial value, the conversion ends up here
|
||||
# to determine the field's value, and generates an infinite
|
||||
# recursion.
|
||||
value = self.convert_to_cache(defaults[self.name], record)
|
||||
env.cache.set(record, self, value)
|
||||
|
||||
@@ -2804,6 +2806,123 @@ class Many2oneReference(Integer):
|
||||
return model_ids
|
||||
|
||||
|
||||
class Command(enum.IntEnum):
|
||||
"""
|
||||
:class:`~odoo.fields.One2many` and :class:`~odoo.fields.Many2many` fields
|
||||
expect a special command to manipulate the relation they implement.
|
||||
|
||||
Internally, each command is a 3-elements tuple where the first element is a
|
||||
mandatory integer that identifies the command, the second element is either
|
||||
the related record id to apply the command on (commands update, delete,
|
||||
unlink and link) either 0 (commands create, clear and set), the third
|
||||
element is either the ``values`` to write on the record (commands create
|
||||
and update) either the new ``ids`` list of related records (command set),
|
||||
either 0 (commands delete, unlink, link, and clear).
|
||||
|
||||
Via Python, we encourage developers craft new commands via the various
|
||||
functions of this namespace. We also encourage developers to use the
|
||||
command identifier constant names when comparing the 1st element of
|
||||
existing commands.
|
||||
|
||||
Via RPC, it is impossible nor to use the functions nor the command constant
|
||||
names. It is required to instead write the literal 3-elements tuple where
|
||||
the first element is the integer identifier of the command.
|
||||
"""
|
||||
|
||||
CREATE = 0
|
||||
UPDATE = 1
|
||||
DELETE = 2
|
||||
UNLINK = 3
|
||||
LINK = 4
|
||||
CLEAR = 5
|
||||
SET = 6
|
||||
|
||||
@classmethod
|
||||
def create(cls, values: dict):
|
||||
"""
|
||||
Create new records in the comodel using ``values``, link the created
|
||||
records to ``self``.
|
||||
|
||||
In case of a :class:`~odoo.fields.Many2many` relation, one unique
|
||||
new record is created in the comodel such that all records in `self`
|
||||
are linked to the new record.
|
||||
|
||||
In case of a :class:`~odoo.fields.One2many` relation, one new record
|
||||
is created in the comodel for every record in ``self`` such that every
|
||||
record in ``self`` is linked to exactly one of the new records.
|
||||
|
||||
Return the command triple :samp:`(CREATE, 0, {values})`
|
||||
"""
|
||||
return (cls.CREATE, 0, values)
|
||||
|
||||
@classmethod
|
||||
def update(cls, id: int, values: dict):
|
||||
"""
|
||||
Write ``values`` on the related record.
|
||||
|
||||
Return the command triple :samp:`(UPDATE, {id}, {values})`
|
||||
"""
|
||||
return (cls.UPDATE, id, values)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, id: int):
|
||||
"""
|
||||
Remove the related record from the database and remove its relation
|
||||
with ``self``.
|
||||
|
||||
In case of a :class:`~odoo.fields.Many2many` relation, removing the
|
||||
record from the database may be prevented if it is still linked to
|
||||
other records.
|
||||
|
||||
Return the command triple :samp:`(DELETE, {id}, 0)`
|
||||
"""
|
||||
return (cls.DELETE, id, 0)
|
||||
|
||||
@classmethod
|
||||
def unlink(cls, id: int):
|
||||
"""
|
||||
Remove the relation between ``self`` and the related record.
|
||||
|
||||
In case of a :class:`~odoo.fields.One2many` relation, the given record
|
||||
is deleted from the database if the inverse field is set as
|
||||
``ondelete='cascade'``. Otherwise, the value of the inverse field is
|
||||
set to False and the record is kept.
|
||||
|
||||
Return the command triple :samp:`(UNLINK, {id}, 0)`
|
||||
"""
|
||||
return (cls.UNLINK, id, 0)
|
||||
|
||||
@classmethod
|
||||
def link(cls, id: int):
|
||||
"""
|
||||
Add a relation between ``self`` and the related record.
|
||||
|
||||
Return the command triple :samp:`(LINK, {id}, 0)`
|
||||
"""
|
||||
return (cls.LINK, id, 0)
|
||||
|
||||
@classmethod
|
||||
def clear(cls):
|
||||
"""
|
||||
Remove all records from the relation with ``self``. It behaves like
|
||||
executing the `unlink` command on every record.
|
||||
|
||||
Return the command triple :samp:`(CLEAR, 0, 0)`
|
||||
"""
|
||||
return (cls.CLEAR, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def set(cls, ids: list):
|
||||
"""
|
||||
Replace the current relations of ``self`` by the given ones. It behaves
|
||||
like executing the ``unlink`` command on every removed relation then
|
||||
executing the ``link`` command on every new relation.
|
||||
|
||||
Return the command triple :samp:`(SET, 0, {ids})`
|
||||
"""
|
||||
return (cls.SET, 0, ids)
|
||||
|
||||
|
||||
class _RelationalMulti(_Relational):
|
||||
""" Abstract class for relational fields *2many. """
|
||||
|
||||
@@ -2862,22 +2981,22 @@ class _RelationalMulti(_Relational):
|
||||
# modify ids with the commands
|
||||
for command in value:
|
||||
if isinstance(command, (tuple, list)):
|
||||
if command[0] == 0:
|
||||
if command[0] == Command.CREATE:
|
||||
ids.add(comodel.new(command[2], ref=command[1]).id)
|
||||
elif command[0] == 1:
|
||||
elif command[0] == Command.UPDATE:
|
||||
line = browse(command[1])
|
||||
if validate:
|
||||
line.update(command[2])
|
||||
else:
|
||||
line._update_cache(command[2], validate=False)
|
||||
ids.add(line.id)
|
||||
elif command[0] in (2, 3):
|
||||
elif command[0] in (Command.DELETE, Command.UNLINK):
|
||||
ids.discard(browse(command[1]).id)
|
||||
elif command[0] == 4:
|
||||
elif command[0] == Command.LINK:
|
||||
ids.add(browse(command[1]).id)
|
||||
elif command[0] == 5:
|
||||
elif command[0] == Command.CLEAR:
|
||||
ids.clear()
|
||||
elif command[0] == 6:
|
||||
elif command[0] == Command.SET:
|
||||
ids = OrderedSet(browse(it).id for it in command[2])
|
||||
elif isinstance(command, dict):
|
||||
ids.add(comodel.new(command).id)
|
||||
@@ -2927,7 +3046,7 @@ class _RelationalMulti(_Relational):
|
||||
if isinstance(value, BaseModel) and value._name == self.comodel_name:
|
||||
# make result with new and existing records
|
||||
inv_names = {field.name for field in record._field_inverses[self]}
|
||||
result = [(6, 0, [])]
|
||||
result = [Command.set([])]
|
||||
for record in value:
|
||||
origin = record._origin
|
||||
if not origin:
|
||||
@@ -2936,7 +3055,7 @@ class _RelationalMulti(_Relational):
|
||||
for name in record._cache
|
||||
if name not in inv_names
|
||||
})
|
||||
result.append((0, 0, values))
|
||||
result.append(Command.create(values))
|
||||
else:
|
||||
result[0][2].append(origin.id)
|
||||
if record != origin:
|
||||
@@ -2946,11 +3065,11 @@ class _RelationalMulti(_Relational):
|
||||
if name not in inv_names and record[name] != origin[name]
|
||||
})
|
||||
if values:
|
||||
result.append((1, origin.id, values))
|
||||
result.append(Command.update(origin.id, values))
|
||||
return result
|
||||
|
||||
if value is False or value is None:
|
||||
return [(5,)]
|
||||
return [Command.clear()]
|
||||
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
@@ -2992,13 +3111,13 @@ class _RelationalMulti(_Relational):
|
||||
|
||||
for idx, (recs, value) in enumerate(records_commands_list):
|
||||
if isinstance(value, tuple):
|
||||
value = [(6, 0, value)]
|
||||
value = [Command.set(value)]
|
||||
elif isinstance(value, BaseModel) and value._name == self.comodel_name:
|
||||
value = [(6, 0, value._ids)]
|
||||
value = [Command.set(value._ids)]
|
||||
elif value is False or value is None:
|
||||
value = [(5,)]
|
||||
value = [Command.clear()]
|
||||
elif isinstance(value, list) and value and not isinstance(value[0], (tuple, list)):
|
||||
value = [(6, 0, tuple(value))]
|
||||
value = [Command.set(tuple(value))]
|
||||
if not isinstance(value, list):
|
||||
raise ValueError("Wrong value for %s: %s" % (self, value))
|
||||
records_commands_list[idx] = (recs, value)
|
||||
@@ -3151,22 +3270,22 @@ class One2many(_RelationalMulti):
|
||||
|
||||
for recs, commands in records_commands_list:
|
||||
for command in (commands or ()):
|
||||
if command[0] == 0:
|
||||
if command[0] == Command.CREATE:
|
||||
for record in recs:
|
||||
to_create.append(dict(command[2], **{inverse: record.id}))
|
||||
allow_full_delete = False
|
||||
elif command[0] == 1:
|
||||
elif command[0] == Command.UPDATE:
|
||||
comodel.browse(command[1]).write(command[2])
|
||||
elif command[0] == 2:
|
||||
elif command[0] == Command.DELETE:
|
||||
to_delete.append(command[1])
|
||||
elif command[0] == 3:
|
||||
elif command[0] == Command.UNLINK:
|
||||
unlink(comodel.browse(command[1]))
|
||||
elif command[0] == 4:
|
||||
elif command[0] == Command.LINK:
|
||||
to_inverse.setdefault(recs[-1], set()).add(command[1])
|
||||
allow_full_delete = False
|
||||
elif command[0] in (5, 6) :
|
||||
elif command[0] in (Command.CLEAR, Command.SET):
|
||||
# do not try to delete anything in creation mode if nothing has been created before
|
||||
line_ids = command[2] if command[0] == 6 else []
|
||||
line_ids = command[2] if command[0] == Command.SET else []
|
||||
if not allow_full_delete and not line_ids:
|
||||
continue
|
||||
flush()
|
||||
@@ -3192,21 +3311,21 @@ class One2many(_RelationalMulti):
|
||||
|
||||
for recs, commands in records_commands_list:
|
||||
for command in (commands or ()):
|
||||
if command[0] == 0:
|
||||
if command[0] == Command.CREATE:
|
||||
for record in recs:
|
||||
link(record, comodel.new(command[2], ref=command[1]))
|
||||
elif command[0] == 1:
|
||||
elif command[0] == Command.UPDATE:
|
||||
comodel.browse(command[1]).write(command[2])
|
||||
elif command[0] == 2:
|
||||
elif command[0] == Command.DELETE:
|
||||
unlink(comodel.browse(command[1]))
|
||||
elif command[0] == 3:
|
||||
elif command[0] == Command.UNLINK:
|
||||
unlink(comodel.browse(command[1]))
|
||||
elif command[0] == 4:
|
||||
elif command[0] == Command.LINK:
|
||||
link(recs[-1], comodel.browse(command[1]))
|
||||
elif command[0] in (5, 6):
|
||||
elif command[0] in (Command.CLEAR, Command.SET):
|
||||
# assign the given lines to the last record only
|
||||
cache.update(recs, self, [()] * len(recs))
|
||||
lines = comodel.browse(command[2] if command[0] == 6 else [])
|
||||
lines = comodel.browse(command[2] if command[0] == Command.SET else [])
|
||||
cache.set(recs[-1], self, lines._ids)
|
||||
|
||||
return records
|
||||
@@ -3238,22 +3357,22 @@ class One2many(_RelationalMulti):
|
||||
|
||||
for recs, commands in records_commands_list:
|
||||
for command in commands:
|
||||
if command[0] == 0:
|
||||
if command[0] == Command.CREATE:
|
||||
for record in recs:
|
||||
line = comodel.new(command[2], ref=command[1])
|
||||
line[inverse] = record
|
||||
elif command[0] == 1:
|
||||
elif command[0] == Command.UPDATE:
|
||||
browse([command[1]]).update(command[2])
|
||||
elif command[0] == 2:
|
||||
elif command[0] == Command.DELETE:
|
||||
browse([command[1]])[inverse] = False
|
||||
elif command[0] == 3:
|
||||
elif command[0] == Command.UNLINK:
|
||||
browse([command[1]])[inverse] = False
|
||||
elif command[0] == 4:
|
||||
elif command[0] == Command.LINK:
|
||||
browse([command[1]])[inverse] = recs[-1]
|
||||
elif command[0] in (5, 6):
|
||||
elif command[0] in (Command.CLEAR, Command.SET):
|
||||
# assign the given lines to the last record only
|
||||
cache.update(recs, self, [()] * len(recs))
|
||||
lines = comodel.browse(command[2] if command[0] == 6 else [])
|
||||
lines = comodel.browse(command[2] if command[0] == Command.SET else [])
|
||||
cache.set(recs[-1], self, lines._ids)
|
||||
|
||||
else:
|
||||
@@ -3267,21 +3386,21 @@ class One2many(_RelationalMulti):
|
||||
|
||||
for recs, commands in records_commands_list:
|
||||
for command in commands:
|
||||
if command[0] == 0:
|
||||
if command[0] == Command.CREATE:
|
||||
for record in recs:
|
||||
link(record, comodel.new(command[2], ref=command[1]))
|
||||
elif command[0] == 1:
|
||||
elif command[0] == Command.UPDATE:
|
||||
browse([command[1]]).update(command[2])
|
||||
elif command[0] == 2:
|
||||
elif command[0] == Command.DELETE:
|
||||
unlink(browse([command[1]]))
|
||||
elif command[0] == 3:
|
||||
elif command[0] == Command.UNLINK:
|
||||
unlink(browse([command[1]]))
|
||||
elif command[0] == 4:
|
||||
elif command[0] == Command.LINK:
|
||||
link(recs[-1], browse([command[1]]))
|
||||
elif command[0] in (5, 6):
|
||||
elif command[0] in (Command.CLEAR, Command.SET):
|
||||
# assign the given lines to the last record only
|
||||
cache.update(recs, self, [()] * len(recs))
|
||||
lines = comodel.browse(command[2] if command[0] == 6 else [])
|
||||
lines = comodel.browse(command[2] if command[0] == Command.SET else [])
|
||||
cache.set(recs[-1], self, lines._ids)
|
||||
|
||||
return records
|
||||
@@ -3531,20 +3650,20 @@ class Many2many(_RelationalMulti):
|
||||
for command in (commands or ()):
|
||||
if not isinstance(command, (list, tuple)) or not command:
|
||||
continue
|
||||
if command[0] == 0:
|
||||
if command[0] == Command.CREATE:
|
||||
to_create.append((recs._ids, command[2]))
|
||||
elif command[0] == 1:
|
||||
elif command[0] == Command.UPDATE:
|
||||
comodel.browse(command[1]).write(command[2])
|
||||
elif command[0] == 2:
|
||||
elif command[0] == Command.DELETE:
|
||||
to_delete.append(command[1])
|
||||
elif command[0] == 3:
|
||||
elif command[0] == Command.UNLINK:
|
||||
relation_remove(recs._ids, command[1])
|
||||
elif command[0] == 4:
|
||||
elif command[0] == Command.LINK:
|
||||
relation_add(recs._ids, command[1])
|
||||
elif command[0] in (5, 6):
|
||||
elif command[0] in (Command.CLEAR, Command.SET):
|
||||
# new lines must no longer be linked to records
|
||||
to_create = [(set(ids) - set(recs._ids), vals) for (ids, vals) in to_create]
|
||||
relation_set(recs._ids, command[2] if command[0] == 6 else ())
|
||||
relation_set(recs._ids, command[2] if command[0] == Command.SET else ())
|
||||
|
||||
if to_create:
|
||||
# create lines in batch, and link them
|
||||
@@ -3648,28 +3767,28 @@ class Many2many(_RelationalMulti):
|
||||
for command in commands:
|
||||
if not isinstance(command, (list, tuple)) or not command:
|
||||
continue
|
||||
if command[0] == 0:
|
||||
if command[0] == Command.CREATE:
|
||||
line_id = comodel.new(command[2], ref=command[1]).id
|
||||
for line_ids in new_relation.values():
|
||||
line_ids.add(line_id)
|
||||
elif command[0] == 1:
|
||||
elif command[0] == Command.UPDATE:
|
||||
line_id = new(command[1])
|
||||
comodel.browse([line_id]).update(command[2])
|
||||
elif command[0] == 2:
|
||||
elif command[0] == Command.DELETE:
|
||||
line_id = new(command[1])
|
||||
for line_ids in new_relation.values():
|
||||
line_ids.discard(line_id)
|
||||
elif command[0] == 3:
|
||||
elif command[0] == Command.UNLINK:
|
||||
line_id = new(command[1])
|
||||
for line_ids in new_relation.values():
|
||||
line_ids.discard(line_id)
|
||||
elif command[0] == 4:
|
||||
elif command[0] == Command.LINK:
|
||||
line_id = new(command[1])
|
||||
for line_ids in new_relation.values():
|
||||
line_ids.add(line_id)
|
||||
elif command[0] in (5, 6):
|
||||
elif command[0] in (Command.CLEAR, Command.SET):
|
||||
# new lines must no longer be linked to records
|
||||
line_ids = command[2] if command[0] == 6 else ()
|
||||
line_ids = command[2] if command[0] == Command.SET else ()
|
||||
line_ids = set(new(line_id) for line_id in line_ids)
|
||||
for id_ in recs._ids:
|
||||
new_relation[id_] = set(line_ids)
|
||||
|
||||
+30
-48
@@ -1300,10 +1300,11 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
# convert default values to the right format
|
||||
#
|
||||
# we explicitly avoid using _convert_to_write() for x2many fields,
|
||||
# because the latter leaves values like [(4, 2), (4, 3)], which are not
|
||||
# supported by the web client as default values; stepping through the
|
||||
# cache allows to normalize such a list to [(6, 0, [2, 3])], which is
|
||||
# properly supported by the web client
|
||||
# because the latter leaves values like [(Command.LINK, 2),
|
||||
# (Command.LINK, 3)], which are not supported by the web client as
|
||||
# default values; stepping through the cache allows to normalize
|
||||
# such a list to [(Command.SET, 0, [2, 3])], which is properly
|
||||
# supported by the web client
|
||||
for fname, value in defaults.items():
|
||||
if fname in self._fields:
|
||||
field = self._fields[fname]
|
||||
@@ -1844,10 +1845,10 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
for name, value in defaults.items():
|
||||
if self._fields[name].type == 'many2many' and value and isinstance(value[0], int):
|
||||
# convert a list of ids into a list of commands
|
||||
defaults[name] = [(6, 0, value)]
|
||||
defaults[name] = [Command.set(value)]
|
||||
elif self._fields[name].type == 'one2many' and value and isinstance(value[0], dict):
|
||||
# convert a list of dicts into a list of commands
|
||||
defaults[name] = [(0, 0, x) for x in value]
|
||||
defaults[name] = [Command.create(x) for x in value]
|
||||
defaults.update(values)
|
||||
return defaults
|
||||
|
||||
@@ -3529,37 +3530,18 @@ Fields:
|
||||
:const:`odoo.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT`
|
||||
* .. _openerp/models/relationals/format:
|
||||
|
||||
:class:`~odoo.fields.One2many` and
|
||||
:class:`~odoo.fields.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, 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 :meth:`~.create`.
|
||||
``(2, id, 0)``
|
||||
removes the record of id ``id`` from the set, then deletes it
|
||||
(from the database). Can not be used in :meth:`~.create`.
|
||||
``(3, id, 0)``
|
||||
removes the record of id ``id`` from the set, but does not
|
||||
delete it. Can not be used in
|
||||
:meth:`~.create`.
|
||||
``(4, id, 0)``
|
||||
adds an existing record of id ``id`` to the set.
|
||||
``(5, 0, 0)``
|
||||
removes all records from the set, equivalent to using the
|
||||
command ``3`` on every record explicitly. Can not be used in
|
||||
:meth:`~.create`.
|
||||
``(6, 0, 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``.
|
||||
"""
|
||||
The expected value of a :class:`~odoo.fields.One2many` or
|
||||
:class:`~odoo.fields.Many2many` relational field is a list of
|
||||
:class:`~odoo.fields.Command` that manipulate the relation the
|
||||
implement. There are a total of 7 commands:
|
||||
:meth:`~odoo.fields.Command.create`,
|
||||
:meth:`~odoo.fields.Command.update`,
|
||||
:meth:`~odoo.fields.Command.delete`,
|
||||
:meth:`~odoo.fields.Command.unlink`,
|
||||
:meth:`~odoo.fields.Command.link`,
|
||||
:meth:`~odoo.fields.Command.clear`, and
|
||||
:meth:`~odoo.fields.Command.set`.
|
||||
"""
|
||||
if not self:
|
||||
return True
|
||||
|
||||
@@ -4534,11 +4516,11 @@ Fields:
|
||||
# duplicate following the order of the ids because we'll rely on
|
||||
# it later for copying translations in copy_translation()!
|
||||
lines = [rec.copy_data()[0] for rec in self[name].sorted(key='id')]
|
||||
# the lines are duplicated using the wrong (old) parent, but then
|
||||
# are reassigned to the correct one thanks to the (0, 0, ...)
|
||||
default[name] = [(0, 0, line) for line in lines if line]
|
||||
# the lines are duplicated using the wrong (old) parent, but then are
|
||||
# reassigned to the correct one thanks to the (Command.CREATE, 0, ...)
|
||||
default[name] = [Command.create(line) for line in lines if line]
|
||||
elif field.type == 'many2many':
|
||||
default[name] = [(6, 0, self[name].ids)]
|
||||
default[name] = [Command.set(self[name].ids)]
|
||||
else:
|
||||
default[name] = field.convert_to_write(self[name], self)
|
||||
|
||||
@@ -6047,7 +6029,7 @@ Fields:
|
||||
result[name] = field.convert_to_onchange(self[name], record, {})
|
||||
else:
|
||||
# x2many fields: serialize value as commands
|
||||
result[name] = commands = [(5,)]
|
||||
result[name] = commands = [Command.clear()]
|
||||
# The purpose of the following line is to enable the prefetching.
|
||||
# In the loop below, line._prefetch_ids actually depends on the
|
||||
# value of record[name] in cache (see prefetch_ids on x2many
|
||||
@@ -6062,7 +6044,7 @@ Fields:
|
||||
if not line.id:
|
||||
# new line: send diff from scratch
|
||||
line_diff = line_snapshot.diff({})
|
||||
commands.append((0, line.id.ref or 0, line_diff))
|
||||
commands.append((Command.CREATE, line.id.ref or 0, line_diff))
|
||||
else:
|
||||
# existing line: check diff from database
|
||||
# (requires a clean record cache!)
|
||||
@@ -6071,9 +6053,9 @@ Fields:
|
||||
# send all fields because the web client
|
||||
# might need them to evaluate modifiers
|
||||
line_diff = line_snapshot.diff({})
|
||||
commands.append((1, line.id, line_diff))
|
||||
commands.append(Command.update(line.id, line_diff))
|
||||
else:
|
||||
commands.append((4, line.id))
|
||||
commands.append(Command.link(line.id))
|
||||
return result
|
||||
|
||||
nametree = PrefixTree(self.browse(), field_onchange)
|
||||
@@ -6093,9 +6075,9 @@ Fields:
|
||||
# retrieve all line ids in commands
|
||||
line_ids = set()
|
||||
for cmd in values[name]:
|
||||
if cmd[0] in (1, 4):
|
||||
if cmd[0] in (Command.UPDATE, Command.LINK):
|
||||
line_ids.add(cmd[1])
|
||||
elif cmd[0] == 6:
|
||||
elif cmd[0] == Command.SET:
|
||||
line_ids.update(cmd[2])
|
||||
# prefetch stored fields on lines
|
||||
lines = self[name].browse(line_ids)
|
||||
@@ -6507,4 +6489,4 @@ def lazy_name_get(self):
|
||||
|
||||
# keep those imports here to avoid dependency cycle errors
|
||||
from .osv import expression
|
||||
from .fields import Field, Datetime
|
||||
from .fields import Field, Datetime, Command
|
||||
|
||||
@@ -53,6 +53,7 @@ class RecordDictWrapper(dict):
|
||||
|
||||
def _get_idref(self, env, model_str, idref):
|
||||
idref2 = dict(idref,
|
||||
Command=odoo.fields.Command,
|
||||
time=time,
|
||||
DateTime=datetime,
|
||||
datetime=datetime,
|
||||
@@ -301,10 +302,10 @@ form: module.record_id""" % (xml_id,)
|
||||
for group in g_names:
|
||||
if group.startswith('-'):
|
||||
group_id = self.id_get(group[1:])
|
||||
groups_value.append((3, group_id))
|
||||
groups_value.append(odoo.Command.unlink(group_id))
|
||||
else:
|
||||
group_id = self.id_get(group)
|
||||
groups_value.append((4, group_id))
|
||||
groups_value.append(odoo.Command.link(group_id))
|
||||
res['groups_id'] = groups_value
|
||||
if rec.get('paperformat'):
|
||||
pf_name = rec.get('paperformat')
|
||||
@@ -405,10 +406,10 @@ form: module.record_id""" % (xml_id,)
|
||||
for group in g_names:
|
||||
if group.startswith('-'):
|
||||
group_id = self.id_get(group[1:])
|
||||
groups_value.append((3, group_id))
|
||||
groups_value.append(odoo.Command.unlink(group_id))
|
||||
else:
|
||||
group_id = self.id_get(group)
|
||||
groups_value.append((4, group_id))
|
||||
groups_value.append(odoo.Command.link(group_id))
|
||||
res['groups_id'] = groups_value
|
||||
|
||||
if rec.get('target'):
|
||||
@@ -467,10 +468,10 @@ form: module.record_id""" % (xml_id,)
|
||||
for group in rec.get('groups', '').split(','):
|
||||
if group.startswith('-'):
|
||||
group_id = self.id_get(group[1:])
|
||||
groups.append((3, group_id))
|
||||
groups.append(odoo.Command.unlink(group_id))
|
||||
elif group:
|
||||
group_id = self.id_get(group)
|
||||
groups.append((4, group_id))
|
||||
groups.append(odoo.Command.link(group_id))
|
||||
if groups:
|
||||
values['groups_id'] = groups
|
||||
|
||||
@@ -551,7 +552,7 @@ form: module.record_id""" % (xml_id,)
|
||||
_fields = env[rec_model]._fields
|
||||
# if the current field is many2many
|
||||
if (f_name in _fields) and _fields[f_name].type == 'many2many':
|
||||
f_val = [(6, 0, [x[f_use] for x in s])]
|
||||
f_val = [odoo.Command.set([x[f_use] for x in s])]
|
||||
elif len(s):
|
||||
# otherwise (we are probably in a many2one field),
|
||||
# take the first element of the search
|
||||
@@ -637,7 +638,7 @@ form: module.record_id""" % (xml_id,)
|
||||
groups = el.attrib.pop('groups', None)
|
||||
if groups:
|
||||
grp_lst = [("ref('%s')" % x) for x in groups.split(',')]
|
||||
record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]"))
|
||||
record.append(Field(name="groups_id", eval="[Command.set(["+', '.join(grp_lst)+"])]"))
|
||||
if el.get('primary') == 'True':
|
||||
# Pseudo clone mode, we'll set the t-name to the full canonical xmlid
|
||||
el.append(
|
||||
|
||||
Reference in New Issue
Block a user