Files
tryton/trytond/model/modeldb.py
T

1483 lines
67 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 ModelStorage, OPERATORS
from trytond.model import fields
from trytond.netsvc import LocalService
from trytond.backend import FIELDS, TableHandler
import datetime
class ModelDB(ModelStorage):
"""
Define a model with storage in database in Tryton.
"""
_table = None # The name of the table in database
_order = None
_order_name = None # Use to force order field when sorting on Many2One
_sequence = None
_sql = ''
_auto = True #XXX to remove when module will use the new models
def __init__(self):
super(ModelDB, self).__init__()
self._sql_constraints = []
self._order = [('id', 'ASC')]
self._sql_error_messages = {}
if not self._table:
self._table = self._name.replace('.', '_')
if not self._sequence:
self._sequence = self._table+'_id_seq'
def init(self, cursor, module_name):
super(ModelDB, self).init(cursor, module_name)
if not self._auto or self.table_query():
return
# create/update table in the database
table = TableHandler(cursor, self._table, self._name, module_name)
datetime_field = FIELDS['datetime']
integer_field = FIELDS['integer']
logs = (
('create_date', datetime_field.sql_type(None), datetime_field.symbol_c,
datetime_field.symbol_f, lambda *a: datetime.datetime.now()),
('write_date', datetime_field.sql_type(None), datetime_field.symbol_c,
datetime_field.symbol_f, None),
('create_uid', (integer_field.sql_type(None)[0],
'INTEGER REFERENCES res_user ON DELETE SET NULL',),
integer_field.symbol_c, integer_field.symbol_f, lambda *a: 0),
('write_uid', (integer_field.sql_type(None)[0],
'INTEGER REFERENCES res_user ON DELETE SET NULL'),
integer_field.symbol_c, integer_field.symbol_f, None),
)
for log in logs:
table.add_raw_column(log[0], log[1], (log[2], log[3]),
default_fun=log[4], migrate=False)
for field_name, field in self._columns.iteritems():
default_fun = None
if field_name in (
'id',
'write_uid',
'write_date',
'create_uid',
'create_date',
):
continue
if not hasattr(field, 'set'):
if field_name in self._defaults:
default_fun = self._defaults[field_name]
def unpack_wrapper(fun):
def unpack_result(*a):
try: # XXX ugly hack: some default fct try
# to access the non-existing table
result = fun(*a)
except:
return None
clean_results = self._clean_defaults(
{field_name: result})
return clean_results[field_name]
return unpack_result
default_fun = unpack_wrapper(default_fun)
table.add_raw_column(field_name,
FIELDS[field._type].sql_type(field),
(FIELDS[field._type].symbol_c,
FIELDS[field._type].symbol_f), default_fun,
hasattr(field, 'size') and field.size or None)
if isinstance(field, (fields.Integer, fields.Float)):
table.db_default(field_name, 0)
if isinstance(field, fields.Many2One):
if field.model_name in ('res.user', 'res.group'):
ref = field.model_name.replace('.','_')
else:
ref = self.pool.get(field.model_name)._table
table.add_fk(field_name, ref, field.ondelete)
table.index_action(
field_name, action=field.select and 'add' or 'remove')
required = field.required
if isinstance(field, (fields.Integer, fields.Float,
fields.Boolean)):
required = True
table.not_null_action(
field_name, action=required and 'add' or 'remove')
elif isinstance(field, fields.Many2Many):
if field.model_name in ('res.user', 'res.group'):
ref = field.model_name.replace('.','_')
else:
ref = self.pool.get(field.model_name)._table
table.add_m2m(field_name, ref, field.relation_name,
field.origin, field.target, field.ondelete_origin,
field.ondelete_target)
elif not isinstance(field, (fields.One2Many, fields.Function)):
raise Exception('Unknow field type !')
for field_name, field in self._columns.iteritems():
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
self._rebuild_tree(cursor, 0, field_name, False, 0)
for ident, constraint, msg in self._sql_constraints:
table.add_constraint(ident, constraint)
def _get_error_messages(self):
res = super(ModelDB, self)._get_error_messages()
res += self._sql_error_messages.values()
for _, _, error in self._sql_constraints:
res.append(error)
return res
def table_query(self, context=None):
'''
Return None if the model is a real table in the database
or return a tuple with the SQL query and the arguments.
:param context: the context
:return: None or a tuple with a SQL query and arguments
'''
return None
def create(self, cursor, user, values, context=None):
if self.table_query(context):
return False
values = values.copy()
# Clean values
for key in ('create_uid', 'create_date', 'id'):
if key in values:
del values[key]
# Get default values
default = []
avoid_table = []
for (i, j) in self._inherits.items():
if j in values:
avoid_table.append(i)
for i in self._columns.keys(): # + self._inherit_fields.keys():
if not i in values \
and i not in ('create_uid', 'create_date',
'write_uid', 'write_date'):
default.append(i)
for i in self._inherit_fields.keys():
if (not i in values) \
and (not self._inherit_fields[i][0] in avoid_table):
default.append(i)
if len(default):
defaults = self.default_get(cursor, user, default, context)
values.update(self._clean_defaults(defaults))
# Get new id
cursor.execute("SELECT NEXTVAL('" + self._sequence + "')")
(id_new,) = cursor.fetchone()
(upd0, upd1, upd2) = ('', '', [])
upd_todo = []
# Create inherits
tocreate = {}
for i in self._inherits:
if self._inherits[i] not in values:
tocreate[i] = {}
for i in values.keys():
if i in self._inherit_fields:
(inherits, col, col_detail) = self._inherit_fields[i]
tocreate[inherits][i] = values[i]
#XXX add check if field is not also on base model
del values[i]
for inherits in tocreate:
inherits_obj = self.pool.get(inherits)
inherits_id = inherits_obj.create(cursor, user, tocreate[inherits],
context=context)
upd0 += ',' + self._inherits[inherits]
upd1 += ',%s'
upd2.append(inherits_id)
# Insert record
for field in values:
if not hasattr(self._columns[field], 'set'):
upd0 = upd0 + ',"' + field + '"'
upd1 = upd1 + ',' + FIELDS[self._columns[field]._type].symbol_c
upd2.append(FIELDS[self._columns[field]._type].symbol_f(
values[field]))
else:
upd_todo.append(field)
if field in self._columns \
and hasattr(self._columns[field], 'selection') \
and self._columns[field].selection \
and values[field]:
if self._columns[field]._type == 'reference':
val = values[field].split(',')[0]
else:
val = values[field]
if isinstance(self._columns[field].selection, (tuple, list)):
if val not in dict(self._columns[field].selection):
raise Exception('ValidateError',
'The value "%s" for the field "%s" ' \
'is not in the selection' % \
(val, field))
else:
if val not in dict(getattr(self,
self._columns[field].selection)(
cursor, user, context=context)):
raise Exception('ValidateError',
'The value "%s" for the field "%s" ' \
'is not in the selection' % \
(val, field))
upd0 += ', create_uid, create_date'
upd1 += ', %s, now()'
upd2.append(user)
cursor.execute('INSERT INTO "' + self._table + '" ' \
'(id' + upd0 + ') ' \
'VALUES (' + str(id_new) + upd1 + ')', tuple(upd2))
upd_todo.sort(lambda x, y: self._columns[x].priority - \
self._columns[y].priority)
for field in upd_todo:
self._columns[field].set(cursor, user, id_new, self, field, values[field],
context=context)
self._validate(cursor, user, [id_new], context=context)
# Check for Modified Preorder Tree Traversal
for k in self._columns:
field = self._columns[k]
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
self._update_tree(cursor, user, id_new, k, field.left, field.right)
wf_service = LocalService("workflow")
wf_service.trg_create(user, self._name, id_new, cursor, context=context)
return id_new
def read(self, cursor, user, ids, fields_names=None, context=None):
rule_obj = self.pool.get('ir.rule')
translation_obj = self.pool.get('ir.translation')
super(ModelDB, self).read(cursor, user, ids,
fields_names=fields_names, context=context)
if context is None:
context = {}
if not fields_names:
fields_names = list(set(self._columns.keys() \
+ self._inherit_fields.keys()))
int_id = False
if isinstance(ids, (int, long)):
int_id = True
ids = [ids]
if not ids:
return []
# construct a clause for the rules :
domain1, domain2 = rule_obj.domain_get(cursor, user, self._name,
context=context)
# all inherited fields + all non inherited fields
fields_pre = [x for x in fields_names if (x in self._columns \
and not hasattr(self._columns[x], 'set')) or \
(x == '_timestamp')] + \
self._inherits.values()
res = []
table_query = ''
table_args = []
if self.table_query(context):
table_query, table_args = self.table_query(context)
table_query = '(' + table_query + ') AS '
if len(fields_pre) :
fields_pre2 = [(x in ('create_date', 'write_date')) \
and ('date_trunc(\'second\', ' + x + ') as ' + x) \
or '"' + x + '"' for x in fields_pre \
if x != '_timestamp']
if '_timestamp' in fields_pre:
if not self.table_query(context):
fields_pre2 += ['(CASE WHEN write_date IS NOT NULL ' \
'THEN write_date ELSE create_date END) ' \
'AS _timestamp']
else:
fields_pre2 += ['now()::timestamp AS _timestamp']
if len(ids) > cursor.IN_MAX:
cursor.execute('SELECT id ' \
'FROM ' + table_query + '"' + self._table + '" ' \
'ORDER BY ' + \
','.join([self._table + '.' + x[0] + ' ' + x[1]
for x in self._order]), table_args)
i = 0
ids_sorted = {}
for row in cursor.fetchall():
ids_sorted[row[0]] = i
i += 1
ids = ids[:]
ids.sort(lambda x, y: ids_sorted[x] - ids_sorted[y])
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
if domain1:
cursor.execute(('SELECT ' + \
','.join(fields_pre2 + ['id']) + \
' FROM ' + table_query + '\"' + self._table +'\" ' \
'WHERE id IN ' \
'(' + ','.join(['%s' for x in sub_ids]) + ')'\
' AND (' + domain1 + ') ORDER BY ' + \
','.join([self._table + '.' + x[0] + ' ' + x[1] \
for x in self._order])),
table_args + sub_ids + domain2)
if not cursor.rowcount == len({}.fromkeys(sub_ids)):
raise Exception('AccessError',
'You try to bypass an access rule ' \
'(Document type: %s).' % \
self._description)
else:
cursor.execute('SELECT ' + \
','.join(fields_pre2 + ['id']) + \
' FROM ' + table_query + '\"' + self._table + '\" ' \
'WHERE id IN ' \
'(' + ','.join(['%s' for x in sub_ids]) + ')'\
' ORDER BY ' + \
','.join([self._table + '.' + x[0] + ' ' + x[1] \
for x in self._order]), table_args + sub_ids)
res.extend(cursor.dictfetchall())
else:
res = [{'id': x} for x in ids]
for field in fields_pre:
if field == '_timestamp':
continue
if self._columns[field].translate:
ids = [x['id'] for x in res]
res_trans = translation_obj._get_ids(cursor,
self._name + ',' + field, 'model',
context.get('language') or 'en_US', ids)
for i in res:
i[field] = res_trans.get(i['id'], False) or i[field]
for table in self._inherits:
field = self._inherits[table]
inherits_fields = list(
set(self._inherit_fields.keys()).intersection(
set(fields_names)).difference(
set(self._columns.keys())))
if not inherits_fields:
continue
res2 = self.pool.get(table).read(cursor, user,
[x[field] for x in res], fields_names=inherits_fields,
context=context)
res3 = {}
for i in res2:
res3[i['id']] = i
del i['id']
for record in res:
record.update(res3[record[field]])
if field not in fields_names:
del record[field]
ids = [x['id'] for x in res]
# all non inherited fields for which there is a get attribute
fields_post = [x for x in fields_names if x in self._columns \
and hasattr(self._columns[x], 'get')]
func_fields = {}
for field in fields_post:
if isinstance(self._columns[field], fields.Function) \
and not isinstance(self._columns[field], fields.Property):
key = (self._columns[field].fnct, self._columns[field].arg)
func_fields.setdefault(key, [])
func_fields[key].append(field)
continue
# get the value of that field for all records/ids
res2 = self._columns[field].get(cursor, user, ids, self, field,
values=res, context=context)
for record in res:
record[field] = res2[record['id']]
for i in func_fields:
field_list = func_fields[i]
field = field_list[0]
res2 = self._columns[field].get(cursor, user, ids, self, field_list,
values=res, context=context)
for field in res2:
for record in res:
record[field] = res2[field][record['id']]
if int_id:
return res[0]
return res
def write(self, cursor, user, ids, values, context=None):
super(ModelDB, self).write(cursor, user, ids, values, context=context)
if context is None:
context = {}
if not ids:
return True
if self.table_query(context):
return True
values = values.copy()
if isinstance(ids, (int, long)):
ids = [ids]
else:
# _update_tree works if only one record has changed
update_tree = False
for k in self._columns:
field = self._columns[k]
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
update_tree = True
if update_tree:
for object_id in ids:
self.write(cursor, user, object_id, values, context=context)
return True
if context.get('_timestamp', False):
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
clause = '(id = %s AND ' \
'(CASE WHEN write_date IS NOT NULL ' \
'THEN write_date ELSE create_date END) ' \
' > %s)'
args = []
for i in sub_ids:
if context['_timestamp'].get(self._name + ',' + str(i)):
args.append(i)
args.append(context['_timestamp'][
self._name + ',' +str(i)])
if args:
cursor.execute("SELECT id " \
'FROM "' + self._table + '" ' \
'WHERE ' + ' OR '.join(
[clause for x in range(len(args)/2)]), args)
if cursor.rowcount:
raise Exception('ConcurrencyException',
'Records were modified in the meanwhile')
for i in ids:
if context['_timestamp'].get(self._name + ',' + str(i)):
del context['_timestamp'][self._name + ',' +str(i)]
if 'write_uid' in values:
del values['write_uid']
if 'write_date' in values:
del values['write_date']
if 'id' in values:
del values['id']
upd0 = []
upd1 = []
upd_todo = []
updend = []
direct = []
for field in values:
if field in self._columns:
if not hasattr(self._columns[field], 'set'):
if (not self._columns[field].translate) \
or (context.get('language') or 'en_US') == 'en_US':
upd0.append('"' + field + '"=' + \
FIELDS[self._columns[field]._type].symbol_c)
upd1.append(FIELDS[self._columns[field]._type].symbol_f(
values[field]))
direct.append(field)
else:
upd_todo.append(field)
else:
updend.append(field)
if field in self._columns \
and hasattr(self._columns[field], 'selection') \
and self._columns[field].selection \
and values[field]:
if self._columns[field]._type == 'reference':
val = values[field].split(',')[0]
else:
val = values[field]
if isinstance(self._columns[field].selection, (tuple, list)):
if val not in dict(self._columns[field].selection):
raise Exception('ValidateError',
'The value "%s" for the field "%s" ' \
'is not in the selection' % \
(val, field))
else:
if val not in dict(getattr(self,
self._columns[field].selection)(
cursor, user, context=context)):
raise Exception('ValidateError',
'The value "%s" for the field "%s" ' \
'is not in the selection' % \
(val, field))
upd0.append('write_uid = %s')
upd0.append('write_date = now()')
upd1.append(user)
if len(upd0):
domain1, domain2 = self.pool.get('ir.rule').domain_get(cursor,
user, self._name, context=context)
if domain1:
domain1 = ' AND (' + domain1 + ') '
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
ids_str = ','.join(['%s' for x in sub_ids])
if domain1:
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE id IN (' + ids_str + ') ' + domain1,
sub_ids + domain2)
if not cursor.rowcount == len({}.fromkeys(sub_ids)):
raise Exception('AccessError',
'You try to bypass an access rule ' \
'(Document type: %s).' % \
self._description)
else:
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE id IN (' + ids_str + ')', sub_ids)
if not cursor.rowcount == len({}.fromkeys(sub_ids)):
raise Exception('AccessError',
'You try to bypass an access rule ' \
'(Document type: %s).' % \
self._description)
if domain1:
cursor.execute('UPDATE "' + self._table + '" ' \
'SET ' + ','.join(upd0) + ' ' \
'WHERE id IN (' + ids_str + ') ' + domain1,
upd1 + sub_ids + domain2)
else:
cursor.execute('UPDATE "' + self._table + '" ' \
'SET ' + ','.join(upd0) + ' ' \
'WHERE id IN (' + ids_str + ') ', upd1 + sub_ids)
for field in direct:
if self._columns[field].translate:
self.pool.get('ir.translation')._set_ids(cursor, user,
self._name + ',' + field, 'model',
context.get('language') or 'en_US', ids,
values[field])
# call the 'set' method of fields which are not classic_write
upd_todo.sort(lambda x, y: self._columns[x].priority - \
self._columns[y].priority)
for field in upd_todo:
for select_id in ids:
self._columns[field].set(cursor, user, select_id, self, field,
values[field], context=context)
for table in self._inherits:
col = self._inherits[table]
nids = []
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
ids_str = ','.join(['%s' for x in sub_ids])
cursor.execute('SELECT DISTINCT "' + col + '" ' \
'FROM "' + self._table + '" WHERE id IN (' + ids_str + ')',
sub_ids)
nids.extend([x[0] for x in cursor.fetchall()])
values2 = {}
for val in updend:
if self._inherit_fields[val][0] == table:
values2[val] = values[val]
self.pool.get(table).write(cursor, user, nids, values2,
context=context)
self._validate(cursor, user, ids, context=context)
# Check for Modified Preorder Tree Traversal
for k in self._columns:
field = self._columns[k]
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
if field.left in values or field.right in values:
raise Exception('ValidateError', 'You can not update fields: ' \
'"%s", "%s"' % (field.left, field.right))
if len(ids) == 1:
self._update_tree(cursor, user, ids[0], k,
field.left, field.right)
else:
self._rebuild_tree(cursor, 0, k, False, 0)
wf_service = LocalService("workflow")
for obj_id in ids:
wf_service.trg_write(user, self._name, obj_id, cursor,
context=context)
return True
def delete(self, cursor, user, ids, context=None):
super(ModelDB, self).delete(cursor, user, ids, context=context)
if context is None:
context = {}
if not ids:
return True
if self.table_query(context):
return True
if isinstance(ids, (int, long)):
ids = [ids]
if context.get('_timestamp', False):
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
clause = '(id = %s AND ' \
'(CASE WHEN write_date IS NOT NULL ' \
'THEN write_date ELSE create_date END) ' \
' > %s)'
args = []
for i in sub_ids:
if context['_timestamp'].get(self._name + ',' + str(i)):
args.append(i)
args.append(context['_timestamp'][
self._name + ',' +str(i)])
if args:
cursor.execute("SELECT id " \
'FROM "' + self._table + '" ' \
'WHERE ' + ' OR '.join(
[clause for x in range(len(args)/2)]), args)
if cursor.rowcount:
raise Exception('ConcurrencyException',
'Records were modified in the meanwhile')
for i in ids:
if context['_timestamp'].get(self._name + ',' + str(i)):
del context['_timestamp'][self._name + ',' +str(i)]
#TODO use search
cursor.execute(
"SELECT id FROM wkf_instance "\
"WHERE res_id IN (" + ",".join(["%s" for i in ids]) + ") "\
"AND res_type = %s AND state != 'complete'",
ids + [self._name])
if cursor.rowcount != 0:
self.raise_user_error(cursor, 'delete_workflow_record',
context=context)
wf_service = LocalService("workflow")
for obj_id in ids:
wf_service.trg_delete(user, self._name, obj_id, cursor,
context=context)
tree_ids = {}
for k in self._columns:
field = self._columns[k]
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
cursor.execute('SELECT id FROM "' + self._table + '" '\
'WHERE "' + k + '" IN (' \
+ ','.join(['%s' for x in ids]) + ')',
ids)
tree_ids[k] = [x[0] for x in cursor.fetchall()]
domain1, domain2 = self.pool.get('ir.rule').domain_get(cursor, user,
self._name, context=context)
if domain1:
domain1 = ' AND (' + domain1 + ') '
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
str_d = ','.join(('%s',) * len(sub_ids))
if domain1:
cursor.execute('SELECT id FROM "'+self._table+'" ' \
'WHERE id IN (' + str_d + ') ' + domain1,
sub_ids + domain2)
if not cursor.rowcount == len({}.fromkeys(sub_ids)):
raise Exception('AccessError',
'You try to bypass an access rule ' \
'(Document type: %s).' % self._description)
if domain1:
cursor.execute('DELETE FROM "'+self._table+'" ' \
'WHERE id IN (' + str_d + ') ' + domain1,
sub_ids + domain2)
else:
cursor.execute('DELETE FROM "'+self._table+'" ' \
'WHERE id IN (' + str_d + ')', sub_ids)
for k in tree_ids.keys():
field = self._columns[k]
if len(tree_ids[k]) == 1:
self._update_tree(cursor, user, tree_ids[k][0], k,
field.left, field.right)
else:
self._rebuild_tree(cursor, 0, k, False, 0)
return True
def search(self, cursor, user, domain, offset=0, limit=None, order=None,
context=None, count=False, query_string=False):
rule_obj = self.pool.get('ir.rule')
# Get domain clauses
qu1, qu2, tables, tables_args = self.search_domain(cursor, user,
domain, context=context)
# Get order by
order_by = []
for field, otype in (order or self._order):
if otype.upper() not in ('DESC', 'ASC'):
raise Exception('Error', 'Wrong order type (%s)!' % otype)
order_by2, tables2, tables2_args = self._order_calc(cursor, user,
field, otype, context=context)
order_by += order_by2
for table in tables2:
if table not in tables:
tables.append(table)
if tables2_args.get(table):
tables_args.extend(tables2_args.get(table))
order_by = ','.join(order_by)
# Get limit
limit_str = limit and (type(limit) in (float, int, long))\
and ' LIMIT %d' % limit or ''
# Get offset
offset_str = offset and (type(offset) in (float, int, long))\
and ' OFFSET %d' % offset or ''
# construct a clause for the rules :
domain1, domain2 = rule_obj.domain_get(cursor, user, self._name,
context=context)
if domain1:
if qu1:
qu1 += ' AND ' + domain1
else:
qu1 = domain1
qu2 += domain2
if count:
cursor.execute('SELECT COUNT(%s.id) FROM ' % self._table +
' '.join(tables) + ' WHERE ' + (qu1 or 'True') +
limit_str + offset_str, tables_args + qu2)
res = cursor.fetchall()
return res[0][0]
# execute the "main" query to fetch the ids we were searching for
query_str = 'SELECT %s.id FROM ' % self._table + \
' '.join(tables) + ' WHERE ' + (qu1 or 'True') + \
' ORDER BY ' + order_by + limit_str + offset_str
if query_string:
return (query_str, tables_args + qu2)
cursor.execute(query_str, tables_args + qu2)
res = cursor.fetchall()
return [x[0] for x in res]
def search_domain(self, cursor, user, domain, active_test=True, context=None):
'''
Return SQL clause and arguments for the domain
:param cursor: the database cursor
:param user: the user id
:param domain: a domain like in search
:param active_test: a boolean to add 'active' test
:param context: the context
:return: a tuple with
- a SQL clause string
- a list of arguments for the SQL clause
- a list a tables used in the SQL clause
- a list of arguments for the tables
'''
domain = self._search_domain_active(domain, active_test=active_test,
context=context)
table_query = ''
tables_args = []
if self.table_query(context):
table_query, tables_args = self.table_query(context)
table_query = '(' + table_query + ') AS '
tables = [table_query + '"' + self._table + '"']
qu1, qu2 = self.__search_domain_oper(cursor, user, domain, tables,
tables_args, context=context)
return qu1, qu2, tables, tables_args
def __search_domain_oper(self, cursor, user, domain, tables, tables_args,
context=None):
operator = 'AND'
if len(domain) and isinstance(domain[0], basestring):
if domain[0] not in ('AND', 'OR'):
raise Exception('ValidateError', 'Operator "%s" not supported' \
% domain[0])
operator = domain[0]
domain = domain[1:]
tuple_args = []
list_args = []
for arg in domain:
#add test for xmlrpc that doesn't handle tuple
if isinstance(arg, tuple) \
or (isinstance(arg, list) and len(arg) > 2 \
and arg[1] in OPERATORS):
tuple_args.append(tuple(arg))
elif isinstance(arg, list):
list_args.append(arg)
qu1, qu2 = self.__search_domain_calc(cursor, user,
tuple_args, tables, tables_args, context=context)
if len(qu1):
qu1 = (' ' + operator + ' ').join(qu1)
else:
qu1 = ''
for domain2 in list_args:
qu1b, qu2b = self.__search_domain_oper(cursor, user, domain2,
tables, tables_args, context=context)
if not qu1b:
qu1b = 'true'
if qu1 and qu1b:
qu1 += ' ' + operator + ' ' + qu1b
elif qu1b:
qu1 = qu1b
qu2 += qu2b
if qu1:
qu1 = '(' + qu1 + ')'
return qu1, qu2
def __search_domain_calc(self, cursor, user, domain, tables, tables_args,
context=None):
if context is None:
context = {}
domain = domain[:]
for arg in domain:
if arg[1] not in OPERATORS:
raise Exception('ValidateError', 'Argument "%s" not supported' \
% arg[1])
i = 0
joins = []
while i < len(domain):
table = self
fargs = domain[i][0].split('.', 1)
if fargs[0] in self._inherit_fields:
itable = self.pool.get(self._inherit_fields[fargs[0]][0])
table_query = ''
table_arg = []
if itable.table_query(context):
table_query, table_args = self.table_query(context)
table_query = '(' + table_query + ') AS '
table_join = 'LEFT JOIN ' + table_query + \
'"' + itable._table + '" ON ' \
'%s.id = %s.%s' % (itable._table, self._table,
self._inherits[itable._name])
if table_join not in tables:
tables.append(table_join)
tables_args += table_arg
field = table._columns.get(fargs[0], False)
if not field:
if not fargs[0] in self._inherit_fields:
raise Exception('ValidateError', 'Field "%s" doesn\'t ' \
'exist on "%s"' % (fargs[0], self._name))
table = self.pool.get(self._inherit_fields[fargs[0]][0])
field = table._columns.get(fargs[0], False)
if len(fargs) > 1:
if field._type == 'many2one':
domain[i] = (fargs[0], 'inselect',
self.pool.get(field.model_name).search(cursor, user,
[(fargs[1], domain[i][1], domain[i][2])],
context=context, query_string=True), table)
i += 1
continue
else:
raise Exception('ValidateError', 'Clause on field "%s" ' \
'doesn\'t work on "%s"' % (domain[i][0], self._name))
if hasattr(field, 'search'):
arg = [domain.pop(i)]
j = i
while j < len(domain):
if domain[j][0] == arg[0][0]:
arg.append(domain.pop(j))
else:
j += 1
domain.extend(field.search(cursor, user, table,
arg[0][0], arg, context=context))
elif field._type == 'one2many':
field_obj = self.pool.get(field.model_name)
if isinstance(domain[i][2], basestring):
# get the ids of the records of the "distant" resource
ids2 = [x[0] for x in field_obj.name_search(cursor, user,
domain[i][2], [], domain[i][1], context=context)]
else:
ids2 = domain[i][2]
table_query = ''
table_args = []
if field_obj.table_query(context):
table_query, table_args = field_obj.table_query(context)
table_query = '(' + table_query + ') AS '
if ids2 == True or ids2 == False:
query1 = 'SELECT "' + field.field + '" ' \
'FROM ' + table_query + '"' + field_obj._table + '" ' \
'WHERE "' + field.field + '" IS NOT NULL'
query2 = table_args
clause = 'inselect'
if ids2 == False:
clause = 'notinselect'
domain[i] = ('id', clause, (query1, query2))
elif not ids2:
domain[i] = ('id', '=', '0')
else:
if len(ids2) < cursor.IN_MAX:
query1 = 'SELECT "' + field.field + '" ' \
'FROM ' + table_query + '"' + field_obj._table + '" ' \
'WHERE id IN (' + \
','.join(['%s' for x in ids2]) + ')'
query2 = table_args + ids2
domain[i] = ('id', 'inselect', (query1, query2))
else:
ids3 = []
for i in range(0, len(ids2), cursor.IN_MAX):
sub_ids2 = ids2[i:i + cursor.IN_MAX]
cursor.execute(
'SELECT "' + field.field + \
'" FROM ' + table_query + '"' + field_obj._table + '" ' \
'WHERE id IN (' + \
','.join(['%s' for x in sub_ids2]) + ')',
table_args + sub_ids2)
ids3.extend([x[0] for x in cursor.fetchall()])
domain[i] = ('id', 'in', ids3)
i += 1
elif field._type == 'many2many':
# XXX must find a solution for long id list
if domain[i][1] in ('child_of', 'not child_of'):
if isinstance(domain[i][2], basestring):
ids2 = [x[0] for x in self.pool.get(
field.model_name).name_search(cursor, user, domain[i][2], [],
'like', context=context)]
elif isinstance(domain[i][2], (int, long)):
ids2 = [domain[i][2]]
else:
ids2 = domain[i][2]
def _rec_get(ids, table, parent):
if not ids:
return []
ids2 = table.search(cursor, user,
[(parent, 'in', ids), (parent, '!=', False)],
context=context)
return ids + _rec_get(ids2, table, parent)
if field.model_name != table._name:
if len(domain[i]) != 4:
raise Exception('Error', 'Programming error: ' \
'child_of on field "%s" is not allowed!' % \
(domain[i][0],))
ids2 = self.pool.get(field.model_name).search(cursor, user,
[(domain[i][3], 'child_of', ids2)],
context=context)
query1 = 'SELECT "' + field.origin + '" ' \
'FROM "' + field.relation_name + '" ' \
'WHERE "' + field.target + '" IN (' + \
','.join(['%s' for x in ids2]) + ') ' \
'AND "' + field.origin + '" IS NOT NULL'
query2 = [str(x) for x in ids2]
if domain[i][1] == 'child_of':
domain[i] = ('id', 'inselect', (query1, query2))
else:
domain[i] = ('id', 'notinselect', (query1, query2))
else:
if domain[i][1] == 'child_of':
domain[i] = ('id', 'in', ids2 + _rec_get(ids2,
table, domain[i][0]))
else:
domain[i] = ('id', 'not in', ids2 + _rec_get(ids2,
table, domain[i][0]))
else:
if isinstance(domain[i][2], basestring):
res_ids = [x[0] for x in self.pool.get(field.model_name
).name_search(cursor, user, domain[i][2], [],
domain[i][1], context=context)]
else:
res_ids = domain[i][2]
if res_ids == True or res_ids == False:
query1 = 'SELECT "' + field.origin + '" ' \
'FROM "' + field.relation_name + '" '\
'WHERE "' + field.origin + '" IS NOT NULL'
query2 = []
clause = 'inselect'
if res_ids == False:
clause = 'notinselect'
domain[i] = ('id', clause, (query1, query2))
elif not res_ids:
domain[i] = ('id', '=', '0')
else:
query1 = 'SELECT "' + field.origin + '" ' \
'FROM "' + field.relation_name + '" ' \
'WHERE "' + field.target + '" IN (' + \
','.join(['%s' for x in res_ids]) + ')'
query2 = [str(x) for x in res_ids]
domain[i] = ('id', 'inselect', (query1, query2))
i += 1
elif field._type == 'many2one':
# XXX must find a solution for long id list
if domain[i][1] in ('child_of', 'not child_of'):
if isinstance(domain[i][2], basestring):
ids2 = [x[0] for x in self.pool.get(
field.model_name).name_search(cursor, user, domain[i][2],
[], 'like', context=context)]
elif isinstance(domain[i][2], (int, long)):
ids2 = [domain[i][2]]
else:
ids2 = domain[i][2]
def _rec_get(ids, table, parent):
if not ids:
return []
ids2 = table.search(cursor, user,
[(parent, 'in', ids), (parent, '!=', False)],
context=context)
return ids + _rec_get(ids2, table, parent)
if field.model_name != table._name:
if len(domain[i]) != 4:
raise Exception('Error', 'Programming error: ' \
'child_of on field "%s" is not allowed!' % \
(domain[i][0],))
ids2 = self.pool.get(field.model_name).search(cursor, user,
[(domain[i][3], 'child_of', ids2)],
context=context)
if domain[i][1] == 'child_of':
domain[i] = (domain[i][0], 'in', ids2, table)
else:
domain[i] = (domain[i][0], 'not in', ids2, table)
else:
if field.left and field.right:
cursor.execute('SELECT "' + field.left + '", ' \
'"' + field.right + '" ' + \
'FROM "' + self._table + '" ' + \
'WHERE id IN ' + \
'(' + ','.join(['%s' for x in ids2]) + ')',
ids2)
clause = 'false '
for left, right in cursor.fetchall():
clause += 'OR '
clause += '( "' + field.left + '" >= ' + \
str(left) + ' ' + \
'AND "' + field.right + '" <= ' + \
str(right) + ')'
query = 'SELECT id FROM "' + self._table + '" ' + \
'WHERE ' + clause
if domain[i][1] == 'child_of':
domain[i] = ('id', 'inselect', (query, []))
else:
domain[i] = ('id', 'notinselect', (query, []))
else:
if domain[i][1] == 'child_of':
domain[i] = ('id', 'in', ids2 + _rec_get(
ids2, table, domain[i][0]), table)
else:
domain[i] = ('id', 'not in', ids2 + _rec_get(
ids2, table, domain[i][0]), table)
else:
if isinstance(domain[i][2], basestring):
res_ids = self.pool.get(field.model_name).name_search(cursor,
user, domain[i][2], [], domain[i][1],
context=context)
domain[i] = (domain[i][0], 'in', [x[0] for x in res_ids],
table)
else:
domain[i] += (table,)
i += 1
else:
if field.translate:
exprs = ['%s', '%s']
if domain[i][1] in ('like', 'ilike', 'not like', 'not ilike'):
exprs = ['%% %s%%', '%s%%']
oper = 'OR'
if domain[i][1] in ('not like', 'not ilike', '!='):
oper = 'AND'
if self._name == 'ir.model':
table_join = 'LEFT JOIN "ir_translation" ' \
'ON (ir_translation.name = ' \
'ir_model.model||\',%s\' ' \
'AND ir_translation.res_id = 0' \
'AND ir_translation.lang = %%s ' \
'AND ir_translation.type = \'model\' ' \
'AND ir_translation.fuzzy = false)' % \
(domain[i][0],)
elif self._name == 'ir.model.field':
if domain[i][0] == 'field_description':
ttype = 'field'
else:
ttype = 'help'
table_join = 'LEFT JOIN "ir_model" ' \
'ON ir_model.id = ir_model_field.model ' \
'LEFT JOIN "ir_translation" ' \
'ON (ir_translation.name = ' \
'ir_model.model||\',\'||%s.name ' \
'AND ir_translation.res_id = 0' \
'AND ir_translation = %%s ' \
'AND ir_translation.type = \'%s\' ' \
'AND ir_translation.fuzzy = false)' % \
(table._table, ttype)
else:
table_join = 'LEFT JOIN "ir_translation" ' \
'ON (ir_translation.res_id = %s.id ' \
'AND ir_translation.name = \'%s,%s\' ' \
'AND ir_translation.lang = %%s ' \
'AND ir_translation.type = \'model\' ' \
'AND ir_translation.fuzzy = false)' % \
(table._table, table._name, domain[i][0])
table_join_args = [context.get('language') or 'en_US']
table_query = ''
table_args = []
if table.table_query(context):
table_query, table_args = table.table_query(context)
table_query = '(' + table_query + ') AS '
trans_field = 'COALESCE(NULLIF(' \
'ir_translation.value, \'\'), ' \
+ table._table + '.' + domain[i][0] + ')'
query1 = '(SELECT ' + table._table + '.id ' \
'FROM ' + table_query + '"' + table._table + '" ' \
+ table_join + ' ' \
'WHERE (' + trans_field + ' ' + \
domain[i][1] + ' %s ' + oper + ' ' + trans_field + ' ' + \
domain[i][1] + ' %s))'
query2 = table_args + table_join_args + [exprs[0] % domain[i][2],
exprs[1] % domain[i][2]]
domain[i] = ('id', 'inselect', (query1, query2), table)
else:
domain[i] += (table,)
i += 1
domain.extend(joins)
qu1, qu2 = [], []
for arg in domain:
table = self
if len(arg) > 3:
table = arg[3]
if arg[1] in ('inselect', 'notinselect'):
clause = 'IN'
if arg[1] == 'notinselect':
clause = 'NOT IN'
qu1.append('(%s.%s %s (%s))' % (table._table, arg[0], clause,
arg[2][0]))
qu2 += arg[2][1]
elif arg[1] in ('in', 'not in'):
if len(arg[2]) > 0:
todel = []
for xitem in range(len(arg[2])):
if arg[2][xitem] == False \
and isinstance(arg[2][xitem],bool):
todel.append(xitem)
arg2 = arg[2][:]
for xitem in todel[::-1]:
del arg2[xitem]
#TODO fix max_stack_depth
if len(arg2):
if arg[0] == 'id':
qu1.append(('(%s.id ' + arg[1] + ' (%s))') % \
(table._table,
','.join(['%s'] * len(arg2)),))
else:
symbol_c = FIELDS[table._columns[arg[0]]._type]\
.symbol_c
qu1.append(('(%s.%s ' + arg[1] + ' (%s))') % \
(table._table, arg[0], ','.join(
[symbol_c] * len(arg2))))
if todel:
if table._columns[arg[0]]._type == 'boolean':
if arg[1] == 'in':
qu1[-1] = '(' + qu1[-1] + ' OR ' \
'%s.%s = false)' % \
(table._table, arg[0])
else:
qu1[-1] = '(' + qu1[-1] + ' OR ' \
'%s.%s != false)' % \
(table._table, arg[0])
else:
if arg[1] == 'in':
qu1[-1] = '(' + qu1[-1] + ' OR ' \
'%s.%s IS NULL)' % \
(table._table, arg[0])
else:
qu1[-1] = '(' + qu1[-1] + ' OR ' \
'%s.%s IS NOT NULL)' % \
(table._table, arg[0])
qu2 += arg2
elif todel:
if table._columns[arg[0]]._type == 'boolean':
if arg[1] == 'in':
qu1.append('(%s.%s = false)' % \
(table._table, arg[0]))
else:
qu1.append('(%s.%s != false)' % \
(table._table, arg[0]))
else:
if arg[1] == 'in':
qu1.append('(%s.%s IS NULL)' % \
(table._table, arg[0]))
else:
qu1.append('(%s.%s IS NOT NULL)' % \
(table._table, arg[0]))
else:
if arg[1] == 'in':
qu1.append(' false')
else:
qu1.append(' true')
else:
if (arg[2] is False) and (arg[1] == '='):
if table._columns[arg[0]]._type == 'boolean':
qu1.append('(%s.%s = false)' % \
(table._table, arg[0]))
else:
qu1.append('(%s.%s IS NULL)' % \
(table._table, arg[0]))
elif (arg[2] is False) and (arg[1] == '!='):
qu1.append('(%s.%s IS NOT NULL)' % \
(table._table, arg[0]))
else:
if arg[0] == 'id':
qu1.append('(%s.%s %s %%s)' % \
(table._table, arg[0], arg[1]))
qu2.append(arg[2])
else:
add_null = False
if arg[1] in ('like', 'ilike', 'not like', 'not ilike'):
qu2.append('%% %s%%' % arg[2])
qu2.append('%s%%' % arg[2])
if not arg[2]:
add_null = True
else:
if arg[0] in table._columns:
qu2.append(FIELDS[table._columns[arg[0]]._type].\
symbol_f(arg[2]))
if arg[0] in table._columns:
if arg[1] in ('like', 'ilike'):
qu1.append('(%s.%s %s %s OR %s.%s %s %s)' % \
(table._table, arg[0], arg[1], '%s',
table._table, arg[0], arg[1], '%s'))
elif arg[1] in ('not like', 'not ilike'):
qu1.append('(%s.%s %s %s AND %s.%s %s %s)' % \
(table._table, arg[0], arg[1], '%s',
table._table, arg[0], arg[1], '%s'))
else:
qu1.append('(%s.%s %s %s)' % (table._table,
arg[0], arg[1], FIELDS[table._columns[
arg[0]]._type].symbol_c))
else:
if arg[1] in ('like', 'ilike'):
qu1.append('(%s.%s %s \'%s\' or %s.%s %s \'%s\')' % \
(table._table, arg[0], arg[1], arg[2],
table._table, arg[0], arg[1], arg[2]))
elif arg[1] in ('not like', 'not ilike'):
qu1.append('(%s.%s %s \'%s\' and %s.%s %s \'%s\')' % \
(table._table, arg[0], arg[1], arg[2],
table._table, arg[0], arg[1], arg[2]))
else:
qu1.append('(%s.%s %s \'%s\')' % \
(table._table, arg[0], arg[1], arg[2]))
if add_null:
qu1[-1] = '('+qu1[-1]+' OR '+arg[0]+' is null)'
return qu1, qu2
def _order_calc(self, cursor, user, field, otype, context=None):
order_by = []
tables = []
tables_args = {}
field_name = None
table_name = None
link_field = None
if context is None:
context = {}
if field in self._columns:
table_name = self._table
if not hasattr(self._columns[field], 'set'):
field_name = field
if self._columns[field].order_field:
field_name = self._columns[field].order_field
if isinstance(self._columns[field], fields.Many2One):
obj = self.pool.get(self._columns[field].model_name)
table_name = obj._table
link_field = field
field_name = None
if obj._rec_name in obj._columns:
field_name = obj._rec_name
if obj._order_name in obj._columns:
field_name = obj._order_name
if field_name:
order_by, tables, tables_args = obj._order_calc(cursor,
user, field_name, otype, context=context)
if table_name == self._table:
table_join = 'LEFT JOIN "' + table_name + '" AS ' \
'"' + table_name + '.' + link_field + '" ON ' \
'"%s.%s".id = %s.%s' % (table_name, link_field,
self._table, link_field)
for i in range(len(order_by)):
if table_name in order_by[i]:
order_by[i] = order_by[i].replace(table_name,
'"' + table_name + '.' + \
link_field + '"')
else:
table_join = 'LEFT JOIN "' + table_name + '" ON ' \
'%s.id = %s.%s' % (table_name, self._table,
link_field)
if table_join not in tables:
tables.insert(0, table_join)
return order_by, tables, tables_args
obj2 = None
if obj._rec_name in obj._inherit_fields.keys():
obj2 = self.pool.get(
obj._inherit_fields[obj._rec_name][0])
field_name = obj._rec_name
if obj._order_name in obj._inherit_fields.keys():
obj2 = self.pool.get(
obj._inherit_fields[obj._order_name][0])
field_name = obj._order_name
if obj2 and field_name:
table_name2 = obj2._table
link_field2 = obj._inherits[obj2._name]
order_by, tables, tables_args = obj2._order_calc(cursor,
user, field_name, otype, context=context)
table_join = 'LEFT JOIN "' + table_name + '" ON ' \
'%s.id = %s.%s' % \
(table_name, self._table, link_field)
if table_join not in tables:
tables.insert(0, table_join)
table_join2 = 'LEFT JOIN "' + table_name2 + '" ON ' \
'%s.id = %s.%s' % \
(table_name2, obj._table, link_field2)
if table_join2 not in tables:
tables.insert(1, table_join2)
return order_by, tables, tables_args
if field_name in self._columns \
and self._columns[field_name].translate:
translation_table = 'ir_translation_%s_%s' % \
(table_name, field_name)
if self._name == 'ir.model':
table_join = 'LEFT JOIN "ir_translation" ' \
'AS "%s" ON ' \
'(%s.name = ir_model.model||\',%s\' ' \
'AND %s.res_id = 0 ' \
'AND %s.lang = %%s ' \
'AND %s.type = \'model\' ' \
'AND %s.fuzzy = false)' % \
(translation_table, translation_table, field_name,
translation_table, translation_table,
translation_table, translation_table)
elif self._name == 'ir.model.field':
if field_name == 'field_description':
ttype = 'field'
else:
ttype = 'help'
table_join = 'LEFT JOIN "ir_model" ON ' \
'ir_model.id = ir_model_field.model'
if table_join not in tables:
tables.append(table_join)
table_join = 'LEFT JOIN "ir_translation" ' \
'AS "%s" ON ' \
'(%s.name = ir_model.model||\',\'||%s.name ' \
'AND %s.res_id = 0 ' \
'AND %s.lang = %%s ' \
'AND %s.type = \'%s\' ' \
'AND %s.fuzzy = false)' % \
(translation_table, translation_table, table_name,
translation_table, translation_table,
translation_table, ttype, translation_table)
else:
table_join = 'LEFT JOIN "ir_translation" ' \
'AS "%s" ON ' \
'(%s.res_id = %s.id ' \
'AND %s.name = \'%s,%s\' ' \
'AND %s.lang = %%s ' \
'AND %s.type = \'model\' ' \
'AND %s.fuzzy = false)' % \
(translation_table, translation_table, table_name,
translation_table, self._name, field_name,
translation_table, translation_table,
translation_table)
if table_join not in tables:
tables.append(table_join)
tables_args[table_join] = [context.get('language') or 'en_US']
order_by.append('COALESCE(NULLIF(' \
+ translation_table + '.value, \'\'), ' \
+ table_name + '.' + field_name + ') ' + otype)
return order_by, tables, tables_args
if field_name in self._columns \
and self._columns[field_name]._type == 'selection' \
and self._columns[field_name].order_field is None:
selections = self.fields_get(cursor, user, [field_name],
context=context)[field_name]['selection']
if not isinstance(selections, (tuple, list)):
selections = getattr(self,
self._columns[field_name].selection)(cursor,
user, context=context)
order = 'CASE ' + table_name + '.' + field_name
for selection in selections:
order += ' WHEN \'%s\' THEN \'%s\'' % selection
order += ' ELSE ' + table_name + '.' + field_name + ' END'
order_by.append(order + ' ' + otype)
return order_by, tables, tables_args
if field_name:
if '%(table)s' in field_name or '%(order)s' in field_name:
order_by.append(field_name % {
'table': table_name,
'order': otype,
})
else:
order_by.append(table_name + '.' + field_name + ' ' + otype)
return order_by, tables, tables_args
if field in self._inherit_fields.keys():
obj = self.pool.get(self._inherit_fields[field][0])
table_name = obj._table
link_field = self._inherits[obj._name]
order_by, tables, tables_args = obj._order_calc(cursor, user, field,
otype, context=context)
table_join = 'LEFT JOIN "' + table_name + '" ON ' \
'%s.id = %s.%s' % \
(table_name, self._table, link_field)
if table_join not in tables:
tables.insert(0, table_join)
return order_by, tables, tables_args
raise Exception('Error', 'Wrong field name (%s) in order!' % field)