1031 lines
43 KiB
Python
1031 lines
43 KiB
Python
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
#this repository contains the full copyright notices and license terms.
|
|
from trytond.model import Model
|
|
from trytond.model import fields
|
|
from trytond.model.browse import BrowseRecordList, BrowseRecord, BrowseRecordNull
|
|
from trytond.model.browse import EvalEnvironment
|
|
import datetime
|
|
import time
|
|
from decimal import Decimal
|
|
|
|
OPERATORS = (
|
|
'child_of',
|
|
'not child_of',
|
|
'=',
|
|
'!=',
|
|
'like',
|
|
'not like',
|
|
'ilike',
|
|
'not ilike',
|
|
'in',
|
|
'not in',
|
|
'<=',
|
|
'>=',
|
|
'<',
|
|
'>',
|
|
)
|
|
|
|
|
|
|
|
class ModelStorage(Model):
|
|
"""
|
|
Define a model with storage capability in Tryton.
|
|
"""
|
|
|
|
id = fields.Integer('ID', readonly=True)
|
|
create_uid = fields.Many2One('res.user', 'Create User', required=True,
|
|
readonly=True)
|
|
create_date = fields.DateTime('Create Date', required=True, readonly=True)
|
|
write_uid = fields.Many2One('res.user', 'Write User', readonly=True)
|
|
write_date = fields.DateTime('Write Date', readonly=True)
|
|
|
|
def __init__(self):
|
|
super(ModelStorage, self).__init__()
|
|
self._rpc_allowed += [
|
|
'create',
|
|
'read',
|
|
'write',
|
|
'delete',
|
|
'copy',
|
|
'search',
|
|
'search_count',
|
|
'search_read',
|
|
'name_get',
|
|
'name_search',
|
|
'export_data',
|
|
'import_data',
|
|
]
|
|
self._constraints = []
|
|
|
|
def default_create_uid(self, cursor, user, context=None):
|
|
"Default value for uid field"
|
|
return int(user)
|
|
|
|
def default_create_date(self, cursor, user, context=None):
|
|
"Default value for create_date field"
|
|
return datetime.datetime.today()
|
|
|
|
def default_sequence(self, cursor, user, context=None):
|
|
'''
|
|
Return the default value for sequence field.
|
|
'''
|
|
cursor.execute('SELECT MAX(sequence) ' \
|
|
'FROM "' + self._table + '"')
|
|
res = cursor.fetchone()
|
|
if res:
|
|
return res[0]
|
|
return 0
|
|
|
|
def create(self, cursor, user, values, context=None):
|
|
'''
|
|
Create records.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param values: a dictionary with fields names as key
|
|
and created values as value
|
|
:param context: the context
|
|
:return: the id of the created record
|
|
'''
|
|
model_access_obj = self.pool.get('ir.model.access')
|
|
model_access_obj.check(cursor, user, self._name, 'create',
|
|
context=context)
|
|
return False
|
|
|
|
def read(self, cursor, user, ids, fields_names=None, context=None):
|
|
'''
|
|
Read records.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param ids: a list of ids or an id
|
|
:param fields_names: fields names to read if None read all fields
|
|
:param context: the context
|
|
:return: a list of dictionnary or a dictionnary if ids is an id
|
|
the dictionnaries will have fields names as key
|
|
and fields value as value
|
|
'''
|
|
model_access_obj = self.pool.get('ir.model.access')
|
|
|
|
model_access_obj.check(cursor, user, self._name, 'read',
|
|
context=context)
|
|
if isinstance(ids, (int, long)):
|
|
return {}
|
|
return []
|
|
|
|
def write(self, cursor, user, ids, values, context=None):
|
|
'''
|
|
Write values on records
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param ids: a list of ids or an id
|
|
:param values: a dictionary with fields names as key
|
|
and written values as value
|
|
:param context: the context
|
|
:return: True if succeed
|
|
'''
|
|
model_access_obj = self.pool.get('ir.model.access')
|
|
rule_group_obj = self.pool.get('ir.rule.group')
|
|
rule_obj = self.pool.get('ir.rule')
|
|
|
|
model_access_obj.check(cursor, user, self._name, 'write',
|
|
context=context)
|
|
if not self.check_xml_record(cursor, user, ids, values,
|
|
context=context):
|
|
self.raise_user_error(cursor, 'write_xml_record',
|
|
error_description='xml_record_desc',
|
|
context=context)
|
|
# Restart rule cache
|
|
if rule_group_obj.search(cursor, 0, [
|
|
('model.model', '=', self._name),
|
|
], context=context):
|
|
rule_obj.domain_get(cursor.dbname)
|
|
return False
|
|
|
|
def delete(self, cursor, user, ids, context=None):
|
|
'''
|
|
Delete records.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param ids: a list of ids or an id
|
|
:param context: the context
|
|
:return: True if succeed
|
|
'''
|
|
model_access_obj = self.pool.get('ir.model.access')
|
|
|
|
model_access_obj.check(cursor, user, self._name, 'delete',
|
|
context=context)
|
|
if not self.check_xml_record(cursor, user, ids, None, context=context):
|
|
self.raise_user_error(cursor, 'delete_xml_record',
|
|
error_description='xml_record_desc',
|
|
context=context)
|
|
return False
|
|
|
|
def copy(self, cursor, user, ids, default=None, context=None):
|
|
'''
|
|
Duplicate the record in ids.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param ids: a list of ids or an id
|
|
:param default: a dictionnary with field name as keys and
|
|
new value for the field as value
|
|
:param context: the context
|
|
:return: a list of new ids or the new id
|
|
'''
|
|
lang_obj = self.pool.get('ir.lang')
|
|
if default is None:
|
|
default = {}
|
|
if context is None:
|
|
context = {}
|
|
|
|
int_id = False
|
|
if isinstance(ids, (int, long)):
|
|
int_id = True
|
|
ids = [ids]
|
|
|
|
if 'state' not in default:
|
|
if 'state' in self._defaults:
|
|
default['state'] = self._defaults['state'](cursor, user,
|
|
context)
|
|
|
|
def convert_data(fields, data):
|
|
data = data.copy()
|
|
data_o2m = {}
|
|
for field_name in fields:
|
|
ftype = fields[field_name]['type']
|
|
|
|
if field_name in (
|
|
'create_date',
|
|
'create_uid',
|
|
'write_date',
|
|
'write_uid',
|
|
):
|
|
del data[field_name]
|
|
|
|
if field_name in default:
|
|
data[field_name] = default[field_name]
|
|
elif ftype == 'function':
|
|
del data[field_name]
|
|
elif ftype == 'many2one':
|
|
try:
|
|
data[field_name] = data[field_name] and \
|
|
data[field_name][0]
|
|
except:
|
|
pass
|
|
elif ftype in ('one2many',):
|
|
if data[field_name]:
|
|
data_o2m[field_name] = data[field_name]
|
|
data[field_name] = False
|
|
elif ftype == 'many2many':
|
|
if data[field_name]:
|
|
data[field_name] = [('set', data[field_name])]
|
|
if 'id' in data:
|
|
del data['id']
|
|
for i in self._inherits:
|
|
if self._inherits[i] in data:
|
|
del data[self._inherits[i]]
|
|
return data, data_o2m
|
|
|
|
new_ids = {}
|
|
datas = self.read(cursor, user, ids, context=context)
|
|
fields = self.fields_get(cursor, user, context=context)
|
|
for data in datas:
|
|
data_id = data['id']
|
|
data, data_o2m = convert_data(fields, data)
|
|
new_ids[data_id] = self.create(cursor, user, data, context=context)
|
|
for field_name in data_o2m:
|
|
relation_model = self.pool.get(fields[field_name]['relation'])
|
|
relation_field = fields[field_name]['relation_field']
|
|
relation_model.copy(cursor, user, data_o2m[field_name],
|
|
default={relation_field: new_ids[data_id]},
|
|
context=context)
|
|
|
|
fields_translate = {}
|
|
for field_name, field in fields.iteritems():
|
|
if field_name in self._columns and \
|
|
self._columns[field_name].translate:
|
|
fields_translate[field_name] = field
|
|
elif field_name in self._inherit_fields and \
|
|
self._inherit_fields[field_name][2].translate:
|
|
fields_translate[field_name] = field
|
|
|
|
if fields_translate:
|
|
lang_ids = lang_obj.search(cursor, user, [
|
|
('translatable', '=', True),
|
|
], context=context)
|
|
if lang_ids:
|
|
lang_ids += lang_obj.search(cursor, user, [
|
|
('code', '=', 'en_US'),
|
|
], context=context)
|
|
langs = lang_obj.browse(cursor, user, lang_ids, context=context)
|
|
for lang in langs:
|
|
ctx = context.copy()
|
|
ctx['language'] = lang.code
|
|
datas = self.read(cursor, user, ids,
|
|
fields_names=fields_translate.keys() + ['id'],
|
|
context=ctx)
|
|
for data in datas:
|
|
data_id = data['id']
|
|
data, _ = convert_data(fields_translate, data)
|
|
self.write(cursor, user, new_ids[data_id], data, context=ctx)
|
|
if int_id:
|
|
return new_ids.values()[0]
|
|
return new_ids.values()
|
|
|
|
def search(self, cursor, user, domain, offset=0, limit=None, order=None,
|
|
context=None, count=False):
|
|
'''
|
|
Return a list of id that match the clauses defined in args.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param domain: a list of tuples or lists
|
|
lists are construct like this:
|
|
['operator', args, args, ...]
|
|
operator can be 'AND' or 'OR', if it is missing the default
|
|
value will be 'AND'
|
|
tuples are construct like this:
|
|
('field name', 'operator', value)
|
|
field name: is a field name from the model or a relational field
|
|
by using '.' as separator.
|
|
operator must be in OPERATORS
|
|
:param offset: an integer to specify the offset for the result
|
|
:param limit: an integer to specify the number of result
|
|
:param order: a list of tuple that are constructed like this:
|
|
('field name', 'DESC|ASC')
|
|
it allow to specify the order of result
|
|
:param context: the context
|
|
:param count: a boolean to return only the len of the result
|
|
:return: a list of ids or an interger
|
|
'''
|
|
if count:
|
|
return 0
|
|
return []
|
|
|
|
def search_count(self, cursor, user, domain, context=None):
|
|
'''
|
|
Return the number of record that match the domain. (See search)
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param domain: a domain like in search
|
|
:param context: the context
|
|
:return: an integer
|
|
'''
|
|
res = self.search(cursor, user, domain, context=context, count=True)
|
|
if isinstance(res, list):
|
|
return len(res)
|
|
return res
|
|
|
|
def search_read(self, cursor, user, domain, offset=0, limit=None, order=None,
|
|
context=None, fields_names=None):
|
|
'''
|
|
Call search function and read in once.
|
|
Usefull for the client to reduce the number of calls.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param domain: a domain like in search
|
|
:param offset: an integer to specify the offset for the result
|
|
:param limit: an integer to specify the number of result
|
|
:param order: a list of tuple that are constructed like this:
|
|
('field name', 'DESC|ASC')
|
|
it allow to specify the order of result
|
|
:param context: the context
|
|
:param fields_names: fields names to read if None read all fields
|
|
:return: a list of dictionnary or a dictionnary if limit is 1
|
|
the dictionnaries will have fields names as key
|
|
and fields value as value
|
|
'''
|
|
ids = self.search(cursor, user, domain, offset=offset, limit=limit,
|
|
order=order, context=context)
|
|
if limit == 1:
|
|
ids = ids[0]
|
|
return self.read(cursor, user, ids, fields_names=fields_names,
|
|
context=context)
|
|
|
|
def _search_domain_active(self, domain, active_test=True, context=None):
|
|
if context is None:
|
|
context = {}
|
|
|
|
domain = domain[:]
|
|
# if the object has a field named 'active', filter out all inactive
|
|
# records unless they were explicitely asked for
|
|
if not (('active' in self._columns or \
|
|
'active' in self._inherit_fields.keys()) \
|
|
and (active_test and context.get('active_test', True))):
|
|
return domain
|
|
|
|
def process(domain):
|
|
i = 0
|
|
active_found = False
|
|
while i < len(domain):
|
|
if isinstance(domain[i], list):
|
|
domain[i] = process(domain[i])
|
|
if isinstance(domain[i], tuple):
|
|
if domain[i][0] == 'active':
|
|
active_found = True
|
|
i += 1
|
|
if not active_found:
|
|
if domain and ((isinstance(domain[0], basestring) \
|
|
and domain[0] == 'AND') \
|
|
or (not isinstance(domain[0], basestring))):
|
|
domain.append(('active', '=', 1))
|
|
else:
|
|
domain = ['AND', domain, ('active', '=', 1)]
|
|
return domain
|
|
return process(domain)
|
|
|
|
def name_get(self, cursor, user, ids, context=None):
|
|
'''
|
|
Return a list of tuple for each ids.
|
|
The tuple contains the id and the name of the record.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param ids: a list of ids or an id
|
|
:param context: the context
|
|
:return: a list of tuple for each ids
|
|
'''
|
|
if not ids:
|
|
return []
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
return [(r['id'], unicode(r[self._rec_name])) for r in self.read(cursor,
|
|
user, ids, [self._rec_name], context=context)]
|
|
|
|
def name_search(self, cursor, user, name='', args=None, operator='ilike',
|
|
context=None, limit=None):
|
|
'''
|
|
Return a list of tuple like in name_get
|
|
where the name and the domain match.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param name: the name searched
|
|
:param args: a domain like in search
|
|
:param operator: the operator to use to compare name with _rec_name
|
|
:param context: the context
|
|
:param limit: a integer to limit the result
|
|
:return: a list of tuple for each ids like in name_get
|
|
'''
|
|
if args is None:
|
|
args = []
|
|
if name:
|
|
args = ['AND', args, (self._rec_name, operator, name)]
|
|
ids = self.search(cursor, user, args, limit=limit, context=context)
|
|
res = self.name_get(cursor, user, ids, context=context)
|
|
return res
|
|
|
|
def browse(self, cursor, user, ids, context=None):
|
|
'''
|
|
Return a browse a BrowseRecordList for the ids
|
|
or BrowseRecord if ids is a integer.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param ids: a list of ids or an id
|
|
:param context: the context
|
|
:return: a BrowseRecordList or a BrowseRecord
|
|
'''
|
|
cache = {}
|
|
if isinstance(ids, (int, long)):
|
|
return BrowseRecord(cursor, user, ids, self, cache,
|
|
context=context)
|
|
return BrowseRecordList([BrowseRecord(cursor, user, x, self, cache,
|
|
context=context) for x in ids], context)
|
|
|
|
def __export_row(self, cursor, user, row, fields_names, context=None):
|
|
lines = []
|
|
data = ['' for x in range(len(fields_names))]
|
|
done = []
|
|
for fpos in range(len(fields_names)):
|
|
field = fields_names[fpos]
|
|
if field:
|
|
row2 = row
|
|
i = 0
|
|
while i < len(field):
|
|
row2 = row2[field[i]]
|
|
if not row2:
|
|
break
|
|
if isinstance(row2, (BrowseRecordList, list)):
|
|
first = True
|
|
fields2 = [(x[:i+1]==field[:i+1] and x[i+1:]) \
|
|
or [] for x in fields_names]
|
|
if fields2 in done:
|
|
break
|
|
done.append(fields2)
|
|
for row2 in row2:
|
|
lines2 = self.__export_row(cursor, user, row2,
|
|
fields2, context)
|
|
if first:
|
|
for fpos2 in range(len(fields_names)):
|
|
if lines2 and lines2[0][fpos2]:
|
|
data[fpos2] = lines2[0][fpos2]
|
|
lines += lines2[1:]
|
|
first = False
|
|
else:
|
|
lines += lines2
|
|
break
|
|
i += 1
|
|
if i == len(field):
|
|
data[fpos] = row2 or ''
|
|
return [data] + lines
|
|
|
|
def export_data(self, cursor, user, ids, fields_names, context=None):
|
|
'''
|
|
Return list of list of values for each ids.
|
|
The list of values follow the fields_names.
|
|
Relational fields are defined with '/' at any deep.
|
|
|
|
:param cursor: the database cursor
|
|
:param ids: a list of ids
|
|
:param fields_names: a list of fields names
|
|
:param context: the context
|
|
:return: a list of list of values for each ids
|
|
'''
|
|
fields_names = [x.split('/') for x in fields_names]
|
|
datas = []
|
|
for row in self.browse(cursor, user, ids, context):
|
|
datas += self.__export_row(cursor, user, row, fields_names, context)
|
|
return datas
|
|
|
|
def import_data(self, cursor, user, fields_names, datas, context=None):
|
|
'''
|
|
Create record for each values in datas.
|
|
The fields name of values must be defined in fields_names.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param fields_names: a list of fields names
|
|
:param datas: the datas to import
|
|
:param context: the context
|
|
:return: a tuple with
|
|
- the number of records imported
|
|
- the last values if failed
|
|
- the exception if failed
|
|
- the warning if failed
|
|
'''
|
|
if context is None:
|
|
context = {}
|
|
fields_names = [x.split('/') for x in fields_names]
|
|
logger = logging.getLogger('import')
|
|
|
|
def process_liness(self, datas, prefix, fields_def, position=0):
|
|
line = datas[position]
|
|
row = {}
|
|
translate = {}
|
|
todo = []
|
|
warning = ''
|
|
|
|
# Import normal fields_names
|
|
for i in range(len(fields_names)):
|
|
if i >= len(line):
|
|
raise Exception('ImportError',
|
|
'Please check that all your lines have %d cols.' % \
|
|
(len(fields_names),))
|
|
field = fields_names[i]
|
|
if (len(field) == len(prefix) + 1) \
|
|
and field[len(prefix)].endswith(':id'):
|
|
res_id = False
|
|
if line[i]:
|
|
if fields_def[field[len(prefix)][:-3]]['type'] \
|
|
== 'many2many':
|
|
res_id = []
|
|
for word in line[i].split(','):
|
|
module, xml_id = word.rsplit('.', 1)
|
|
ir_model_data_obj = \
|
|
self.pool.get('ir.model.data')
|
|
new_id = ir_model_data_obj._get_id(cursor,
|
|
user, module, xml_id)
|
|
res_id2 = ir_model_data_obj.read(cursor, user,
|
|
[new_id], ['res_id'])[0]['res_id']
|
|
if res_id2:
|
|
res_id.append(res_id2)
|
|
if len(res_id):
|
|
res_id = [('set', res_id)]
|
|
else:
|
|
module, xml_id = line[i].rsplit('.', 1)
|
|
ir_model_data_obj = self.pool.get('ir.model.data')
|
|
new_id = ir_model_data_obj._get_id(cursor, user,
|
|
module, xml_id)
|
|
res_id = ir_model_data_obj.read(cursor, user,
|
|
[new_id], ['res_id'])[0]['res_id']
|
|
row[field[0][:-3]] = res_id or False
|
|
continue
|
|
if (len(field) == len(prefix)+1) and \
|
|
len(field[len(prefix)].split(':lang=')) == 2:
|
|
field, lang = field[len(prefix)].split(':lang=')
|
|
translate.setdefault(lang, {})[field]=line[i] or False
|
|
continue
|
|
if (len(field) == len(prefix)+1) and \
|
|
(prefix == field[0:len(prefix)]):
|
|
if fields_def[field[len(prefix)]]['type'] == 'integer':
|
|
res = line[i] and int(line[i])
|
|
elif fields_def[field[len(prefix)]]['type'] == 'float':
|
|
res = line[i] and float(line[i])
|
|
elif fields_def[field[len(prefix)]]['type'] == 'selection':
|
|
res = False
|
|
if isinstance(
|
|
fields_def[field[len(prefix)]]['selection'],
|
|
(tuple, list)):
|
|
sel = fields_def[field[len(prefix)]]['selection']
|
|
else:
|
|
sel = getattr(self, fields_def[field[len(prefix)]]\
|
|
['selection'])(cursor, user, context)
|
|
for key, val in sel:
|
|
if str(key) == line[i]:
|
|
res = key
|
|
if line[i] and not res:
|
|
logger.warning("key '%s' not found " \
|
|
"in selection field '%s'" % \
|
|
(line[i], field[len(prefix)]))
|
|
elif fields_def[field[len(prefix)]]['type'] == 'many2one':
|
|
res = False
|
|
if line[i]:
|
|
relation = \
|
|
fields_def[field[len(prefix)]]['relation']
|
|
res2 = self.pool.get(relation).name_search(cursor,
|
|
user, line[i], [], operator='=')
|
|
res = (res2 and res2[0][0]) or False
|
|
if not res:
|
|
warning += ('Relation not found: ' + line[i] + \
|
|
' on ' + relation + ' !\n')
|
|
logger.warning(
|
|
'Relation not found: ' + line[i] + \
|
|
' on ' + relation + ' !\n')
|
|
elif fields_def[field[len(prefix)]]['type'] == 'many2many':
|
|
res = []
|
|
if line[i]:
|
|
relation = \
|
|
fields_def[field[len(prefix)]]['relation']
|
|
for word in line[i].split(','):
|
|
res2 = self.pool.get(relation).name_search(
|
|
cursor, user, word, [], operator='=')
|
|
res3 = (res2 and res2[0][0]) or False
|
|
if not res3:
|
|
warning += ('Relation not found: ' + \
|
|
line[i] + ' on '+relation + ' !\n')
|
|
logger.warning(
|
|
'Relation not found: ' + line[i] + \
|
|
' on '+relation + ' !\n')
|
|
else:
|
|
res.append(res3)
|
|
if len(res):
|
|
res = [('set', res)]
|
|
else:
|
|
res = line[i] or False
|
|
row[field[len(prefix)]] = res
|
|
elif (prefix==field[0:len(prefix)]):
|
|
if field[0] not in todo:
|
|
todo.append(field[len(prefix)])
|
|
|
|
# Import one2many fields
|
|
nbrmax = 1
|
|
for field in todo:
|
|
newfd = self.pool.get(fields_def[field]['relation']).fields_get(
|
|
cursor, user, context=context)
|
|
res = process_liness(self, datas, prefix + [field], newfd,
|
|
position)
|
|
(newrow, max2, warning2, translate2) = res
|
|
nbrmax = max(nbrmax, max2)
|
|
warning = warning + warning2
|
|
reduce(lambda x, y: x and y, newrow)
|
|
row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
|
|
[('create', newrow)]) or []
|
|
i = max2
|
|
while (position+i)<len(datas):
|
|
test = True
|
|
for j in range(len(fields_names)):
|
|
field2 = fields_names[j]
|
|
if (len(field2) <= (len(prefix)+1)) \
|
|
and datas[position+i][j]:
|
|
test = False
|
|
if not test:
|
|
break
|
|
|
|
(newrow, max2, warning2, translate2) = \
|
|
process_liness(self, datas, prefix+[field], newfd,
|
|
position + i)
|
|
warning = warning + warning2
|
|
if reduce(lambda x, y: x or y, newrow.values()):
|
|
row[field].append(('create', newrow))
|
|
i += max2
|
|
nbrmax = max(nbrmax, i)
|
|
|
|
if len(prefix) == 0:
|
|
for i in range(max(nbrmax, 1)):
|
|
datas.pop(0)
|
|
result = (row, nbrmax, warning, translate)
|
|
return result
|
|
|
|
fields_def = self.fields_get(cursor, user, context=context)
|
|
done = 0
|
|
|
|
while len(datas):
|
|
res = {}
|
|
try:
|
|
(res, other, warning, translate) = \
|
|
process_liness(self, datas, [], fields_def)
|
|
if warning:
|
|
cursor.rollback()
|
|
return (-1, res, warning, '')
|
|
new_id = self.create(cursor, user, res, context=context)
|
|
for lang in translate:
|
|
context2 = context.copy()
|
|
context2['language'] = lang
|
|
self.write(cursor, user, new_id, translate[lang],
|
|
context=context2)
|
|
except Exception, exp:
|
|
logger.error(exp)
|
|
cursor.rollback()
|
|
return (-1, res, exp[0], warning)
|
|
done += 1
|
|
return (done, 0, 0, 0)
|
|
|
|
def check_xml_record(self, cursor, user, ids, values, context=None):
|
|
"""
|
|
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
|
|
forbid all modification on records/fields originating from
|
|
xml. Values is the dictionary of written values. If values is
|
|
equal to None, no field by field check is performed, False is
|
|
return has soon has one of the record comes from the xml.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param ids: a list of ids or an id
|
|
:param values: a dictionary with fields names as key and
|
|
written values as value
|
|
:param context: the context
|
|
:return: True or False
|
|
"""
|
|
model_data_obj = self.pool.get('ir.model.data')
|
|
# Allow root user to update/delete
|
|
if user == 0:
|
|
return True
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
model_data_ids = model_data_obj.search(cursor, 0, [
|
|
('model', '=', self._name),
|
|
('db_id', 'in', ids),
|
|
], context=context)
|
|
if not model_data_ids:
|
|
return True
|
|
if values == None:
|
|
return False
|
|
for line in model_data_obj.browse(cursor, 0, model_data_ids,
|
|
context=context):
|
|
if not line.values:
|
|
continue
|
|
xml_values = eval(line.values, {
|
|
'Decimal': Decimal,
|
|
'datetime': datetime,
|
|
})
|
|
for key, val in values.iteritems():
|
|
if key in xml_values and val != xml_values[key]:
|
|
return False
|
|
return True
|
|
|
|
def check_recursion(self, cursor, user, ids, parent='parent'):
|
|
'''
|
|
Function that check if there is no recursion in the tree
|
|
composed with parent as parent field name.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param ids: a list of ids
|
|
:param parent: the parent field name
|
|
:return: True or False
|
|
'''
|
|
ids_parent = ids[:]
|
|
while len(ids_parent):
|
|
ids_parent2 = set()
|
|
for record in self.browse(cursor, user, ids_parent):
|
|
if record[parent].id:
|
|
ids_parent2.add(record[parent].id)
|
|
ids_parent = list(ids_parent2)
|
|
for i in ids_parent:
|
|
if i in ids:
|
|
return False
|
|
return True
|
|
|
|
def _validate(self, cursor, user, ids, context=None):
|
|
if context is None:
|
|
context = {}
|
|
context = context.copy()
|
|
field_error = []
|
|
field_err_str = []
|
|
for field in self._constraints:
|
|
if not getattr(self, field[0])(cursor, user, ids):
|
|
self.raise_user_error(cursor, field[1], context=context)
|
|
|
|
if not 'res.user' in self.pool.object_name_list():
|
|
ctx_pref = {
|
|
}
|
|
else:
|
|
user_obj = self.pool.get('res.user')
|
|
ctx_pref = user_obj.get_preferences(cursor, user,
|
|
context_only=True, context=context)
|
|
|
|
def get_error_args(field_name):
|
|
model_field_obj = self.pool.get('ir.model.field')
|
|
error_args = (field_name, self._name)
|
|
if model_field_obj:
|
|
model_field_ids = model_field_obj.search(cursor,
|
|
user, [
|
|
('name', '=', field_name),
|
|
('model.model', '=', self._name),
|
|
], context=context, limit=1)
|
|
if model_field_ids:
|
|
model_field = model_field_obj.browse(cursor,
|
|
user, model_field_ids[0],
|
|
context=context)
|
|
error_args = (model_field.field_description,
|
|
model_field.model.name)
|
|
return error_args
|
|
|
|
context.update(ctx_pref)
|
|
records = self.browse(cursor, user, ids, context=context)
|
|
for field_name, field in self._columns.iteritems():
|
|
# validate domain
|
|
if field._type in ('many2one', 'many2many', 'one2many') \
|
|
and field.domain:
|
|
relation_obj = self.pool.get(field.model_name)
|
|
if isinstance(field.domain, basestring):
|
|
ctx = context.copy()
|
|
ctx.update(ctx_pref)
|
|
for record in records:
|
|
env = EvalEnvironment(record, self)
|
|
env.update(ctx)
|
|
env['current_date'] = datetime.datetime.today()
|
|
env['time'] = time
|
|
env['context'] = context
|
|
env['active_id'] = record.id
|
|
domain = eval(field.domain, env)
|
|
relation_ids = []
|
|
if record[field_name]:
|
|
if field._type in ('many2one',):
|
|
relation_ids.append(record[field_name].id)
|
|
else:
|
|
relation_ids.extend(
|
|
[x.id for x in record[field_name]])
|
|
if relation_ids and not relation_obj.search(cursor,
|
|
user, [
|
|
'AND',
|
|
[('id', 'in', relation_ids)],
|
|
domain,
|
|
], context=context):
|
|
self.raise_user_error(cursor,
|
|
'domain_validation_record',
|
|
error_args=get_error_args(field_name),
|
|
context=context)
|
|
else:
|
|
relation_ids = []
|
|
for record in records:
|
|
if record[field_name]:
|
|
if field._type in ('many2one',):
|
|
relation_ids.append(record[field_name].id)
|
|
else:
|
|
relation_ids.extend(
|
|
[x.id for x in record[field_name]])
|
|
if relation_ids:
|
|
find_ids = relation_obj.search(cursor, user, [
|
|
'AND',
|
|
[('id', 'in', relation_ids)],
|
|
field.domain,
|
|
], context=context)
|
|
if not set(relation_ids) == set(find_ids):
|
|
self.raise_user_error(cursor,
|
|
'domain_validation_record',
|
|
error_args=get_error_args(field_name),
|
|
context=context)
|
|
# validate states required
|
|
if field.states and 'required' in field.states:
|
|
if isinstance(field.states['required'], basestring):
|
|
ctx = context.copy()
|
|
ctx.update(ctx_pref)
|
|
for record in records:
|
|
env = EvalEnvironment(record, self)
|
|
env.update(ctx)
|
|
env['current_date'] = datetime.datetime.today()
|
|
env['time'] = time
|
|
env['context'] = context
|
|
env['active_id'] = record.id
|
|
required = eval(field.states['required'], env)
|
|
if required and not record[field_name]:
|
|
self.raise_user_error(cursor,
|
|
'required_validation_record',
|
|
error_args=get_error_args(field_name),
|
|
context=context)
|
|
else:
|
|
if field.states['required']:
|
|
for record in records:
|
|
if not record[field_name]:
|
|
self.raise_user_error(cursor,
|
|
'required_validation_record',
|
|
error_args=get_error_args(field_name),
|
|
context=context)
|
|
|
|
def _clean_defaults(self, defaults):
|
|
vals = {}
|
|
for field in defaults.keys():
|
|
fld_def = (field in self._columns) and self._columns[field] \
|
|
or self._inherit_fields[field][2]
|
|
if fld_def._type in ('many2one',):
|
|
if isinstance(defaults[field], (list, tuple)):
|
|
vals[field] = defaults[field][0]
|
|
else:
|
|
vals[field] = defaults[field]
|
|
elif fld_def._type in ('one2many',):
|
|
obj = self.pool.get(self._columns[field].model_name)
|
|
vals[field] = []
|
|
for defaults2 in defaults[field]:
|
|
vals2 = obj._clean_defaults(defaults2)
|
|
vals[field].append(('create', vals2))
|
|
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
|
|
|
|
def _rebuild_tree(self, cursor, user, parent, parent_id, left):
|
|
'''
|
|
Rebuild left, right value for the tree.
|
|
'''
|
|
right = left + 1
|
|
|
|
child_ids = self.search(cursor, 0, [
|
|
(parent, '=', parent_id),
|
|
])
|
|
|
|
for child_id in child_ids:
|
|
right = self._rebuild_tree(cursor, user, parent, child_id, right)
|
|
|
|
field = self._columns[parent]
|
|
|
|
if parent_id:
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + field.left + '" = %s, ' \
|
|
'"' + field.right + '" = %s ' \
|
|
'WHERE id = %s', (left, right, parent_id))
|
|
return right + 1
|
|
|
|
def _update_tree(self, cursor, user, object_id, field_name, left, right):
|
|
'''
|
|
Update left, right values for the tree.
|
|
Remarks:
|
|
- the value (right - left - 1) / 2 will not give
|
|
the number of children node
|
|
- the order of the tree respects the default _order
|
|
'''
|
|
cursor.execute('SELECT "' + left + '", "' + right + '" ' \
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE id = %s', (object_id,))
|
|
if not cursor.rowcount:
|
|
return
|
|
old_left, old_right = cursor.fetchone()
|
|
if old_left == old_right:
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + right + '" = "' + right + '" + 1 ' \
|
|
'WHERE id = %s', (object_id,))
|
|
old_right += 1
|
|
|
|
parent_right = 1
|
|
|
|
cursor.execute('SELECT "' + field_name + '" ' \
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE id = %s', (object_id,))
|
|
parent_id = cursor.fetchone()[0] or False
|
|
|
|
if parent_id:
|
|
cursor.execute('SELECT "' + right + '" ' \
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE id = %s', (parent_id,))
|
|
parent_right = cursor.fetchone()[0]
|
|
else:
|
|
cursor.execute('SELECT MAX("' + right + '") ' \
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE "' + field_name + '" IS NULL')
|
|
if cursor.rowcount:
|
|
parent_right = cursor.fetchone()[0] + 1
|
|
|
|
cursor.execute('SELECT id FROM "' + self._table + '" ' \
|
|
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
|
|
(old_left, old_right))
|
|
child_ids = [x[0] for x in cursor.fetchall()]
|
|
|
|
# ids for left update
|
|
cursor.execute('SELECT id FROM "' + self._table + '" ' \
|
|
'WHERE "' + left + '" >= %s ' \
|
|
'AND id NOT IN (' + ','.join(['%s' for x in child_ids]) + ')',
|
|
[parent_right] + child_ids)
|
|
left_ids = [x[0] for x in cursor.fetchall()]
|
|
|
|
# ids for right update
|
|
cursor.execute('SELECT id FROM "' + self._table + '" ' \
|
|
'WHERE "' + right + '" >= %s ' \
|
|
'AND id NOT IN (' + ','.join(['%s' for x in child_ids]) + ')',
|
|
[parent_right] + child_ids)
|
|
right_ids = [x[0] for x in cursor.fetchall()]
|
|
|
|
if left_ids:
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" + ' \
|
|
+ str(old_right - old_left + 1) + ' ' \
|
|
'WHERE id IN (' + ','.join(['%s' for x in left_ids]) + ')',
|
|
left_ids)
|
|
if right_ids:
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + right + '" = "' + right + '" + ' \
|
|
+ str(old_right - old_left + 1) + ' ' \
|
|
'WHERE id IN (' + ','.join(['%s' for x in right_ids]) + ')',
|
|
right_ids)
|
|
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" + ' \
|
|
+ str(parent_right - old_left) + ', ' \
|
|
'"' + right + '" = "' + right + '" + ' \
|
|
+ str(parent_right - old_left) + ' ' \
|
|
'WHERE id IN (' + ','.join(['%s' for x in child_ids]) + ')',
|
|
child_ids)
|
|
|
|
# Use root user to by-pass rules
|
|
brother_ids = self.search(cursor, 0, [
|
|
(field_name, '=', parent_id),
|
|
])
|
|
if brother_ids[-1] != object_id:
|
|
next_id = brother_ids[brother_ids.index(object_id) + 1]
|
|
cursor.execute('SELECT "' + left + '", "' + right + '" ' \
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE id = %s', (next_id,))
|
|
next_left, next_right = cursor.fetchone()
|
|
cursor.execute('SELECT "' + left + '", "' + right + '" '\
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE id = %s', (object_id,))
|
|
current_left, current_right = cursor.fetchone()
|
|
|
|
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" + ' \
|
|
+ str(old_right - old_left + 1) + ', ' \
|
|
'"' + right + '" = "' + right + '" + ' \
|
|
+ str(old_right - old_left + 1) + ' ' \
|
|
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
|
|
(next_left, current_left))
|
|
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" - ' \
|
|
+ str(current_left - next_left) + ', ' \
|
|
'"' + right + '" = "' + right + '" - ' \
|
|
+ str(current_left - next_left) + ' ' \
|
|
'WHERE id in (' + ','.join(['%s' for x in child_ids]) + ')',
|
|
child_ids)
|