diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index bf324af15cb..ddf34e4da63 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -74,7 +74,6 @@ class _column(object): _classic_read = True _classic_write = True _auto_join = False - _prefetch = True _properties = False _type = 'unknown' _obj = None @@ -85,59 +84,64 @@ class _column(object): _symbol_get = None _deprecated = False - copy = True # whether value is copied by BaseModel.copy() - string = None - help = "" - required = False - readonly = False - _domain = [] - _context = {} - states = None - priority = 0 - change_default = False - size = None - ondelete = None - translate = False - select = False - manual = False - write = False - read = False - selectable = True - group_operator = False - groups = False # CSV list of ext IDs of groups - deprecated = False # Optional deprecation warning + __slots__ = [ + 'copy', # whether value is copied by BaseModel.copy() + 'string', + 'help', + 'required', + 'readonly', + '_domain', + '_context', + 'states', + 'priority', + 'change_default', + 'size', + 'ondelete', + 'translate', + 'select', + 'manual', + 'write', + 'read', + 'selectable', + 'group_operator', + 'groups', # CSV list of ext IDs of groups + 'deprecated', # Optional deprecation warning + '_args', + '_prefetch', + ] - def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args): + def __init__(self, string='unknown', required=False, readonly=False, domain=[], context={}, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args): """ The 'manual' keyword argument specifies if the field is a custom one. It corresponds to the 'state' column in ir_model_fields. """ - args0 = { - 'string': string, - 'help': args.pop('help', None), - 'required': required, - 'readonly': readonly, - '_domain': domain, - '_context': context, - 'states': states, - 'priority': priority, - 'change_default': change_default, - 'size': size, - 'ondelete': ondelete.lower() if ondelete else None, - 'translate': translate, - 'select': select, - 'manual': manual, - 'group_operator': args.pop('group_operator', None), - 'groups': args.pop('groups', None), - 'deprecated': args.pop('deprecated', None), - } - for key, val in args0.iteritems(): - if val: - setattr(self, key, val) + # add parameters and default values + args['copy'] = args.get('copy', True) + args['string'] = string + args['help'] = args.get('help', '') + args['required'] = required + args['readonly'] = readonly + args['_domain'] = domain + args['_context'] = context + args['states'] = states + args['priority'] = priority + args['change_default'] = change_default + args['size'] = size + args['ondelete'] = ondelete.lower() if ondelete else None + args['translate'] = translate + args['select'] = select + args['manual'] = manual + args['write'] = args.get('write', False) + args['read'] = args.get('read', False) + args['selectable'] = args.get('selectable', True) + args['group_operator'] = args.get('group_operator', None) + args['groups'] = args.get('groups', None) + args['deprecated'] = args.get('deprecated', None) + args['_prefetch'] = args.get('_prefetch', True) - self._args = args or EMPTY_DICT + self._args = EMPTY_DICT for key, val in args.iteritems(): setattr(self, key, val) @@ -145,6 +149,32 @@ class _column(object): if not self._classic_write or self.deprecated or self.manual: self._prefetch = False + def __getattr__(self, name): + """ Access a non-slot attribute. """ + if name == '_args': + raise AttributeError(name) + try: + return self._args[name] + except KeyError: + raise AttributeError(name) + + def __setattr__(self, name, value): + """ Set a slot or non-slot attribute. """ + try: + object.__setattr__(self, name, value) + except AttributeError: + if self._args: + self._args[name] = value + else: + self._args = {name: value} # replace EMPTY_DICT + + def __delattr__(self, name): + """ Remove a non-slot attribute. """ + try: + del self._args[name] + except KeyError: + raise AttributeError(name) + def new(self, _computed_field=False, **args): """ Return a column like `self` with the given parameters; the parameter `_computed_field` tells whether the corresponding field is computed. @@ -224,6 +254,7 @@ class boolean(_column): _symbol_c = '%s' _symbol_f = bool _symbol_set = (_symbol_c, _symbol_f) + __slots__ = [] def __init__(self, string='unknown', required=False, **args): super(boolean, self).__init__(string=string, required=required, **args) @@ -239,6 +270,7 @@ class integer(_column): _symbol_f = lambda x: int(x or 0) _symbol_set = (_symbol_c, _symbol_f) _symbol_get = lambda self,x: x or 0 + __slots__ = [] def __init__(self, string='unknown', required=False, **args): super(integer, self).__init__(string=string, required=required, **args) @@ -246,6 +278,7 @@ class integer(_column): class reference(_column): _type = 'reference' _classic_read = False # post-process to handle missing target + __slots__ = ['selection'] def __init__(self, string, selection, size=None, **args): if callable(selection): @@ -298,6 +331,7 @@ def _symbol_set_char(self, symb): class char(_column): _type = 'char' + __slots__ = ['_symbol_f', '_symbol_set', '_symbol_set_char'] def __init__(self, string="unknown", size=None, **args): _column.__init__(self, string=string, size=size or None, **args) @@ -307,11 +341,13 @@ class char(_column): class text(_column): _type = 'text' + __slots__ = [] class html(text): _type = 'html' _symbol_c = '%s' + __slots__ = ['_sanitize', '_strip_style', '_symbol_f', '_symbol_set'] def _symbol_set_html(self, value): if value is None or value is False: @@ -347,6 +383,7 @@ class float(_column): _type = 'float' _symbol_c = '%s' _symbol_get = lambda self,x: x or 0.0 + __slots__ = ['_digits', '_digits_compute', '_symbol_f', '_symbol_set'] @property def digits(self): @@ -374,6 +411,7 @@ class float(_column): class date(_column): _type = 'date' + __slots__ = [] MONTHS = [ ('01', 'January'), @@ -464,6 +502,7 @@ class date(_column): class datetime(_column): _type = 'datetime' + __slots__ = [] MONTHS = [ ('01', 'January'), @@ -533,7 +572,7 @@ class datetime(_column): class binary(_column): _type = 'binary' - _symbol_c = '%s' + _classic_read = False # Binary values may be byte strings (python 2.6 byte array), but # the legacy OpenERP convention is to transfer and store binaries @@ -541,17 +580,16 @@ class binary(_column): # unicode in some circumstances, hence the str() cast in symbol_f. # This str coercion will only work for pure ASCII unicode strings, # on purpose - non base64 data must be passed as a 8bit byte strings. + _symbol_c = '%s' _symbol_f = lambda symb: symb and Binary(str(symb)) or None - _symbol_set = (_symbol_c, _symbol_f) _symbol_get = lambda self, x: x and str(x) - _classic_read = False - _prefetch = False + __slots__ = ['filters'] def __init__(self, string='unknown', filters=None, **args): - _column.__init__(self, string=string, **args) - self.filters = filters + args['_prefetch'] = args.get('_prefetch', False) + _column.__init__(self, string=string, filters=filters, **args) def get(self, cr, obj, ids, name, user=None, context=None, values=None): if not context: @@ -579,13 +617,13 @@ class binary(_column): class selection(_column): _type = 'selection' + __slots__ = ['selection'] def __init__(self, selection, string='unknown', **args): if callable(selection): from openerp import api selection = api.expected(api.cr_uid_context, selection) - _column.__init__(self, string=string, **args) - self.selection = selection + _column.__init__(self, string=string, selection=selection, **args) def to_field_args(self): args = super(selection, self).to_field_args() @@ -646,9 +684,10 @@ class many2one(_column): _symbol_f = lambda x: x or None _symbol_set = (_symbol_c, _symbol_f) - ondelete = 'set null' + __slots__ = ['ondelete', '_obj', '_auto_join'] def __init__(self, obj, string='unknown', auto_join=False, **args): + args['ondelete'] = args.get('ondelete', 'set null') _column.__init__(self, string=string, **args) self._obj = obj self._auto_join = auto_join @@ -694,13 +733,14 @@ class many2one(_column): class one2many(_column): _classic_read = False _classic_write = False - _prefetch = False _type = 'one2many' - # one2many columns are not copied by default - copy = False + __slots__ = ['_obj', '_fields_id', '_limit', '_auto_join'] def __init__(self, obj, fields_id, string='unknown', limit=None, auto_join=False, **args): + # one2many columns are not copied by default + args['copy'] = args.get('copy', False) + args['_prefetch'] = args.get('_prefetch', False) _column.__init__(self, string=string, **args) self._obj = obj self._fields_id = fields_id @@ -841,12 +881,14 @@ class many2many(_column): """ _classic_read = False _classic_write = False - _prefetch = False _type = 'many2many' + __slots__ = ['_obj', '_rel', '_id1', '_id2', '_limit', '_auto_join'] + def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args): """ """ + args['_prefetch'] = args.get('_prefetch', False) _column.__init__(self, string=string, **args) self._obj = obj if rel and '.' in rel: @@ -856,6 +898,7 @@ class many2many(_column): self._id1 = id1 self._id2 = id2 self._limit = limit + self._auto_join = False def to_field_args(self): args = super(many2many, self).to_field_args() @@ -1238,14 +1281,30 @@ class function(_column): } """ - _classic_read = False - _classic_write = False - _prefetch = False - _type = 'function' _properties = True - # function fields are not copied by default - copy = False + __slots__ = [ + '_type', + '_classic_read', + '_classic_write', + '_symbol_c', + '_symbol_f', + '_symbol_set', + '_symbol_get', + + '_fnct', + '_arg', + '_fnct_inv', + '_fnct_inv_arg', + '_fnct_search', + '_multi', + 'store', + + '_digits', + '_digits_compute', + 'selection', + '_obj', + ] @property def digits(self): @@ -1259,33 +1318,43 @@ class function(_column): # multi: compute several fields in one call # def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, store=False, multi=False, **args): + self._classic_read = False + self._classic_write = False + self._prefetch = False + self._symbol_c = '%s' + self._symbol_f = _symbol_set + self._symbol_set = (self._symbol_c, self._symbol_f) + self._symbol_get = None + # pop attributes that should not be assigned to self self._digits = args.pop('digits', (16,2)) self._digits_compute = args.pop('digits_compute', None) + self._obj = args.pop('relation', obj) + + # function fields are not copied by default + args['copy'] = args.get('copy', False) _column.__init__(self, **args) - self._obj = obj + + self._type = type self._fnct = fnct - self._fnct_inv = fnct_inv self._arg = arg + self._fnct_inv = fnct_inv + self._fnct_inv_arg = fnct_inv_arg + self._fnct_search = fnct_search + self.store = store self._multi = multi - if 'relation' in args: - self._obj = args['relation'] + + if not fnct_inv: + self.readonly = 1 + + if not fnct_search and not store: + self.selectable = False if callable(args.get('selection')): from openerp import api self.selection = api.expected(api.cr_uid_context, args['selection']) - self._fnct_inv_arg = fnct_inv_arg - if not fnct_inv: - self.readonly = 1 - self._type = type - self._fnct_search = fnct_search - self.store = store - - if not fnct_search and not store: - self.selectable = False - if store: if self._type != 'many2one': # m2o fields need to return tuples with name_get, not just foreign keys @@ -1421,6 +1490,7 @@ class related(function): 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'), } """ + __slots__ = ['arg', '_relations'] def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None): # assume self._arg = ('foo', 'bar', 'baz') @@ -1472,7 +1542,8 @@ class related(function): pass -class sparse(function): +class sparse(function): + __slots__ = ['serialization_field'] def convert_value(self, obj, cr, uid, record, value, read_value, context=None): """ @@ -1521,7 +1592,6 @@ class sparse(function): return read_value return value - def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None): if not type(ids) == list: ids = [ids] @@ -1564,7 +1634,6 @@ class sparse(function): def __init__(self, serialization_field, **kwargs): self.serialization_field = serialization_field super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs) - # --------------------------------------------------------- @@ -1572,6 +1641,8 @@ class sparse(function): # --------------------------------------------------------- class dummy(function): + __slots__ = ['arg', '_relations'] + def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None): return [] @@ -1595,23 +1666,27 @@ class serialized(_column): Note: only plain components allowed. """ - + _type = 'serialized' + __slots__ = [] + def _symbol_set_struct(val): return simplejson.dumps(val) def _symbol_get_struct(self, val): return simplejson.loads(val or '{}') - - _prefetch = False - _type = 'serialized' _symbol_c = '%s' _symbol_f = _symbol_set_struct _symbol_set = (_symbol_c, _symbol_f) _symbol_get = _symbol_get_struct + def __init__(self, *args, **kwargs): + kwargs['_prefetch'] = kwargs.get('_prefetch', False) + super(serialized, self).__init__(*args, **kwargs) + # TODO: review completly this class for speed improvement class property(function): + __slots__ = [] def to_field_args(self): args = super(property, self).to_field_args()