Files
tryton/trytond/model/modelstorage.py
T

1518 lines
61 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-02-02 13:08:25 +01:00
import datetime
import time
2012-09-24 22:16:53 +02:00
import csv
import warnings
2012-09-24 22:16:53 +02:00
from decimal import Decimal
2013-03-20 22:49:16 +01:00
from itertools import islice, ifilter, chain, izip
from functools import reduce
2014-03-16 20:45:12 +01:00
from operator import itemgetter
2014-06-08 00:41:57 +02:00
from collections import defaultdict
2012-09-24 22:16:53 +02:00
2010-08-19 22:01:12 +02:00
from trytond.model import Model
from trytond.model import fields
from trytond.tools import reduce_domain, memoize, is_instance_method, \
grouped_slice
2010-08-19 22:01:12 +02:00
from trytond.pyson import PYSONEncoder, PYSONDecoder, PYSON
2014-11-19 21:35:51 +01:00
from trytond.const import OPERATORS
from trytond.config import config
2010-08-19 22:01:12 +02:00
from trytond.transaction import Transaction
2011-07-03 13:20:58 +02:00
from trytond.pool import Pool
2016-03-03 23:27:38 +01:00
from trytond.cache import LRUDict, LRUDictTransaction, freeze
from trytond import backend
2012-09-24 22:16:53 +02:00
from trytond.rpc import RPC
from .modelview import ModelView
2014-11-01 11:52:55 +01:00
from .descriptors import dualmethod
2009-02-02 13:08:25 +01:00
2015-03-09 19:40:41 +01:00
__all__ = ['ModelStorage', 'EvalEnvironment']
2012-09-24 22:16:53 +02:00
2009-02-02 13:08:25 +01:00
2014-11-19 21:35:51 +01:00
def cache_size():
return Transaction().context.get('_record_cache_size',
config.getint('cache', 'record'))
2009-02-02 13:08:25 +01:00
class ModelStorage(Model):
"""
Define a model with storage capability in Tryton.
"""
2009-09-08 17:01:27 +02:00
create_uid = fields.Many2One('res.user', 'Create User', readonly=True)
2013-09-06 22:32:37 +02:00
create_date = fields.Timestamp('Create Date', readonly=True)
2009-02-02 13:08:25 +01:00
write_uid = fields.Many2One('res.user', 'Write User', readonly=True)
2013-09-06 22:32:37 +02:00
write_date = fields.Timestamp('Write Date', readonly=True)
2010-03-11 16:42:50 +01:00
rec_name = fields.Function(fields.Char('Name'), 'get_rec_name',
searcher='search_rec_name')
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def __setup__(cls):
super(ModelStorage, cls).__setup__()
if issubclass(cls, ModelView):
cls.__rpc__.update({
2013-02-27 22:50:31 +01:00
'create': RPC(readonly=False,
result=lambda r: map(int, r)),
2012-09-24 22:16:53 +02:00
'read': RPC(),
2013-12-19 14:44:01 +01:00
'write': RPC(readonly=False,
instantiate=slice(0, None, 2)),
2012-09-24 22:16:53 +02:00
'delete': RPC(readonly=False, instantiate=0),
'copy': RPC(readonly=False, instantiate=0,
result=lambda r: map(int, r)),
'search': RPC(result=lambda r: map(int, r)),
'search_count': RPC(),
'search_read': RPC(),
'export_data': RPC(instantiate=0),
'import_data': RPC(readonly=False),
})
cls._constraints = []
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@staticmethod
def default_create_uid():
2009-09-13 15:37:11 +02:00
"Default value for uid field."
2010-08-19 22:01:12 +02:00
return int(Transaction().user)
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@staticmethod
def default_create_date():
2009-09-13 15:37:11 +02:00
"Default value for create_date field."
2009-02-02 13:08:25 +01:00
return datetime.datetime.today()
2012-09-24 22:16:53 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2009-02-02 13:08:25 +01:00
'''
2013-01-06 22:18:13 +01:00
Returns the list of created records.
2009-02-02 13:08:25 +01:00
'''
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
ModelAccess = pool.get('ir.model.access')
ModelFieldAccess = pool.get('ir.model.field.access')
2011-01-22 23:56:13 +01:00
2012-09-24 22:16:53 +02:00
ModelAccess.check(cls.__name__, 'create')
2013-01-06 22:18:13 +01:00
all_fields = list(set(chain(*(v.iterkeys() for v in vlist))))
ModelFieldAccess.check(cls.__name__, all_fields, 'write')
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
# Increase transaction counter
Transaction().counter += 1
@classmethod
2013-01-06 22:18:13 +01:00
def trigger_create(cls, records):
'''
Trigger create actions
'''
2012-09-24 22:16:53 +02:00
Trigger = Pool().get('ir.trigger')
triggers = Trigger.get_triggers(cls.__name__, 'create')
if not triggers:
return
for trigger in triggers:
2013-01-06 22:18:13 +01:00
triggers = []
for record in records:
if Trigger.eval(trigger, record):
triggers.append(record)
if triggers:
Trigger.trigger_action(triggers, trigger)
2012-09-24 22:16:53 +02:00
@classmethod
def read(cls, ids, fields_names=None):
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
Read fields_names of record ids.
If fields_names is None, it read all fields.
The order is not guaranteed.
2009-02-02 13:08:25 +01:00
'''
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
ModelAccess = pool.get('ir.model.access')
ModelFieldAccess = pool.get('ir.model.field.access')
2009-02-02 13:08:25 +01:00
if not fields_names:
fields_names = []
for field_name in cls._fields.keys():
if ModelAccess.check_relation(cls.__name__, field_name,
mode='read'):
fields_names.append(field_name)
2012-09-24 22:16:53 +02:00
ModelAccess.check(cls.__name__, 'read')
ModelFieldAccess.check(cls.__name__, fields_names, 'read')
2009-02-02 13:08:25 +01:00
return []
2012-09-24 22:16:53 +02:00
@classmethod
2013-12-19 14:44:01 +01:00
def write(cls, records, values, *args):
2009-02-02 13:08:25 +01:00
'''
2009-09-13 15:37:11 +02:00
Write values on records.
2009-02-02 13:08:25 +01:00
'''
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
ModelAccess = pool.get('ir.model.access')
ModelFieldAccess = pool.get('ir.model.field.access')
2009-02-02 13:08:25 +01:00
2013-12-19 14:44:01 +01:00
assert not len(args) % 2
actions = iter((records, values) + args)
all_records = []
all_fields = set()
2013-12-19 14:44:01 +01:00
for records, values in zip(actions, actions):
if not cls.check_xml_record(records, values):
cls.raise_user_error('write_xml_record',
error_description='xml_record_desc')
all_records += records
all_fields.update(values.iterkeys())
ModelAccess.check(cls.__name__, 'write')
ModelFieldAccess.check(cls.__name__, all_fields, 'write')
2009-08-11 11:13:14 +02:00
2012-09-24 22:16:53 +02:00
# Increase transaction counter
Transaction().counter += 1
# Clean local cache
2013-12-19 14:44:01 +01:00
for record in all_records:
2012-09-24 22:16:53 +02:00
local_cache = record._local_cache.get(record.id)
if local_cache:
local_cache.clear()
# Clean transaction cache
for cache in Transaction().cache.itervalues():
2012-09-24 22:16:53 +02:00
if cls.__name__ in cache:
2013-12-19 14:44:01 +01:00
for record in all_records:
2012-09-24 22:16:53 +02:00
if record.id in cache[cls.__name__]:
cache[cls.__name__][record.id].clear()
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def trigger_write_get_eligibles(cls, records):
'''
2012-09-24 22:16:53 +02:00
Return eligible records for write actions by triggers
'''
2012-09-24 22:16:53 +02:00
Trigger = Pool().get('ir.trigger')
triggers = Trigger.get_triggers(cls.__name__, 'write')
if not triggers:
return {}
eligibles = {}
for trigger in triggers:
2012-09-24 22:16:53 +02:00
eligibles[trigger] = []
for record in records:
2012-09-24 22:16:53 +02:00
if not Trigger.eval(trigger, record):
eligibles[trigger].append(record)
return eligibles
2012-09-24 22:16:53 +02:00
@classmethod
def trigger_write(cls, eligibles):
'''
2012-09-24 22:16:53 +02:00
Trigger write actions.
eligibles is a dictionary of the lists of eligible records by triggers
'''
2012-09-24 22:16:53 +02:00
Trigger = Pool().get('ir.trigger')
for trigger, records in eligibles.iteritems():
triggered = []
for record in records:
if Trigger.eval(trigger, record):
triggered.append(record)
if triggered:
Trigger.trigger_action(triggered, trigger)
2012-09-24 22:16:53 +02:00
@classmethod
def delete(cls, records):
2009-02-02 13:08:25 +01:00
'''
Delete records.
'''
2012-09-24 22:16:53 +02:00
ModelAccess = Pool().get('ir.model.access')
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
ModelAccess.check(cls.__name__, 'delete')
if not cls.check_xml_record(records, None):
cls.raise_user_error('delete_xml_record',
2010-08-19 22:01:12 +02:00
error_description='xml_record_desc')
2009-08-11 11:13:14 +02:00
2012-09-24 22:16:53 +02:00
# Increase transaction counter
Transaction().counter += 1
# Clean transaction cache
for cache in Transaction().cache.values():
for cache in (cache, cache.get('_language_cache', {}).values()):
2012-09-24 22:16:53 +02:00
if cls.__name__ in cache:
for record in records:
if record.id in cache[cls.__name__]:
del cache[cls.__name__][record.id]
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def trigger_delete(cls, records):
'''
Trigger delete actions
'''
2012-09-24 22:16:53 +02:00
Trigger = Pool().get('ir.trigger')
triggers = Trigger.get_triggers(cls.__name__, 'delete')
if not triggers:
return
for trigger in triggers:
2012-09-24 22:16:53 +02:00
triggered = []
for record in records:
2012-09-24 22:16:53 +02:00
if Trigger.eval(trigger, record):
triggered.append(record)
if triggered:
Trigger.trigger_action(triggered, trigger)
2012-09-24 22:16:53 +02:00
@classmethod
def copy(cls, records, default=None):
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
Duplicate the records and return a list of new records.
2009-02-02 13:08:25 +01:00
'''
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
Lang = pool.get('ir.lang')
2009-02-02 13:08:25 +01:00
if default is None:
default = {}
if 'state' not in default:
2012-09-24 22:16:53 +02:00
if 'state' in cls._defaults:
default['state'] = cls._defaults['state']()
2009-02-02 13:08:25 +01:00
2010-02-18 18:50:35 +01:00
def convert_data(field_defs, data):
data = data.copy()
2010-02-18 18:50:35 +01:00
for field_name in field_defs:
ftype = field_defs[field_name]['type']
2009-02-02 13:08:25 +01:00
if field_name in (
2013-02-27 22:50:31 +01:00
'create_date',
'create_uid',
'write_date',
'write_uid',
):
2009-02-02 13:08:25 +01:00
del data[field_name]
if field_name in default:
data[field_name] = default[field_name]
2012-10-10 20:04:08 +02:00
elif (isinstance(cls._fields[field_name], fields.Function)
and not isinstance(cls._fields[field_name],
fields.Property)):
2009-02-02 13:08:25 +01:00
del data[field_name]
2010-12-03 18:01:46 +01:00
elif ftype in ('many2one', 'one2one'):
2009-02-02 13:08:25 +01:00
try:
data[field_name] = data[field_name] and \
2013-02-27 22:50:31 +01:00
data[field_name][0]
2010-07-08 19:53:39 +02:00
except Exception:
2009-02-02 13:08:25 +01:00
pass
elif ftype in ('one2many',):
if data[field_name]:
data[field_name] = [('copy', data[field_name])]
2009-02-02 13:08:25 +01:00
elif ftype == 'many2many':
if data[field_name]:
2013-12-20 12:32:07 +01:00
data[field_name] = [('add', data[field_name])]
2009-02-02 13:08:25 +01:00
if 'id' in data:
del data['id']
return data
2009-02-02 13:08:25 +01:00
2015-10-18 16:50:41 +02:00
# Reset MPTT field to the default value
mptt = set()
for field in cls._fields.itervalues():
if (isinstance(field, fields.Many2One)
and field.model_name == cls.__name__
and field.left and field.right):
mptt.add(field.left)
mptt.add(field.right)
fields_names = [n for n, f in cls._fields.iteritems()
if (not isinstance(f, fields.Function)
2015-10-18 16:50:41 +02:00
or isinstance(f, fields.Property))
and n not in mptt]
2012-09-24 22:16:53 +02:00
ids = map(int, records)
datas = cls.read(ids, fields_names=fields_names)
datas = dict((d['id'], d) for d in datas)
2012-09-24 22:16:53 +02:00
field_defs = cls.fields_get(fields_names=fields_names)
2013-01-06 22:18:13 +01:00
to_create = []
for id in ids:
data = convert_data(field_defs, datas[id])
2013-01-06 22:18:13 +01:00
to_create.append(data)
new_records = cls.create(to_create)
new_ids = dict(izip(ids, map(int, new_records)))
2009-02-02 13:08:25 +01:00
fields_translate = {}
2010-02-18 18:50:35 +01:00
for field_name, field in field_defs.iteritems():
2012-09-24 22:16:53 +02:00
if field_name in cls._fields and \
getattr(cls._fields[field_name], 'translate', False):
2009-02-02 13:08:25 +01:00
fields_translate[field_name] = field
if fields_translate:
langs = Lang.search([
2009-02-02 13:08:25 +01:00
('translatable', '=', True),
2010-08-19 22:01:12 +02:00
])
if langs:
2009-02-02 13:08:25 +01:00
for lang in langs:
2012-11-22 22:42:45 +01:00
# Prevent fuzzing translations when copying as the terms
# should be the same.
with Transaction().set_context(language=lang.code,
fuzzy_translation=False):
2012-09-24 22:16:53 +02:00
datas = cls.read(ids,
2010-08-19 22:01:12 +02:00
fields_names=fields_translate.keys() + ['id'])
for data in datas:
data_id = data['id']
data = convert_data(fields_translate, data)
2012-09-24 22:16:53 +02:00
cls.write([cls(new_ids[data_id])], data)
return cls.browse(new_ids.values())
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def search(cls, domain, offset=0, limit=None, order=None, count=False):
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
Return a list of records that match the domain.
2009-02-02 13:08:25 +01:00
'''
if count:
return 0
return []
2012-09-24 22:16:53 +02:00
@classmethod
def search_count(cls, domain):
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
Return the number of records that match the domain.
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
res = cls.search(domain, count=True)
2009-02-02 13:08:25 +01:00
if isinstance(res, list):
return len(res)
return res
2012-09-24 22:16:53 +02:00
@classmethod
def search_read(cls, domain, offset=0, limit=None, order=None,
2010-08-19 22:01:12 +02:00
fields_names=None):
2009-02-02 13:08:25 +01:00
'''
2009-09-13 15:37:11 +02:00
Call search and read functions at once.
Useful for the client to reduce the number of calls.
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
records = cls.search(domain, offset=offset, limit=limit, order=order)
if not fields_names:
2013-03-20 22:49:16 +01:00
fields_names = cls._fields.keys()
2009-09-07 11:18:22 +02:00
if 'id' not in fields_names:
fields_names.append('id')
2015-03-05 11:43:27 +01:00
rows = cls.read(map(int, records), fields_names)
index = {r.id: i for i, r in enumerate(records)}
rows.sort(key=lambda r: index[r['id']])
return rows
2012-09-24 22:16:53 +02:00
@classmethod
def _search_domain_active(cls, domain, active_test=True):
# reduce_domain return a new instance so we can safety modify domain
domain = reduce_domain(domain)
2009-02-02 13:08:25 +01:00
# if the object has a field named 'active', filter out all inactive
# records unless they were explicitely asked for
2013-03-20 22:49:16 +01:00
if not ('active' in cls._fields
and active_test
and Transaction().context.get('active_test', True)):
2009-02-02 13:08:25 +01:00
return domain
def process(domain):
i = 0
active_found = False
while i < len(domain):
arg = domain[i]
2014-12-02 12:54:01 +01:00
# add test for xmlrpc that doesn't handle tuple
2013-02-27 22:50:31 +01:00
if (isinstance(arg, tuple)
or (isinstance(arg, list)
and len(arg) > 2
and arg[1] in OPERATORS)):
if arg[0] == 'active':
2009-02-02 13:08:25 +01:00
active_found = True
elif isinstance(arg, list):
domain[i] = process(domain[i])
2009-02-02 13:08:25 +01:00
i += 1
if not active_found:
2013-02-27 22:50:31 +01:00
if (domain and ((isinstance(domain[0], basestring)
and domain[0] == 'AND')
or (not isinstance(domain[0], basestring)))):
domain.append(('active', '=', True))
2009-02-02 13:08:25 +01:00
else:
domain = ['AND', domain, ('active', '=', True)]
2009-02-02 13:08:25 +01:00
return domain
return process(domain)
2012-09-24 22:16:53 +02:00
def get_rec_name(self, name):
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
Return the rec_name of the instance.
2009-09-13 15:37:11 +02:00
It is used by the Function field rec_name.
2009-02-02 13:08:25 +01:00
'''
rec_name = self._rec_name
2013-03-20 22:49:16 +01:00
if rec_name not in self._fields:
rec_name = 'id'
2012-09-24 22:16:53 +02:00
return unicode(getattr(self, rec_name))
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def search_rec_name(cls, name, clause):
2009-02-02 13:08:25 +01:00
'''
2009-09-13 15:37:11 +02:00
Return a list of arguments for search on rec_name.
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
rec_name = cls._rec_name
2013-03-20 22:49:16 +01:00
if rec_name not in cls._fields:
return []
return [(rec_name,) + tuple(clause[1:])]
2009-02-02 13:08:25 +01:00
2012-12-05 13:25:18 +01:00
@classmethod
def search_global(cls, text):
'''
2015-02-04 13:37:02 +01:00
Yield tuples (record, name, icon) for text
2012-12-05 13:25:18 +01:00
'''
# TODO improve search clause
for record in cls.search([
('rec_name', 'ilike', '%%%s%%' % text),
]):
2015-02-04 13:37:02 +01:00
yield record, record.rec_name, None
2012-12-05 13:25:18 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def browse(cls, ids):
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
Return a list of instance for the ids
2009-02-02 13:08:25 +01:00
'''
2016-03-15 19:01:47 +01:00
transaction = Transaction()
2012-09-24 22:16:53 +02:00
ids = map(int, ids)
2016-03-03 23:27:38 +01:00
local_cache = LRUDictTransaction(cache_size())
2016-03-15 19:01:47 +01:00
transaction_cache = transaction.get_cache()
return [cls(x, _ids=ids,
_local_cache=local_cache,
_transaction_cache=transaction_cache,
_transaction=transaction) for x in ids]
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@staticmethod
def __export_row(record, fields_names):
2011-07-03 13:20:58 +02:00
pool = Pool()
2009-02-02 13:08:25 +01:00
lines = []
data = ['' for x in range(len(fields_names))]
done = []
for fpos in range(len(fields_names)):
fields_tree = fields_names[fpos]
if not fields_tree:
continue
value = record
i = 0
while i < len(fields_tree):
2012-09-24 22:16:53 +02:00
if not isinstance(value, ModelStorage):
break
field_name = fields_tree[i]
descriptor = None
if '.' in field_name:
field_name, descriptor = field_name.split('.')
2012-09-24 22:16:53 +02:00
eModel = pool.get(value.__name__)
2013-03-20 22:49:16 +01:00
field = eModel._fields[field_name]
if field.states and 'invisible' in field.states:
pyson_invisible = PYSONEncoder().encode(
field.states['invisible'])
2012-09-24 22:16:53 +02:00
env = EvalEnvironment(value, eModel)
2010-08-19 22:01:12 +02:00
env.update(Transaction().context)
env['current_date'] = datetime.datetime.today()
env['time'] = time
2010-08-19 22:01:12 +02:00
env['context'] = Transaction().context
env['active_id'] = value.id
invisible = PYSONDecoder(env).decode(pyson_invisible)
if invisible:
value = ''
2009-02-02 13:08:25 +01:00
break
if descriptor:
value = getattr(field, descriptor)().__get__(value, eModel)
else:
value = getattr(value, field_name)
2012-09-24 22:16:53 +02:00
if isinstance(value, (list, tuple)):
first = True
child_fields_names = [(x[:i + 1] == fields_tree[:i + 1] and
x[i + 1:]) or [] for x in fields_names]
if child_fields_names in done:
2009-02-02 13:08:25 +01:00
break
done.append(child_fields_names)
for child_record in value:
2012-09-24 22:16:53 +02:00
child_lines = ModelStorage.__export_row(child_record,
2010-08-19 22:01:12 +02:00
child_fields_names)
if first:
for child_fpos in xrange(len(fields_names)):
if child_lines and child_lines[0][child_fpos]:
data[child_fpos] = \
2013-02-27 22:50:31 +01:00
child_lines[0][child_fpos]
lines += child_lines[1:]
first = False
else:
lines += child_lines
break
i += 1
if i == len(fields_tree):
if value is None:
value = ''
2012-09-24 22:16:53 +02:00
elif isinstance(value, Model):
value = str(value)
data[fpos] = value
2009-02-02 13:08:25 +01:00
return [data] + lines
2012-09-24 22:16:53 +02:00
@classmethod
def export_data(cls, records, fields_names):
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
Return list of list of values for each record.
2009-09-13 15:37:11 +02:00
The list of values follows fields_names.
Relational fields are defined with '/' at any depth.
2009-02-02 13:08:25 +01:00
'''
fields_names = [x.split('/') for x in fields_names]
2012-09-24 22:16:53 +02:00
data = []
for record in records:
data += cls.__export_row(record, fields_names)
return data
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def import_data(cls, fields_names, data):
2009-02-02 13:08:25 +01:00
'''
2012-09-24 22:16:53 +02:00
Create records for all values in data.
2009-09-13 15:37:11 +02:00
The field names of values must be defined in fields_names.
2009-02-02 13:08:25 +01:00
'''
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-04-01 23:08:15 +02:00
2014-02-09 23:58:45 +01:00
@memoize(1000)
def get_many2one(relation, value):
if not value:
return None
Relation = pool.get(relation)
res = Relation.search([
('rec_name', '=', value),
], limit=2)
if len(res) < 1:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('relation_not_found', (value, relation))
2014-02-09 23:58:45 +01:00
elif len(res) > 1:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('too_many_relations_found',
(value, relation))
2014-02-09 23:58:45 +01:00
else:
res = res[0].id
return res
2014-02-09 23:58:45 +01:00
@memoize(1000)
def get_many2many(relation, value):
if not value:
return None
res = []
Relation = pool.get(relation)
2016-02-07 16:46:16 +01:00
for word in csv.reader(value.splitlines(), delimiter=',',
2014-02-09 23:58:45 +01:00
quoting=csv.QUOTE_NONE, escapechar='\\').next():
res2 = Relation.search([
('rec_name', '=', word),
2010-08-19 22:01:12 +02:00
], limit=2)
2014-02-09 23:58:45 +01:00
if len(res2) < 1:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('relation_not_found',
(word, relation))
2014-02-09 23:58:45 +01:00
elif len(res2) > 1:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('too_many_relations_found',
(word, relation))
else:
2014-02-09 23:58:45 +01:00
res.extend(res2)
if len(res):
res = [('add', [x.id for x in res])]
return res
2014-02-09 23:58:45 +01:00
def get_one2one(relation, value):
return ('add', get_many2one(relation, value))
2014-02-09 23:58:45 +01:00
@memoize(1000)
def get_reference(value, field):
if not value:
return None
try:
relation, value = value.split(',', 1)
except Exception:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('reference_syntax_error',
(value, '/'.join(field)))
2014-02-09 23:58:45 +01:00
Relation = pool.get(relation)
res = Relation.search([
('rec_name', '=', value),
], limit=2)
if len(res) < 1:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('relation_not_found', (value, relation))
2014-02-09 23:58:45 +01:00
elif len(res) > 1:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('too_many_relations_found',
(value, relation))
2014-02-09 23:58:45 +01:00
else:
res = '%s,%s' % (relation, res[0].id)
return res
2010-12-03 18:01:46 +01:00
2014-02-09 23:58:45 +01:00
@memoize(1000)
def get_by_id(value, field):
if not value:
return None
relation = None
ftype = fields_def[field[-1][:-3]]['type']
if ftype == 'many2many':
2016-02-07 16:46:16 +01:00
value = csv.reader(value.splitlines(), delimiter=',',
2014-02-09 23:58:45 +01:00
quoting=csv.QUOTE_NONE, escapechar='\\').next()
elif ftype == 'reference':
try:
relation, value = value.split(',', 1)
2010-07-08 19:53:39 +02:00
except Exception:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('reference_syntax_error',
(value, '/'.join(field)))
2014-02-09 23:58:45 +01:00
value = [value]
else:
value = [value]
res_ids = []
for word in value:
try:
module, xml_id = word.rsplit('.', 1)
except Exception:
2014-03-18 19:49:51 +01:00
cls.raise_user_error('xml_id_syntax_error',
(word, '/'.join(field)))
2014-02-09 23:58:45 +01:00
db_id = ModelData.get_id(module, xml_id)
res_ids.append(db_id)
if ftype == 'many2many' and res_ids:
return [('add', res_ids)]
elif ftype == 'reference' and res_ids:
return '%s,%s' % (relation, str(res_ids[0]))
return res_ids and res_ids[0] or False
2009-02-02 13:08:25 +01:00
2014-02-09 23:58:45 +01:00
def process_lines(data, prefix, fields_def, position=0):
2012-09-24 22:16:53 +02:00
line = data[position]
2009-02-02 13:08:25 +01:00
row = {}
translate = {}
todo = set()
prefix_len = len(prefix)
2009-02-02 13:08:25 +01:00
# Import normal fields_names
for i, field in enumerate(fields_names):
2009-02-02 13:08:25 +01:00
if i >= len(line):
raise Exception('ImportError',
2012-04-01 23:08:15 +02:00
'Please check that all your lines have %d cols.'
% len(fields_names))
is_prefix_len = (len(field) == (prefix_len + 1))
value = line[i]
if is_prefix_len and field[-1].endswith(':id'):
2014-02-09 23:58:45 +01:00
row[field[0][:-3]] = get_by_id(value, field)
elif is_prefix_len and ':lang=' in field[-1]:
field_name, lang = field[-1].split(':lang=')
translate.setdefault(lang, {})[field_name] = value or False
elif is_prefix_len and prefix == field[:-1]:
this_field_def = fields_def[field[-1]]
field_type = this_field_def['type']
res = None
if field_type == 'boolean':
if value.lower() == 'true':
res = True
elif value.lower() == 'false':
res = False
elif not value:
res = False
2009-02-02 13:08:25 +01:00
else:
res = bool(int(value))
elif field_type == 'integer':
res = int(value) if value else None
elif field_type == 'float':
res = float(value) if value else None
elif field_type == 'numeric':
res = Decimal(value) if value else None
elif field_type == 'date':
2014-03-18 19:49:51 +01:00
res = (datetime.datetime.strptime(value,
'%Y-%m-%d').date()
2013-09-06 22:32:37 +02:00
if value else None)
elif field_type == 'datetime':
2014-03-18 19:49:51 +01:00
res = (datetime.datetime.strptime(value,
'%Y-%m-%d %H:%M:%S')
2013-09-06 22:32:37 +02:00
if value else None)
elif field_type == 'many2one':
res = get_many2one(this_field_def['relation'], value)
elif field_type == 'many2many':
res = get_many2many(this_field_def['relation'], value)
2010-12-03 18:01:46 +01:00
elif field_type == 'one2one':
res = get_one2one(this_field_def['relation'], value)
elif field_type == 'reference':
2014-02-09 23:58:45 +01:00
res = get_reference(value, field)
2009-02-02 13:08:25 +01:00
else:
res = value or None
row[field[-1]] = res
elif prefix == field[0:prefix_len]:
todo.add(field[prefix_len])
2009-02-02 13:08:25 +01:00
# Import one2many fields
nbrmax = 1
for field in todo:
2011-07-03 13:20:58 +02:00
newfd = pool.get(fields_def[field]['relation']
2010-08-19 22:01:12 +02:00
).fields_get()
2012-09-24 22:16:53 +02:00
res = process_lines(data, prefix + [field], newfd,
2009-02-02 13:08:25 +01:00
position)
2010-02-18 18:50:35 +01:00
(newrow, max2, _) = res
2009-02-02 13:08:25 +01:00
nbrmax = max(nbrmax, max2)
reduce(lambda x, y: x and y, newrow)
row[field] = (reduce(lambda x, y: x or y, newrow.values()) and
2013-01-06 22:18:13 +01:00
[('create', [newrow])]) or []
2009-02-02 13:08:25 +01:00
i = max2
2012-09-24 22:16:53 +02:00
while (position + i) < len(data):
2009-02-02 13:08:25 +01:00
test = True
for j, field2 in enumerate(fields_names):
2013-02-27 22:50:31 +01:00
if (len(field2) <= (prefix_len + 1)
and data[position + i][j]):
2009-02-02 13:08:25 +01:00
test = False
break
2009-02-02 13:08:25 +01:00
if not test:
break
2010-02-18 18:50:35 +01:00
(newrow, max2, _) = \
2013-02-27 22:50:31 +01:00
process_lines(data, prefix + [field], newfd,
position + i)
2009-02-02 13:08:25 +01:00
if reduce(lambda x, y: x or y, newrow.values()):
2013-01-06 22:18:13 +01:00
row[field].append(('create', [newrow]))
2009-02-02 13:08:25 +01:00
i += max2
nbrmax = max(nbrmax, i)
if prefix_len == 0:
for i in xrange(max(nbrmax, 1)):
2012-09-24 22:16:53 +02:00
data.pop(0)
return (row, nbrmax, translate)
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
ModelData = pool.get('ir.model.data')
len_fields_names = len(fields_names)
2012-09-24 22:16:53 +02:00
assert all(len(x) == len_fields_names for x in data)
fields_names = [x.split('/') for x in fields_names]
2012-09-24 22:16:53 +02:00
fields_def = cls.fields_get()
2009-02-02 13:08:25 +01:00
2014-03-16 20:45:12 +01:00
to_create, translations, languages = [], [], set()
2012-09-24 22:16:53 +02:00
while len(data):
2014-03-18 19:49:51 +01:00
(res, _, translate) = \
process_lines(data, [], fields_def)
to_create.append(res)
translations.append(translate)
languages.update(translate)
2014-03-16 20:45:12 +01:00
new_records = cls.create(to_create)
for language in languages:
translated = [t.get(language, {}) for t in translations]
with Transaction().set_context(language=language):
cls.write(*chain(*ifilter(itemgetter(1),
izip(([r] for r in new_records), translated))))
2014-03-18 19:49:51 +01:00
return len(new_records)
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
@classmethod
def check_xml_record(cls, records, values):
2009-02-02 13:08:25 +01:00
"""
Check if a list of records and their corresponding fields are
originating from xml data. This is used by write and delete
functions: if the return value is True the records can be
written/deleted, False otherwise. The default behaviour is to
2009-09-13 15:37:11 +02:00
forbid any modification on records/fields originating from
2009-02-02 13:08:25 +01:00
xml. Values is the dictionary of written values. If values is
equal to None, no field by field check is performed, False is
2009-09-13 15:37:11 +02:00
returned as soon as one of the record comes from the xml.
2009-02-02 13:08:25 +01:00
"""
2012-09-24 22:16:53 +02:00
ModelData = Pool().get('ir.model.data')
2009-02-02 13:08:25 +01:00
# Allow root user to update/delete
2010-08-19 22:01:12 +02:00
if Transaction().user == 0:
2009-02-02 13:08:25 +01:00
return True
2014-09-17 21:43:53 +02:00
with Transaction().set_context(_check_access=False):
models_data = ModelData.search([
2012-09-24 22:16:53 +02:00
('model', '=', cls.__name__),
('db_id', 'in', map(int, records)),
2010-08-19 22:01:12 +02:00
])
if not models_data:
2010-08-19 22:01:12 +02:00
return True
2013-02-27 22:50:31 +01:00
if values is None:
2010-08-19 22:01:12 +02:00
return False
for model_data in models_data:
2016-02-11 22:18:29 +01:00
if not model_data.values or model_data.noupdate:
2010-08-19 22:01:12 +02:00
continue
xml_values = ModelData.load_values(model_data.values)
2010-08-19 22:01:12 +02:00
for key, val in values.iteritems():
if key in xml_values and val != xml_values[key]:
return False
2009-02-02 13:08:25 +01:00
return True
2012-09-24 22:16:53 +02:00
@classmethod
def check_recursion(cls, records, parent='parent', rec_name='rec_name'):
2009-02-02 13:08:25 +01:00
'''
2009-09-13 15:37:11 +02:00
Function that checks if there is no recursion in the tree
2009-02-02 13:08:25 +01:00
composed with parent as parent field name.
'''
2013-03-20 22:49:16 +01:00
parent_type = cls._fields[parent]._type
2013-09-04 12:35:02 +00:00
if parent_type not in ('many2one', 'many2many', 'one2one'):
raise Exception(
2013-02-27 22:50:31 +01:00
'Unsupported field type "%s" for field "%s" on "%s"'
% (parent_type, parent, cls.__name__))
visited = set()
for record in records:
walked = set()
2012-09-24 22:16:53 +02:00
walker = getattr(record, parent)
while walker:
if parent_type == 'many2many':
2010-02-18 18:50:35 +01:00
for walk in walker:
walked.add(walk.id)
if walk.id == record.id:
parent_rec_name = ', '.join(getattr(r, rec_name)
for r in getattr(record, parent))
cls.raise_user_error('recursion_error', {
'rec_name': getattr(record, rec_name),
'parent_rec_name': parent_rec_name,
})
2012-09-24 22:16:53 +02:00
walker = list(chain(*(getattr(walk, parent)
for walk in walker if walk.id not in visited)))
else:
walked.add(walker.id)
if walker.id == record.id:
cls.raise_user_error('recursion_error', {
'rec_name': getattr(record, rec_name),
'parent_rec_name': getattr(getattr(record,
parent), rec_name)
})
2012-09-24 22:16:53 +02:00
walker = (getattr(walker, parent) not in visited
and getattr(walker, parent))
visited.update(walked)
2012-09-24 22:16:53 +02:00
@classmethod
def _get_error_args(cls, field_name):
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
ModelField = pool.get('ir.model.field')
error_args = {
'field': field_name,
'model': cls.__name__
}
2012-09-24 22:16:53 +02:00
if ModelField:
model_fields = ModelField.search([
2009-04-06 12:10:11 +02:00
('name', '=', field_name),
2012-09-24 22:16:53 +02:00
('model.model', '=', cls.__name__),
2010-08-19 22:01:12 +02:00
], limit=1)
2012-09-24 22:16:53 +02:00
if model_fields:
model_field, = model_fields
error_args.update({
'field': model_field.field_description,
'model': model_field.model.name,
})
2009-04-06 12:10:11 +02:00
return error_args
@classmethod
def validate(cls, records):
pass
2012-09-24 22:16:53 +02:00
@classmethod
def _validate(cls, records, field_names=None):
2011-07-03 13:20:58 +02:00
pool = Pool()
# Ensure that records are readable
2014-09-17 21:43:53 +02:00
with Transaction().set_context(_check_access=False):
records = cls.browse(records)
2012-09-24 22:16:53 +02:00
def call(name):
method = getattr(cls, name)
if not is_instance_method(cls, name):
2012-09-24 22:16:53 +02:00
return method(records)
else:
return all(method(r) for r in records)
for field in cls._constraints:
2013-10-05 18:18:42 +02:00
warnings.warn(
'_constraints is deprecated, override validate instead',
DeprecationWarning, stacklevel=2)
2012-09-24 22:16:53 +02:00
if not call(field[0]):
cls.raise_user_error(field[1])
2009-02-02 13:08:25 +01:00
2014-12-16 22:02:44 +01:00
ctx_pref = {}
if Transaction().user:
try:
User = pool.get('res.user')
except KeyError:
pass
else:
ctx_pref = User.get_preferences(context_only=True)
2009-02-02 13:08:25 +01:00
def is_pyson(test):
if isinstance(test, PYSON):
return True
if isinstance(test, (list, tuple)):
for i in test:
if isinstance(i, PYSON):
return True
if isinstance(i, (list, tuple)):
if is_pyson(i):
return True
if isinstance(test, dict):
for key, value in test.items():
if isinstance(value, PYSON):
return True
if isinstance(value, (list, tuple, dict)):
if is_pyson(value):
return True
return False
def validate_domain(field):
if not field.domain:
return
2015-03-15 16:27:27 +01:00
if field._type in ['dict', 'reference']:
2014-03-16 15:30:13 +01:00
return
if field._type in ('many2one', 'one2many'):
Relation = pool.get(field.model_name)
elif field._type in ('many2many', 'one2one'):
Relation = field.get_target()
else:
Relation = cls
2014-06-08 00:41:57 +02:00
domains = defaultdict(list)
if is_pyson(field.domain):
pyson_domain = PYSONEncoder().encode(field.domain)
for record in records:
env = EvalEnvironment(record, cls)
env.update(Transaction().context)
env['current_date'] = datetime.datetime.today()
env['time'] = time
env['context'] = Transaction().context
env['active_id'] = record.id
2014-06-08 00:41:57 +02:00
domain = freeze(PYSONDecoder(env).decode(pyson_domain))
domains[domain].append(record)
else:
2014-06-08 00:41:57 +02:00
domains[freeze(field.domain)].extend(records)
for domain, sub_records in domains.iteritems():
validate_relation_domain(field, sub_records, Relation, domain)
def validate_relation_domain(field, records, Relation, domain):
if field._type in ('many2one', 'one2many', 'many2many', 'one2one'):
relations = set()
for record in records:
if getattr(record, field.name):
if field._type in ('many2one', 'one2one'):
relations.add(getattr(record, field.name))
else:
relations.update(getattr(record, field.name))
else:
# Cache alignment is not a problem
relations = set(records)
if relations:
for sub_relations in grouped_slice(relations):
sub_relations = set(sub_relations)
finds = Relation.search(['AND',
[('id', 'in', [r.id for r in sub_relations])],
domain,
])
if sub_relations != set(finds):
cls.raise_user_error('domain_validation_record',
error_args=cls._get_error_args(field.name))
field_names = set(field_names or [])
function_fields = {name for name, field in cls._fields.iteritems()
if isinstance(field, fields.Function)}
ctx_pref['active_test'] = False
2010-08-19 22:01:12 +02:00
with Transaction().set_context(ctx_pref):
2012-09-24 22:16:53 +02:00
for field_name, field in cls._fields.iteritems():
depends = set(field.depends)
if (field_names
and field_name not in field_names
and not (depends & field_names)
and not (depends & function_fields)):
continue
2010-08-19 22:01:12 +02:00
if isinstance(field, fields.Function) and \
not field.setter:
continue
validate_domain(field)
2012-06-13 23:24:47 +02:00
def required_test(value, field_name):
2012-09-24 22:16:53 +02:00
if (isinstance(value, (type(None), type(False), list,
2013-01-26 15:18:41 +01:00
tuple, basestring, dict))
2012-06-13 23:24:47 +02:00
and not value):
2012-09-24 22:16:53 +02:00
cls.raise_user_error('required_validation_record',
error_args=cls._get_error_args(field_name))
2010-08-19 22:01:12 +02:00
# validate states required
if field.states and 'required' in field.states:
if is_pyson(field.states['required']):
pyson_required = PYSONEncoder().encode(
field.states['required'])
for record in records:
2012-09-24 22:16:53 +02:00
env = EvalEnvironment(record, cls)
2010-08-19 22:01:12 +02:00
env.update(Transaction().context)
env['current_date'] = datetime.datetime.today()
env['time'] = time
env['context'] = Transaction().context
env['active_id'] = record.id
required = PYSONDecoder(env).decode(pyson_required)
2012-06-13 23:24:47 +02:00
if required:
2012-09-24 22:16:53 +02:00
required_test(getattr(record, field_name),
field_name)
2010-08-19 22:01:12 +02:00
else:
if field.states['required']:
for record in records:
2012-09-24 22:16:53 +02:00
required_test(getattr(record, field_name),
field_name)
2010-08-19 22:01:12 +02:00
# validate required
if field.required:
for record in records:
2012-09-24 22:16:53 +02:00
required_test(getattr(record, field_name), field_name)
2010-08-19 22:01:12 +02:00
# validate size
if hasattr(field, 'size') and field.size is not None:
2010-08-19 22:01:12 +02:00
for record in records:
if isinstance(field.size, PYSON):
pyson_size = PYSONEncoder().encode(field.size)
2012-09-24 22:16:53 +02:00
env = EvalEnvironment(record, cls)
env.update(Transaction().context)
env['current_date'] = datetime.datetime.today()
env['time'] = time
env['context'] = Transaction().context
env['active_id'] = record.id
field_size = PYSONDecoder(env).decode(pyson_size)
else:
field_size = field.size
size = len(getattr(record, field_name) or '')
if (size > field_size >= 0):
error_args = cls._get_error_args(field_name)
error_args['size'] = size
cls.raise_user_error('size_validation_record',
error_args=error_args)
2009-02-02 13:08:25 +01:00
def digits_test(value, digits, field_name):
def raise_user_error(value):
error_args = cls._get_error_args(field_name)
error_args['digits'] = digits[1]
error_args['value'] = repr(value)
2012-09-24 22:16:53 +02:00
cls.raise_user_error('digits_validation_record',
error_args=error_args)
if value is None:
return
if isinstance(value, Decimal):
2012-04-01 23:08:15 +02:00
if (value.quantize(Decimal(str(10.0 ** -digits[1])))
!= value):
raise_user_error(value)
elif backend.name() != 'mysql':
if not (round(value, digits[1]) == float(value)):
raise_user_error(value)
2010-08-19 22:01:12 +02:00
# validate digits
if hasattr(field, 'digits') and field.digits:
if is_pyson(field.digits):
pyson_digits = PYSONEncoder().encode(field.digits)
for record in records:
2012-09-24 22:16:53 +02:00
env = EvalEnvironment(record, cls)
2010-08-19 22:01:12 +02:00
env.update(Transaction().context)
env['current_date'] = datetime.datetime.today()
env['time'] = time
env['context'] = Transaction().context
env['active_id'] = record.id
digits = PYSONDecoder(env).decode(pyson_digits)
2012-09-24 22:16:53 +02:00
digits_test(getattr(record, field_name), digits,
field_name)
2010-08-19 22:01:12 +02:00
else:
for record in records:
2012-09-24 22:16:53 +02:00
digits_test(getattr(record, field_name),
field.digits, field_name)
2012-12-13 19:18:37 +01:00
# validate selection
if hasattr(field, 'selection') and field.selection:
if isinstance(field.selection, (tuple, list)):
test = set(dict(field.selection).keys())
for record in records:
value = getattr(record, field_name)
if field._type == 'reference':
if isinstance(value, ModelStorage):
value = value.__class__.__name__
elif value:
value, _ = value.split(',')
if not isinstance(field.selection, (tuple, list)):
sel_func = getattr(cls, field.selection)
if not is_instance_method(cls, field.selection):
test = sel_func()
2014-10-29 20:38:17 +01:00
else:
test = sel_func(record)
test = set(dict(test))
# None and '' are equivalent
if '' in test or None in test:
test.add('')
test.add(None)
2012-12-13 19:18:37 +01:00
if value not in test:
error_args = cls._get_error_args(field_name)
error_args['value'] = value
2012-12-13 19:18:37 +01:00
cls.raise_user_error('selection_validation_record',
error_args=error_args)
2012-12-13 19:18:37 +01:00
2012-05-16 13:04:36 +02:00
def format_test(value, format, field_name):
if not value:
return
if not isinstance(value, datetime.time):
value = value.time()
if value != datetime.datetime.strptime(
value.strftime(format), format).time():
error_args = cls._get_error_args(field_name)
error_args['value'] = value
2012-09-24 22:16:53 +02:00
cls.raise_user_error('time_format_validation_record',
error_args=error_args)
2012-05-16 13:04:36 +02:00
# validate time format
if (field._type in ('datetime', 'time')
and field_name not in ('create_date', 'write_date')):
if is_pyson(field.format):
pyson_format = PYSONDecoder().encode(field.format)
for record in records:
2012-09-24 22:16:53 +02:00
env = EvalEnvironment(record, cls)
2012-05-16 13:04:36 +02:00
env.update(Transaction().context)
env['current_date'] = datetime.datetime.today()
env['time'] = time
env['context'] = Transaction().context
env['active_id'] = record.id
format = PYSONDecoder(env).decode(pyson_format)
2012-09-24 22:16:53 +02:00
format_test(getattr(record, field_name), format,
field_name)
2012-05-16 13:04:36 +02:00
else:
for record in records:
2012-09-24 22:16:53 +02:00
format_test(getattr(record, field_name),
field.format, field_name)
2012-05-16 13:04:36 +02:00
for record in records:
record.pre_validate()
cls.validate(records)
2012-09-24 22:16:53 +02:00
@classmethod
def _clean_defaults(cls, defaults):
2011-07-03 13:20:58 +02:00
pool = Pool()
2009-02-02 13:08:25 +01:00
vals = {}
for field in defaults.keys():
if '.' in field: # skip all related fields
continue
2013-03-20 22:49:16 +01:00
fld_def = cls._fields[field]
2010-12-03 18:01:46 +01:00
if fld_def._type in ('many2one', 'one2one'):
2009-02-02 13:08:25 +01:00
if isinstance(defaults[field], (list, tuple)):
vals[field] = defaults[field][0]
else:
vals[field] = defaults[field]
elif fld_def._type in ('one2many',):
2011-07-03 13:20:58 +02:00
obj = pool.get(fld_def.model_name)
2009-02-02 13:08:25 +01:00
vals[field] = []
for defaults2 in defaults[field]:
vals2 = obj._clean_defaults(defaults2)
2013-01-06 22:18:13 +01:00
vals[field].append(('create', [vals2]))
2009-02-02 13:08:25 +01:00
elif fld_def._type in ('many2many',):
2013-12-20 12:32:07 +01:00
vals[field] = [('add', defaults[field])]
2009-02-02 13:08:25 +01:00
elif fld_def._type in ('boolean',):
vals[field] = bool(defaults[field])
else:
vals[field] = defaults[field]
return vals
2012-09-24 22:16:53 +02:00
def __init__(self, id=None, **kwargs):
_ids = kwargs.pop('_ids', None)
_local_cache = kwargs.pop('_local_cache', None)
2016-03-15 19:01:47 +01:00
_transaction_cache = kwargs.pop('_transaction_cache', None)
transaction = kwargs.pop('_transaction', None)
if transaction is None:
transaction = Transaction()
self._transaction = transaction
self._user = transaction.user
self._context = transaction.context
2014-01-26 14:27:24 +01:00
if id is not None:
id = int(id)
2012-09-24 22:16:53 +02:00
if _ids is not None:
self._ids = _ids
assert id in _ids
else:
2014-01-26 14:27:24 +01:00
self._ids = [id]
2012-09-24 22:16:53 +02:00
2016-03-15 19:01:47 +01:00
if _transaction_cache is not None:
self._transaction_cache = _transaction_cache
else:
self._transaction_cache = transaction.get_cache()
2012-09-24 22:16:53 +02:00
if _local_cache is not None:
2016-03-03 23:27:38 +01:00
assert isinstance(_local_cache, LRUDictTransaction)
2012-09-24 22:16:53 +02:00
self._local_cache = _local_cache
else:
2016-03-03 23:27:38 +01:00
self._local_cache = LRUDictTransaction(cache_size())
2012-09-24 22:16:53 +02:00
2014-01-26 14:27:24 +01:00
super(ModelStorage, self).__init__(id, **kwargs)
2012-09-24 22:16:53 +02:00
@property
def _cache(self):
cache = self._transaction_cache
2012-09-24 22:16:53 +02:00
if self.__name__ not in cache:
2014-11-19 21:35:51 +01:00
cache[self.__name__] = LRUDict(cache_size())
2012-09-24 22:16:53 +02:00
return cache[self.__name__]
def __getattr__(self, name):
try:
return super(ModelStorage, self).__getattr__(name)
except AttributeError:
2016-02-07 16:46:16 +01:00
if self.id is None or self.id < 0:
2012-09-24 22:16:53 +02:00
raise
2016-03-03 23:27:38 +01:00
self._local_cache.refresh()
2012-09-24 22:16:53 +02:00
# fetch the definition of the field
2013-03-20 22:49:16 +01:00
try:
2012-09-24 22:16:53 +02:00
field = self._fields[name]
2013-03-20 22:49:16 +01:00
except KeyError:
raise AttributeError('"%s" has no attribute "%s"' % (self, name))
2012-09-24 22:16:53 +02:00
try:
return self._local_cache[self.id][name]
except KeyError:
pass
try:
if field._type not in ('many2one', 'reference'):
return self._cache[self.id][name]
except KeyError:
pass
2012-09-24 22:16:53 +02:00
# build the list of fields we will fetch
ffields = {
name: field,
}
if field.loading == 'eager':
FieldAccess = Pool().get('ir.model.field.access')
fread_accesses = {}
fread_accesses.update(FieldAccess.check(self.__name__,
self._fields.keys(), 'read', access=True))
to_remove = set(x for x, y in fread_accesses.iteritems()
if not y and x != name)
2014-11-19 21:35:51 +01:00
threshold = config.getint('cache', 'field')
2012-09-24 22:16:53 +02:00
def not_cached(item):
fname, field = item
return (fname not in self._cache.get(self.id, {})
and fname not in self._local_cache.get(self.id, {}))
def to_load(item):
fname, field = item
return (field.loading == 'eager'
and fname not in to_remove)
def overrided(item):
fname, field = item
return fname in self._fields
ifields = ifilter(to_load,
ifilter(not_cached,
self._fields.iteritems()))
ifields = islice(ifields, 0, threshold)
ffields.update(ifields)
# add datetime_field
for field in ffields.values():
if hasattr(field, 'datetime_field') and field.datetime_field:
2013-03-20 22:49:16 +01:00
datetime_field = self._fields[field.datetime_field]
2012-09-24 22:16:53 +02:00
ffields[field.datetime_field] = datetime_field
# add depends of field with context
for field in ffields.values():
if field.context:
2015-04-11 13:35:28 -04:00
eval_fields = fields.get_eval_fields(field.context)
for context_field_name in eval_fields:
if context_field_name in field.depends:
continue
context_field = self._fields.get(context_field_name)
if context_field not in ffields:
ffields[context_field_name] = context_field
2012-09-24 22:16:53 +02:00
def filter_(id_):
return (name not in self._cache.get(id_, {})
and name not in self._local_cache.get(id_, {}))
def unique(ids):
s = set()
for id_ in ids:
if id_ not in s:
s.add(id_)
yield id_
2012-09-24 22:16:53 +02:00
index = self._ids.index(self.id)
ids = chain(islice(self._ids, index, None),
islice(self._ids, 0, max(index - 1, 0)))
ids = islice(unique(ifilter(filter_, ids)),
self._transaction.database.IN_MAX)
2012-09-24 22:16:53 +02:00
def instantiate(field, value, data):
if field._type in ('many2one', 'one2one', 'reference'):
if value is None or value is False:
return None
elif field._type in ('one2many', 'many2many'):
if not value:
return ()
try:
if field._type == 'reference':
model_name, record_id = value.split(',')
Model = Pool().get(model_name)
try:
value = int(record_id)
except ValueError:
return value
else:
Model = field.get_target()
except KeyError:
return value
2016-03-15 19:01:47 +01:00
transaction = Transaction()
ctx = {}
if field.context:
pyson_context = PYSONEncoder().encode(field.context)
ctx.update(PYSONDecoder(data).decode(pyson_context))
datetime_ = None
2012-09-24 22:16:53 +02:00
if getattr(field, 'datetime_field', None):
datetime_ = data.get(field.datetime_field)
ctx = {'_datetime': datetime_}
2016-03-15 19:01:47 +01:00
with transaction.set_context(**ctx):
kwargs = {}
key = (Model, freeze(ctx))
2016-03-15 19:01:47 +01:00
kwargs['_local_cache'] = model2cache.setdefault(key,
2016-03-03 23:27:38 +01:00
LRUDictTransaction(cache_size()))
2016-03-15 19:01:47 +01:00
kwargs['_ids'] = ids = model2ids.setdefault(key, [])
kwargs['_transaction_cache'] = transaction.get_cache()
kwargs['_transaction'] = transaction
2012-09-24 22:16:53 +02:00
if field._type in ('many2one', 'one2one', 'reference'):
2016-03-15 19:01:47 +01:00
value = int(value)
2012-09-24 22:16:53 +02:00
ids.append(value)
2016-03-15 19:01:47 +01:00
return Model(value, **kwargs)
2012-09-24 22:16:53 +02:00
elif field._type in ('one2many', 'many2many'):
2016-03-15 19:01:47 +01:00
ids.extend(int(x) for x in value)
return tuple(Model(id, **kwargs) for id in value)
2012-09-24 22:16:53 +02:00
model2ids = {}
model2cache = {}
# Read the data
with Transaction().set_current_transaction(self._transaction), \
2016-03-15 19:01:47 +01:00
self._transaction.set_user(self._user), \
self._transaction.set_context(self._context):
if self.id in self._cache and name in self._cache[self.id]:
# Use values from cache
ids = islice(chain(islice(self._ids, index, None),
islice(self._ids, 0, max(index - 1, 0))),
self._transaction.database.IN_MAX)
ffields = {name: ffields[name]}
read_data = [{'id': i, name: self._cache[i][name]}
for i in ids
if i in self._cache and name in self._cache[i]]
else:
read_data = self.read(list(ids), ffields.keys())
2012-09-24 22:16:53 +02:00
# create browse records for 'remote' models
for data in read_data:
2012-09-24 22:16:53 +02:00
for fname, field in ffields.iteritems():
fvalue = data[fname]
2012-09-24 22:16:53 +02:00
if field._type in ('many2one', 'one2one', 'one2many',
'many2many', 'reference'):
fvalue = instantiate(field, data[fname], data)
2012-09-24 22:16:53 +02:00
if data['id'] == self.id and fname == name:
value = fvalue
if (field._type not in ('many2one', 'one2one', 'one2many',
'many2many', 'reference', 'binary')
and not isinstance(field, fields.Function)):
continue
2012-09-24 22:16:53 +02:00
if data['id'] not in self._local_cache:
self._local_cache[data['id']] = {}
self._local_cache[data['id']][fname] = fvalue
if (field._type not in ('many2one', 'reference')
or field.context
or getattr(field, 'datetime_field', None)
or isinstance(field, fields.Function)):
del data[fname]
2012-09-24 22:16:53 +02:00
if data['id'] not in self._cache:
self._cache[data['id']] = {}
self._cache[data['id']].update(data)
return value
@property
def _save_values(self):
values = {}
if not self._values:
return values
for fname, value in self._values.iteritems():
2013-03-20 22:49:16 +01:00
field = self._fields[fname]
2012-09-24 22:16:53 +02:00
if field._type in ('many2one', 'one2one', 'reference'):
if value:
2016-02-07 16:46:16 +01:00
if ((value.id is None or value.id < 0)
and field._type != 'reference'):
2012-09-24 22:16:53 +02:00
value.save()
if field._type == 'reference':
value = str(value)
else:
value = value.id
if field._type in ('one2many', 'many2many'):
targets = value
2016-02-07 16:46:16 +01:00
if self.id is not None and self.id >= 0:
2013-12-20 12:32:07 +01:00
_values, self._values = self._values, None
try:
to_remove = [t.id for t in getattr(self, fname)]
finally:
self._values = _values
else:
to_remove = []
to_add = []
to_create = []
to_write = []
2012-09-24 22:16:53 +02:00
for target in targets:
2016-02-07 16:46:16 +01:00
if target.id is None or target.id < 0:
if field._type == 'one2many':
# Don't store old target link
setattr(target, field.field, None)
2013-12-20 12:32:07 +01:00
to_create.append(target._save_values)
2012-09-24 22:16:53 +02:00
else:
2013-12-20 12:32:07 +01:00
if target.id in to_remove:
to_remove.remove(target.id)
else:
to_add.append(target.id)
2014-04-18 19:31:47 +02:00
target_values = target._save_values
if target_values:
to_write.append(
('write', [target.id], target_values))
2013-12-20 12:32:07 +01:00
value = []
if to_remove:
value.append(('remove', to_remove))
if to_add:
value.append(('add', to_add))
if to_create:
value.append(('create', to_create))
if to_write:
value.extend(to_write)
2012-09-24 22:16:53 +02:00
values[fname] = value
return values
2014-11-01 11:52:55 +01:00
@dualmethod
def save(cls, records):
if not records:
return
values = {}
save_values = {}
to_create = []
to_write = []
transaction = records[0]._transaction
2014-11-01 11:52:55 +01:00
user = records[0]._user
context = records[0]._context
for record in records:
assert transaction == record._transaction
2014-11-01 11:52:55 +01:00
assert user == record._user
assert context == record._context
save_values[record] = record._save_values
values[record] = record._values
record._values = None
if record.id is None or record.id < 0:
to_create.append(record)
elif save_values[record]:
to_write.append(record)
transaction = Transaction()
try:
with transaction.set_current_transaction(transaction), \
2014-11-01 11:52:55 +01:00
transaction.set_user(user), \
transaction.set_context(context):
if to_create:
news = cls.create([save_values[r] for r in to_create])
for record, new in izip(to_create, news):
record._ids.remove(record.id)
2016-03-04 00:19:18 +01:00
record._id = new.id
2014-11-01 11:52:55 +01:00
record._ids.append(record.id)
if to_write:
cls.write(*sum(
(([r], save_values[r]) for r in to_write), ()))
except:
for record in records:
record._values = values[record]
raise
for record in records:
record._init_values = None
2012-09-24 22:16:53 +02:00
class EvalEnvironment(dict):
def __init__(self, record, Model):
super(EvalEnvironment, self).__init__()
self._record = record
self._model = Model
def __getitem__(self, item):
if item.startswith('_parent_'):
field = item[8:]
2013-03-20 22:49:16 +01:00
model_name = self._model._fields[field].model_name
2012-09-24 22:16:53 +02:00
ParentModel = Pool().get(model_name)
return EvalEnvironment(getattr(self._record, field), ParentModel)
2013-03-20 22:49:16 +01:00
if item in self._model._fields:
2012-09-24 22:16:53 +02:00
value = getattr(self._record, item)
if isinstance(value, Model):
if self._model._fields[item]._type == 'reference':
return str(value)
2012-09-24 22:16:53 +02:00
return value.id
2012-10-09 17:19:36 +02:00
elif isinstance(value, (list, tuple)):
2012-09-24 22:16:53 +02:00
return [r.id for r in value]
else:
return value
return super(EvalEnvironment, self).__getitem__(item)
def __getattr__(self, item):
try:
return self.__getitem__(item)
except KeyError, exception:
raise AttributeError(*exception.args)
def get(self, item, default=None):
try:
return self.__getitem__(item)
except Exception:
pass
return super(EvalEnvironment, self).get(item, default)
def __nonzero__(self):
return bool(self._record)