Fields Attributes:
* _classic_read: is a classic sql fields
* _type : field type
+ * _auto_join: for one2many and many2one fields, tells whether select
+ queries will join the relational table instead of replacing the
+ field condition by an equivalent-one based on a search.
* readonly
* required
* size
import openerp.tools as tools
from openerp.tools.translate import _
from openerp.tools import float_round, float_repr
+from openerp.tools import html_sanitize
import simplejson
-from openerp.tools.html_sanitize import html_sanitize
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
"""
_classic_read = True
_classic_write = True
+ _auto_join = False
_prefetch = True
_properties = False
_type = 'unknown'
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:
- if args[a]:
- setattr(self, a, args[a])
+ setattr(self, a, args[a])
def restart(self):
pass
_logger.debug(
"required=True is deprecated: making a boolean field"
" `required` has no effect, as NULL values are "
- "automatically turned into False.")
+ "automatically turned into False. args: %r",args)
class integer(_column):
_type = 'integer'
return model.name_get(cr, uid, [int(res_id)], context=context)[0][1]
return tools.ustr(value)
+# takes a string (encoded in utf8) and returns a string (encoded in utf8)
+def _symbol_set_char(self, symb):
+
+ #TODO:
+ # * we need to remove the "symb==False" from the next line BUT
+ # for now too many things rely on this broken behavior
+ # * the symb==None test should be common to all data types
+ if symb is None or symb == False:
+ return None
+
+ # we need to convert the string to a unicode object to be able
+ # to evaluate its length (and possibly truncate it) reliably
+ u_symb = tools.ustr(symb)
+ return u_symb[:self.size].encode('utf8')
+
class char(_column):
_type = 'char'
def __init__(self, string="unknown", size=None, **args):
_column.__init__(self, string=string, size=size or None, **args)
- self._symbol_set = (self._symbol_c, self._symbol_set_char)
-
- # takes a string (encoded in utf8) and returns a string (encoded in utf8)
- def _symbol_set_char(self, symb):
- #TODO:
- # * we need to remove the "symb==False" from the next line BUT
- # for now too many things rely on this broken behavior
- # * the symb==None test should be common to all data types
- if symb is None or symb == False:
- return None
-
- # we need to convert the string to a unicode object to be able
- # to evaluate its length (and possibly truncate it) reliably
- u_symb = tools.ustr(symb)
-
- return u_symb[:self.size].encode('utf8')
+ # self._symbol_set_char defined to keep the backward compatibility
+ 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 = 'html'
_symbol_c = '%s'
def _symbol_f(x):
+ if x is None or x == False:
+ return None
return html_sanitize(x)
_symbol_set = (_symbol_c, _symbol_f)
This method may be passed as value to initialize _defaults.
:param Model model: model (osv) for which the date value is being
- computed - technical field, currently ignored,
- automatically passed when used in _defaults.
+ computed - automatically passed when used in
+ _defaults.
:param datetime timestamp: optional datetime value to use instead of
the current date and time (must be a
datetime, regular dates can't be converted
today = timestamp or DT.datetime.now()
context_today = None
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:
try:
utc = pytz.timezone('UTC')
- context_tz = pytz.timezone(context['tz'])
+ context_tz = pytz.timezone(tz_name)
utc_today = utc.localize(today, is_dst=False) # UTC = no DST
context_today = utc_today.astimezone(context_tz)
except Exception:
"""
assert isinstance(timestamp, DT.datetime), 'Datetime instance expected'
if context and context.get('tz'):
+ 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']
+ if tz_name:
try:
utc = pytz.timezone('UTC')
- context_tz = pytz.timezone(context['tz'])
+ 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:
_symbol_f = lambda x: x or None
_symbol_set = (_symbol_c, _symbol_f)
- def __init__(self, obj, string='unknown', **args):
+ 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:
_prefetch = False
_type = 'one2many'
- def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
+ def __init__(self, obj, fields_id, string='unknown', limit=None, auto_join=False, **args):
_column.__init__(self, string=string, **args)
self._obj = obj
self._fields_id = fields_id
self._limit = limit
+ self._auto_join = auto_join
#one2many can't be used as condition for defaults
assert(self.change_default != True)
col1 = '%s_id' % source_model._table
if not col2:
col2 = '%s_id' % dest_model._table
- return (tbl, col1, col2)
+ return tbl, col1, col2
def _get_query_and_where_params(self, cr, model, ids, values, where_params):
""" Extracted from ``get`` to facilitate fine-tuning of the generated
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 = ''
+ order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
limit_str = ''
if self._limit is not None:
self._classic_write = True
if type=='binary':
self._symbol_get=lambda x:x and str(x)
+ else:
+ self._prefetch = True
if type == 'float':
self._symbol_c = float._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)
+
def digits_change(self, cr):
if self._type == 'float':
if self.digits_compute:
"""
def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
- self._field_get2(cr, uid, obj, context)
- i = len(self._arg)-1
- sarg = name
- while i>0:
- if type(sarg) in [type([]), type( (1,) )]:
- where = [(self._arg[i], 'in', sarg)]
- else:
- where = [(self._arg[i], '=', sarg)]
- if domain:
- where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
- domain = []
- sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
- i -= 1
- return [(self._arg[0], 'in', sarg)]
+ # assume self._arg = ('foo', 'bar', 'baz')
+ # domain = [(name, op, val)] => search [('foo.bar.baz', op, val)]
+ 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):
- 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 = 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)
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+ for record 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})
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
- if self._type in ('one2many', 'many2many'):
- res = dict([(i, []) for i in ids])
- else:
- res = {}.fromkeys(ids, False)
-
- objlst = obj.browse(cr, SUPERUSER_ID, ids, context=context)
- for data in objlst:
- if not data:
- continue
- t_data = data
- relation = obj._name
- for i in range(len(self.arg)):
- field_detail = self._relations[i]
- relation = field_detail['object']
- try:
- if not t_data[self.arg[i]]:
- t_data = False
- break
- except:
- t_data = False
+ 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
- if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
- t_data = t_data[self.arg[i]][0]
- elif t_data:
- t_data = t_data[self.arg[i]]
- if type(t_data) == type(objlst[0]):
- res[data.id] = t_data.id
- elif t_data:
- res[data.id] = t_data
- if self._type=='many2one':
- ids = filter(None, res.values())
- if ids:
- # 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.
- ng = dict(obj.pool.get(self._obj).name_get(cr, SUPERUSER_ID, ids, context=context))
- for r in res:
- if res[r]:
- res[r] = (res[r], ng[res[r]])
+ res[record.id] = value
+
+ if self._type == 'many2one':
+ # res[id] is a browse_record or False; 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.get(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())
+
elif self._type in ('one2many', 'many2many'):
- for r in res:
- if res[r]:
- res[r] = [x.id for x in res[r]]
+ # 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())
+
return res
def __init__(self, *arg, **args):
# TODO: improve here to change self.store = {...} according to related objects
pass
- def _field_get2(self, cr, uid, obj, context=None):
- if self._relations:
- return
- result = []
- obj_name = obj._name
- for i in range(len(self._arg)):
- f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
- result.append({
- 'object': obj_name,
- 'type': f['type']
-
- })
- if f.get('relation',False):
- obj_name = f['relation']
- result[-1]['relation'] = f['relation']
- self._relations = result
class sparse(function):
def __init__(self, serialization_field, **kwargs):
self.serialization_field = serialization_field
- return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
+ super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
class column_info(object):
- """Struct containing details about an osv column, either one local to
- its model, or one inherited via _inherits.
-
- :attr name: name of the column
- :attr column: column instance, subclass of osv.fields._column
- :attr parent_model: if the column is inherited, name of the model
- that contains it, None for local columns.
- :attr parent_column: the name of the column containing the m2o
- relationship to the parent model that contains
- this column, None for local columns.
- :attr original_parent: if the column is inherited, name of the original
- parent model that contains it i.e in case of multilevel
- inheritence, None for local columns.
+ """ Struct containing details about an osv column, either one local to
+ its model, or one inherited via _inherits.
+
+ .. attribute:: name
+
+ name of the column
+
+ .. attribute:: column
+
+ column instance, subclass of :class:`_column`
+
+ .. attribute:: parent_model
+
+ if the column is inherited, name of the model that contains it,
+ ``None`` for local columns.
+
+ .. attribute:: parent_column
+
+ the name of the column containing the m2o relationship to the
+ parent model that contains this column, ``None`` for local columns.
+
+ .. attribute:: original_parent
+
+ if the column is inherited, name of the original parent model that
+ contains it i.e in case of multilevel inheritance, ``None`` for
+ local columns.
"""
def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
self.name = name
self.parent_column = parent_column
self.original_parent = original_parent
+ def __str__(self):
+ return '%s(%s, %s, %s, %s, %s)' % (
+ 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: