Files
tryton/trytond/model/model.py
T

443 lines
17 KiB
Python
Raw Normal View History

2014-12-02 12:54:01 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2009-01-29 19:22:03 +01:00
2010-08-19 22:01:12 +02:00
import copy
import collections
2014-06-06 13:26:44 +02:00
from functools import total_ordering
2012-09-24 22:16:53 +02:00
2009-02-03 18:58:25 +01:00
from trytond.model import fields
2011-03-08 18:44:35 +01:00
from trytond.error import WarningErrorMixin
from trytond.pool import Pool, PoolBase
2010-02-18 18:50:35 +01:00
from trytond.pyson import PYSONEncoder
2010-08-19 22:01:12 +02:00
from trytond.transaction import Transaction
2011-07-20 19:04:52 +02:00
from trytond.url import URLMixin
2012-09-24 22:16:53 +02:00
from trytond.rpc import RPC
__all__ = ['Model']
2014-06-06 13:26:44 +02:00
@total_ordering
class Model(WarningErrorMixin, URLMixin, PoolBase):
2009-01-29 19:22:03 +01:00
"""
Define a model in Tryton.
"""
2010-02-18 18:50:35 +01:00
_rec_name = 'name'
id = fields.Integer('ID', readonly=True)
2009-01-29 19:22:03 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def __setup__(cls):
super(Model, cls).__setup__()
2012-09-24 22:16:53 +02:00
cls.__rpc__ = {
'default_get': RPC(),
'fields_get': RPC(),
2012-09-29 14:39:59 +02:00
'pre_validate': RPC(instantiate=0),
2012-09-24 22:16:53 +02:00
}
cls._error_messages = {}
2009-01-29 19:22:03 +01:00
2014-01-26 14:27:24 +01:00
# Copy fields and update depends
2012-09-24 22:16:53 +02:00
for attr in dir(cls):
2010-01-16 14:24:26 +01:00
if attr.startswith('_'):
2009-01-29 19:22:03 +01:00
continue
2014-01-26 14:27:24 +01:00
if not isinstance(getattr(cls, attr), fields.Field):
continue
field_name = attr
2014-05-06 22:42:42 +02:00
field = getattr(cls, field_name)
# Copy the original field definition to prevent side-effect with
# the mutable attributes
for parent_cls in cls.__mro__:
parent_field = getattr(parent_cls, field_name, None)
if isinstance(parent_field, fields.Field):
field = parent_field
field = copy.deepcopy(field)
2014-01-26 14:27:24 +01:00
setattr(cls, field_name, field)
2012-09-24 22:16:53 +02:00
@classmethod
def __post_setup__(cls):
super(Model, cls).__post_setup__()
2009-01-29 19:22:03 +01:00
2012-09-24 22:16:53 +02:00
# Set _fields
cls._fields = {}
for attr in dir(cls):
if attr.startswith('_'):
continue
if isinstance(getattr(cls, attr), fields.Field):
cls._fields[attr] = getattr(cls, attr)
2009-01-29 19:22:03 +01:00
2012-09-24 22:16:53 +02:00
# Set _defaults
cls._defaults = {}
fields_names = cls._fields.keys()
2009-01-29 19:22:03 +01:00
for field_name in fields_names:
2012-09-24 22:16:53 +02:00
default_method = getattr(cls, 'default_%s' % field_name, False)
if isinstance(default_method, collections.Callable):
2012-09-24 22:16:53 +02:00
cls._defaults[field_name] = default_method
2009-01-29 19:22:03 +01:00
2012-09-24 22:16:53 +02:00
for k in cls._defaults:
2013-03-20 22:49:16 +01:00
assert k in cls._fields, \
2012-09-24 22:16:53 +02:00
'Default function defined in %s but field %s does not exist!' \
% (cls.__name__, k,)
2009-01-29 19:22:03 +01:00
2012-09-24 22:16:53 +02:00
# Set name to fields
for name, field in cls._fields.iteritems():
if field.name is None:
field.name = name
else:
assert field.name == name, (
'Duplicate fields on %s: %s, %s'
% (cls, field.name, name))
@classmethod
def _get_name(cls):
'''
Returns the first non-empty line of the model docstring.
'''
assert cls.__doc__, '%s has no docstring' % cls
2012-10-19 12:29:05 +02:00
lines = cls.__doc__.splitlines()
for line in lines:
line = line.strip()
if line:
return line
2012-09-24 22:16:53 +02:00
@classmethod
def __register__(cls, module_name):
2009-01-29 19:22:03 +01:00
"""
2009-09-13 15:37:11 +02:00
Add model in ir.model and ir.model.field.
2009-01-29 19:22:03 +01:00
"""
super(Model, cls).__register__(module_name)
2012-09-24 22:16:53 +02:00
pool = Pool()
Translation = pool.get('ir.translation')
2013-09-06 22:32:37 +02:00
Model_ = pool.get('ir.model')
ModelField = pool.get('ir.model.field')
2009-01-29 19:22:03 +01:00
2013-09-06 22:32:37 +02:00
model_id = Model_.register(cls, module_name)
ModelField.register(cls, module_name, model_id)
2009-01-29 19:22:03 +01:00
2013-09-06 22:32:37 +02:00
Translation.register_model(cls, module_name)
Translation.register_fields(cls, module_name)
Translation.register_error_messages(cls, module_name)
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
2014-06-08 00:33:31 +02:00
def default_get(cls, fields_names, with_rec_name=True):
2009-01-29 19:22:03 +01:00
'''
2009-09-13 15:37:11 +02:00
Return a dict with the default values for each field in fields_names.
2012-09-24 22:16:53 +02:00
If with_rec_name is True, rec_name will be added.
2009-01-29 19:22:03 +01:00
'''
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
Property = pool.get('ir.property')
2009-01-29 19:22:03 +01:00
value = {}
# get the default values defined in the object
for field_name in fields_names:
2012-09-24 22:16:53 +02:00
if field_name in cls._defaults:
value[field_name] = cls._defaults[field_name]()
2013-03-20 22:49:16 +01:00
field = cls._fields[field_name]
if (field._type == 'boolean'
2014-12-02 12:54:01 +01:00
and field_name not in value):
value[field_name] = False
if isinstance(field, fields.Property):
2012-09-24 22:16:53 +02:00
value[field_name] = Property.get(field_name, cls.__name__)
if (with_rec_name
and field._type in ('many2one',)
and value.get(field_name)):
2012-09-24 22:16:53 +02:00
Target = pool.get(field.model_name)
if 'rec_name' in Target._fields:
value[field_name + '.rec_name'] = Target(
value[field_name]).rec_name
2009-01-29 19:22:03 +01:00
if not with_rec_name:
for field in value.keys():
if field.endswith('.rec_name'):
del value[field]
2009-01-29 19:22:03 +01:00
return value
2012-09-24 22:16:53 +02:00
@classmethod
def fields_get(cls, fields_names=None):
2009-01-29 19:22:03 +01:00
"""
2010-05-08 14:22:26 +02:00
Return the definition of each field on the model.
2009-01-29 19:22:03 +01:00
"""
res = {}
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
Translation = pool.get('ir.translation')
FieldAccess = pool.get('ir.model.field.access')
ModelAccess = pool.get('ir.model.access')
2011-01-22 23:56:13 +01:00
2014-12-02 12:54:01 +01:00
# Add translation to cache
2012-01-27 21:11:06 +01:00
language = Transaction().language
2009-01-29 19:22:03 +01:00
trans_args = []
2012-09-24 22:16:53 +02:00
for field in (x for x in cls._fields.keys()
2009-01-29 19:22:03 +01:00
if ((not fields_names) or x in fields_names)):
2012-09-24 22:16:53 +02:00
trans_args.append((cls.__name__ + ',' + field, 'field', language,
2010-08-19 22:01:12 +02:00
None))
2012-09-24 22:16:53 +02:00
trans_args.append((cls.__name__ + ',' + field, 'help', language,
2010-08-19 22:01:12 +02:00
None))
2012-09-24 22:16:53 +02:00
if hasattr(cls._fields[field], 'selection'):
2013-02-27 22:50:31 +01:00
if (isinstance(cls._fields[field].selection, (tuple, list))
2012-09-24 22:16:53 +02:00
and ((hasattr(cls._fields[field],
2013-02-27 22:50:31 +01:00
'translate_selection')
and cls._fields[field].translate_selection)
2012-09-24 22:16:53 +02:00
or not hasattr(cls._fields[field],
2013-02-27 22:50:31 +01:00
'translate_selection'))):
2012-09-24 22:16:53 +02:00
sel = cls._fields[field].selection
2009-01-29 19:22:03 +01:00
for (key, val) in sel:
2012-09-24 22:16:53 +02:00
trans_args.append((cls.__name__ + ',' + field,
2010-08-19 22:01:12 +02:00
'selection', language, val))
2012-09-24 22:16:53 +02:00
Translation.get_sources(trans_args)
2009-01-29 19:22:03 +01:00
encoder = PYSONEncoder()
2012-11-28 22:42:51 +01:00
accesses = FieldAccess.get_access([cls.__name__])[cls.__name__]
2012-09-24 22:16:53 +02:00
for field in (x for x in cls._fields.keys()
2009-01-29 19:22:03 +01:00
if ((not fields_names) or x in fields_names)):
res[field] = {
2012-09-24 22:16:53 +02:00
'type': cls._fields[field]._type,
'name': field,
}
2009-01-29 19:22:03 +01:00
for arg in (
'string',
'readonly',
'states',
'size',
'required',
'translate',
'help',
'select',
'on_change',
'add_remove',
'on_change_with',
2011-03-30 17:20:31 +02:00
'autocomplete',
2009-01-29 19:22:03 +01:00
'sort',
2009-02-23 13:27:21 +01:00
'datetime_field',
2011-01-07 10:58:43 +01:00
'loading',
2011-02-16 17:50:09 +01:00
'filename',
'selection_change_with',
'domain',
2015-02-09 22:58:56 +01:00
'converter',
2009-01-29 19:22:03 +01:00
):
2012-09-24 22:16:53 +02:00
if getattr(cls._fields[field], arg, None) is not None:
2014-01-26 14:27:24 +01:00
value = getattr(cls._fields[field], arg)
if isinstance(value, set):
value = list(value)
else:
value = copy.copy(value)
res[field][arg] = value
2013-01-21 12:46:24 +01:00
if not accesses.get(field, {}).get('write', True):
2009-01-29 19:22:03 +01:00
res[field]['readonly'] = True
if res[field].get('states') and \
'readonly' in res[field]['states']:
del res[field]['states']['readonly']
for arg in ('digits', 'invisible'):
2012-09-24 22:16:53 +02:00
if hasattr(cls._fields[field], arg) \
and getattr(cls._fields[field], arg):
res[field][arg] = copy.copy(getattr(cls._fields[field],
2009-01-29 19:22:03 +01:00
arg))
2013-09-06 22:32:37 +02:00
if (isinstance(cls._fields[field],
2015-11-15 12:45:41 -03:00
(fields.Function, fields.One2Many, fields.Many2Many))
2013-09-06 22:32:37 +02:00
and not getattr(cls, 'order_%s' % field, None)):
2009-01-29 19:22:03 +01:00
res[field]['sortable'] = False
2012-09-24 22:16:53 +02:00
if ((isinstance(cls._fields[field], fields.Function)
and not cls._fields[field].searcher)
or (cls._fields[field]._type in ('binary', 'sha'))
or (isinstance(cls._fields[field], fields.Property)
and isinstance(cls._fields[field]._field,
fields.Many2One))):
2011-10-03 09:45:21 +02:00
res[field]['searchable'] = False
else:
res[field]['searchable'] = True
2009-01-29 19:22:03 +01:00
2010-08-19 22:01:12 +02:00
if Transaction().context.get('language'):
2009-01-29 19:22:03 +01:00
# translate the field label
2012-09-24 22:16:53 +02:00
res_trans = Translation.get_source(
cls.__name__ + ',' + field, 'field',
Transaction().context['language'])
2009-01-29 19:22:03 +01:00
if res_trans:
res[field]['string'] = res_trans
2012-09-24 22:16:53 +02:00
help_trans = Translation.get_source(
cls.__name__ + ',' + field, 'help',
Transaction().context['language'])
2009-01-29 19:22:03 +01:00
if help_trans:
res[field]['help'] = help_trans
2012-09-24 22:16:53 +02:00
if hasattr(cls._fields[field], 'selection'):
if isinstance(cls._fields[field].selection, (tuple, list)):
sel = copy.copy(cls._fields[field].selection)
2013-02-27 22:50:31 +01:00
if (Transaction().context.get('language')
and ((hasattr(cls._fields[field],
'translate_selection')
and cls._fields[field].translate_selection)
2012-09-24 22:16:53 +02:00
or not hasattr(cls._fields[field],
2013-02-27 22:50:31 +01:00
'translate_selection'))):
2009-01-29 19:22:03 +01:00
# translate each selection option
sel2 = []
for (key, val) in sel:
2012-09-24 22:16:53 +02:00
val2 = Translation.get_source(
cls.__name__ + ',' + field, 'selection',
language, val)
2009-01-29 19:22:03 +01:00
sel2.append((key, val2 or val))
sel = sel2
res[field]['selection'] = sel
else:
# call the 'dynamic selection' function
res[field]['selection'] = copy.copy(
2012-09-24 22:16:53 +02:00
cls._fields[field].selection)
2009-01-29 19:22:03 +01:00
if res[field]['type'] in (
'one2many',
'many2many',
'many2one',
2010-12-03 18:01:46 +01:00
'one2one',
2009-01-29 19:22:03 +01:00
):
2012-09-24 22:16:53 +02:00
if hasattr(cls._fields[field], 'model_name'):
relation = copy.copy(cls._fields[field].model_name)
else:
2011-07-03 13:20:58 +02:00
relation = copy.copy(
2012-09-24 22:16:53 +02:00
cls._fields[field].get_target().__name__)
res[field]['relation'] = relation
2012-09-24 22:16:53 +02:00
res[field]['context'] = copy.copy(cls._fields[field].context)
2012-11-28 22:42:51 +01:00
res[field]['create'] = accesses.get(field, {}).get('create',
True)
res[field]['delete'] = accesses.get(field, {}).get('delete',
True)
2009-02-03 18:58:25 +01:00
if res[field]['type'] == 'one2many' \
and getattr(cls._fields[field], 'field', None):
2009-01-29 19:22:03 +01:00
res[field]['relation_field'] = copy.copy(
2012-09-24 22:16:53 +02:00
cls._fields[field].field)
2013-10-27 19:31:36 +01:00
if res[field]['type'] == 'many2one':
target = cls._fields[field].get_target()
relation_fields = []
2013-10-27 19:31:36 +01:00
for target_name, target_field in target._fields.iteritems():
if (target_field._type == 'one2many'
and target_field.model_name == cls.__name__
and target_field.field == field):
relation_fields.append(target_name)
# Set relation_field only if there is no ambiguity
if len(relation_fields) == 1:
res[field]['relation_field'], = relation_fields
2012-05-16 13:04:36 +02:00
if res[field]['type'] in ('datetime', 'time'):
2012-09-24 22:16:53 +02:00
res[field]['format'] = copy.copy(cls._fields[field].format)
if res[field]['type'] == 'selection':
res[field]['context'] = copy.copy(cls._fields[field].context)
2013-01-26 15:18:41 +01:00
if res[field]['type'] == 'dict':
res[field]['schema_model'] = cls._fields[field].schema_model
res[field]['domain'] = copy.copy(cls._fields[field].domain)
2013-01-26 15:18:41 +01:00
res[field]['context'] = copy.copy(cls._fields[field].context)
res[field]['create'] = accesses.get(field, {}).get('create',
True)
res[field]['delete'] = accesses.get(field, {}).get('delete',
True)
2009-01-29 19:22:03 +01:00
# convert attributes into pyson
for attr in ('states', 'domain', 'context', 'digits', 'size',
2012-05-16 13:04:36 +02:00
'add_remove', 'format'):
if attr in res[field]:
res[field][attr] = encoder.encode(res[field][attr])
for i in res.keys():
2009-01-29 19:22:03 +01:00
# filter out fields which aren't in the fields_names list
if fields_names:
2009-01-29 19:22:03 +01:00
if i not in fields_names:
del res[i]
elif not ModelAccess.check_relation(cls.__name__, i, mode='read'):
del res[i]
2009-01-29 19:22:03 +01:00
return res
2012-03-05 23:39:58 +01:00
2012-09-29 14:39:59 +02:00
def pre_validate(self):
pass
2012-09-24 22:16:53 +02:00
def __init__(self, id=None, **kwargs):
super(Model, self).__init__()
if id is not None:
id = int(id)
2016-03-04 00:19:18 +01:00
self._id = id
2016-03-15 19:01:47 +01:00
if kwargs:
self._values = {}
parent_values = {}
for name, value in kwargs.iteritems():
if not name.startswith('_parent_'):
setattr(self, name, value)
else:
parent_values[name] = value
for name, value in parent_values.iteritems():
parent_name, field = name.split('.', 1)
parent_name = parent_name[8:] # Strip '_parent_'
parent = getattr(self, parent_name, None)
if parent is not None:
setattr(parent, field, value)
else:
setattr(self, parent_name, {field: value})
self._init_values = self._values.copy()
else:
self._values = None
self._init_values = None
2012-09-24 22:16:53 +02:00
def __getattr__(self, name):
2016-03-04 00:19:18 +01:00
try:
return self._values[name]
except (KeyError, TypeError):
raise AttributeError("'%s' Model has no attribute '%s': %s"
% (self.__name__, name, self._values))
2012-09-24 22:16:53 +02:00
def __contains__(self, name):
2013-03-20 22:49:16 +01:00
return name in self._fields
2012-09-24 22:16:53 +02:00
def __int__(self):
return int(self.id)
def __str__(self):
return '%s,%s' % (self.__name__, self.id)
def __unicode__(self):
return u'%s,%s' % (self.__name__, self.id)
def __repr__(self):
if self.id < 0:
return "Pool().get('%s')(**%s)" % (self.__name__,
repr(self._default_values))
else:
return "Pool().get('%s')(%s)" % (self.__name__, self.id)
def __eq__(self, other):
if not isinstance(other, Model):
return NotImplemented
2014-10-29 20:38:17 +01:00
elif self.id is None or other.id is None:
2014-10-31 10:54:30 +01:00
return id(self) == id(other)
2012-09-24 22:16:53 +02:00
return (self.__name__, self.id) == (other.__name__, other.id)
2013-08-21 18:02:30 +02:00
def __lt__(self, other):
if not isinstance(other, Model) or self.__name__ != other.__name__:
return NotImplemented
return self.id < other.id
2012-09-24 22:16:53 +02:00
def __ne__(self, other):
2014-10-29 20:38:17 +01:00
return not self == other
2012-09-24 22:16:53 +02:00
def __hash__(self):
return hash((self.__name__, self.id))
def __nonzero__(self):
return True
@property
def _default_values(self):
2014-10-29 20:38:17 +01:00
"""Return the values not stored.
By default, the value of a field is its internal representation except:
- for Many2One and One2One field: the id
- for Reference field: the string model,id
- for Many2Many: the list of ids
- for One2Many: the list of `_default_values`
"""
2012-09-24 22:16:53 +02:00
values = {}
if self._values:
for fname, value in self._values.iteritems():
2013-03-20 22:49:16 +01:00
field = self._fields[fname]
2014-10-29 20:38:17 +01:00
if field._type in ('many2one', 'one2one', 'reference'):
if value:
if field._type == 'reference':
value = str(value)
else:
value = value.id
elif field._type in ('one2many', 'many2many'):
if field._type == 'one2many':
value = [r._default_values for r in value]
else:
value = [r.id for r in value]
2012-09-24 22:16:53 +02:00
values[fname] = value
return values