X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=openerp%2Fmodels.py;h=2562e1084102e3f79bcd163558914c9b1be9e1df;hb=36174fcc6e49fe11ed16325b229690bb48736e62;hp=ef5c2bb6a6204c513d52dec1f15cfe14f7d4358e;hpb=68777c5860cadafd939751269d4820e4c014ec98;p=odoo%2Fodoo.git diff --git a/openerp/models.py b/openerp/models.py index ef5c2bb..2562e10 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -39,7 +39,6 @@ """ -import copy import datetime import functools import itertools @@ -237,6 +236,12 @@ class MetaModel(api.Meta): if not self._custom: self.module_to_models.setdefault(self._module, []).append(self) + # transform columns into new-style fields (enables field inheritance) + for name, column in self._columns.iteritems(): + if name in self.__dict__: + _logger.warning("Field %r erasing an existing value", name) + setattr(self, name, column.to_field()) + class NewId(object): """ Pseudo-ids for new records. """ @@ -474,6 +479,18 @@ class BaseModel(object): cls._columns.pop(name, None) @classmethod + def _pop_field(cls, name): + """ Remove the field with the given `name` from the model. + This method should only be used for manual fields. + """ + field = cls._fields.pop(name) + cls._columns.pop(name, None) + cls._all_columns.pop(name, None) + if hasattr(cls, name): + delattr(cls, name) + return field + + @classmethod def _add_magic_fields(cls): """ Introduce magic fields on the current class @@ -586,9 +603,6 @@ class BaseModel(object): ) columns.update(cls._columns) - defaults = dict(parent_class._defaults) - defaults.update(cls._defaults) - inherits = dict(parent_class._inherits) inherits.update(cls._inherits) @@ -613,7 +627,6 @@ class BaseModel(object): '_name': name, '_register': False, '_columns': columns, - '_defaults': defaults, '_inherits': inherits, '_depends': depends, '_constraints': constraints, @@ -627,7 +640,7 @@ class BaseModel(object): '_name': name, '_register': False, '_columns': dict(cls._columns), - '_defaults': dict(cls._defaults), + '_defaults': {}, # filled by Field._determine_default() '_inherits': dict(cls._inherits), '_depends': dict(cls._depends), '_constraints': list(cls._constraints), @@ -636,12 +649,6 @@ class BaseModel(object): } cls = type(cls._name, (cls,), attrs) - # float fields are registry-dependent (digit attribute); duplicate them - # to avoid issues - for key, col in cls._columns.items(): - if col._type == 'float': - cls._columns[key] = copy.copy(col) - # instantiate the model, and initialize it model = object.__new__(cls) model.__init__(pool, cr) @@ -803,7 +810,7 @@ class BaseModel(object): # inheritance between different models) cls._fields = {} for attr, field in getmembers(cls, Field.__instancecheck__): - if not field._origin: + if not field.inherited: cls._add_field(attr, field.copy()) # introduce magic fields @@ -1359,15 +1366,7 @@ class BaseModel(object): self[name] = self.env['ir.property'].get(name, self._name) return - # 4. look up _defaults - if name in self._defaults: - value = self._defaults[name] - if callable(value): - value = value(self._model, cr, uid, context) - self[name] = value - return - - # 5. delegate to field + # 4. delegate to field field.determine_default(self) def fields_get_keys(self, cr, user, context=None): @@ -1679,8 +1678,9 @@ class BaseModel(object): @api.depends(lambda self: (self._rec_name,) if self._rec_name else ()) def _compute_display_name(self): - for i, got_name in enumerate(self.name_get()): - self[i].display_name = got_name[1] + names = dict(self.name_get()) + for record in self: + record.display_name = names.get(record.id, False) @api.multi def name_get(self): @@ -1854,7 +1854,8 @@ class BaseModel(object): pass - def _read_group_fill_results(self, cr, uid, domain, groupby, remaining_groupbys, aggregated_fields, + def _read_group_fill_results(self, cr, uid, domain, groupby, remaining_groupbys, + aggregated_fields, count_field, read_group_result, read_group_order=None, context=None): """Helper method for filling in empty groups for all possible values of the field being grouped by""" @@ -1888,8 +1889,7 @@ class BaseModel(object): result.append(left_side) known_values[grouped_value] = left_side else: - count_attr = groupby + '_count' - known_values[grouped_value].update({count_attr: left_side[count_attr]}) + known_values[grouped_value].update({count_field: left_side[count_field]}) def append_right(right_side): grouped_value = right_side[0] if not grouped_value in known_values: @@ -2134,12 +2134,13 @@ class BaseModel(object): count_field = groupby_fields[0] if len(groupby_fields) >= 1 else '_' else: count_field = '_' + count_field += '_count' prefix_terms = lambda prefix, terms: (prefix + " " + ",".join(terms)) if terms else '' prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else '' query = """ - SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s_count %(extra_fields)s + SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s %(extra_fields)s FROM %(from)s %(where)s %(groupby)s @@ -2179,7 +2180,7 @@ class BaseModel(object): # method _read_group_fill_results need to be completely reimplemented # in a sane way result = self._read_group_fill_results(cr, uid, domain, groupby_fields[0], groupby[len(annotated_groupbys):], - aggregated_fields, result, read_group_order=order, + aggregated_fields, count_field, result, read_group_order=order, context=context) return result @@ -2259,28 +2260,13 @@ class BaseModel(object): if val is not False: cr.execute(update_query, (ss[1](val), key)) - def _check_selection_field_value(self, cr, uid, field, value, context=None): - """Raise except_orm if value is not among the valid values for the selection field""" - if self._columns[field]._type == 'reference': - val_model, val_id_str = value.split(',', 1) - val_id = False - try: - val_id = long(val_id_str) - except ValueError: - pass - if not val_id: - raise except_orm(_('ValidateError'), - _('Invalid value for reference field "%s.%s" (last part must be a non-zero integer): "%s"') % (self._table, field, value)) - val = val_model - else: - val = value - if isinstance(self._columns[field].selection, (tuple, list)): - if val in dict(self._columns[field].selection): - return - elif val in dict(self._columns[field].selection(self, cr, uid, context=context)): - return - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s.%s" is not in the selection') % (value, self._name, field)) + @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) def _check_removed_columns(self, cr, log=False): # iterate on the database columns to drop the NOT NULL constraints @@ -2416,23 +2402,14 @@ class BaseModel(object): def _set_default_value_on_column(self, cr, column_name, context=None): - # ideally should use add_default_value but fails - # due to ir.values not being ready + # ideally, we should use default_get(), but it fails due to ir.values + # not being ready - # get old-style default + # get default value default = self._defaults.get(column_name) if callable(default): default = default(self, cr, SUPERUSER_ID, context) - # get new_style default if no old-style - if default is None: - record = self.new(cr, SUPERUSER_ID, context=context) - field = self._fields[column_name] - field.determine_default(record) - defaults = dict(record._cache) - if column_name in defaults: - default = field.convert_to_write(defaults[column_name]) - column = self._columns[column_name] ss = column._symbol_set db_default = ss[1](default) @@ -2483,8 +2460,8 @@ class BaseModel(object): self._create_table(cr) has_rows = False else: - cr.execute('SELECT min(id) FROM "%s"' % (self._table,)) - has_rows = cr.fetchone()[0] is not None + cr.execute('SELECT 1 FROM "%s" LIMIT 1' % self._table) + has_rows = cr.rowcount cr.commit() if self._parent_store: @@ -2938,9 +2915,9 @@ class BaseModel(object): for attr, field in cls.pool[parent_model]._fields.iteritems(): if attr not in cls._fields: cls._add_field(attr, field.copy( + inherited=True, related=(parent_field, attr), related_sudo=False, - _origin=field, )) cls._inherits_reload_src() @@ -2991,7 +2968,9 @@ class BaseModel(object): """ Setup the fields (dependency triggers, etc). """ for field in self._fields.itervalues(): if partial and field.manual and \ - field.relational and field.comodel_name not in self.pool: + field.relational and \ + (field.comodel_name not in self.pool or \ + (field.type == 'one2many' and field.inverse_name not in self.pool[field.comodel_name]._fields)): # do not set up manual fields that refer to unknown models continue field.setup(self.env) @@ -3028,6 +3007,8 @@ class BaseModel(object): for fname, field in self._fields.iteritems(): if allfields and fname not in allfields: continue + if not field.setup_done: + continue if field.groups and not recs.user_has_groups(field.groups): continue res[fname] = field.get_description(recs.env) @@ -3155,6 +3136,9 @@ class BaseModel(object): elif self.env.field_todo(field): # field must be recomputed, do not prefetch records to recompute records -= self.env.field_todo(field) + elif not self._context.get('prefetch_fields', True): + # do not prefetch other fields + pass elif self._columns[field.name]._prefetch: # here we can optimize: prefetch all classic and many2one fields fnames = set(fname @@ -3537,6 +3521,7 @@ class BaseModel(object): self.check_access_rule(cr, uid, ids, 'unlink', context=context) pool_model_data = self.pool.get('ir.model.data') ir_values_obj = self.pool.get('ir.values') + ir_attachment_obj = self.pool.get('ir.attachment') for sub_ids in cr.split_for_in_conditions(ids): cr.execute('delete from ' + self._table + ' ' \ 'where id IN %s', (sub_ids,)) @@ -3558,6 +3543,13 @@ class BaseModel(object): if ir_value_ids: ir_values_obj.unlink(cr, uid, ir_value_ids, context=context) + # For the same reason, removing the record relevant to ir_attachment + # The search is performed with sql as the search method of ir_attachment is overridden to hide attachments of deleted records + cr.execute('select id from ir_attachment where res_model = %s and res_id in %s', (self._name, sub_ids)) + ir_attachment_ids = [ir_attachment[0] for ir_attachment in cr.fetchall()] + if ir_attachment_ids: + ir_attachment_obj.unlink(cr, uid, ir_attachment_ids, context=context) + # invalidate the *whole* cache, since the orm does not handle all # changes made in the database, like cascading delete! recs.invalidate_cache() @@ -3676,6 +3668,7 @@ class BaseModel(object): readonly = None self.check_field_access_rights(cr, user, 'write', vals.keys()) + deleted_related = defaultdict(list) for field in vals.keys(): fobj = None if field in self._columns: @@ -3684,6 +3677,10 @@ class BaseModel(object): fobj = self._inherit_fields[field][2] if not fobj: continue + if fobj._type in ['one2many', 'many2many'] and vals[field]: + for wtuple in vals[field]: + if isinstance(wtuple, (tuple, list)) and wtuple[0] == 2: + deleted_related[fobj._obj].append(wtuple[1]) groups = fobj.write if groups: @@ -3887,7 +3884,8 @@ class BaseModel(object): for id in ids_to_update: if id not in done[key]: done[key][id] = True - todo.append(id) + if id not in deleted_related[model_name]: + todo.append(id) self.pool[model_name]._store_set_values(cr, user, todo, fields_to_recompute, context) # recompute new-style fields @@ -5690,13 +5688,28 @@ class BaseModel(object): # determine which fields have been modified for name, oldval in values.iteritems(): + field = self._fields[name] newval = record[name] - if newval != oldval or getattr(newval, '_dirty', False): - field = self._fields[name] - result['value'][name] = field.convert_to_write( - newval, record._origin, subfields.get(name), - ) - todo.add(name) + if field.type in ('one2many', 'many2many'): + if newval != oldval or newval._dirty: + # put new value in result + result['value'][name] = field.convert_to_write( + newval, record._origin, subfields.get(name), + ) + todo.add(name) + else: + # keep result: newval may have been dirty before + pass + else: + if newval != oldval: + # put new value in result + result['value'][name] = field.convert_to_write( + newval, record._origin, subfields.get(name), + ) + todo.add(name) + else: + # clean up result to not return another value + result['value'].pop(name, None) # At the moment, the client does not support updates on a *2many field # while this one is modified by the user.