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
|
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
|
2018-07-27 09:32:28 +02:00
|
|
|
from trytond.pool import Pool, PoolBase, PoolMeta
|
2019-02-13 10:37:40 +01:00
|
|
|
from trytond.pyson import PYSONEncoder, PYSONDecoder
|
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']
|
|
|
|
|
|
|
|
|
|
|
2018-07-27 09:32:28 +02:00
|
|
|
class ModelMeta(PoolMeta):
|
|
|
|
|
@property
|
|
|
|
|
def __queue__(self):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
Queue = pool.get('ir.queue')
|
|
|
|
|
return Queue.caller(self)
|
|
|
|
|
|
|
|
|
|
|
2014-06-06 13:26:44 +02:00
|
|
|
@total_ordering
|
2018-12-29 14:19:08 +01:00
|
|
|
class Model(URLMixin, PoolBase, metaclass=ModelMeta):
|
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):
|
2013-05-27 18:47:00 +02:00
|
|
|
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
|
|
|
}
|
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):
|
2013-05-27 18:47:00 +02:00
|
|
|
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 = {}
|
2018-06-26 19:11:19 +02:00
|
|
|
fields_names = list(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)
|
2018-09-11 00:19:52 +02:00
|
|
|
if callable(default_method):
|
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
|
2018-06-26 19:11:19 +02:00
|
|
|
for name, field in cls._fields.items():
|
2012-09-24 22:16:53 +02:00
|
|
|
if field.name is None:
|
|
|
|
|
field.name = name
|
|
|
|
|
else:
|
|
|
|
|
assert field.name == name, (
|
|
|
|
|
'Duplicate fields on %s: %s, %s'
|
|
|
|
|
% (cls, field.name, name))
|
2010-03-19 15:17:50 +01:00
|
|
|
|
2012-10-16 18:26:37 +02:00
|
|
|
@classmethod
|
|
|
|
|
def _get_name(cls):
|
|
|
|
|
'''
|
|
|
|
|
Returns the first non-empty line of the model docstring.
|
|
|
|
|
'''
|
2015-02-04 23:16:11 +01:00
|
|
|
assert cls.__doc__, '%s has no docstring' % cls
|
2012-10-19 12:29:05 +02:00
|
|
|
lines = cls.__doc__.splitlines()
|
2012-10-16 18:26:37 +02:00
|
|
|
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
|
|
|
"""
|
2013-05-27 18:47:00 +02: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)
|
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()
|
2009-01-29 19:22:03 +01:00
|
|
|
value = {}
|
|
|
|
|
|
2016-08-04 23:54:50 +02:00
|
|
|
default_rec_name = Transaction().context.get('default_rec_name')
|
|
|
|
|
if (default_rec_name
|
|
|
|
|
and cls._rec_name in cls._fields
|
|
|
|
|
and cls._rec_name in fields_names):
|
|
|
|
|
value[cls._rec_name] = default_rec_name
|
|
|
|
|
|
2009-01-29 19:22:03 +01:00
|
|
|
# get the default values defined in the object
|
2010-10-20 19:29:20 +02:00
|
|
|
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]
|
2010-10-20 19:29:20 +02:00
|
|
|
if (field._type == 'boolean'
|
2014-12-02 12:54:01 +01:00
|
|
|
and field_name not in value):
|
2010-10-20 19:29:20 +02:00
|
|
|
value[field_name] = False
|
|
|
|
|
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:
|
2018-12-21 23:41:56 +01:00
|
|
|
value.setdefault(
|
|
|
|
|
field_name + '.', {})['rec_name'] = Target(
|
|
|
|
|
value[field_name]).rec_name
|
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
|
|
|
"""
|
2019-02-13 10:37:40 +01:00
|
|
|
definition = {}
|
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')
|
2014-03-15 11:01:14 +01:00
|
|
|
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 = []
|
2019-02-13 10:37:40 +01:00
|
|
|
for fname, field in cls._fields.items():
|
|
|
|
|
if fields_names and fname not in fields_names:
|
|
|
|
|
continue
|
|
|
|
|
trans_args.extend(field.definition_translations(cls, language))
|
2012-09-24 22:16:53 +02:00
|
|
|
Translation.get_sources(trans_args)
|
2009-01-29 19:22:03 +01:00
|
|
|
|
2010-01-20 10:55:19 +01:00
|
|
|
encoder = PYSONEncoder()
|
2019-02-13 10:37:40 +01:00
|
|
|
decoder = PYSONDecoder(noeval=True)
|
2010-01-20 10:55:19 +01:00
|
|
|
|
2012-11-28 22:42:51 +01:00
|
|
|
accesses = FieldAccess.get_access([cls.__name__])[cls.__name__]
|
2019-02-13 10:37:40 +01:00
|
|
|
for fname, field in cls._fields.items():
|
|
|
|
|
if fields_names and fname not in fields_names:
|
|
|
|
|
continue
|
|
|
|
|
definition[fname] = field.definition(cls, language)
|
2013-01-21 12:46:24 +01:00
|
|
|
if not accesses.get(field, {}).get('write', True):
|
2019-02-13 10:37:40 +01:00
|
|
|
definition[fname]['readonly'] = True
|
|
|
|
|
states = decoder.decode(definition[fname]['states'])
|
|
|
|
|
states.pop('readonly', None)
|
|
|
|
|
definition[fname]['states'] = encoder.encode(states)
|
|
|
|
|
for right in ['create', 'delete']:
|
|
|
|
|
definition[fname][right] = accesses.get(
|
|
|
|
|
fname, {}).get(right, True)
|
2009-01-29 19:22:03 +01:00
|
|
|
|
2019-02-13 10:37:40 +01:00
|
|
|
for fname in list(definition.keys()):
|
2009-01-29 19:22:03 +01:00
|
|
|
# filter out fields which aren't in the fields_names list
|
2014-03-15 11:01:14 +01:00
|
|
|
if fields_names:
|
2019-02-13 10:37:40 +01:00
|
|
|
if fname not in fields_names:
|
|
|
|
|
del definition[fname]
|
|
|
|
|
elif not ModelAccess.check_relation(
|
|
|
|
|
cls.__name__, fname, mode='read'):
|
|
|
|
|
del definition[fname]
|
|
|
|
|
return definition
|
2012-03-05 23:39:58 +01:00
|
|
|
|
2012-09-29 14:39:59 +02:00
|
|
|
def pre_validate(self):
|
|
|
|
|
pass
|
|
|
|
|
|
2019-03-15 12:27:44 +01:00
|
|
|
@classmethod
|
|
|
|
|
def __names__(cls, field=None):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
IrModel = pool.get('ir.model')
|
|
|
|
|
IrModelField = pool.get('ir.model.field')
|
|
|
|
|
|
|
|
|
|
names = {
|
|
|
|
|
'model': IrModel.get_name(cls.__name__),
|
|
|
|
|
}
|
|
|
|
|
if field:
|
|
|
|
|
names['field'] = IrModelField.get_name(cls.__name__, field)
|
|
|
|
|
return names
|
|
|
|
|
|
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 = {}
|
2018-06-26 19:11:19 +02:00
|
|
|
for name, value in kwargs.items():
|
2016-03-15 19:01:47 +01:00
|
|
|
if not name.startswith('_parent_'):
|
|
|
|
|
setattr(self, name, value)
|
|
|
|
|
else:
|
|
|
|
|
parent_values[name] = value
|
2017-04-19 10:31:45 +02:00
|
|
|
|
|
|
|
|
def set_parent_value(record, name, value):
|
2016-03-15 19:01:47 +01:00
|
|
|
parent_name, field = name.split('.', 1)
|
|
|
|
|
parent_name = parent_name[8:] # Strip '_parent_'
|
2017-04-19 10:31:45 +02:00
|
|
|
parent = getattr(record, parent_name, None)
|
2016-03-15 19:01:47 +01:00
|
|
|
if parent is not None:
|
2017-04-19 10:31:45 +02:00
|
|
|
if not field.startswith('_parent_'):
|
|
|
|
|
setattr(parent, field, value)
|
|
|
|
|
else:
|
|
|
|
|
set_parent_value(parent, field, value)
|
2016-03-15 19:01:47 +01:00
|
|
|
else:
|
2017-04-19 10:31:45 +02:00
|
|
|
setattr(record, parent_name, {field: value})
|
|
|
|
|
|
2018-06-26 19:11:19 +02:00
|
|
|
for name, value in parent_values.items():
|
2017-04-19 10:31:45 +02:00
|
|
|
set_parent_value(self, name, value)
|
2016-03-15 19:01:47 +01:00
|
|
|
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):
|
2018-06-26 19:11:19 +02:00
|
|
|
return '%s,%s' % (self.__name__, self.id)
|
2012-09-24 22:16:53 +02:00
|
|
|
|
|
|
|
|
def __repr__(self):
|
2016-06-03 13:01:39 +02:00
|
|
|
if self.id is None or self.id < 0:
|
2012-09-24 22:16:53 +02:00
|
|
|
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))
|
|
|
|
|
|
2018-06-26 19:11:19 +02:00
|
|
|
def __bool__(self):
|
2012-09-24 22:16:53 +02:00
|
|
|
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:
|
2018-06-26 19:11:19 +02:00
|
|
|
for fname, value in self._values.items():
|
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
|