diff options
| author | Raphael Collet <rco@odoo.com> | 2017-11-16 17:21:06 +0100 |
|---|---|---|
| committer | Raphael Collet <rco@odoo.com> | 2018-01-16 10:47:13 +0100 |
| commit | 869f062e0898361bb261a0e457e75b2ae24cb785 (patch) | |
| tree | 957e1a6a01746491e785b2a9756f6b2b89e85913 /odoo | |
| parent | be0f24a96bcd6d66448f422d21ab126297885bd9 (diff) | |
[REF] models: improve code of `_create`/`_write`
Also remove method `self._check_selection_field_value`, and perform the check
in field method `convert_to_column`.
Diffstat (limited to 'odoo')
| -rw-r--r-- | odoo/addons/base/models/ir_translation.py | 8 | ||||
| -rw-r--r-- | odoo/fields.py | 29 | ||||
| -rw-r--r-- | odoo/models.py | 218 | ||||
| -rw-r--r-- | odoo/osv/expression.py | 5 |
4 files changed, 126 insertions, 134 deletions
diff --git a/odoo/addons/base/models/ir_translation.py b/odoo/addons/base/models/ir_translation.py index 3b4c27dc326..b7c819c88a9 100644 --- a/odoo/addons/base/models/ir_translation.py +++ b/odoo/addons/base/models/ir_translation.py @@ -186,7 +186,7 @@ class IrTranslation(models.Model): name = fields.Char(string='Translated field', required=True) res_id = fields.Integer(string='Record ID', index=True) - lang = fields.Selection(selection='_get_languages', string='Language') + lang = fields.Selection(selection='_get_languages', string='Language', validate=False) type = fields.Selection(TRANSLATION_TYPE, string='Type', index=True) src = fields.Text(string='Internal Source') # stored in database, kept for backward compatibility source = fields.Text(string='Source term', compute='_compute_source', @@ -267,12 +267,6 @@ class IrTranslation(models.Model): return res @api.model - def _check_selection_field_value(self, field, value): - if field == 'lang': - return - return super(IrTranslation, self)._check_selection_field_value(field, value) - - @api.model def _get_ids(self, name, tt, lang, ids): """ Return the translations of records. diff --git a/odoo/fields.py b/odoo/fields.py index c7c3d84abe1..70a9e1af80e 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -787,7 +787,7 @@ class Field(MetaField('DummyField', (object,), {})): """ Return the null value for this field in the record format. """ return False - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): """ Convert ``value`` from the ``write`` format to the SQL format. """ if value is None or value is False: return None @@ -1145,7 +1145,7 @@ class Boolean(Field): type = 'boolean' column_type = ('bool', 'bool') - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): return bool(value) def convert_to_cache(self, value, record, validate=True): @@ -1166,7 +1166,7 @@ class Integer(Field): _description_group_operator = property(attrgetter('group_operator')) - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): return int(value or 0) def convert_to_cache(self, value, record, validate=True): @@ -1232,7 +1232,7 @@ class Float(Field): _description_digits = property(attrgetter('digits')) _description_group_operator = property(attrgetter('group_operator')) - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): result = float(value or 0.0) digits = self.digits if digits: @@ -1286,11 +1286,11 @@ class Monetary(Field): assert self.currency_field in model._fields, \ "Field %s with unknown currency_field %r" % (self, self.currency_field) - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): # retrieve currency from values or record if values and self.currency_field in values: field = record._fields[self.currency_field] - currency = field.convert_to_cache(values[self.currency_field], record) + currency = field.convert_to_cache(values[self.currency_field], record, validate) currency = field.convert_to_record(currency, record) else: # Note: this is wrong if 'record' is several records with different @@ -1419,7 +1419,7 @@ class Char(_String): assert self.size is None or isinstance(self.size, int), \ "Char field %s with non-integer size %r" % (self, self.size) - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): if value is None or value is False: return None # we need to convert the string to a unicode object to be able @@ -1484,7 +1484,7 @@ class Html(_String): _description_strip_style = property(attrgetter('strip_style')) _description_strip_classes = property(attrgetter('strip_classes')) - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): if value is None or value is False: return None if self.sanitize: @@ -1671,7 +1671,7 @@ class Binary(Field): _description_attachment = property(attrgetter('attachment')) - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): # Binary values may be byte strings (python 2.6 byte array), but # the legacy OpenERP convention is to transfer and store binaries # as base64-encoded strings. The base64 string may be provided as a @@ -1755,6 +1755,7 @@ class Selection(Field): type = 'selection' _slots = { 'selection': None, # [(value, string), ...], function or method name + 'validate': True, # whether validating upon write } def __init__(self, selection=Default, string=Default, **kwargs): @@ -1820,6 +1821,11 @@ class Selection(Field): selection = selection(env[self.model_name]) return [value for value, _ in selection] + def convert_to_column(self, value, record, values=None, validate=True): + if validate and self.validate: + value = self.convert_to_cache(value, record) + return super(Selection, self).convert_to_column(value, record, values, validate) + def convert_to_cache(self, value, record, validate=True): if not validate: return value or False @@ -1846,6 +1852,9 @@ class Reference(Selection): def column_type(self): return ('varchar', pg_varchar()) + def convert_to_column(self, value, record, values=None, validate=True): + return Field.convert_to_column(self, value, record, values, validate) + def convert_to_cache(self, value, record, validate=True): # cache format: (res_model, res_id) or False def process(res_model, res_id): @@ -1987,7 +1996,7 @@ class Many2one(_Relational): for record in records: cache.set(record, self, self.convert_to_cache(value, record, validate=False)) - def convert_to_column(self, value, record, values=None): + def convert_to_column(self, value, record, values=None, validate=True): return value or None def convert_to_cache(self, value, record, validate=True): diff --git a/odoo/models.py b/odoo/models.py index 0e5a1ad10fd..6b7a48328b0 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -41,6 +41,7 @@ import dateutil.relativedelta import psycopg2 from lxml import etree from lxml.builder import E +from psycopg2.extensions import AsIs import odoo from . import SUPERUSER_ID @@ -2015,14 +2016,6 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): self.invalidate_cache(['parent_left', 'parent_right']) return True - @api.model - def _check_selection_field_value(self, field, value): - """ Check whether value is among the valid values for the given - selection/reference field, and raise an exception if not. - """ - field = self._fields[field] - field.convert_to_cache(value, self) - @api.model_cr def _check_removed_columns(self, log=False): # iterate on the database columns to drop the NOT NULL constraints of @@ -3035,9 +3028,9 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): # check Python constraints for inversed fields self._validate_fields(set(inverse_vals) - set(store_vals)) - # recompute fields - if self.env.recompute and self._context.get('recompute', True): - self.recompute() + # recompute fields + if self.env.recompute and self._context.get('recompute', True): + self.recompute() return True @@ -3050,11 +3043,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): cr = self._cr - # for recomputing new-style fields - extra_fields = ['write_date', 'write_uid'] if self._log_access else [] - self.modified(list(vals) + extra_fields) - - # for updating parent_left, parent_right + # determine records that require updating parent_left, parent_right parents_changed = [] if self._parent_store and (self._parent_name in vals) and \ not self._context.get('defer_parent_store_computation'): @@ -3074,81 +3063,88 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): cr.execute(query, (tuple(self.ids),)) parents_changed = [x[0] for x in cr.fetchall()] - updates = [] # list of (column, expr) or (column, pattern, value) - upd_todo = [] # list of column names to set explicitly - direct = [] # list of direcly updated columns - has_trans = self.env.lang and self.env.lang != 'en_US' + # determine SQL values + columns = [] # list of (column_name, format, value) + updated = [] # list of updated or translated columns + other_fields = [] # list of non-column fields single_lang = len(self.env['res.lang'].get_installed()) <= 1 + has_translation = self.env.lang and self.env.lang != 'en_US' + for name, val in vals.items(): field = self._fields[name] assert field.store + if field.deprecated: - _logger.warning('Field %s.%s is deprecated: %s', self._name, name, field.deprecated) - if hasattr(field, 'selection') and val: - self._check_selection_field_value(name, val) + _logger.warning('Field %s is deprecated: %s', field, field.deprecated) + if field.column_type: - if single_lang or not (has_trans and field.translate is True): + if single_lang or not (has_translation and field.translate is True): # val is not a translation: update the table val = field.convert_to_column(val, self, vals) - updates.append((name, field.column_format, val)) - direct.append(name) + columns.append((name, field.column_format, val)) + updated.append(name) else: - upd_todo.append(name) + other_fields.append(field) if self._log_access: - updates.append(('write_uid', '%s', self._uid)) - updates.append(('write_date', "(now() at time zone 'UTC')")) - direct.append('write_uid') - direct.append('write_date') + columns.append(('write_uid', '%s', self._uid)) + columns.append(('write_date', '%s', AsIs("(now() at time zone 'UTC')"))) + updated.append('write_uid') + updated.append('write_date') + + # mark fields to recompute (the ones that depend on old values) + self.modified(vals) - if updates: + # update columns + if columns: self.check_access_rule('write') query = 'UPDATE "%s" SET %s WHERE id IN %%s' % ( - self._table, ','.join('"%s"=%s' % (u[0], u[1]) for u in updates), + self._table, ','.join('"%s"=%s' % (column[0], column[1]) for column in columns), ) - params = tuple(u[2] for u in updates if len(u) > 2) + params = [column[2] for column in columns] for sub_ids in cr.split_for_in_conditions(set(self.ids)): - cr.execute(query, params + (sub_ids,)) + cr.execute(query, params + [sub_ids]) if cr.rowcount != len(sub_ids): raise MissingError(_('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description) - # TODO: optimize - for name in direct: + for name in updated: field = self._fields[name] if callable(field.translate): # The source value of a field has been modified, # synchronize translated terms when possible. - self.env['ir.translation']._sync_terms_translations(self._fields[name], self) + self.env['ir.translation']._sync_terms_translations(field, self) - elif has_trans and field.translate: + elif has_translation and field.translate: # The translated value of a field has been modified. src_trans = self.read([name])[0][name] if not src_trans: # Insert value to DB src_trans = vals[name] self.with_context(lang=None).write({name: src_trans}) - val = field.convert_to_column(vals[name], self, vals) tname = "%s,%s" % (self._name, name) + val = field.convert_to_column(vals[name], self, vals) self.env['ir.translation']._set_ids( tname, 'model', self.env.lang, self.ids, val, src_trans) - # invalidate and mark new-style fields to recompute; do this before - # setting other fields, because it can require the value of computed - # fields, e.g., a one2many checking constraints on records - self.modified(direct) - - # defaults in context must be removed when call a one2many or many2many - rel_context = {key: val - for key, val in self._context.items() - if not key.startswith('default_')} + # mark fields to recompute; do this before setting other fields, because + # the latter can require the value of computed fields, e.g., a one2many + # checking constraints on records + self.modified(updated) + + # set the value of non-column fields + if other_fields: + # discard default values from context + other = self.with_context({ + key: val + for key, val in self._context.items() + if not key.startswith('default_') + }) - # call the 'write' method of fields which are not columns - for name in upd_todo: - field = self._fields[name] - field.write(self.with_context(rel_context), vals[name]) + for field in sorted(other_fields, key=attrgetter('_sequence')): + field.write(other, vals[field.name]) - # for recomputing new-style fields - self.modified(upd_todo) + # mark fields to recompute + self.modified(field.name for field in other_fields) # check Python constraints self._validate_fields(vals) @@ -3211,10 +3207,6 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): self.invalidate_cache(['parent_left', 'parent_right']) - # recompute new-style fields - if self.env.recompute and self._context.get('recompute', True): - self.recompute() - return True @api.model @@ -3283,7 +3275,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): parent_record.write(parent_vals) # create record with stored fields - record = self.browse(self._create(store_vals)) + record = self._create(store_vals) with self.env.protecting(protected_fields, record): # put the values of inverse fields in cache, and inverse them @@ -3303,60 +3295,55 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): @api.model def _create(self, vals): - # list of column assignments defined as tuples like: - # (column_name, format_string, column_value) - # (column_name, sql_formula) - # Those tuples will be used by the string formatting for the INSERT - # statement below. - updates = [ - ('id', "nextval('%s')" % self._sequence), - ] - - upd_todo = [] - protected_fields = [] - for name, val in list(vals.items()): - field = self._fields[name] - assert field.store - if field.inverse: - protected_fields.append(field) + self = self.browse() # set boolean fields to False by default (to make search more powerful) for name, field in self._fields.items(): - if field.type == 'boolean' and field.store and name not in vals: - vals[name] = False + if field.type == 'boolean' and field.store: + vals.setdefault(name, False) # determine SQL values - self = self.browse() + columns = [] # list of (column_name, format, value) + other_fields = [] # list of non-column fields + protected_fields = [] # list of fields to not recompute on self + translated_names = [] # list of translated field names + + columns.append(('id', "nextval(%s)", self._sequence)) + for name, val in vals.items(): field = self._fields[name] - if field.store and field.column_type: + assert field.store + + if field.column_type: column_val = field.convert_to_column(val, self, vals) - updates.append((name, field.column_format, column_val)) + columns.append((name, field.column_format, column_val)) + if field.translate is True: + translated_names.append(name) else: - upd_todo.append(name) + other_fields.append(field) - if hasattr(field, 'selection') and val: - self._check_selection_field_value(name, val) + if field.inverse: + protected_fields.append(field) if self._log_access: - updates.append(('create_uid', '%s', self._uid)) - updates.append(('write_uid', '%s', self._uid)) - updates.append(('create_date', "(now() at time zone 'UTC')")) - updates.append(('write_date', "(now() at time zone 'UTC')")) + columns.append(('create_uid', '%s', self._uid)) + columns.append(('write_uid', '%s', self._uid)) + columns.append(('create_date', '%s', AsIs("(now() at time zone 'UTC')"))) + columns.append(('write_date', '%s', AsIs("(now() at time zone 'UTC')"))) # insert a row for this record cr = self._cr query = """INSERT INTO "%s" (%s) VALUES(%s) RETURNING id""" % ( self._table, - ', '.join('"%s"' % u[0] for u in updates), - ', '.join(u[1] for u in updates), + ', '.join('"%s"' % column[0] for column in columns), + ', '.join(column[1] for column in columns), ) - cr.execute(query, tuple(u[2] for u in updates if len(u) > 2)) + cr.execute(query, [column[2] for column in columns]) # from now on, self is the new record - id_new, = cr.fetchone() - self = self.browse(id_new) + self = self.browse(cr.fetchone()[0]) + # update parent_left, parent_right if self._parent_store and not self._context.get('defer_parent_store_computation'): if self.pool._init: self.pool._init_parent[self._name] = True @@ -3388,46 +3375,47 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): cr.execute("UPDATE %s SET parent_right=parent_right+2 WHERE parent_right>=%%s" % self._table, (pleft,)) cr.execute("UPDATE %s SET parent_left=%%s, parent_right=%%s WHERE id=%%s" % self._table, - (pleft, pleft + 1, id_new)) + (pleft, pleft + 1, record_id)) self.invalidate_cache(['parent_left', 'parent_right']) with self.env.protecting(protected_fields, self): - # invalidate and mark new-style fields to recompute; do this before - # setting other fields, because it can require the value of computed - # fields, e.g., a one2many checking constraints on records + # mark fields to recompute; do this before setting other fields, + # because the latter can require the value of computed fields, e.g., + # a one2many checking constraints on records self.modified(self._fields) - # defaults in context must be removed when call a one2many or many2many - rel_context = {key: val - for key, val in self._context.items() - if not key.startswith('default_')} + # set the value of non-column fields + if other_fields: + # discard default values from context + other = self.with_context({ + key: val + for key, val in self._context.items() + if not key.startswith('default_') + }) - # call the 'write' method of fields which are not columns - for name in sorted(upd_todo, key=lambda name: self._fields[name]._sequence): - field = self._fields[name] - field.write(self.with_context(rel_context), vals[name], create=True) + for field in sorted(other_fields, key=attrgetter('_sequence')): + field.write(other, vals[field.name], create=True) - # for recomputing new-style fields - self.modified(upd_todo) + # mark fields to recompute + self.modified(field.name for field in other_fields) # check Python constraints self._validate_fields(vals) + # recompute fields if self.env.recompute and self._context.get('recompute', True): - # recompute new-style fields self.recompute() self.check_access_rule('create') + # add translations if self.env.lang and self.env.lang != 'en_US': - # add translations for self.env.lang - for name, val in vals.items(): - field = self._fields[name] - if field.store and field.column_type and field.translate is True: - tname = "%s,%s" % (self._name, name) - self.env['ir.translation']._set_ids(tname, 'model', self.env.lang, self.ids, val, val) + for name in translated_names: + tname = "%s,%s" % (self._name, name) + val = vals[name] + self.env['ir.translation']._set_ids(tname, 'model', self.env.lang, self.ids, val, val) - return id_new + return self # TODO: ameliorer avec NULL @api.model diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index 26013885437..07d7f4c574f 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -1201,7 +1201,7 @@ class expression(object): else: field = model._fields[left] instr = ','.join([field.column_format] * len(params)) - params = [field.convert_to_column(p, record=model) for p in params] + params = [field.convert_to_column(p, model, validate=False) for p in params] query = '(%s."%s" %s (%s))' % (table_alias, left, operator, instr) else: # The case for (left, 'in', []) or (left, 'not in', []). @@ -1257,7 +1257,8 @@ class expression(object): query = '(%s OR %s."%s" IS NULL)' % (query, table_alias, left) params = ['%%%s%%' % native_str] else: - params = [model._fields[left].convert_to_column(right, model)] + field = model._fields[left] + params = [field.convert_to_column(right, model, validate=False)] return query, params |
