yield klass.__dict__[name]
-def default_compute(field, value):
- """ Return a compute function for the given default `value`; `value` is
- either a constant, or a unary function returning the default value.
- """
- name = field.name
- func = value if callable(value) else lambda rec: value
- def compute(recs):
- for rec in recs:
- rec[name] = func(rec)
- return compute
-
-
class MetaField(type):
""" Metaclass for field classes. """
by_type = {}
_free_attrs = None # list of semantic-free attribute names
automatic = False # whether the field is automatically created ("magic" field)
- _origin = None # the column or field interfaced by self, if any
+ inherited = False # whether the field is inherited (_inherits)
+ column = None # the column interfaced by the field
+ setup_done = False # whether the field has been set up
name = None # name of the field
type = None # type of the field (string)
related = None # sequence of field names, for related fields
related_sudo = True # whether related fields should be read as admin
company_dependent = False # whether `self` is company-dependent (property field)
- default = None # default value
+ default = None # default(recs) returns the default value
string = None # field label
help = None # field tooltip
attrs.update(self._attrs) # necessary in case self is not in cls
# initialize `self` with `attrs`
+ if 'default' in attrs and not callable(attrs['default']):
+ # make default callable
+ value = attrs['default']
+ attrs['default'] = lambda recs: value
if attrs.get('compute'):
# by default, computed fields are not stored, not copied and readonly
attrs['store'] = attrs.get('store', False)
def reset(self):
""" Prepare `self` for a new setup. """
- self._setup_done = False
+ self.setup_done = False
# self._triggers is a set of pairs (field, path) that represents the
# computed fields that depend on `self`. When `self` is modified, it
# invalidates the cache of each `field`, and registers the records to
and other properties). This method is idempotent: it has no effect
if `self` has already been set up.
"""
- if not self._setup_done:
- self._setup_done = True
+ if not self.setup_done:
+ self.setup_done = True
self._setup(env)
def _setup(self, env):
self.compute = self._compute_related
self.inverse = self._inverse_related
if field._description_searchable(env):
+ # allow searching on self only if the related field is searchable
self.search = self._search_related
# copy attributes from field to self (string, help, etc.)
def make_depends(deps):
return tuple(deps(recs) if callable(deps) else deps)
- # transform self.default into self.compute
- if self.default is not None and self.compute is None:
- self.compute = default_compute(self, self.default)
-
# convert compute into a callable and determine depends
if isinstance(self.compute, basestring):
# if the compute method has been overridden, concatenate all their _depends
return False
def _description_searchable(self, env):
- return self._description_store(env) or bool(self.search)
+ if self.store:
+ column = env[self.model_name]._columns.get(self.name)
+ return bool(getattr(column, 'store', True)) or \
+ bool(getattr(column, '_fnct_search', False))
+ return bool(self.search)
+
+ def _description_sortable(self, env):
+ if self.store:
+ column = env[self.model_name]._columns.get(self.name)
+ return bool(getattr(column, 'store', True))
+ if self.inherited:
+ # self is sortable if the inherited field is itself sortable
+ return self.related_field._description_sortable(env)
+ return False
_description_manual = property(attrgetter('manual'))
_description_depends = property(attrgetter('depends'))
def to_column(self):
""" return a low-level field object corresponding to `self` """
assert self.store
- if self._origin:
- assert isinstance(self._origin, fields._column)
- return self._origin
+ # some columns are registry-dependent, like float fields (digits);
+ # duplicate them to avoid sharing between registries
_logger.debug("Create fields._column for Field %s", self)
args = {}
for attr, prop in self.column_attrs:
args['relation'] = self.comodel_name
return fields.property(**args)
+ if isinstance(self.column, fields.function):
+ # it is too tricky to recreate a function field, so for that case,
+ # we make a stupid (and possibly incorrect) copy of the column
+ return copy(self.column)
+
return getattr(fields, self.type)(**args)
# properties used by to_column() to create a column instance
with records.env.do_in_draft():
try:
self._compute_value(records)
- except MissingError:
- # some record is missing, retry on existing records only
- self._compute_value(records.exists())
+ except (AccessError, MissingError):
+ # some record is forbidden or missing, retry record by record
+ for record in records:
+ try:
+ self._compute_value(record)
+ except Exception as exc:
+ record._cache[self.name] = FailedValue(exc)
def determine_value(self, record):
""" Determine the value of `self` for `record`. """
def determine_default(self, record):
""" determine the default value of field `self` on `record` """
- if self.compute:
+ if self.default:
+ value = self.default(record)
+ record._cache[self] = self.convert_to_cache(value, record)
+ elif self.compute:
self._compute_value(record)
else:
record._cache[self] = SpecialValue(self.null(record.env))
type = 'integer'
def convert_to_cache(self, value, record, validate=True):
+ if isinstance(value, dict):
+ # special case, when an integer field is used as inverse for a one2many
+ return value.get('id', False)
return int(value or 0)
def convert_to_read(self, value, use_name_get=True):
def __init__(self, string=None, digits=None, **kwargs):
super(Float, self).__init__(string=string, _digits=digits, **kwargs)
+ def _setup_digits(self, env):
+ """ Setup the digits for `self` and its corresponding column """
+ self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
+ if self.store:
+ column = env[self.model_name]._columns[self.name]
+ column.digits_change(env.cr)
+
def _setup_regular(self, env):
super(Float, self)._setup_regular(env)
- self.digits = self._digits(env.cr) if callable(self._digits) else self._digits
+ self._setup_digits(env)
_related_digits = property(attrgetter('digits'))
field = self.related_field
self.selection = lambda model: field._description_selection(model.env)
- def _setup_regular(self, env):
- super(Selection, self)._setup_regular(env)
- # determine selection (applying extensions)
- cls = type(env[self.model_name])
+ def set_class_name(self, cls, name):
+ super(Selection, self).set_class_name(cls, name)
+ # determine selection (applying 'selection_add' extensions)
selection = None
- for field in resolve_all_mro(cls, self.name, reverse=True):
+ for field in resolve_all_mro(cls, name, reverse=True):
if isinstance(field, type(self)):
# We cannot use field.selection or field.selection_add here
# because those attributes are overridden by `set_class_name`.
def __init__(self, comodel_name=None, string=None, **kwargs):
super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
- def _setup_regular(self, env):
- super(Many2one, self)._setup_regular(env)
-
- # self.inverse_fields is populated by the corresponding One2many field
-
+ def set_class_name(self, cls, name):
+ super(Many2one, self).set_class_name(cls, name)
# determine self.delegate
- self.delegate = self.name in env[self.model_name]._inherits.values()
+ if not self.delegate:
+ self.delegate = name in cls._inherits.values()
_column_ondelete = property(attrgetter('ondelete'))
_column_auto_join = property(attrgetter('auto_join'))
# evaluate name_get() as superuser, because the visibility of a
# many2one field value (id and name) depends on the current record's
# access rights, and not the value's access rights.
- return value.sudo().name_get()[0]
+ try:
+ return value.sudo().name_get()[0]
+ except MissingError:
+ # Should not happen, unless the foreign key is missing.
+ return False
else:
return value.id
result += result.new(command[2])
elif command[0] == 1:
result.browse(command[1]).update(command[2])
+ result += result.browse(command[1]) - result
elif command[0] == 2:
# note: the record will be deleted by write()
result -= result.browse(command[1])
# imported here to avoid dependency cycle issues
from openerp import SUPERUSER_ID
-from .exceptions import Warning, MissingError
+from .exceptions import Warning, AccessError, MissingError
from .models import BaseModel, MAGIC_COLUMNS
from .osv import fields