Files
tryton/trytond/model/modelstorage.py
T

1438 lines
58 KiB
Python
Raw Normal View History

2009-02-02 13:08:25 +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
2009-06-04 00:11:42 +02:00
import logging
2012-09-24 22:16:53 +02:00
import contextlib
import traceback
import sys
import csv
import warnings
try:
import cStringIO as StringIO
2010-02-18 18:50:35 +01:00
except ImportError:
import StringIO
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
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
2012-09-24 22:16:53 +02:00
from trytond.tools import safe_eval, reduce_domain
2010-08-19 22:01:12 +02:00
from trytond.pyson import PYSONEncoder, PYSONDecoder, PYSON
2012-09-24 22:16:53 +02:00
from trytond.const import OPERATORS, RECORD_CACHE_SIZE, BROWSE_FIELD_TRESHOLD
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
2011-06-14 11:44:26 +02:00
from trytond.cache import LRUDict
from trytond.config import CONFIG
2012-09-24 22:16:53 +02:00
from trytond.rpc import RPC
from .modelview import ModelView
2009-02-02 13:08:25 +01:00
2012-09-24 22:16:53 +02:00
__all__ = ['ModelStorage']
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(),
'write': RPC(readonly=False, instantiate=0),
'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
2012-09-24 22:16:53 +02:00
ModelAccess.check(cls.__name__, 'read')
ModelFieldAccess.check(cls.__name__,
fields_names or cls._fields.keys(), 'read')
2009-02-02 13:08:25 +01:00
return []
2012-09-24 22:16:53 +02:00
@classmethod
def write(cls, records, values):
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
2012-09-24 22:16:53 +02:00
ModelAccess.check(cls.__name__, 'write')
2012-09-27 11:33:34 +02:00
ModelFieldAccess.check(cls.__name__,
[x for x in values if x in cls._fields], 'write')
2012-09-24 22:16:53 +02:00
if not cls.check_xml_record(records, values):
cls.raise_user_error('write_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 local cache
for record in records:
local_cache = record._local_cache.get(record.id)
if local_cache:
local_cache.clear()
2009-08-11 11:13:14 +02:00
# Clean cursor cache
2012-09-24 22:16:53 +02:00
for cache in Transaction().cursor.cache.itervalues():
if cls.__name__ in cache:
for record in records:
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
2009-08-11 11:13:14 +02:00
# Clean cursor cache
2010-08-19 22:01:12 +02:00
for cache in Transaction().cursor.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()
data_o2m = {}
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_o2m[field_name] = data[field_name]
data[field_name] = None
2009-02-02 13:08:25 +01:00
elif ftype == 'many2many':
if data[field_name]:
data[field_name] = [('set', data[field_name])]
if 'id' in data:
del data['id']
return data, data_o2m
2009-02-02 13:08:25 +01:00
2009-02-04 17:08:14 +01:00
new_ids = {}
fields_names = [n for n, f in cls._fields.iteritems()
if (not isinstance(f, fields.Function)
or isinstance(f, fields.Property))]
2012-09-24 22:16:53 +02:00
ids = map(int, records)
datas = cls.read(ids, fields_names=fields_names)
field_defs = cls.fields_get(fields_names=fields_names)
2013-01-06 22:18:13 +01:00
data_ids = []
to_create = []
o2m_to_create = []
2009-02-02 13:08:25 +01:00
for data in datas:
2013-01-06 22:18:13 +01:00
data_ids.append(data['id'])
2010-02-18 18:50:35 +01:00
data, data_o2m = convert_data(field_defs, data)
2013-01-06 22:18:13 +01:00
to_create.append(data)
o2m_to_create.append(data_o2m)
new_records = cls.create(to_create)
2013-01-06 22:18:13 +01:00
for data_id, new_record, data_o2m in izip(data_ids, new_records,
o2m_to_create):
new_ids[data_id] = new_record.id
for field_name in data_o2m:
2012-09-24 22:16:53 +02:00
Relation = pool.get(
2010-02-18 18:50:35 +01:00
field_defs[field_name]['relation'])
relation_field = field_defs[field_name]['relation_field']
if relation_field:
2013-03-20 22:49:16 +01:00
field = Relation._fields[relation_field]
2013-02-04 20:02:37 +01:00
if field._type == 'reference':
value = str(new_record)
else:
value = new_record.id
2012-09-24 22:16:53 +02:00
Relation.copy(Relation.browse(data_o2m[field_name]),
2013-02-04 20:02:37 +01:00
default={relation_field: value})
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')
2012-09-24 22:16:53 +02:00
return cls.read(map(int, records), fields_names)
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]
#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):
'''
Yield tuples (id, rec_name, icon) for text
'''
# TODO improve search clause
for record in cls.search([
('rec_name', 'ilike', '%%%s%%' % text),
]):
yield record.id, record.rec_name, None
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
'''
2012-09-24 22:16:53 +02:00
ids = map(int, ids)
2011-06-14 11:44:26 +02:00
local_cache = LRUDict(RECORD_CACHE_SIZE)
2012-09-24 22:16:53 +02:00
return [cls(int(x), _ids=ids, _local_cache=local_cache) 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]
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
2012-09-24 22:16:53 +02:00
value = getattr(value, field_name)
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.
2012-09-24 22:16:53 +02:00
It returns a tuple with
2009-02-02 13:08:25 +01:00
- the number of records imported
- the last values if failed
- the exception if failed
- the warning if failed
'''
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-04-01 23:08:15 +02:00
2012-09-24 22:16:53 +02:00
def process_lines(data, prefix, fields_def, position=0):
def warn(msgname, *args):
2012-09-24 22:16:53 +02:00
msg = cls.raise_user_error(msgname, args,
2010-08-19 22:01:12 +02:00
raise_exception=False)
logger.warn(msg)
def get_selection(selection, value):
res = None
if not isinstance(selection, (tuple, list)):
2012-09-24 22:16:53 +02:00
selection = getattr(cls, selection)()
2010-02-18 18:50:35 +01:00
for key, _ in selection:
if str(key) == value:
res = key
break
if value and not res:
warn('not_found_in_selection', value, '/'.join(field))
return res
def get_many2one(relation, value):
if not value:
return None
2012-09-24 22:16:53 +02:00
Relation = pool.get(relation)
res = Relation.search([
('rec_name', '=', value),
2010-08-19 22:01:12 +02:00
], limit=2)
if len(res) < 1:
warn('relation_not_found', value, relation)
res = None
elif len(res) > 1:
warn('too_many_relations_found', value, relation)
res = None
else:
res = res[0].id
return res
def get_many2many(relation, value):
if not value:
return None
res = []
2012-09-24 22:16:53 +02:00
Relation = pool.get(relation)
for word in csv.reader(StringIO.StringIO(value), delimiter=',',
quoting=csv.QUOTE_NONE, escapechar='\\').next():
2012-09-24 22:16:53 +02:00
res2 = Relation.search([
('rec_name', '=', word),
2010-08-19 22:01:12 +02:00
], limit=2)
if len(res2) < 1:
warn('relation_not_found', word, relation)
elif len(res2) > 1:
warn('too_many_relations_found', word, relation)
else:
res.extend(res2)
if len(res):
res = [('set', [x.id for x in res])]
return res
2010-12-03 18:01:46 +01:00
def get_one2one(relation, value):
return ('set', get_many2one(relation, value))
def get_reference(value):
if not value:
return None
try:
relation, value = value.split(',', 1)
2010-07-08 19:53:39 +02:00
except Exception:
warn('reference_syntax_error', value, '/'.join(field))
return None
2012-09-24 22:16:53 +02:00
Relation = pool.get(relation)
res = Relation.search([
('rec_name', '=', value),
2010-08-19 22:01:12 +02:00
], limit=2)
if len(res) < 1:
warn('relation_not_found', value, relation)
res = None
elif len(res) > 1:
warn('too_many_relations_found', value, relation)
res = None
else:
2012-10-09 17:42:02 +02:00
res = '%s,%s' % (relation, res[0].id)
return res
def get_by_id(value):
if not value:
return None
relation = None
2010-02-18 18:50:35 +01:00
ftype = fields_def[field[-1][:-3]]['type']
if ftype == 'many2many':
value = csv.reader(StringIO.StringIO(value), delimiter=',',
quoting=csv.QUOTE_NONE, escapechar='\\').next()
2010-02-18 18:50:35 +01:00
elif ftype == 'reference':
try:
relation, value = value.split(',', 1)
2010-07-08 19:53:39 +02:00
except Exception:
warn('reference_syntax_error', value, '/'.join(field))
return None
value = [value]
else:
value = [value]
res_ids = []
for word in value:
try:
module, xml_id = word.rsplit('.', 1)
2010-07-08 19:53:39 +02:00
except Exception:
warn('xml_id_syntax_error', word, '/'.join(field))
continue
2012-09-24 22:16:53 +02:00
db_id = ModelData.get_id(module, xml_id)
res_ids.append(db_id)
2010-02-18 18:50:35 +01:00
if ftype == 'many2many' and res_ids:
return [('set', res_ids)]
2010-02-18 18:50:35 +01:00
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
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'):
row[field[0][:-3]] = get_by_id(value)
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':
2013-09-06 22:32:37 +02:00
res = (datetime.date(
*time.strptime(value, '%Y-%m-%d')[:3])
if value else None)
elif field_type == 'datetime':
2013-09-06 22:32:37 +02:00
res = (datetime.datetime(
*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6])
if value else None)
elif field_type == 'selection':
res = get_selection(this_field_def['selection'], value)
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':
res = get_reference(value)
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')
# logger for collecting warnings for the client
2013-10-05 18:18:42 +02:00
logger = logging.Logger("import")
warning_stream = StringIO.StringIO()
2013-10-05 18:18:42 +02:00
logger.addHandler(logging.StreamHandler(warning_stream))
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
done = 0
warning = ''
2012-09-24 22:16:53 +02:00
while len(data):
2009-02-02 13:08:25 +01:00
res = {}
try:
2010-02-18 18:50:35 +01:00
(res, _, translate) = \
2013-02-27 22:50:31 +01:00
process_lines(data, [], fields_def)
warning = warning_stream.getvalue()
2009-02-02 13:08:25 +01:00
if warning:
2010-08-19 22:01:12 +02:00
# XXX should raise Exception
Transaction().cursor.rollback()
2009-02-02 13:08:25 +01:00
return (-1, res, warning, '')
2013-01-06 22:18:13 +01:00
new_id, = cls.create([res])
2009-02-02 13:08:25 +01:00
for lang in translate:
2010-08-19 22:01:12 +02:00
with Transaction().set_context(language=lang):
2012-09-24 22:16:53 +02:00
cls.write(new_id, translate[lang])
2009-02-02 13:08:25 +01:00
except Exception, exp:
logger = logging.getLogger('import')
2009-02-02 13:08:25 +01:00
logger.error(exp)
2010-08-19 22:01:12 +02:00
# XXX should raise Exception
Transaction().cursor.rollback()
tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
warning = '%s\n%s' % (tb_s, warning)
2010-03-10 20:41:59 +01:00
return (-1, res, exp, warning)
2009-02-02 13:08:25 +01:00
done += 1
return (done, 0, 0, 0)
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
2010-08-19 22:01:12 +02:00
with Transaction().set_user(0):
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:
if not model_data.values:
2010-08-19 22:01:12 +02:00
continue
xml_values = safe_eval(model_data.values, {
2010-08-19 22:01:12 +02:00
'Decimal': Decimal,
'datetime': datetime,
})
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_name, cls.__name__)
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
2009-04-06 12:10:11 +02:00
error_args = (model_field.field_description,
model_field.model.name)
return error_args
@classmethod
def validate(cls, records):
pass
2012-09-24 22:16:53 +02:00
@classmethod
def _validate(cls, records):
2011-07-03 13:20:58 +02:00
pool = Pool()
# Ensure that records are readable
with Transaction().set_user(0, set_context=True):
records = cls.browse(records)
2012-09-24 22:16:53 +02:00
def call(name):
method = getattr(cls, name)
if not hasattr(method, 'im_self') or method.im_self:
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
2011-07-03 13:20:58 +02:00
if not 'res.user' in pool.object_name_list() \
2010-08-19 22:01:12 +02:00
or Transaction().user == 0:
2009-02-02 13:08:25 +01:00
ctx_pref = {
}
else:
2012-09-24 22:16:53 +02:00
User = pool.get('res.user')
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
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():
2010-08-19 22:01:12 +02:00
if isinstance(field, fields.Function) and \
not field.setter:
continue
# validate domain
2010-12-03 18:01:46 +01:00
if (field._type in
('many2one', 'many2many', 'one2many', 'one2one')
2013-02-27 22:50:31 +01:00
and field.domain):
2010-08-19 22:01:12 +02:00
if field._type in ('many2one', 'one2many'):
2012-09-24 22:16:53 +02:00
Relation = pool.get(field.model_name)
2010-08-19 22:01:12 +02:00
else:
2012-09-24 22:16:53 +02:00
Relation = field.get_target()
2010-08-19 22:01:12 +02:00
if is_pyson(field.domain):
pyson_domain = PYSONEncoder().encode(field.domain)
2009-02-02 13:08:25 +01:00
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
domain = PYSONDecoder(env).decode(pyson_domain)
relation_ids = []
2012-09-24 22:16:53 +02:00
if getattr(record, field_name):
2013-05-18 19:13:12 +02:00
if field._type in ('many2one', 'one2one'):
2012-09-24 22:16:53 +02:00
relation_ids.append(
getattr(record, field_name).id)
2010-08-19 22:01:12 +02:00
else:
relation_ids.extend(
2012-09-24 22:16:53 +02:00
[x.id for x in getattr(record,
field_name)])
if relation_ids and not Relation.search([
2010-08-19 22:01:12 +02:00
'AND',
[('id', 'in', relation_ids)],
domain,
]):
2012-09-24 22:16:53 +02:00
cls.raise_user_error(
2010-08-19 22:01:12 +02:00
'domain_validation_record',
2012-09-24 22:16:53 +02:00
error_args=cls._get_error_args(
2010-08-19 22:01:12 +02:00
field_name))
else:
relation_ids = []
for record in records:
2012-09-24 22:16:53 +02:00
if getattr(record, field_name):
2013-05-18 19:13:12 +02:00
if field._type in ('many2one', 'one2one'):
2012-09-24 22:16:53 +02:00
relation_ids.append(
getattr(record, field_name).id)
2010-08-19 22:01:12 +02:00
else:
relation_ids.extend(
2012-09-24 22:16:53 +02:00
[x.id for x in getattr(record,
field_name)])
2010-08-19 22:01:12 +02:00
if relation_ids:
2012-09-24 22:16:53 +02:00
finds = Relation.search([
2010-08-19 22:01:12 +02:00
'AND',
[('id', 'in', relation_ids)],
field.domain,
])
2012-09-24 22:16:53 +02:00
find_ids = map(int, finds)
2010-08-19 22:01:12 +02:00
if not set(relation_ids) == set(find_ids):
2012-09-24 22:16:53 +02:00
cls.raise_user_error(
2010-08-19 22:01:12 +02:00
'domain_validation_record',
2012-09-24 22:16:53 +02:00
error_args=cls._get_error_args(
2010-08-19 22:01:12 +02:00
field_name))
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
2012-09-24 22:16:53 +02:00
if (len(getattr(record, field_name) or '')
> field_size >= 0):
cls.raise_user_error(
2012-04-01 23:08:15 +02:00
'size_validation_record',
2012-09-24 22:16:53 +02:00
error_args=cls._get_error_args(field_name))
2009-02-02 13:08:25 +01:00
def digits_test(value, digits, field_name):
def raise_user_error():
2012-09-24 22:16:53 +02:00
cls.raise_user_error('digits_validation_record',
error_args=cls._get_error_args(field_name))
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()
elif CONFIG.options['db_type'] != 'mysql':
if not (round(value, digits[1]) == float(value)):
raise_user_error()
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 field.selection_change_with:
test = sel_func(record)
else:
test = sel_func()
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:
cls.raise_user_error('selection_validation_record',
error_args=cls._get_error_args(field_name))
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():
2012-09-24 22:16:53 +02:00
cls.raise_user_error('time_format_validation_record',
error_args=cls._get_error_args(field_name))
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():
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',):
vals[field] = [('set', defaults[field])]
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)
super(ModelStorage, self).__init__(id, **kwargs)
self._cursor = Transaction().cursor
self._user = Transaction().user
self._context = Transaction().context
if _ids is not None:
self._ids = _ids
assert id in _ids
else:
self._ids = [self.id]
self._cursor_cache = self._cursor.get_cache(self._context)
if _local_cache is not None:
self._local_cache = _local_cache
else:
self._local_cache = LRUDict(RECORD_CACHE_SIZE)
2012-11-28 22:37:57 +01:00
self._local_cache.counter = Transaction().counter
2012-09-24 22:16:53 +02:00
@property
def _cache(self):
cache = self._cursor_cache
if self.__name__ not in cache:
cache[self.__name__] = LRUDict(RECORD_CACHE_SIZE)
return cache[self.__name__]
def __getattr__(self, name):
try:
return super(ModelStorage, self).__getattr__(name)
except AttributeError:
if self.id < 0:
raise
counter = Transaction().counter
2012-11-28 22:37:57 +01:00
if self._local_cache.counter != counter:
2012-09-24 22:16:53 +02:00
self._local_cache.clear()
2012-11-28 22:37:57 +01:00
self._local_cache.counter = counter
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)
threshold = BROWSE_FIELD_TRESHOLD
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
def filter_(id_):
return (name not in self._cache.get(id_, {})
and name not in self._local_cache.get(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(ifilter(filter_, ids), self._cursor.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
ctx = {}
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_}
2012-11-22 22:54:07 +01:00
with Transaction().set_context(**ctx):
local_cache = model2cache.setdefault((Model, datetime_),
2012-09-24 22:16:53 +02:00
LRUDict(RECORD_CACHE_SIZE))
ids = model2ids.setdefault((Model, datetime_), [])
2012-09-24 22:16:53 +02:00
if field._type in ('many2one', 'one2one', 'reference'):
ids.append(value)
return Model(value, _ids=ids, _local_cache=local_cache)
elif field._type in ('one2many', 'many2many'):
ids.extend(value)
return tuple(Model(id, _ids=ids, _local_cache=local_cache)
for id in value)
model2ids = {}
model2cache = {}
# Read the data
with contextlib.nested(Transaction().set_cursor(self._cursor),
Transaction().set_user(self._user),
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._cursor.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')
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 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:
if value.id < 0 and field._type != 'reference':
value.save()
if field._type == 'reference':
value = str(value)
else:
value = value.id
if field._type in ('one2many', 'many2many'):
targets = value
value = [
('set', []),
]
for target in targets:
if target.id < 0:
2013-01-06 22:18:13 +01:00
value.append(('create', [target._save_values]))
2012-09-24 22:16:53 +02:00
else:
value[0][1].append(target.id)
if target._save_values:
value.append(
('write', [target.id], target._save_values))
2012-09-24 22:16:53 +02:00
values[fname] = value
return values
def save(self):
2013-03-12 10:34:14 +01:00
save_values = self._save_values
values = self._values
2012-09-24 22:16:53 +02:00
self._values = None
2013-03-12 10:34:14 +01:00
if save_values or self.id < 0:
try:
with contextlib.nested(Transaction().set_cursor(self._cursor),
Transaction().set_user(self._user),
Transaction().set_context(self._context)):
if self.id < 0:
self._ids.remove(self.id)
try:
self.id = self.create([save_values])[0].id
finally:
self._ids.append(self.id)
else:
self.write([self], save_values)
except:
self._values = values
raise
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)