import base64
import datetime as DT
+import functools
import logging
import pytz
import re
import xmlrpclib
+from operator import itemgetter
from psycopg2 import Binary
import openerp
_symbol_f = _symbol_set
_symbol_set = (_symbol_c, _symbol_f)
_symbol_get = None
-
- # used to hide a certain field type in the list of field types
_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
+
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):
"""
It corresponds to the 'state' column in ir_model_fields.
"""
- if domain is None:
- domain = []
- if context is None:
- context = {}
- self.states = states or {}
- self.string = string
- self.readonly = readonly
- self.required = required
- self.size = size
- self.help = args.get('help', '')
- self.priority = priority
- self.change_default = change_default
- self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
- self.translate = translate
- self._domain = domain
- self._context = context
- self.write = False
- self.read = False
- self.view_load = 0
- self.select = select
- self.manual = manual
- self.selectable = True
- self.group_operator = args.get('group_operator', False)
- self.groups = False # CSV list of ext IDs of groups that can access this field
- self.deprecated = False # Optional deprecation warning
- for a in args:
- setattr(self, a, args[a])
-
+ args0 = {
+ 'string': string,
+ '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,
+ }
+ for key, val in args0.iteritems():
+ if val:
+ setattr(self, key, val)
+
+ self._args = args
+ for key, val in args.iteritems():
+ setattr(self, key, val)
+
+ # prefetch only if self._classic_write, not self.groups, and not
+ # self.deprecated
+ if not self._classic_write or self.deprecated:
+ self._prefetch = False
+
+ def new(self, **args):
+ """ return a column like `self` with the given parameters """
+ # memory optimization: reuse self whenever possible; you can reduce the
+ # average memory usage per registry by 10 megabytes!
+ return self if self.same_parameters(args) else type(self)(**args)
+
+ def same_parameters(self, args):
+ dummy = object()
+ return all(
+ # either both are falsy, or they are equal
+ (not val1 and not val) or (val1 == val)
+ for key, val in args.iteritems()
+ for val1 in [getattr(self, key, getattr(self, '_' + key, dummy))]
+ )
+
+ def to_field(self):
+ """ convert column `self` to a new-style field """
+ from openerp.fields import Field
+ return Field.by_type[self._type](**self.to_field_args())
+
+ def to_field_args(self):
+ """ return a dictionary with all the arguments to pass to the field """
+ base_items = [
+ ('column', self), # field interfaces self
+ ('copy', self.copy),
+ ]
+ truthy_items = filter(itemgetter(1), [
+ ('index', self.select),
+ ('manual', self.manual),
+ ('string', self.string),
+ ('help', self.help),
+ ('readonly', self.readonly),
+ ('required', self.required),
+ ('states', self.states),
+ ('groups', self.groups),
+ ('change_default', self.change_default),
+ ('deprecated', self.deprecated),
+ ('size', self.size),
+ ('ondelete', self.ondelete),
+ ('translate', self.translate),
+ ('domain', self._domain),
+ ('context', self._context),
+ ])
+ return dict(base_items + truthy_items + self._args.items())
+
def restart(self):
pass
class boolean(_column):
_type = 'boolean'
_symbol_c = '%s'
- _symbol_f = lambda x: x and 'True' or 'False'
+ _symbol_f = bool
_symbol_set = (_symbol_c, _symbol_f)
def __init__(self, string='unknown', required=False, **args):
_type = 'reference'
_classic_read = False # post-process to handle missing target
- def __init__(self, string, selection, size, **args):
+ def __init__(self, string, selection, size=None, **args):
+ if callable(selection):
+ from openerp import api
+ selection = api.expected(api.cr_uid_context, selection)
_column.__init__(self, string=string, size=size, selection=selection, **args)
+ def to_field_args(self):
+ args = super(reference, self).to_field_args()
+ args['selection'] = self.selection
+ return args
+
def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
result = {}
# copy initial values fetched previously.
model_name, res_id = value.split(',')
if model_name in obj.pool and res_id:
model = obj.pool[model_name]
- return model.name_get(cr, uid, [int(res_id)], context=context)[0][1]
+ names = model.name_get(cr, uid, [int(res_id)], context=context)
+ return names[0][1] if names else False
return tools.ustr(value)
# takes a string (encoded in utf8) and returns a string (encoded in utf8)
self._symbol_f = self._symbol_set_char = lambda x: _symbol_set_char(self, x)
self._symbol_set = (self._symbol_c, self._symbol_f)
-
class text(_column):
_type = 'text'
+
class html(text):
_type = 'html'
_symbol_c = '%s'
- def _symbol_f(x):
- if x is None or x == False:
+
+ def _symbol_set_html(self, value):
+ if value is None or value is False:
return None
- return html_sanitize(x)
-
- _symbol_set = (_symbol_c, _symbol_f)
+ if not self._sanitize:
+ return value
+ return html_sanitize(value)
+
+ def __init__(self, string='unknown', sanitize=True, **args):
+ super(html, self).__init__(string=string, **args)
+ self._sanitize = sanitize
+ # symbol_set redefinition because of sanitize specific behavior
+ self._symbol_f = self._symbol_set_html
+ self._symbol_set = (self._symbol_c, self._symbol_f)
+
+ def to_field_args(self):
+ args = super(html, self).to_field_args()
+ args['sanitize'] = self._sanitize
+ return args
import __builtin__
# synopsis: digits_compute(cr) -> (precision, scale)
self.digits_compute = digits_compute
+ def new(self, **args):
+ # float columns are database-dependent, so always recreate them
+ return type(self)(**args)
+
+ def to_field_args(self):
+ args = super(float, self).to_field_args()
+ args['digits'] = self.digits_compute or self.digits
+ return args
+
def digits_change(self, cr):
if self.digits_compute:
self.digits = self.digits_compute(cr)
class date(_column):
_type = 'date'
+ MONTHS = [
+ ('01', 'January'),
+ ('02', 'February'),
+ ('03', 'March'),
+ ('04', 'April'),
+ ('05', 'May'),
+ ('06', 'June'),
+ ('07', 'July'),
+ ('08', 'August'),
+ ('09', 'September'),
+ ('10', 'October'),
+ ('11', 'November'),
+ ('12', 'December')
+ ]
+
@staticmethod
def today(*args):
""" Returns the current date in a format fit for being a
if context and context.get('tz'):
tz_name = context['tz']
else:
- tz_name = model.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
+ user = model.pool['res.users'].browse(cr, SUPERUSER_ID, uid)
+ tz_name = user.tz
if tz_name:
try:
utc = pytz.timezone('UTC')
exc_info=True)
return (context_today or today).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ @staticmethod
+ def date_to_datetime(model, cr, uid, userdate, context=None):
+ """ Convert date values expressed in user's timezone to
+ server-side UTC timestamp, assuming a default arbitrary
+ time of 12:00 AM - because a time is needed.
+
+ :param str userdate: date string in in user time zone
+ :return: UTC datetime string for server-side use
+ """
+ user_date = DT.datetime.strptime(userdate, tools.DEFAULT_SERVER_DATE_FORMAT)
+ if context and context.get('tz'):
+ tz_name = context['tz']
+ else:
+ tz_name = model.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
+ if tz_name:
+ utc = pytz.timezone('UTC')
+ context_tz = pytz.timezone(tz_name)
+ user_datetime = user_date + DT.timedelta(hours=12.0)
+ local_timestamp = context_tz.localize(user_datetime, is_dst=False)
+ user_datetime = local_timestamp.astimezone(utc)
+ return user_datetime.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ return user_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
+
+
class datetime(_column):
_type = 'datetime'
+
+ MONTHS = [
+ ('01', 'January'),
+ ('02', 'February'),
+ ('03', 'March'),
+ ('04', 'April'),
+ ('05', 'May'),
+ ('06', 'June'),
+ ('07', 'July'),
+ ('08', 'August'),
+ ('09', 'September'),
+ ('10', 'October'),
+ ('11', 'November'),
+ ('12', 'December')
+ ]
+
@staticmethod
def now(*args):
""" Returns the current datetime in a format fit for being a
tz_name = context['tz']
else:
registry = openerp.modules.registry.RegistryManager.get(cr.dbname)
- tz_name = registry.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
+ user = registry['res.users'].browse(cr, SUPERUSER_ID, uid)
+ tz_name = user.tz
+ utc_timestamp = pytz.utc.localize(timestamp, is_dst=False) # UTC = no DST
if tz_name:
try:
- utc = pytz.timezone('UTC')
context_tz = pytz.timezone(tz_name)
- utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
return utc_timestamp.astimezone(context_tz)
except Exception:
_logger.debug("failed to compute context/client-specific timestamp, "
"using the UTC value",
exc_info=True)
- return timestamp
+ return utc_timestamp
class binary(_column):
_type = 'binary'
_type = '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
+ def to_field_args(self):
+ args = super(selection, self).to_field_args()
+ args['selection'] = self.selection
+ return args
+
+ @classmethod
+ def reify(cls, cr, uid, model, field, context=None):
+ """ Munges the field's ``selection`` attribute as necessary to get
+ something useable out of it: calls it if it's a function, applies
+ translations to labels if it's not.
+
+ A callable ``selection`` is considered translated on its own.
+
+ :param orm.Model model:
+ :param _column field:
+ """
+ if callable(field.selection):
+ return field.selection(model, cr, uid, context)
+
+ if not (context and 'lang' in context):
+ return field.selection
+
+ # field_to_dict isn't given a field name, only a field object, we
+ # need to get the name back in order to perform the translation lookup
+ field_name = next(
+ name for name, column in model._columns.iteritems()
+ if column == field)
+
+ translation_filter = "%s,%s" % (model._name, field_name)
+ translate = functools.partial(
+ model.pool['ir.translation']._get_source,
+ cr, uid, translation_filter, 'selection', context['lang'])
+
+ return [
+ (value, translate(label))
+ for value, label in field.selection
+ ]
+
# ---------------------------------------------------------
# Relationals fields
# ---------------------------------------------------------
_symbol_f = lambda x: x or None
_symbol_set = (_symbol_c, _symbol_f)
+ ondelete = 'set null'
+
def __init__(self, obj, string='unknown', auto_join=False, **args):
_column.__init__(self, string=string, **args)
self._obj = obj
self._auto_join = auto_join
- def get(self, cr, obj, ids, name, user=None, context=None, values=None):
- if context is None:
- context = {}
- if values is None:
- values = {}
-
- res = {}
- for r in values:
- res[r['id']] = r[name]
- for id in ids:
- res.setdefault(id, '')
- obj = obj.pool[self._obj]
-
- # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
- # we use uid=1 because the visibility of a many2one field value (just id and name)
- # must be the access right of the parent form and not the linked object itself.
- records = dict(obj.name_get(cr, SUPERUSER_ID,
- list(set([x for x in res.values() if isinstance(x, (int,long))])),
- context=context))
- for id in res:
- if res[id] in records:
- res[id] = (res[id], records[res[id]])
- else:
- res[id] = False
- return res
+ def to_field_args(self):
+ args = super(many2one, self).to_field_args()
+ args['comodel_name'] = self._obj
+ args['auto_join'] = self._auto_join
+ return args
def set(self, cr, obj_src, id, field, values, user=None, context=None):
if not context:
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
return obj.pool[self._obj].search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
-
@classmethod
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
return value[1] if isinstance(value, tuple) else tools.ustr(value)
_prefetch = False
_type = 'one2many'
+ # one2many columns are not copied by default
+ copy = False
+
def __init__(self, obj, fields_id, string='unknown', limit=None, auto_join=False, **args):
_column.__init__(self, string=string, **args)
self._obj = obj
#one2many can't be used as condition for defaults
assert(self.change_default != True)
+ def to_field_args(self):
+ args = super(one2many, self).to_field_args()
+ args['comodel_name'] = self._obj
+ args['inverse_name'] = self._fields_id
+ args['auto_join'] = self._auto_join
+ args['limit'] = self._limit
+ return args
+
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
- if context is None:
- context = {}
if self._context:
- context = context.copy()
- context.update(self._context)
- if values is None:
- values = {}
-
- res = {}
- for id in ids:
- res[id] = []
+ context = dict(context or {})
+ context.update(self._context)
+ # retrieve the records in the comodel
+ comodel = obj.pool[self._obj].browse(cr, user, [], context)
+ inverse = self._fields_id
domain = self._domain(obj) if callable(self._domain) else self._domain
- model = obj.pool[self._obj]
- ids2 = model.search(cr, user, domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
- for r in model._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
- if r[self._fields_id] in res:
- res[r[self._fields_id]].append(r['id'])
- return res
+ domain = domain + [(inverse, 'in', ids)]
+ records = comodel.search(domain, limit=self._limit)
+
+ result = {id: [] for id in ids}
+ # read the inverse of records without prefetching other fields on them
+ for record in records.with_context(prefetch_fields=False):
+ # record[inverse] may be a record or an integer
+ result[int(record[inverse])].append(record.id)
+
+ return result
def set(self, cr, obj, id, field, values, user=None, context=None):
result = []
- if not context:
- context = {}
- if self._context:
- context = context.copy()
+ context = dict(context or {})
context.update(self._context)
- context['no_store_function'] = True
+ context['recompute'] = False # recomputation is done by outer create/write
if not values:
return
obj = obj.pool[self._obj]
else:
cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
elif act[0] == 4:
- # Must use write() to recompute parent_store structure if needed
- obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
+ # table of the field (parent_model in case of inherit)
+ field_model = self._fields_id in obj.pool[self._obj]._columns and self._obj or obj.pool[self._obj]._all_columns[self._fields_id].parent_model
+ field_table = obj.pool[field_model]._table
+ cr.execute("select 1 from {0} where id=%s and {1}=%s".format(field_table, self._fields_id), (act[1], id))
+ if not cr.fetchone():
+ # Must use write() to recompute parent_store structure if needed and check access rules
+ obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
elif act[0] == 5:
reverse_rel = obj._all_columns.get(self._fields_id)
assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
domain = self._domain(obj) if callable(self._domain) else self._domain
return obj.pool[self._obj].name_search(cr, uid, value, domain, operator, context=context,limit=limit)
-
@classmethod
def _as_display_name(cls, field, cr, uid, obj, value, context=None):
raise NotImplementedError('One2Many columns should not be used as record name (_rec_name)')
self._id2 = id2
self._limit = limit
+ def to_field_args(self):
+ args = super(many2many, self).to_field_args()
+ args['comodel_name'] = self._obj
+ args['relation'] = self._rel
+ args['column1'] = self._id1
+ args['column2'] = self._id2
+ args['limit'] = self._limit
+ return args
+
def _sql_names(self, source_model):
"""Return the SQL names defining the structure of the m2m relationship table
_type = 'function'
_properties = True
+ # function fields are not copied by default
+ copy = False
+
#
# multi: compute several fields in one call
#
self.digits = args.get('digits', (16,2))
self.digits_compute = args.get('digits_compute', None)
+ 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:
else:
self._prefetch = True
- if type == 'float':
- self._symbol_c = float._symbol_c
- self._symbol_f = float._symbol_f
- self._symbol_set = float._symbol_set
-
- if type == 'boolean':
- self._symbol_c = boolean._symbol_c
- self._symbol_f = boolean._symbol_f
- self._symbol_set = boolean._symbol_set
-
- if type == 'integer':
- self._symbol_c = integer._symbol_c
- self._symbol_f = integer._symbol_f
- self._symbol_set = integer._symbol_set
-
if type == 'char':
self._symbol_c = char._symbol_c
self._symbol_f = lambda x: _symbol_set_char(self, x)
self._symbol_set = (self._symbol_c, self._symbol_f)
+ else:
+ type_class = globals().get(type)
+ if type_class is not None:
+ self._symbol_c = type_class._symbol_c
+ self._symbol_f = type_class._symbol_f
+ self._symbol_set = type_class._symbol_set
+
+ def new(self, **args):
+ # HACK: function fields are tricky to recreate, simply return a copy
+ import copy
+ return copy.copy(self)
+
+ def to_field_args(self):
+ args = super(function, self).to_field_args()
+ if self._type in ('float',):
+ args['digits'] = self.digits_compute or self.digits
+ elif self._type in ('selection', 'reference'):
+ args['selection'] = self.selection
+ elif self._type in ('many2one', 'one2many', 'many2many'):
+ args['comodel_name'] = self._obj
+ return args
def digits_change(self, cr):
if self._type == 'float':
return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
def postprocess(self, cr, uid, obj, field, value=None, context=None):
+ return self._postprocess_batch(cr, uid, obj, field, {0: value}, context=context)[0]
+
+ def _postprocess_batch(self, cr, uid, obj, field, values, context=None):
+ if not values:
+ return values
+
if context is None:
context = {}
- result = value
+
field_type = obj._columns[field]._type
- if field_type == "many2one":
- # make the result a tuple if it is not already one
- if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
- obj_model = obj.pool[obj._columns[field].relation]
- dict_names = dict(obj_model.name_get(cr, uid, [value], context))
- result = (value, dict_names[value])
+ new_values = dict(values)
if field_type == 'binary':
if context.get('bin_size'):
# client requests only the size of binary fields
- result = get_nice_size(value)
+ for rid, value in values.iteritems():
+ if value:
+ new_values[rid] = get_nice_size(value)
elif not context.get('bin_raw'):
- result = sanitize_binary_value(value)
-
- if field_type == "integer" and value > xmlrpclib.MAXINT:
- # integer/long values greater than 2^31-1 are not supported
- # in pure XMLRPC, so we have to pass them as floats :-(
- # This is not needed for stored fields and non-functional integer
- # fields, as their values are constrained by the database backend
- # to the same 32bits signed int limit.
- result = __builtin__.float(value)
- return result
+ for rid, value in values.iteritems():
+ if value:
+ new_values[rid] = sanitize_binary_value(value)
+
+ return new_values
def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
- result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
- for id in ids:
- if self._multi and id in result:
- for field, value in result[id].iteritems():
- if value:
- result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
- elif result.get(id):
- result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
+ multi = self._multi
+ # if we already have a value, don't recompute it.
+ # This happen if case of stored many2one fields
+ if values and not multi and name in values[0]:
+ result = dict((v['id'], v[name]) for v in values)
+ elif values and multi and all(n in values[0] for n in name):
+ result = dict((v['id'], dict((n, v[n]) for n in name)) for v in values)
+ else:
+ result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
+ if multi:
+ swap = {}
+ for rid, values in result.iteritems():
+ for f, v in values.iteritems():
+ if f not in name:
+ continue
+ swap.setdefault(f, {})[rid] = v
+
+ for field, values in swap.iteritems():
+ new_values = self._postprocess_batch(cr, uid, obj, field, values, context)
+ for rid, value in new_values.iteritems():
+ result[rid][field] = value
+
+ else:
+ result = self._postprocess_batch(cr, uid, obj, name, result, context)
+
return result
def set(self, cr, obj, id, name, value, user=None, context=None):
field = '.'.join(self._arg)
return map(lambda x: (field, x[1], x[2]), domain)
- def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
+ def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
- for record in obj.browse(cr, uid, ids, context=context):
+ for instance in obj.browse(cr, uid, ids, context=context):
# traverse all fields except the last one
for field in self.arg[:-1]:
- record = record[field] or False
- if not record:
- break
- elif isinstance(record, list):
- # record is the result of a one2many or many2many field
- record = record[0]
- if record:
- # write on the last field
- record.write({self.arg[-1]: values})
+ instance = instance[field][:1]
+ if instance:
+ # write on the last field of the target record
+ instance.write({self.arg[-1]: values})
def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
res = {}
for record in obj.browse(cr, SUPERUSER_ID, ids, context=context):
value = record
- for field in self.arg:
- if isinstance(value, list):
- value = value[0]
- value = value[field] or False
- if not value:
- break
- res[record.id] = value
+ # traverse all fields except the last one
+ for field in self.arg[:-1]:
+ value = value[field][:1]
+ # read the last field on the target record
+ res[record.id] = value[self.arg[-1]]
if self._type == 'many2one':
- # res[id] is a browse_record or False; convert it to (id, name) or False.
+ # res[id] is a recordset; convert it to (id, name) or False.
# Perform name_get as root, as seeing the name of a related object depends on
# access right of source document, not target, so user may not have access.
value_ids = list(set(value.id for value in res.itervalues() if value))
value_name = dict(obj.pool[self._obj].name_get(cr, SUPERUSER_ID, value_ids, context=context))
- res = dict((id, value and (value.id, value_name[value.id])) for id, value in res.iteritems())
+ res = dict((id, bool(value) and (value.id, value_name[value.id])) for id, value in res.iteritems())
elif self._type in ('one2many', 'many2many'):
- # res[id] is a list of browse_record or False; convert it to a list of ids
- res = dict((id, value and map(int, value) or []) for id, value in res.iteritems())
+ # res[id] is a recordset; convert it to a list of ids
+ res = dict((id, value.ids) for id, value in res.iteritems())
return res
def __init__(self, *arg, **args):
self.arg = arg
self._relations = []
- super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
+ super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
# ---------------------------------------------------------
# Serialized fields
# TODO: review completly this class for speed improvement
class property(function):
- def _get_default(self, obj, cr, uid, prop_name, context=None):
- return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
+ def to_field_args(self):
+ args = super(property, self).to_field_args()
+ args['company_dependent'] = True
+ return args
- def _get_defaults(self, obj, cr, uid, prop_names, context=None):
- """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
-
- :param list of string prop_names: list of name of property fields for those we want the default value
- :return: map of property field names to their default value
- :rtype: dict
- """
- prop = obj.pool.get('ir.property')
- res = {}
- for prop_name in prop_names:
- res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
- return res
-
- def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
- prop = obj.pool.get('ir.property')
- vids = [obj._name + ',' + str(oid) for oid in ids]
-
- domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
- #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
- if vids:
- domain = [('res_id', 'in', vids)] + domain
- return prop.search(cr, uid, domain, context=context)
-
- # TODO: to rewrite more clean
- def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
- if context is None:
- context = {}
+ def _fnct_search(self, tobj, cr, uid, obj, name, domain, context=None):
+ ir_property = obj.pool['ir.property']
+ result = []
+ for field, operator, value in domain:
+ result += ir_property.search_multi(cr, uid, name, tobj._name, operator, value, context=context)
+ return result
- nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
- if nids:
- cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
-
- default_val = self._get_default(obj, cr, uid, prop_name, context)
-
- property_create = False
- if isinstance(default_val, openerp.osv.orm.browse_record):
- if default_val.id != id_val:
- property_create = True
- elif default_val != id_val:
- property_create = True
-
- if property_create:
- def_id = self._field_get(cr, uid, obj._name, prop_name)
- company = obj.pool.get('res.company')
- cid = company._company_default_get(cr, uid, obj._name, def_id,
- context=context)
- propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
- context=context)
- prop = obj.pool.get('ir.property')
- return prop.create(cr, uid, {
- 'name': propdef.name,
- 'value': id_val,
- 'res_id': obj._name+','+str(id),
- 'company_id': cid,
- 'fields_id': def_id,
- 'type': self._type,
- }, context=context)
- return False
+ def _fnct_write(self, obj, cr, uid, id, prop_name, value, obj_dest, context=None):
+ ir_property = obj.pool['ir.property']
+ ir_property.set_multi(cr, uid, prop_name, obj._name, {id: value}, context=context)
+ return True
def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
- prop = obj.pool.get('ir.property')
- # get the default values (for res_id = False) for the property fields
- default_val = self._get_defaults(obj, cr, uid, prop_names, context)
-
- # build the dictionary that will be returned
- res = {}
- for id in ids:
- res[id] = default_val.copy()
+ ir_property = obj.pool['ir.property']
+ res = {id: {} for id in ids}
for prop_name in prop_names:
- property_field = obj._all_columns.get(prop_name).column
- property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
- # If the property field is a m2o field, we will append the id of the value to name_get_ids
- # in order to make a name_get in batch for all the ids needed.
- name_get_ids = {}
- for id in ids:
- # get the result of ir.property.get() for this res_id and save it in res if it's existing
- obj_reference = obj._name + ',' + str(id)
- value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
- if value:
+ column = obj._all_columns[prop_name].column
+ values = ir_property.get_multi(cr, uid, prop_name, obj._name, ids, context=context)
+ if column._type == 'many2one':
+ # name_get the non-null values as SUPERUSER_ID
+ vals = sum(set(filter(None, values.itervalues())),
+ obj.pool[column._obj].browse(cr, uid, [], context=context))
+ vals_name = dict(vals.sudo().name_get()) if vals else {}
+ for id, value in values.iteritems():
+ ng = False
+ if value and value.id in vals_name:
+ ng = value.id, vals_name[value.id]
+ res[id][prop_name] = ng
+ else:
+ for id, value in values.iteritems():
res[id][prop_name] = value
- # Check existence as root (as seeing the name of a related
- # object depends on access right of source document,
- # not target, so user may not have access) in order to avoid
- # pointing on an unexisting record.
- if property_destination_obj:
- if res[id][prop_name] and obj.pool[property_destination_obj].exists(cr, SUPERUSER_ID, res[id][prop_name].id):
- name_get_ids[id] = res[id][prop_name].id
- else:
- res[id][prop_name] = False
- if property_destination_obj:
- # name_get as root (as seeing the name of a related
- # object depends on access right of source document,
- # not target, so user may not have access.)
- name_get_values = dict(obj.pool[property_destination_obj].name_get(cr, SUPERUSER_ID, name_get_ids.values(), context=context))
- # the property field is a m2o, we need to return a tuple with (id, name)
- for k, v in name_get_ids.iteritems():
- if res[k][prop_name]:
- res[k][prop_name] = (v , name_get_values.get(v))
- return res
-
- def _field_get(self, cr, uid, model_name, prop):
- if not self.field_id.get(cr.dbname):
- cr.execute('SELECT id \
- FROM ir_model_fields \
- WHERE name=%s AND model=%s', (prop, model_name))
- res = cr.fetchone()
- self.field_id[cr.dbname] = res and res[0]
- return self.field_id[cr.dbname]
-
- def __init__(self, obj_prop, **args):
- # TODO remove obj_prop parameter (use many2one type)
- self.field_id = {}
- function.__init__(self, self._fnct_read, False, self._fnct_write,
- obj_prop, multi='properties', **args)
-
- def restart(self):
- self.field_id = {}
-
-
-def field_to_dict(model, cr, user, field, context=None):
- """ Return a dictionary representation of a field.
- The string, help, and selection attributes (if any) are untranslated. This
- representation is the one returned by fields_get() (fields_get() will do
- the translation).
-
- """
-
- res = {'type': field._type}
- # some attributes for m2m/function field are added as debug info only
- if isinstance(field, function):
- res['function'] = field._fnct and field._fnct.func_name or False
- res['store'] = field.store
- if isinstance(field.store, dict):
- res['store'] = str(field.store)
- res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
- res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
- res['fnct_inv_arg'] = field._fnct_inv_arg or False
- if isinstance(field, many2many):
- (table, col1, col2) = field._sql_names(model)
- res['m2m_join_columns'] = [col1, col2]
- res['m2m_join_table'] = table
- for arg in ('string', 'readonly', 'states', 'size', 'group_operator', 'required',
- 'change_default', 'translate', 'help', 'select', 'selectable', 'groups',
- 'deprecated', 'digits', 'invisible', 'filters'):
- if getattr(field, arg, None):
- res[arg] = getattr(field, arg)
-
- if hasattr(field, 'selection'):
- if isinstance(field.selection, (tuple, list)):
- res['selection'] = field.selection
- else:
- # call the 'dynamic selection' function
- res['selection'] = field.selection(model, cr, user, context)
- if res['type'] in ('one2many', 'many2many', 'many2one'):
- res['relation'] = field._obj
- res['domain'] = field._domain(model) if callable(field._domain) else field._domain
- res['context'] = field._context
-
- if isinstance(field, one2many):
- res['relation_field'] = field._fields_id
+ return res
- return res
+ def __init__(self, **args):
+ if 'view_load' in args:
+ _logger.warning("view_load attribute is deprecated on ir.fields. Args: %r", args)
+ args = dict(args)
+ args['obj'] = args.pop('relation', '') or args.get('obj', '')
+ super(property, self).__init__(
+ fnct=self._fnct_read,
+ fnct_inv=self._fnct_write,
+ fnct_search=self._fnct_search,
+ multi='properties',
+ **args
+ )
class column_info(object):
self.__class__.__name__, self.name, self.column,
self.parent_model, self.parent_column, self.original_parent)
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: