diff options
| author | Numerigraphe - Lionel Sausin <ls@numerigraphe.fr> | 2010-11-12 15:10:41 +0100 |
|---|---|---|
| committer | Numerigraphe - Lionel Sausin <ls@numerigraphe.fr> | 2010-11-12 15:10:41 +0100 |
| commit | 182cf2b196da0d1f55ddfdcff62058b423e1bbc0 (patch) | |
| tree | 779b50e2e54b1624ba669b4a3f56bb6407c08d49 /bin/osv/fields.py | |
| parent | e013734045bbaae906748a8026455bac6786568e (diff) | |
| parent | cc0d270bab0d0b7862430c3e527f7edc02d1d587 (diff) | |
[MERGE] sync with the trunk
bzr revid: ls@numerigraphe.fr-20101112141041-19kpvr7pzxqankqo
Diffstat (limited to 'bin/osv/fields.py')
| -rw-r--r-- | bin/osv/fields.py | 271 |
1 files changed, 166 insertions, 105 deletions
diff --git a/bin/osv/fields.py b/bin/osv/fields.py index c6186b9a052..a5839381f27 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -31,13 +31,13 @@ # required # size # -from collections import defaultdict +import datetime as DT import string import netsvc import sys +import warnings from psycopg2 import Binary -import warnings import tools from tools.translate import _ @@ -193,14 +193,42 @@ class float(_column): class date(_column): _type = 'date' + @staticmethod + def today(*args): + """ Returns the current date in a format fit for being a + default value to a ``date`` field. + This method should be provided as is to the _defaults dict, it + should not be called. + """ + return DT.date.today().strftime( + tools.DEFAULT_SERVER_DATE_FORMAT) class datetime(_column): _type = 'datetime' + @staticmethod + def now(*args): + """ Returns the current datetime in a format fit for being a + default value to a ``datetime`` field. + This method should be provided as is to the _defaults dict, it + should not be called. + """ + return DT.datetime.now().strftime( + tools.DEFAULT_SERVER_DATETIME_FORMAT) class time(_column): _type = 'time' + @staticmethod + def now( *args): + """ Returns the current time in a format fit for being a + default value to a ``time`` field. + + This method should be proivided as is to the _defaults dict, + it should not be called. + """ + return DT.datetime.now().strftime( + tools.DEFAULT_SERVER_TIME_FORMAT) class binary(_column): _type = 'binary' @@ -228,7 +256,13 @@ class binary(_column): if v['id'] == i: val = v[name] break - if context.get('bin_size', False) and val: + + # If client is requesting only the size of the field, we return it instead + # of the content. Presumably a separate request will be done to read the actual + # content if it's needed at some point. + # TODO: after 6.0 we should consider returning a dict with size and content instead of + # having an implicit convention for the value + if val and context.get('bin_size_%s' % name, context.get('bin_size')): res[i] = tools.human_size(long(val)) else: res[i] = val @@ -303,14 +337,13 @@ class many2one(_column): def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None): result = {} for id in ids: - result[id] = obj.datas[id][name] + result[id] = obj.datas[id].get(name, False) return result def get(self, cr, obj, ids, name, user=None, context=None, values=None): - if not context: - context = {} - if not values: - values = {} + context = context or {} + values = values or {} + res = {} for r in values: res[r['id']] = r[name] @@ -319,21 +352,14 @@ class many2one(_column): obj = obj.pool.get(self._obj) # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource} - from orm import except_orm - names = {} - for record in list(set(filter(None, res.values()))): - try: - record_name = dict(obj.name_get(cr, user, [record], context)) - except except_orm: - record_name = {} - record_name[record] = '// Access Denied //' - names.update(record_name) - - for r in res.keys(): - if res[r] and res[r] in names: - res[r] = (res[r], names[res[r]]) + # 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, 1, list(set(filter(None, res.values()))), context=context)) + for id in res: + if res[id] in records: + res[id] = (res[id], records[res[id]]) else: - res[r] = False + res[id] = False return res def set(self, cr, obj_src, id, field, values, user=None, context=None): @@ -436,11 +462,14 @@ class one2many(_column): if not values: values = {} - res = defaultdict(list) + res = {} + for id in ids: + res[id] = [] ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context) for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'): - res[r[self._fields_id]].append(r['id']) + if r[self._fields_id] in res: + res[r[self._fields_id]].append(r['id']) return res def set(self, cr, obj, id, field, values, user=None, context=None): @@ -517,31 +546,50 @@ class many2many(_column): return res for id in ids: res[id] = [] - limit_str = self._limit is not None and ' limit %d' % self._limit or '' + if offset: + warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.", + DeprecationWarning, stacklevel=2) obj = obj.pool.get(self._obj) - d1, d2, tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context) - if d1: - d1 = ' and ' + ' and '.join(d1) - else: d1 = '' + # static domains are lists, and are evaluated both here and on client-side, while string + # domains supposed by dynamic and evaluated on client-side only (thus ignored here) + # FIXME: make this distinction explicit in API! + domain = isinstance(self._domain, list) and self._domain or [] + + wquery = obj._where_calc(cr, user, domain, context=context) + obj._apply_ir_rules(cr, user, wquery, 'read', context=context) + from_c, where_c, where_params = wquery.get_sql() + if where_c: + where_c = ' AND ' + where_c + + if offset or self._limit: + order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0]) + else: + order_by = '' + + limit_str = '' + if self._limit is not None: + limit_str = ' LIMIT %d' % self._limit + query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \ - FROM %(rel)s, %(tbl)s \ - WHERE %(rel)s.%(id1)s in %%s \ + FROM %(rel)s, %(from_c)s \ + WHERE %(rel)s.%(id1)s IN %%s \ AND %(rel)s.%(id2)s = %(tbl)s.id \ - %(d1)s \ + %(where_c)s \ + %(order_by)s \ %(limit)s \ - ORDER BY %(tbl)s.%(order)s \ OFFSET %(offset)d' \ % {'rel': self._rel, + 'from_c': from_c, 'tbl': obj._table, 'id1': self._id1, 'id2': self._id2, - 'd1': d1, + 'where_c': where_c, 'limit': limit_str, - 'order': obj._order, + 'order_by': order_by, 'offset': offset, } - cr.execute(query, [tuple(ids)] + d2) + cr.execute(query, [tuple(ids),] + where_params) for r in cr.fetchall(): res[r[1]].append(r[0]) return res @@ -661,7 +709,9 @@ class function(_column): self.selectable = False if store: - self._classic_read = True + if self._type != 'many2one': + # m2o fields need to return tuples with name_get, not just foreign keys + self._classic_read = True self._classic_write = True if type=='binary': self._symbol_get=lambda x:x and str(x) @@ -754,45 +804,42 @@ class related(function): i -= 1 return [(self._arg[0], 'in', sarg)] - def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None): - if values and field_name: - self._field_get2(cr, uid, obj, context) - relation = obj._name - res = {} - if type(ids) != type([]): - ids=[ids] - objlst = obj.browse(cr, uid, ids) - for data in objlst: - t_id = None - t_data = data - relation = obj._name - for i in range(len(self.arg)): - field_detail = self._relations[i] - relation = field_detail['object'] - if not t_data[self.arg[i]]: - if self._type not in ('one2many', 'many2many'): - t_id = t_data['id'] - t_data = False - break - if field_detail['type'] in ('one2many', 'many2many'): - if self._type != "many2one": - t_id = t_data.id - t_data = t_data[self.arg[i]][0] - else: - t_data = False - break - else: + def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None): + self._field_get2(cr, uid, obj, context=context) + if type(ids) != type([]): + ids=[ids] + objlst = obj.browse(cr, uid, ids) + for data in objlst: + t_id = data.id + t_data = data + for i in range(len(self.arg)): + if not t_data: break + field_detail = self._relations[i] + if not t_data[self.arg[i]]: + if self._type not in ('one2many', 'many2many'): t_id = t_data['id'] - t_data = t_data[self.arg[i]] - - if t_id and t_data: - obj.pool.get(field_detail['object']).write(cr,uid,[t_id],{args[-1]:values}, context=context) + t_data = False + elif field_detail['type'] in ('one2many', 'many2many'): + if self._type != "many2one": + t_id = t_data.id + t_data = t_data[self.arg[i]][0] + else: + t_data = False + else: + t_id = t_data['id'] + t_data = t_data[self.arg[i]] + else: + model = obj.pool.get(self._relations[-1]['object']) + model.write(cr, uid, [t_id], {args[-1]: values}, context=context) def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): self._field_get2(cr, uid, obj, context) if not ids: return {} relation = obj._name - res = {}.fromkeys(ids, False) + if self._type in ('one2many', 'many2many'): + res = dict([(i, []) for i in ids]) + else: + res = {}.fromkeys(ids, False) objlst = obj.browse(cr, 1, ids, context=context) for data in objlst: @@ -812,11 +859,11 @@ class related(function): break if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1: t_data = t_data[self.arg[i]][0] - else: + elif t_data: t_data = t_data[self.arg[i]] if type(t_data) == type(objlst[0]): res[data.id] = t_data.id - else: + elif t_data: res[data.id] = t_data if self._type=='many2one': ids = filter(None, res.values()) @@ -886,38 +933,44 @@ class serialized(_column): super(serialized, self).__init__(string=string, **args) +# TODO: review completly this class for speed improvement class property(function): def _get_default(self, obj, cr, uid, prop_name, context=None): - from orm import browse_record - prop = obj.pool.get('ir.property') - domain = prop._get_domain_default(cr, uid, prop_name, obj._name, context) - ids = prop.search(cr, uid, domain, order='company_id', context=context) - if not ids: - return False + return self._get_defaults(obj, cr, uid, [prop_name], context=None)[0][prop_name] - default_value = prop.get_by_id(cr, uid, ids, context=context) - if isinstance(default_value, browse_record): - return default_value.id - return default_value or False + def _get_defaults(self, obj, cr, uid, prop_name, context=None): + prop = obj.pool.get('ir.property') + domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name), ('res_id','=',False)] + ids = prop.search(cr, uid, domain, context=context) + replaces = {} + default_value = {}.fromkeys(prop_name, False) + for prop_rec in prop.browse(cr, uid, ids, context=context): + if default_value.get(prop_rec.fields_id.name, False): + continue + value = prop.get_by_record(cr, uid, prop_rec, context=context) or False + default_value[prop_rec.fields_id.name] = value + if value and (prop_rec.type == 'many2one'): + replaces.setdefault(value._name, {}) + replaces[value._name][value.id] = True + return default_value, replaces 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 = prop._get_domain(cr, uid, prop_name, obj._name, context) - if domain is not None: + 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) - else: - return [] - + 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 = {} - nids = self._get_by_id(obj, cr, uid, prop_name, [id], context) + 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),)) @@ -938,29 +991,37 @@ class property(function): 'company_id': cid, 'fields_id': def_id, 'type': self._type, - }, context=context) + }, context=context) return False def _fnct_read(self, obj, cr, uid, ids, prop_name, obj_dest, context=None): - from orm import browse_record properties = obj.pool.get('ir.property') - - default_val = self._get_default(obj, cr, uid, prop_name, context) - - nids = self._get_by_id(obj, cr, uid, prop_name, ids, context) + domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)] + domain += [('res_id','in', [obj._name + ',' + str(oid) for oid in ids])] + nids = properties.search(cr, uid, domain, context=context) + default_val,replaces = self._get_defaults(obj, cr, uid, prop_name, context) res = {} for id in ids: - res[id] = default_val - for prop in properties.browse(cr, uid, nids): - value = prop.get_by_id(context=context) - if isinstance(value, browse_record): - if not value.exists(): - cr.execute('DELETE FROM ir_property WHERE id=%s', (prop.id,)) - continue - value = value.id - res[prop.res_id.id] = value or False + res[id] = default_val.copy() + + brs = properties.browse(cr, uid, nids, context=context) + for prop in brs: + value = properties.get_by_record(cr, uid, prop, context=context) + res[prop.res_id.id][prop.fields_id.name] = value or False + if value and (prop.type == 'many2one'): + replaces.setdefault(value._name, {}) + replaces[value._name][value.id] = True + + for rep in replaces: + replaces[rep] = dict(obj.pool.get(rep).name_get(cr, uid, replaces[rep].keys(), context=context)) + + for prop in prop_name: + for id in ids: + if res[id][prop] and hasattr(res[id][prop], '_name'): + res[id][prop] = (res[id][prop].id , replaces[res[id][prop]._name].get(res[id][prop].id, False)) + return res @@ -977,7 +1038,7 @@ class property(function): # TODO remove obj_prop parameter (use many2one type) self.field_id = {} function.__init__(self, self._fnct_read, False, self._fnct_write, - obj_prop, **args) + obj_prop, multi='properties', **args) def restart(self): self.field_id = {} |
