"""
-import copy
import datetime
import functools
import itertools
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. """
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
)
columns.update(cls._columns)
- defaults = dict(parent_class._defaults)
- defaults.update(cls._defaults)
-
inherits = dict(parent_class._inherits)
inherits.update(cls._inherits)
'_name': name,
'_register': False,
'_columns': columns,
- '_defaults': defaults,
'_inherits': inherits,
'_depends': depends,
'_constraints': constraints,
'_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),
}
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)
# 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
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):
@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):
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"""
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:
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
# 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
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
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)
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:
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()
""" 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)
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)
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,))
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()
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:
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:
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