2277 lines
102 KiB
Python
2277 lines
102 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.
|
|
import contextlib
|
|
import datetime
|
|
import re
|
|
from functools import reduce
|
|
from decimal import Decimal
|
|
from itertools import islice
|
|
from trytond.config import CONFIG
|
|
from trytond.model import ModelStorage
|
|
from trytond.model import fields
|
|
from trytond.backend import FIELDS, TableHandler
|
|
from trytond.backend import DatabaseIntegrityError
|
|
from trytond.tools import reduce_ids
|
|
from trytond.const import OPERATORS, RECORD_CACHE_SIZE
|
|
from trytond.transaction import Transaction
|
|
from trytond.pool import Pool
|
|
from trytond.cache import LRUDict
|
|
from trytond.exceptions import ConcurrencyException
|
|
_RE_UNIQUE = re.compile('UNIQUE\s*\((.*)\)', re.I)
|
|
_RE_CHECK = re.compile('CHECK\s*\((.*)\)', re.I)
|
|
|
|
|
|
class ModelSQL(ModelStorage):
|
|
"""
|
|
Define a model with storage in database.
|
|
"""
|
|
_table = None # The name of the table in database
|
|
_order = None
|
|
_order_name = None # Use to force order field when sorting on Many2One
|
|
_history = False
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ModelSQL, cls).__setup__()
|
|
cls._sql_constraints = []
|
|
cls._order = [('id', 'ASC')]
|
|
cls._sql_error_messages = {}
|
|
|
|
if not cls._table:
|
|
cls._table = cls.__name__.replace('.', '_')
|
|
|
|
assert cls._table[-9:] != '__history', \
|
|
'Model _table %s cannot end with "__history"' % cls._table
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
super(ModelSQL, cls).__register__(module_name)
|
|
|
|
if cls.table_query():
|
|
return
|
|
|
|
pool = Pool()
|
|
|
|
# create/update table in the database
|
|
table = TableHandler(Transaction().cursor, cls, module_name)
|
|
if cls._history:
|
|
history_table = TableHandler(Transaction().cursor, cls,
|
|
module_name, history=True)
|
|
timestamp_field = FIELDS['timestamp']
|
|
integer_field = FIELDS['integer']
|
|
logs = (
|
|
('create_date', timestamp_field.sql_type(None),
|
|
timestamp_field.sql_format, lambda *a: datetime.datetime.now(),
|
|
cls.create_date.string),
|
|
('write_date', timestamp_field.sql_type(None),
|
|
timestamp_field.sql_format, None, cls.write_date.string),
|
|
('create_uid', (integer_field.sql_type(None)[0],
|
|
'INTEGER REFERENCES res_user ON DELETE SET NULL',),
|
|
integer_field.sql_format, lambda *a: 0, cls.create_uid.string),
|
|
('write_uid', (integer_field.sql_type(None)[0],
|
|
'INTEGER REFERENCES res_user ON DELETE SET NULL'),
|
|
integer_field.sql_format, None, cls.write_uid.string),
|
|
)
|
|
for log in logs:
|
|
table.add_raw_column(log[0], log[1], log[2],
|
|
default_fun=log[3], migrate=False, string=log[4])
|
|
if cls._history:
|
|
history_logs = (
|
|
('create_date', timestamp_field.sql_type(None),
|
|
timestamp_field.sql_format, cls.create_date.string),
|
|
('write_date', timestamp_field.sql_type(None),
|
|
timestamp_field.sql_format, cls.write_date.string),
|
|
('create_uid', (integer_field.sql_type(None)[0],
|
|
'INTEGER REFERENCES res_user ON DELETE SET NULL',),
|
|
integer_field.sql_format, cls.create_uid.string),
|
|
('write_uid', (integer_field.sql_type(None)[0],
|
|
'INTEGER REFERENCES res_user ON DELETE SET NULL'),
|
|
integer_field.sql_format, cls.write_uid.string),
|
|
)
|
|
for log in history_logs:
|
|
history_table.add_raw_column(log[0], log[1], log[2],
|
|
migrate=False, string=log[3])
|
|
history_table.index_action('id', action='add')
|
|
|
|
for field_name, field in cls._fields.iteritems():
|
|
default_fun = None
|
|
if field_name in (
|
|
'id',
|
|
'write_uid',
|
|
'write_date',
|
|
'create_uid',
|
|
'create_date',
|
|
'rec_name',
|
|
):
|
|
continue
|
|
|
|
if not hasattr(field, 'set'):
|
|
if field_name in cls._defaults:
|
|
default_fun = cls._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 Exception:
|
|
return None
|
|
clean_results = cls._clean_defaults(
|
|
{field_name: result})
|
|
return clean_results[field_name]
|
|
return unpack_result
|
|
default_fun = unpack_wrapper(default_fun)
|
|
|
|
if hasattr(field, 'size') and isinstance(field.size, int):
|
|
field_size = field.size
|
|
else:
|
|
field_size = None
|
|
table.add_raw_column(field_name,
|
|
FIELDS[field._type].sql_type(field),
|
|
FIELDS[field._type].sql_format, default_fun,
|
|
field_size, string=field.string)
|
|
if cls._history:
|
|
history_table.add_raw_column(field_name,
|
|
FIELDS[field._type].sql_type(field), None,
|
|
string=field.string)
|
|
|
|
if isinstance(field, (fields.Integer, fields.Float)):
|
|
# migration from tryton 2.2
|
|
table.db_default(field_name, None)
|
|
|
|
if isinstance(field, (fields.Boolean)):
|
|
table.db_default(field_name, False)
|
|
|
|
if isinstance(field, fields.Many2One):
|
|
if field.model_name in ('res.user', 'res.group'):
|
|
ref = field.model_name.replace('.', '_')
|
|
else:
|
|
ref = 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
|
|
table.not_null_action(
|
|
field_name, action=required and 'add' or 'remove')
|
|
|
|
elif not isinstance(field, (fields.One2Many, fields.Function,
|
|
fields.Many2Many)):
|
|
raise Exception('Unknow field type !')
|
|
|
|
for field_name, field in cls._fields.iteritems():
|
|
if isinstance(field, fields.Many2One) \
|
|
and field.model_name == cls.__name__ \
|
|
and field.left and field.right:
|
|
with Transaction().set_user(0):
|
|
cls._rebuild_tree(field_name, False, 0)
|
|
|
|
for ident, constraint, _ in cls._sql_constraints:
|
|
table.add_constraint(ident, constraint)
|
|
|
|
if cls._history:
|
|
cls._update_history_table()
|
|
cursor = Transaction().cursor
|
|
cursor.execute('SELECT id FROM "' + cls._table + '"')
|
|
if cursor.fetchone():
|
|
cursor.execute('SELECT id FROM "' + cls._table + '__history"')
|
|
if not cursor.fetchone():
|
|
columns = ['"' + str(x) + '"' for x in cls._fields
|
|
if not hasattr(cls._fields[x], 'set')]
|
|
cursor.execute('INSERT ' \
|
|
'INTO "' + cls._table + '__history" '\
|
|
'(' + ','.join(columns) + ') ' \
|
|
'SELECT ' + ','.join(columns) + \
|
|
' FROM "' + cls._table + '"')
|
|
cursor.execute('UPDATE "' + cls._table + '__history" ' \
|
|
'SET write_date = NULL')
|
|
|
|
@classmethod
|
|
def _update_history_table(cls):
|
|
if cls._history:
|
|
table = TableHandler(Transaction().cursor, cls)
|
|
history_table = TableHandler(Transaction().cursor, cls,
|
|
history=True)
|
|
for column_name in table._columns:
|
|
string = ''
|
|
if column_name in cls._fields:
|
|
string = cls._fields[column_name].string
|
|
history_table.add_raw_column(column_name,
|
|
(table._columns[column_name]['typname'],
|
|
table._columns[column_name]['typname']),
|
|
None, string=string)
|
|
|
|
@classmethod
|
|
def _get_error_messages(cls):
|
|
res = super(ModelSQL, cls)._get_error_messages()
|
|
res += cls._sql_error_messages.values()
|
|
for _, _, error in cls._sql_constraints:
|
|
res.append(error)
|
|
return res
|
|
|
|
@staticmethod
|
|
def table_query():
|
|
'''
|
|
Return None if the model is a real table in the database
|
|
or return a tuple with the SQL query and the arguments.
|
|
'''
|
|
return None
|
|
|
|
@classmethod
|
|
def create(cls, values):
|
|
super(ModelSQL, cls).create(values)
|
|
cursor = Transaction().cursor
|
|
pool = Pool()
|
|
|
|
if cls.table_query():
|
|
return False
|
|
|
|
values = values.copy()
|
|
|
|
# Clean values
|
|
for key in ('create_uid', 'create_date', 'write_uid', 'write_date',
|
|
'id'):
|
|
if key in values:
|
|
del values[key]
|
|
|
|
# Get default values
|
|
default = []
|
|
avoid_table = []
|
|
for (i, j) in cls._inherits.items():
|
|
if j in values:
|
|
avoid_table.append(i)
|
|
for i in cls._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 cls._inherit_fields.keys():
|
|
if ((not i in values)
|
|
and (not cls._inherit_fields[i][0] in avoid_table)
|
|
and i in cls._defaults):
|
|
default.append(i)
|
|
|
|
if len(default):
|
|
defaults = cls.default_get(default, with_rec_name=False)
|
|
for field in defaults.keys():
|
|
if '.' in field:
|
|
del defaults[field]
|
|
if field in ('create_uid', 'create_date',
|
|
'write_uid', 'write_date'):
|
|
del defaults[field]
|
|
if field in values:
|
|
del defaults[field]
|
|
values.update(cls._clean_defaults(defaults))
|
|
|
|
(upd0, upd1, upd2) = ('', '', [])
|
|
upd_todo = []
|
|
|
|
# Create inherits
|
|
tocreate = {}
|
|
for i in cls._inherits:
|
|
if cls._inherits[i] not in values:
|
|
tocreate[i] = {}
|
|
|
|
for i in values.keys():
|
|
if i in cls._inherit_fields:
|
|
(inherits, _, _) = cls._inherit_fields[i]
|
|
if i in cls._fields:
|
|
continue
|
|
if inherits in tocreate:
|
|
tocreate[inherits][i] = values[i]
|
|
del values[i]
|
|
|
|
for inherits in tocreate:
|
|
Inherits = pool.get(inherits)
|
|
with Transaction().set_user(0, set_context=True):
|
|
inherits_record = Inherits.create(tocreate[inherits])
|
|
values[cls._inherits[inherits]] = inherits_record.id
|
|
|
|
# Insert record
|
|
for fname, value in values.iteritems():
|
|
field = cls._fields[fname]
|
|
if not hasattr(field, 'set'):
|
|
upd0 = upd0 + ',"' + fname + '"'
|
|
upd1 = upd1 + ', %s'
|
|
upd2.append(FIELDS[field._type].sql_format(value))
|
|
else:
|
|
upd_todo.append(fname)
|
|
if (hasattr(field, 'selection')
|
|
and field.selection
|
|
and value):
|
|
if field._type == 'reference':
|
|
if isinstance(value, basestring):
|
|
value = value.split(',')[0]
|
|
else:
|
|
value, _ = value
|
|
if isinstance(field.selection, (tuple, list)):
|
|
test = dict(field.selection)
|
|
else:
|
|
test = dict(getattr(cls, field.selection)())
|
|
if value not in test:
|
|
raise Exception('ValidateError',
|
|
'The value "%s" for the field "%s" '
|
|
'is not in the selection'
|
|
% (value, fname))
|
|
upd0 += ', create_uid, create_date'
|
|
upd1 += ', %s, %s'
|
|
upd2.append(Transaction().user)
|
|
upd2.append(datetime.datetime.now())
|
|
try:
|
|
if cursor.has_returning():
|
|
cursor.execute('INSERT INTO "' + cls._table + '" '
|
|
'(' + upd0[1:] + ') '
|
|
'VALUES (' + upd1[1:] + ') RETURNING id',
|
|
tuple(upd2))
|
|
id_new, = cursor.fetchone()
|
|
else:
|
|
id_new = cursor.nextid(cls._table)
|
|
if id_new:
|
|
cursor.execute('INSERT INTO "' + cls._table + '" '
|
|
'(id' + upd0 + ') '
|
|
'VALUES (' + str(id_new) + upd1 + ')',
|
|
tuple(upd2))
|
|
else:
|
|
cursor.execute('INSERT INTO "' + cls._table + '" '
|
|
'(' + upd0[1:] + ') '
|
|
'VALUES (' + upd1[1:] + ')',
|
|
tuple(upd2))
|
|
id_new = cursor.lastid()
|
|
except DatabaseIntegrityError, exception:
|
|
with contextlib.nested(Transaction().new_cursor(),
|
|
Transaction().set_user(0)):
|
|
for field_name in cls._fields:
|
|
field = cls._fields[field_name]
|
|
# Check required fields
|
|
if field.required and \
|
|
not hasattr(field, 'set') and \
|
|
field_name not in ('create_uid', 'create_date'):
|
|
if not values.get(field_name):
|
|
cls.raise_user_error('required_field',
|
|
error_args=cls._get_error_args(
|
|
field_name))
|
|
if isinstance(field, fields.Many2One) \
|
|
and values.get(field_name):
|
|
Model = pool.get(field.model_name)
|
|
create_records = Transaction().create_records.get(
|
|
field.model_name, set())
|
|
delete_records = Transaction().delete_records.get(
|
|
field.model_name, set())
|
|
if not ((Model.search([
|
|
('id', '=', values[field_name]),
|
|
], order=[])
|
|
or values[field_name] in create_records)
|
|
and values[field_name] not in delete_records):
|
|
cls.raise_user_error('foreign_model_missing',
|
|
error_args=cls._get_error_args(
|
|
field_name))
|
|
for name, _, error in cls._sql_constraints:
|
|
if name in exception[0]:
|
|
cls.raise_user_error(error)
|
|
for name, error in cls._sql_error_messages:
|
|
if name in exception[0]:
|
|
cls.raise_user_error(error)
|
|
raise
|
|
|
|
domain1, domain2 = pool.get('ir.rule').domain_get(cls.__name__,
|
|
mode='create')
|
|
if domain1:
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" ' \
|
|
'WHERE id = %s AND (' + domain1 + ')',
|
|
[id_new] + domain2)
|
|
if not cursor.fetchone():
|
|
cls.raise_user_error('access_error', cls.__name__)
|
|
|
|
Transaction().create_records.setdefault(
|
|
cls.__name__, set()).add(id_new)
|
|
|
|
for field in values:
|
|
if getattr(cls._fields[field], 'translate', False):
|
|
pool.get('ir.translation').set_ids(
|
|
cls.__name__ + ',' + field, 'model',
|
|
Transaction().language, [id_new], values[field])
|
|
|
|
for field in upd_todo:
|
|
cls._fields[field].set([id_new], cls, field, values[field])
|
|
|
|
if cls._history:
|
|
cursor.execute('INSERT INTO "' + cls._table + '__history" '
|
|
'(id' + upd0 + ') '
|
|
'SELECT id' + upd0 + ' '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE id = %s', (id_new,))
|
|
|
|
record = cls(id_new)
|
|
cls._validate([record])
|
|
|
|
# Check for Modified Preorder Tree Traversal
|
|
for k in cls._fields:
|
|
field = cls._fields[k]
|
|
if isinstance(field, fields.Many2One) \
|
|
and field.model_name == cls.__name__ \
|
|
and field.left and field.right:
|
|
cls._update_tree(id_new, k, field.left, field.right)
|
|
|
|
cls.trigger_create(record)
|
|
|
|
return record
|
|
|
|
@classmethod
|
|
def read(cls, ids, fields_names=None):
|
|
pool = Pool()
|
|
Rule = pool.get('ir.rule')
|
|
Translation = pool.get('ir.translation')
|
|
super(ModelSQL, cls).read(ids, fields_names=fields_names)
|
|
cursor = Transaction().cursor
|
|
|
|
if not fields_names:
|
|
fields_names = list(set(cls._fields.keys() \
|
|
+ cls._inherit_fields.keys()))
|
|
|
|
if not ids:
|
|
return []
|
|
|
|
# construct a clause for the rules :
|
|
domain1, domain2 = Rule.domain_get(cls.__name__, mode='read')
|
|
|
|
fields_related = {}
|
|
datetime_fields = []
|
|
for field_name in fields_names:
|
|
if field_name == '_timestamp':
|
|
continue
|
|
if '.' in field_name:
|
|
field, field_related = field_name.split('.', 1)
|
|
fields_related.setdefault(field, [])
|
|
fields_related[field].append(field_related)
|
|
elif field_name in cls._fields:
|
|
field = cls._fields[field_name]
|
|
else:
|
|
field = cls._inherit_fields[field_name][2]
|
|
if hasattr(field, 'datetime_field') and field.datetime_field:
|
|
datetime_fields.append(field.datetime_field)
|
|
|
|
# all inherited fields + all non inherited fields
|
|
fields_pre = [x for x in \
|
|
fields_names + fields_related.keys() + datetime_fields \
|
|
if (x in cls._fields and \
|
|
not hasattr(cls._fields[x], 'set')) or \
|
|
(x == '_timestamp')] + \
|
|
cls._inherits.values()
|
|
|
|
res = []
|
|
table_query = ''
|
|
table_args = []
|
|
|
|
if cls.table_query():
|
|
table_query, table_args = cls.table_query()
|
|
table_query = '(' + table_query + ') AS '
|
|
|
|
in_max = cursor.IN_MAX
|
|
history_order = ''
|
|
history_clause = ''
|
|
history_limit = ''
|
|
history_args = []
|
|
if (cls._history
|
|
and Transaction().context.get('_datetime')
|
|
and not table_query):
|
|
in_max = 1
|
|
table_query = '"' + cls._table + '__history" AS '
|
|
history_clause = ' AND (COALESCE(write_date, create_date) <= %s)'
|
|
history_order = ' ORDER BY COALESCE(write_date, create_date) DESC'
|
|
history_limit = cursor.limit_clause('', 1)
|
|
history_args = [Transaction().context['_datetime']]
|
|
if len(fields_pre):
|
|
fields_pre2 = ['"%s"."%s" AS "%s"' % (cls._table, x, x)
|
|
for x in fields_pre if x != '_timestamp']
|
|
if '_timestamp' in fields_pre:
|
|
if not cls.table_query():
|
|
fields_pre2 += ['CAST(EXTRACT(EPOCH FROM '
|
|
'(COALESCE("%s".write_date, "%s".create_date))) '
|
|
'AS VARCHAR) AS _timestamp' %
|
|
(cls._table, cls._table)]
|
|
if 'id' not in fields_pre:
|
|
fields_pre2 += ['"%s".id AS id' % cls._table]
|
|
|
|
for i in range(0, len(ids), in_max):
|
|
sub_ids = ids[i:i + in_max]
|
|
red_sql, red_ids = reduce_ids('id', sub_ids)
|
|
if domain1:
|
|
cursor.execute('SELECT ' + ','.join(fields_pre2)
|
|
+ ' FROM ' + table_query + '\"' + cls._table + '\" '
|
|
'WHERE ' + red_sql + history_clause +
|
|
' AND (' + domain1 + ') '
|
|
+ history_order
|
|
+ history_limit,
|
|
table_args + red_ids + history_args + domain2)
|
|
else:
|
|
cursor.execute('SELECT ' + ','.join(fields_pre2)
|
|
+ ' FROM ' + table_query + '\"' + cls._table + '\" '
|
|
'WHERE ' + red_sql + history_clause
|
|
+ history_order
|
|
+ history_limit,
|
|
table_args + red_ids + history_args)
|
|
dictfetchall = cursor.dictfetchall()
|
|
if not len(dictfetchall) == len({}.fromkeys(sub_ids)):
|
|
if domain1:
|
|
cursor.execute('SELECT id '
|
|
'FROM ' + table_query + '\"' + cls._table + '\" '
|
|
'WHERE ' + red_sql + history_clause
|
|
+ history_order
|
|
+ history_limit,
|
|
table_args + red_ids + history_args)
|
|
rowcount = cursor.rowcount
|
|
if rowcount == -1 or rowcount is None:
|
|
rowcount = len(cursor.fetchall())
|
|
if rowcount == len({}.fromkeys(sub_ids)):
|
|
cls.raise_user_error('access_error', cls.__name__)
|
|
cls.raise_user_error('read_error', cls.__name__)
|
|
res.extend(dictfetchall)
|
|
else:
|
|
res = [{'id': x} for x in ids]
|
|
|
|
for field in fields_pre:
|
|
if field == '_timestamp':
|
|
continue
|
|
if getattr(cls._fields[field], 'translate', False):
|
|
ids = [x['id'] for x in res]
|
|
res_trans = Translation.get_ids(cls.__name__ + ',' + field,
|
|
'model', Transaction().language, ids)
|
|
for i in res:
|
|
i[field] = res_trans.get(i['id'], False) or i[field]
|
|
|
|
for table in cls._inherits:
|
|
field = cls._inherits[table]
|
|
inherits_fields = list(
|
|
set(cls._inherit_fields.keys()).intersection(
|
|
set(fields_names + fields_related.keys() + datetime_fields)
|
|
).difference(set(cls._fields.keys())))
|
|
if not inherits_fields:
|
|
for record in res:
|
|
if field not in fields_names + fields_related.keys() + \
|
|
datetime_fields:
|
|
del record[field]
|
|
continue
|
|
inherit_related_fields = []
|
|
for inherit_field in inherits_fields:
|
|
if inherit_field in fields_related:
|
|
for field_related in fields_related[inherit_field]:
|
|
inherit_related_fields.append(
|
|
inherit_field + '.' + field_related)
|
|
res2 = pool.get(table).read([x[field] for x in res],
|
|
fields_names=inherits_fields + inherit_related_fields)
|
|
|
|
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 + fields_related.keys() + datetime_fields:
|
|
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 + fields_related.keys() + datetime_fields \
|
|
if x in cls._fields \
|
|
and hasattr(cls._fields[x], 'get')]
|
|
func_fields = {}
|
|
for field in fields_post:
|
|
if isinstance(cls._fields[field], fields.Function):
|
|
key = (cls._fields[field].getter,
|
|
getattr(cls._fields[field], 'datetime_field', None))
|
|
func_fields.setdefault(key, [])
|
|
func_fields[key].append(field)
|
|
continue
|
|
if hasattr(cls._fields[field], 'datetime_field') \
|
|
and cls._fields[field].datetime_field:
|
|
for record in res:
|
|
with Transaction().set_context(_datetime=record[
|
|
cls._fields[field].datetime_field]):
|
|
res2 = cls._fields[field].get([record['id']], cls,
|
|
field, values=[record])
|
|
record[field] = res2[record['id']]
|
|
continue
|
|
# get the value of that field for all records/ids
|
|
res2 = cls._fields[field].get(ids, cls, field, values=res)
|
|
for record in res:
|
|
record[field] = res2[record['id']]
|
|
for key in func_fields:
|
|
field_list = func_fields[key]
|
|
field = field_list[0]
|
|
_, datetime_field = key
|
|
if datetime_field:
|
|
for record in res:
|
|
with Transaction().set_context(
|
|
_datetime=record[datetime_field]):
|
|
res2 = cls._fields[field].get([record['id']], cls,
|
|
field_list, values=[record])
|
|
for field in res2:
|
|
record[field] = res2[field][record['id']]
|
|
continue
|
|
res2 = cls._fields[field].get(ids, cls, field_list, values=res)
|
|
for field in res2:
|
|
for record in res:
|
|
record[field] = res2[field][record['id']]
|
|
|
|
to_del = set()
|
|
fields_related2values = {}
|
|
for field in fields_related.keys() + datetime_fields:
|
|
if field not in fields_names:
|
|
to_del.add(field)
|
|
if field not in cls._fields:
|
|
continue
|
|
if field not in fields_related.keys():
|
|
continue
|
|
fields_related2values.setdefault(field, {})
|
|
if cls._fields[field]._type in ('many2one', 'one2one'):
|
|
if hasattr(cls._fields[field], 'model_name'):
|
|
obj = pool.get(cls._fields[field].model_name)
|
|
else:
|
|
obj = cls._fields[field].get_target()
|
|
if hasattr(cls._fields[field], 'datetime_field') \
|
|
and cls._fields[field].datetime_field:
|
|
for record in res:
|
|
if not record[field]:
|
|
continue
|
|
with Transaction().set_context(_datetime=record[
|
|
cls._fields[field].datetime_field]):
|
|
record2, = obj.read([record[field]],
|
|
fields_related[field])
|
|
record_id = record2['id']
|
|
del record2['id']
|
|
fields_related2values[field].setdefault(record_id, {})
|
|
fields_related2values[
|
|
field][record_id][record['id']] = record2
|
|
else:
|
|
for record in obj.read([x[field] for x in res
|
|
if x[field]], fields_related[field]):
|
|
record_id = record['id']
|
|
del record['id']
|
|
fields_related2values[field].setdefault(record_id, {})
|
|
for record2 in res:
|
|
fields_related2values[
|
|
field][record_id][record2['id']] = record
|
|
elif cls._fields[field]._type == 'reference':
|
|
for record in res:
|
|
if not record[field]:
|
|
continue
|
|
model_name, record_id = record[field].split(',', 1)
|
|
if not model_name:
|
|
continue
|
|
record_id = int(record_id)
|
|
if record_id < 0:
|
|
continue
|
|
obj = pool.get(model_name)
|
|
record2, = obj.read([record_id], fields_related[field])
|
|
del record2['id']
|
|
fields_related2values[field][record[field]] = record2
|
|
|
|
if to_del or fields_related.keys() or datetime_fields:
|
|
for record in res:
|
|
for field in fields_related.keys():
|
|
if field not in cls._fields:
|
|
continue
|
|
for related in fields_related[field]:
|
|
if cls._fields[field]._type in (
|
|
'many2one', 'one2one'):
|
|
if record[field]:
|
|
record[field + '.' + related] = (
|
|
fields_related2values[field][
|
|
record[field]][record['id']][related])
|
|
else:
|
|
record[field + '.' + related] = False
|
|
elif cls._fields[field]._type == 'reference':
|
|
if record[field]:
|
|
model_name, record_id = record[field].split(
|
|
',', 1)
|
|
if not model_name:
|
|
continue
|
|
record_id = int(record_id)
|
|
if record_id < 0:
|
|
continue
|
|
record[field + '.' + related] = \
|
|
fields_related2values[field][
|
|
record[field]][related]
|
|
for field in to_del:
|
|
del record[field]
|
|
|
|
return res
|
|
|
|
@classmethod
|
|
def write(cls, records, values):
|
|
cursor = Transaction().cursor
|
|
pool = Pool()
|
|
Translation = pool.get('ir.translation')
|
|
ids = map(int, records)
|
|
|
|
# Call before cursor cache cleaning
|
|
trigger_eligibles = cls.trigger_write_get_eligibles(records)
|
|
|
|
super(ModelSQL, cls).write(records, values)
|
|
|
|
if not records:
|
|
return True
|
|
|
|
if cls.table_query():
|
|
return True
|
|
|
|
values = values.copy()
|
|
|
|
# _update_tree works if only one record has changed
|
|
update_tree = False
|
|
for k in cls._fields:
|
|
field = cls._fields[k]
|
|
if isinstance(field, fields.Many2One) \
|
|
and field.model_name == cls.__name__ \
|
|
and field.left and field.right:
|
|
update_tree = True
|
|
if update_tree and len(records) > 1:
|
|
for record in records:
|
|
cls.write([record], values)
|
|
return True
|
|
|
|
if Transaction().timestamp:
|
|
for i in range(0, len(ids), cursor.IN_MAX):
|
|
sub_ids = ids[i:i + cursor.IN_MAX]
|
|
clause = ('(id = %s AND '
|
|
'CAST(EXTRACT(EPOCH FROM '
|
|
'COALESCE(write_date, create_date)) AS ' + \
|
|
FIELDS['numeric'].sql_type(cls.create_date)[1] + \
|
|
') > %s)')
|
|
args = []
|
|
for i in sub_ids:
|
|
if Transaction().timestamp.get(
|
|
cls.__name__ + ',' + str(i)):
|
|
args.append(i)
|
|
args.append(Decimal(Transaction().timestamp[
|
|
cls.__name__ + ',' + str(i)]))
|
|
if args:
|
|
cursor.execute("SELECT id " \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE ' + ' OR '.join(
|
|
(clause,) * (len(args) // 2)), args)
|
|
if cursor.fetchone():
|
|
raise ConcurrencyException(
|
|
'Records were modified in the meanwhile')
|
|
for i in ids:
|
|
if Transaction().timestamp.get(cls.__name__ + ',' + str(i)):
|
|
del Transaction().timestamp[cls.__name__ + ',' + str(i)]
|
|
|
|
# Clean values
|
|
for key in ('create_uid', 'create_date', 'write_uid', 'write_date',
|
|
'id'):
|
|
if key in values:
|
|
del values[key]
|
|
|
|
upd0 = []
|
|
upd1 = []
|
|
upd_todo = []
|
|
updend = []
|
|
direct = []
|
|
for field in values:
|
|
if field in cls._fields:
|
|
if not hasattr(cls._fields[field], 'set'):
|
|
if ((not getattr(cls._fields[field], 'translate', False))
|
|
or (Transaction().language == CONFIG['language'])):
|
|
upd0.append(('"' + field + '"', '%s'))
|
|
upd1.append(FIELDS[cls._fields[field]._type]\
|
|
.sql_format(values[field]))
|
|
direct.append(field)
|
|
else:
|
|
upd_todo.append(field)
|
|
else:
|
|
updend.append(field)
|
|
if field in cls._fields \
|
|
and hasattr(cls._fields[field], 'selection') \
|
|
and cls._fields[field].selection \
|
|
and values[field]:
|
|
if cls._fields[field]._type == 'reference':
|
|
if isinstance(values[field], basestring):
|
|
val = values[field].split(',')[0]
|
|
else:
|
|
val, _ = values[field]
|
|
else:
|
|
val = values[field]
|
|
if isinstance(cls._fields[field].selection, (tuple, list)):
|
|
if val not in dict(cls._fields[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(cls,
|
|
cls._fields[field].selection)()):
|
|
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', '%s'))
|
|
upd1.append(Transaction().user)
|
|
upd1.append(datetime.datetime.now())
|
|
|
|
domain1, domain2 = pool.get('ir.rule').domain_get(cls.__name__,
|
|
mode='write')
|
|
if domain1:
|
|
domain1 = ' AND (' + domain1 + ') '
|
|
for i in range(0, len(ids), cursor.IN_MAX):
|
|
sub_ids = ids[i:i + cursor.IN_MAX]
|
|
red_sql, red_ids = reduce_ids('id', sub_ids)
|
|
if domain1:
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" ' \
|
|
'WHERE ' + red_sql + ' ' + domain1,
|
|
red_ids + domain2)
|
|
else:
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" ' \
|
|
'WHERE ' + red_sql, red_ids)
|
|
rowcount = cursor.rowcount
|
|
if rowcount == -1 or rowcount is None:
|
|
rowcount = len(cursor.fetchall())
|
|
if not rowcount == len({}.fromkeys(sub_ids)):
|
|
if domain1:
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" ' \
|
|
'WHERE ' + red_sql, red_ids)
|
|
rowcount = cursor.rowcount
|
|
if rowcount == -1 or rowcount is None:
|
|
rowcount = len(cursor.fetchall())
|
|
if rowcount == len({}.fromkeys(sub_ids)):
|
|
cls.raise_user_error('access_error', cls.__name__)
|
|
cls.raise_user_error('write_error', cls.__name__)
|
|
try:
|
|
cursor.execute('UPDATE "' + cls._table + '" '
|
|
'SET ' + ','.join([x[0] + ' = ' + x[1] for x in upd0])
|
|
+ ' WHERE ' + red_sql, upd1 + red_ids)
|
|
except DatabaseIntegrityError, exception:
|
|
with contextlib.nested(Transaction().new_cursor(),
|
|
Transaction().set_user(0)):
|
|
for field_name in values:
|
|
if field_name not in cls._fields:
|
|
continue
|
|
field = cls._fields[field_name]
|
|
# Check required fields
|
|
if field.required and \
|
|
not hasattr(field, 'set') and \
|
|
not isinstance(field, (fields.Integer,
|
|
fields.Float)) and \
|
|
field_name not in ('create_uid',
|
|
'create_date'):
|
|
if not values[field_name]:
|
|
cls.raise_user_error('required_field',
|
|
error_args=cls._get_error_args(
|
|
field_name))
|
|
if isinstance(field, fields.Many2One) \
|
|
and values[field_name]:
|
|
Model = pool.get(field.model_name)
|
|
create_records = Transaction().create_records.get(
|
|
field.model_name, set())
|
|
delete_records = Transaction().delete_records.get(
|
|
field.model_name, set())
|
|
if not ((Model.search([
|
|
('id', '=',
|
|
values[field_name]),
|
|
], order=[])
|
|
or (values[field_name]
|
|
in create_records))
|
|
and (values[field_name]
|
|
not in delete_records)):
|
|
cls.raise_user_error('foreign_model_missing',
|
|
error_args=cls._get_error_args(
|
|
field_name))
|
|
for name, _, error in cls._sql_constraints:
|
|
if name in exception[0]:
|
|
cls.raise_user_error(error)
|
|
for name, error in cls._sql_error_messages:
|
|
if name in exception[0]:
|
|
cls.raise_user_error(error)
|
|
raise
|
|
|
|
for field in direct:
|
|
if getattr(cls._fields[field], 'translate', False):
|
|
Translation.set_ids(
|
|
cls.__name__ + ',' + field, 'model',
|
|
Transaction().language, ids, values[field])
|
|
|
|
# call the 'set' method of fields
|
|
for field in upd_todo:
|
|
cls._fields[field].set(ids, cls, field, values[field])
|
|
|
|
if cls._history:
|
|
columns = ['"' + str(x) + '"' for x in cls._fields
|
|
if not hasattr(cls._fields[x], 'set')]
|
|
for obj_id in ids:
|
|
cursor.execute('INSERT INTO "' + cls._table + '__history" ' \
|
|
'(' + ','.join(columns) + ') ' \
|
|
'SELECT ' + ','.join(columns) + ' ' + \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE id = %s', (obj_id,))
|
|
|
|
for iname in cls._inherits:
|
|
col = cls._inherits[iname]
|
|
iids = []
|
|
for i in range(0, len(ids), cursor.IN_MAX):
|
|
sub_ids = ids[i:i + cursor.IN_MAX]
|
|
red_sql, red_ids = reduce_ids('id', sub_ids)
|
|
cursor.execute('SELECT DISTINCT "' + col + '" ' \
|
|
'FROM "' + cls._table + '" WHERE ' + red_sql,
|
|
red_ids)
|
|
iids.extend([x[0] for x in cursor.fetchall()])
|
|
|
|
values2 = {}
|
|
for val in updend:
|
|
if cls._inherit_fields[val][0] == iname:
|
|
values2[val] = values[val]
|
|
with Transaction().set_user(0, set_context=True):
|
|
IModel = pool.get(iname)
|
|
IModel.write(IModel.browse(iids), values2)
|
|
|
|
cls._validate(records)
|
|
|
|
# Check for Modified Preorder Tree Traversal
|
|
for k in cls._fields:
|
|
field = cls._fields[k]
|
|
if isinstance(field, fields.Many2One) \
|
|
and field.model_name == cls.__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:
|
|
cls._update_tree(ids[0], k, field.left, field.right)
|
|
else:
|
|
with Transaction().set_user(0):
|
|
cls._rebuild_tree(k, False, 0)
|
|
|
|
cls.trigger_write(trigger_eligibles)
|
|
|
|
return True
|
|
|
|
@classmethod
|
|
def delete(cls, records):
|
|
cursor = Transaction().cursor
|
|
pool = Pool()
|
|
ids = map(int, records)
|
|
|
|
if not ids:
|
|
return True
|
|
|
|
if cls.table_query():
|
|
return True
|
|
|
|
if Transaction().delete and Transaction().delete.get(cls.__name__):
|
|
ids = ids[:]
|
|
for del_id in Transaction().delete[cls.__name__]:
|
|
for i in range(ids.count(del_id)):
|
|
ids.remove(del_id)
|
|
|
|
if Transaction().timestamp:
|
|
for i in range(0, len(ids), cursor.IN_MAX):
|
|
sub_ids = ids[i:i + cursor.IN_MAX]
|
|
clause = ('(id = %s AND '
|
|
'CAST(EXTRACT(EPOCH FROM '
|
|
'COALESCE(write_date, create_date)) AS ' + \
|
|
FIELDS['numeric'].sql_type(cls.create_date)[1] + \
|
|
') > %s)')
|
|
args = []
|
|
for i in sub_ids:
|
|
if Transaction().timestamp.get(
|
|
cls.__name__ + ',' + str(i)):
|
|
args.append(i)
|
|
args.append(Transaction().timestamp[
|
|
cls.__name__ + ',' + str(i)])
|
|
if args:
|
|
cursor.execute("SELECT id "
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE ' + ' OR '.join(
|
|
(clause,) * (len(args) / 2)), args)
|
|
if cursor.fetchone():
|
|
raise ConcurrencyException(
|
|
'Records were modified in the meanwhile')
|
|
for i in ids:
|
|
if Transaction().timestamp.get(cls.__name__ + ',' + str(i)):
|
|
del Transaction().timestamp[cls.__name__ + ',' + str(i)]
|
|
|
|
tree_ids = {}
|
|
for k in cls._fields:
|
|
field = cls._fields[k]
|
|
if isinstance(field, fields.Many2One) \
|
|
and field.model_name == cls.__name__ \
|
|
and field.left and field.right:
|
|
red_sql, red_ids = reduce_ids('"' + k + '"', ids)
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" '
|
|
'WHERE ' + red_sql, red_ids)
|
|
tree_ids[k] = [x[0] for x in cursor.fetchall()]
|
|
|
|
foreign_keys_tocheck = []
|
|
foreign_keys_toupdate = []
|
|
foreign_keys_todelete = []
|
|
for _, model in pool.iterobject():
|
|
if hasattr(model, 'table_query') \
|
|
and model.table_query():
|
|
continue
|
|
if not issubclass(model, ModelStorage):
|
|
continue
|
|
for field_name, field in model._fields.iteritems():
|
|
if isinstance(field, fields.Many2One) \
|
|
and field.model_name == cls.__name__:
|
|
if field.ondelete == 'CASCADE':
|
|
foreign_keys_todelete.append((model, field_name))
|
|
elif field.ondelete == 'SET NULL':
|
|
if field.required:
|
|
foreign_keys_tocheck.append((model, field_name))
|
|
else:
|
|
foreign_keys_toupdate.append((model, field_name))
|
|
else:
|
|
foreign_keys_tocheck.append((model, field_name))
|
|
|
|
Transaction().delete.setdefault(cls.__name__, set()).update(ids)
|
|
|
|
domain1, domain2 = pool.get('ir.rule').domain_get(cls.__name__,
|
|
mode='delete')
|
|
if domain1:
|
|
domain1 = ' AND (' + domain1 + ') '
|
|
|
|
for i in range(0, len(ids), cursor.IN_MAX):
|
|
sub_ids = ids[i:i + cursor.IN_MAX]
|
|
red_sql, red_ids = reduce_ids('id', sub_ids)
|
|
if domain1:
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" '
|
|
'WHERE ' + red_sql + ' ' + domain1,
|
|
red_ids + domain2)
|
|
rowcount = cursor.rowcount
|
|
if rowcount == -1 or rowcount is None:
|
|
rowcount = len(cursor.fetchall())
|
|
if not rowcount == len({}.fromkeys(sub_ids)):
|
|
cls.raise_user_error('access_error', cls._description)
|
|
|
|
cls.trigger_delete(records)
|
|
|
|
for i in range(0, len(ids), cursor.IN_MAX):
|
|
sub_ids = ids[i:i + cursor.IN_MAX]
|
|
sub_records = records[i:i + cursor.IN_MAX]
|
|
red_sql, red_ids = reduce_ids('id', sub_ids)
|
|
|
|
Transaction().delete_records.setdefault(cls.__name__,
|
|
set()).update(sub_ids)
|
|
|
|
for Model, field_name in foreign_keys_toupdate:
|
|
if (not hasattr(Model, 'search')
|
|
or not hasattr(Model, 'write')):
|
|
continue
|
|
red_sql2, red_ids2 = reduce_ids('"' + field_name + '"',
|
|
sub_ids)
|
|
cursor.execute('SELECT id FROM "' + Model._table + '" '
|
|
'WHERE ' + red_sql2, red_ids2)
|
|
models = Model.browse([x[0] for x in cursor.fetchall()])
|
|
if models:
|
|
Model.write(models, {
|
|
field_name: False,
|
|
})
|
|
|
|
for Model, field_name in foreign_keys_todelete:
|
|
if (not hasattr(Model, 'search')
|
|
or not hasattr(Model, 'delete')):
|
|
continue
|
|
red_sql2, red_ids2 = reduce_ids('"' + field_name + '"',
|
|
sub_ids)
|
|
cursor.execute('SELECT id FROM "' + Model._table + '" '
|
|
'WHERE ' + red_sql2, red_ids2)
|
|
models = Model.browse([x[0] for x in cursor.fetchall()])
|
|
if models:
|
|
Model.delete(models)
|
|
|
|
for Model, field_name in foreign_keys_tocheck:
|
|
with Transaction().set_user(0):
|
|
if Model.search([
|
|
(field_name, 'in', sub_ids),
|
|
], order=[]):
|
|
error_args = []
|
|
error_args.append(cls._get_error_args('id')[1])
|
|
error_args.extend(list(
|
|
Model._get_error_args(field_name)))
|
|
cls.raise_user_error('foreign_model_exist',
|
|
error_args=tuple(error_args))
|
|
|
|
super(ModelSQL, cls).delete(sub_records)
|
|
|
|
try:
|
|
cursor.execute('DELETE FROM "' + cls._table + '" '
|
|
'WHERE ' + red_sql, red_ids)
|
|
except DatabaseIntegrityError, exception:
|
|
with Transaction().new_cursor():
|
|
for name, _, error in cls._sql_constraints:
|
|
if name in exception[0]:
|
|
cls.raise_user_error(error)
|
|
for name, error in cls._sql_error_messages:
|
|
if name in exception[0]:
|
|
cls.raise_user_error(error)
|
|
raise
|
|
|
|
if cls._history:
|
|
for obj_id in ids:
|
|
cursor.execute('INSERT INTO "' + cls._table + '__history" ' \
|
|
'(id, write_uid, write_date) VALUES (%s, %s, %s)',
|
|
(obj_id, Transaction().user, datetime.datetime.now()))
|
|
|
|
for k in tree_ids.keys():
|
|
field = cls._fields[k]
|
|
if len(tree_ids[k]) == 1:
|
|
cls._update_tree(tree_ids[k][0], k, field.left, field.right)
|
|
else:
|
|
with Transaction().set_user(0):
|
|
cls._rebuild_tree(k, False, 0)
|
|
|
|
@classmethod
|
|
def search(cls, domain, offset=0, limit=None, order=None, count=False,
|
|
query_string=False):
|
|
pool = Pool()
|
|
Rule = pool.get('ir.rule')
|
|
cursor = Transaction().cursor
|
|
|
|
# Get domain clauses
|
|
qu1, qu2, tables, tables_args = cls.search_domain(domain)
|
|
|
|
# Get order by
|
|
order_by = []
|
|
if order is None or order is False:
|
|
order = cls._order
|
|
for field, otype in order:
|
|
if otype.upper() not in ('DESC', 'ASC'):
|
|
raise Exception('Error', 'Wrong order type (%s)!' % otype)
|
|
order_by2, tables2, tables2_args = cls._order_calc(field, otype)
|
|
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)
|
|
|
|
if type(limit) not in (float, int, long, type(None)):
|
|
raise Exception('Error', 'Wrong limit type (%s)!' % type(limit))
|
|
if type(offset) not in (float, int, long, type(None)):
|
|
raise Exception('Error', 'Wrong offset type (%s)!' % type(offset))
|
|
|
|
# construct a clause for the rules :
|
|
domain1, domain2 = Rule.domain_get(cls.__name__, mode='read')
|
|
if domain1:
|
|
if qu1:
|
|
qu1 += ' AND ' + domain1
|
|
else:
|
|
qu1 = domain1
|
|
qu2 += domain2
|
|
|
|
if count:
|
|
cursor.execute(cursor.limit_clause(
|
|
'SELECT COUNT("%s".id) FROM ' % cls._table +
|
|
' '.join(tables) + (qu1 and ' WHERE ' + qu1 or ''),
|
|
limit, offset), tables_args + qu2)
|
|
res = cursor.fetchall()
|
|
return res[0][0]
|
|
# execute the "main" query to fetch the ids we were searching for
|
|
select_fields = ['"' + cls._table + '".id AS id']
|
|
if cls._history and Transaction().context.get('_datetime') \
|
|
and not query_string:
|
|
select_fields += ['COALESCE("' + cls._table + '".write_date, "'
|
|
+ cls._table + '".create_date) AS _datetime']
|
|
if not query_string:
|
|
select_fields += [
|
|
'"' + cls._table + '"."' + name + '" AS "' + name + '"'
|
|
for name, field in cls._fields.iteritems()
|
|
if not hasattr(field, 'get')
|
|
and name != 'id'
|
|
and not getattr(field, 'translate', False)
|
|
and field.loading == 'eager']
|
|
if not cls.table_query():
|
|
select_fields += ['CAST(EXTRACT(EPOCH FROM '
|
|
'(COALESCE("' + cls._table + '".write_date, '
|
|
'"' + cls._table + '".create_date))) AS VARCHAR'
|
|
') AS _timestamp']
|
|
query_str = cursor.limit_clause(
|
|
'SELECT ' + ','.join(select_fields) + ' FROM ' + \
|
|
' '.join(tables) + (qu1 and ' WHERE ' + qu1 or '') + \
|
|
(order_by and ' ORDER BY ' + order_by or ''), limit, offset)
|
|
if query_string:
|
|
return (query_str, tables_args + qu2)
|
|
cursor.execute(query_str, tables_args + qu2)
|
|
|
|
rows = cursor.dictfetchmany(cursor.IN_MAX)
|
|
cache = cursor.get_cache()
|
|
cache.setdefault(cls.__name__, LRUDict(RECORD_CACHE_SIZE))
|
|
delete_records = Transaction().delete_records.setdefault(cls.__name__,
|
|
set())
|
|
keys = None
|
|
for data in islice(rows, 0, cache.size_limit):
|
|
if data['id'] in delete_records:
|
|
continue
|
|
if not keys:
|
|
keys = data.keys()
|
|
for k in keys[:]:
|
|
if k in ('_timestamp', '_datetime'):
|
|
keys.remove(k)
|
|
continue
|
|
field = cls._fields[k]
|
|
if field._type not in ('many2one',):
|
|
keys.remove(k)
|
|
continue
|
|
for k in keys:
|
|
del data[k]
|
|
cache[cls.__name__].setdefault(data['id'], {}).update(data)
|
|
|
|
if len(rows) >= cursor.IN_MAX:
|
|
select_fields2 = [select_fields[0]]
|
|
if (cls._history
|
|
and Transaction().context.get('_datetime')
|
|
and not query_string):
|
|
select_fields2 += [select_fields[1]]
|
|
cursor.execute(
|
|
'SELECT * FROM (' + \
|
|
cursor.limit_clause(
|
|
'SELECT ' + ','.join(select_fields2) + ' FROM ' + \
|
|
' '.join(tables) + (qu1 and ' WHERE ' + qu1 or '') + \
|
|
(order_by and ' ORDER BY ' + order_by or ''),
|
|
limit, offset) + ') AS "' + cls._table + '"',
|
|
tables_args + qu2)
|
|
rows = cursor.dictfetchall()
|
|
|
|
if cls._history and Transaction().context.get('_datetime'):
|
|
res = []
|
|
ids_date = {}
|
|
for data in rows:
|
|
if data['id'] in ids_date:
|
|
if data['_datetime'] <= ids_date[data['id']]:
|
|
continue
|
|
if data['id'] in res:
|
|
res.remove(data['id'])
|
|
res.append(data['id'])
|
|
ids_date[data['id']] = data['_datetime']
|
|
to_delete = set()
|
|
for i in range(0, len(res), cursor.IN_MAX):
|
|
sub_ids = res[i:i + cursor.IN_MAX]
|
|
reduced_sql, reduced_ids = reduce_ids('id', sub_ids)
|
|
cursor.execute(('SELECT id, write_date '
|
|
'FROM "%s__history" WHERE %s'
|
|
'AND (write_date IS NOT NULL AND create_date IS NULL)'
|
|
) % (cls._table, reduced_sql),
|
|
reduced_ids)
|
|
for deleted_id, delete_date in cursor.fetchall():
|
|
if ids_date[deleted_id] < delete_date:
|
|
to_delete.add(deleted_id)
|
|
return cls.browse(filter(lambda x: x not in to_delete, res))
|
|
|
|
return cls.browse([x['id'] for x in rows])
|
|
|
|
@classmethod
|
|
def search_domain(cls, domain, active_test=True):
|
|
'''
|
|
Return SQL clause, tables to use and arguments for the domain
|
|
Set active_test to add it.
|
|
'''
|
|
domain = cls._search_domain_active(domain, active_test=active_test)
|
|
|
|
table_query = ''
|
|
tables_args = []
|
|
if cls.table_query():
|
|
table_query, tables_args = cls.table_query()
|
|
table_query = '(' + table_query + ') AS '
|
|
|
|
if cls._history and Transaction().context.get('_datetime'):
|
|
table_query = '"' + cls._table + '__history" AS '
|
|
|
|
tables = [table_query + '"' + cls._table + '"']
|
|
|
|
qu1, qu2 = cls.__search_domain_oper(domain, tables, tables_args)
|
|
if cls._history and Transaction().context.get('_datetime'):
|
|
if qu1:
|
|
qu1 += ' AND'
|
|
qu1 += ' (COALESCE("' + cls._table + '".write_date, "' + \
|
|
cls._table + '".create_date) <= %s)'
|
|
qu2 += [Transaction().context['_datetime']]
|
|
return qu1, qu2, tables, tables_args
|
|
|
|
@classmethod
|
|
def __search_domain_oper(cls, domain, tables, tables_args):
|
|
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 = cls.__search_domain_calc(tuple_args, tables, tables_args)
|
|
if len(qu1):
|
|
qu1 = (' ' + operator + ' ').join(qu1)
|
|
else:
|
|
qu1 = ''
|
|
|
|
for domain2 in list_args:
|
|
qu1b, qu2b = cls.__search_domain_oper(domain2, tables,
|
|
tables_args)
|
|
if not qu1b:
|
|
qu1b = '%s'
|
|
qu2b = [True]
|
|
if qu1 and qu1b:
|
|
qu1 += ' ' + operator + ' ' + qu1b
|
|
elif qu1b:
|
|
qu1 = qu1b
|
|
qu2 += qu2b
|
|
if qu1:
|
|
qu1 = '(' + qu1 + ')'
|
|
return qu1, qu2
|
|
|
|
@classmethod
|
|
def __search_domain_calc(cls, domain, tables, tables_args):
|
|
pool = Pool()
|
|
domain = domain[:]
|
|
cursor = Transaction().cursor
|
|
|
|
def column_get(model, field_name):
|
|
field = model._fields.get(field_name)
|
|
if not field:
|
|
if not field_name in model._inherit_fields:
|
|
raise Exception('ValidateError', 'Field "%s" doesn\'t ' \
|
|
'exist on "%s"' % (field_name, model.__name__))
|
|
parent_model = pool.get(model._inherit_fields[field_name][0])
|
|
field = column_get(parent_model, field_name)
|
|
return field
|
|
|
|
def get_model_for_field(model, field):
|
|
if field in model._fields:
|
|
return model
|
|
else:
|
|
parent_model = pool.get(model._inherit_fields[field][0])
|
|
return get_model_for_field(parent_model, field)
|
|
|
|
def get_inherits_join(model, field):
|
|
if field in model._fields:
|
|
return []
|
|
itable = pool.get(model._inherit_fields[field][0])
|
|
table_query = ''
|
|
table_arg = []
|
|
if itable.table_query():
|
|
table_query, table_args = model.table_query()
|
|
table_query = '(' + table_query + ') AS '
|
|
table_join = 'LEFT JOIN ' + table_query + \
|
|
'"' + itable._table + '" ON ' \
|
|
'"%s".id = "%s"."%s"' % (itable._table, model._table,
|
|
model._inherits[itable.__name__])
|
|
inherits = [(table_join, table_arg)]
|
|
inherits.extend(get_inherits_join(itable, field))
|
|
return inherits
|
|
|
|
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):
|
|
fargs = domain[i][0].split('.', 1)
|
|
if fargs[0] in cls._inherit_fields:
|
|
inherit_joins = get_inherits_join(cls, fargs[0])
|
|
for (table_join, table_arg) in inherit_joins:
|
|
if table_join not in tables:
|
|
tables.append(table_join)
|
|
tables_args.extend(table_arg)
|
|
|
|
field = column_get(cls, fargs[0])
|
|
table = get_model_for_field(cls, fargs[0])
|
|
if len(fargs) > 1:
|
|
if field._type == 'many2one':
|
|
Target = pool.get(field.model_name)
|
|
m2o_search = [(fargs[1], domain[i][1], domain[i][2])]
|
|
if ('active' in Target._fields
|
|
or 'active' in Target._inherit_fields):
|
|
m2o_search += [('active', 'in', (True, False))]
|
|
if hasattr(field, 'search'):
|
|
domain.extend([(fargs[0], 'in',
|
|
map(int, Target.search(m2o_search,
|
|
order=[])))])
|
|
domain.pop(i)
|
|
else:
|
|
domain[i] = (fargs[0], 'inselect',
|
|
Target.search(m2o_search, order=[],
|
|
query_string=True), table)
|
|
i += 1
|
|
continue
|
|
elif field._type in ('one2one', 'many2many', 'one2many'):
|
|
if hasattr(field, 'model_name'):
|
|
Target = pool.get(field.model_name)
|
|
else:
|
|
Target = field.get_target()
|
|
if hasattr(field, 'relation_name'):
|
|
Relation = pool.get(field.relation_name)
|
|
origin, target = field.origin, field.target
|
|
else:
|
|
Relation = Target
|
|
origin, target = field.field, 'id'
|
|
if origin in Relation._fields:
|
|
rev_field = Relation._fields[origin]
|
|
else:
|
|
rev_field = Relation._inherit_fields[origin][2]
|
|
if rev_field._type == 'reference':
|
|
sql_type = FIELDS[
|
|
cls.id._type].sql_type(cls.id)[0]
|
|
origin = ('CAST(SPLIT_PART("%s", \',\', 2) AS %s)'
|
|
% (origin, sql_type))
|
|
else:
|
|
origin = '"%s"' % origin
|
|
if hasattr(field, 'search'):
|
|
domain.extend([(fargs[0], 'in',
|
|
map(int, Target.search([
|
|
(fargs[1], domain[i][1],
|
|
domain[i][2]),
|
|
], order=[])))])
|
|
domain.pop(i)
|
|
else:
|
|
query1, query2 = Target.search([
|
|
(fargs[1], domain[i][1], domain[i][2]),
|
|
], order=[], query_string=True)
|
|
query1 = ('SELECT %s FROM "%s" WHERE "%s" IN (%s)' %
|
|
(origin, Relation._table, target, query1))
|
|
domain[i] = ('id', 'inselect', (query1, query2))
|
|
i += 1
|
|
continue
|
|
else:
|
|
raise Exception('ValidateError', 'Clause on field "%s" ' \
|
|
'doesn\'t work on "%s"' %
|
|
(domain[i][0], cls.__name__))
|
|
if hasattr(field, 'search'):
|
|
clause = domain.pop(i)
|
|
domain.extend(field.search(table, clause[0], clause))
|
|
elif field._type == 'one2many':
|
|
Field = pool.get(field.model_name)
|
|
table_query = ''
|
|
table_args = []
|
|
if Field.table_query():
|
|
table_query, table_args = Field.table_query()
|
|
table_query = '(' + table_query + ') AS '
|
|
if field.field in Field._fields:
|
|
rev_field = Field._fields[field.field]
|
|
else:
|
|
rev_field = Field._inherit_fields[field.field][2]
|
|
if rev_field._type == 'reference':
|
|
sql_type = FIELDS[cls.id._type].sql_type(cls.id)[0]
|
|
select = ('CAST(SPLIT_PART("%s", \',\', 2) AS %s)'
|
|
% (field.field, sql_type))
|
|
else:
|
|
select = '"' + field.field + '"'
|
|
|
|
if isinstance(domain[i][2], bool) or domain[i][2] is None:
|
|
query1 = 'SELECT ' + select + ' ' \
|
|
'FROM ' + table_query + \
|
|
'"' + Field._table + '" ' \
|
|
'WHERE "' + field.field + '" IS NOT NULL'
|
|
query2 = table_args
|
|
clause = 'inselect'
|
|
if not domain[i][2]:
|
|
clause = 'notinselect'
|
|
domain[i] = ('id', clause, (query1, query2))
|
|
else:
|
|
if isinstance(domain[i][2], basestring):
|
|
target_field = 'rec_name'
|
|
else:
|
|
target_field = 'id'
|
|
query1, query2 = Field.search([
|
|
(target_field, domain[i][1], domain[i][2]),
|
|
], order=[], query_string=True)
|
|
query1 = ('SELECT ' + select + ' '
|
|
'FROM ' + table_query +
|
|
'"' + Field._table + '" '
|
|
'WHERE id IN (' + query1 + ')')
|
|
query2 = table_args + query2
|
|
domain[i] = ('id', 'inselect', (query1, query2))
|
|
i += 1
|
|
elif field._type in ('many2many', 'one2one'):
|
|
# XXX must find a solution for long id list
|
|
if hasattr(field, 'model_name'):
|
|
Target = pool.get(field.model_name)
|
|
else:
|
|
Target = field.get_target()
|
|
if domain[i][1] in ('child_of', 'not child_of'):
|
|
if isinstance(domain[i][2], basestring):
|
|
ids2 = map(int, Target.search([
|
|
('rec_name', 'ilike', domain[i][2]),
|
|
], order=[]))
|
|
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 = map(int, table.search([
|
|
(parent, 'in', ids),
|
|
(parent, '!=', None),
|
|
], order=[]))
|
|
return ids + _rec_get(ids2, table, parent)
|
|
|
|
if Target.__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 = map(int, Target.search([
|
|
(domain[i][3], 'child_of', ids2),
|
|
], order=[]))
|
|
Relation = pool.get(field.relation_name)
|
|
red_sql, red_ids = reduce_ids('"' + field.target + '"',
|
|
ids2)
|
|
query1 = 'SELECT "' + field.origin + '" ' \
|
|
'FROM "' + Relation._table + '" ' \
|
|
'WHERE ' + red_sql + ' ' \
|
|
'AND "' + field.origin + '" IS NOT NULL'
|
|
query2 = red_ids
|
|
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:
|
|
Relation = pool.get(field.relation_name)
|
|
table_query = ''
|
|
table_args = []
|
|
if Relation.table_query():
|
|
table_query, table_args = Relation.table_query()
|
|
table_query = '(' + table_query + ') AS '
|
|
if field.origin in Relation._fields:
|
|
origin_field = Relation._fields[field.origin]
|
|
else:
|
|
origin_field = \
|
|
Relation._inherit_fields[field.origin][2]
|
|
if origin_field._type == 'reference':
|
|
sql_type = FIELDS[cls.id._type].sql_type(cls.id)[0]
|
|
select = ('CAST(SPLIT_PART("%s", \',\', 2) AS %s)'
|
|
% (field.origin, sql_type))
|
|
else:
|
|
select = '"' + field.origin + '"'
|
|
if isinstance(domain[i][2], bool) or domain[i][2] is None:
|
|
query1 = 'SELECT ' + select + ' ' \
|
|
'FROM ' + table_query + ' '\
|
|
'"' + Relation._table + '" ' \
|
|
'WHERE "' + field.origin + '" IS NOT NULL'
|
|
query2 = table_args
|
|
clause = 'inselect'
|
|
if not domain[i][2]:
|
|
clause = 'notinselect'
|
|
domain[i] = ('id', clause, (query1, query2))
|
|
else:
|
|
if isinstance(domain[i][2], basestring):
|
|
target_field = 'rec_name'
|
|
else:
|
|
target_field = 'id'
|
|
|
|
query1, query2 = Target.search([
|
|
(target_field, domain[i][1], domain[i][2]),
|
|
], order=[], query_string=True)
|
|
query1 = ('SELECT ' + select + ' '
|
|
'FROM ' + table_query +
|
|
'"' + Relation._table + '" '
|
|
'WHERE "' + field.target + '" IN (' + query1 + ')')
|
|
query2 = table_args + query2
|
|
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):
|
|
Field = pool.get(field.model_name)
|
|
ids2 = map(int, Field.search([
|
|
('rec_name', 'like', domain[i][2]),
|
|
], order=[]))
|
|
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 = map(int, table.search([
|
|
(parent, 'in', ids),
|
|
(parent, '!=', None),
|
|
]))
|
|
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 = map(int, pool.get(field.model_name).search([
|
|
(domain[i][3], 'child_of', ids2),
|
|
], order=[]))
|
|
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 and ids2:
|
|
red_sql, red_ids = reduce_ids('id', ids2)
|
|
cursor.execute('SELECT "' + field.left + '", ' \
|
|
'"' + field.right + '" ' + \
|
|
'FROM "' + cls._table + '" ' + \
|
|
'WHERE ' + red_sql, red_ids)
|
|
clause = '(1=0) '
|
|
for left, right in cursor.fetchall():
|
|
clause += 'OR '
|
|
clause += '( "' + field.left + '" >= ' + \
|
|
str(left) + ' ' + \
|
|
'AND "' + field.right + '" <= ' + \
|
|
str(right) + ')'
|
|
|
|
query = 'SELECT id FROM "' + cls._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):
|
|
Field = pool.get(field.model_name)
|
|
m2o_search = [('rec_name', domain[i][1], domain[i][2])]
|
|
if ('active' in Field._fields
|
|
or 'active' in Field._inherit_fields):
|
|
m2o_search += [('active', 'in', (True, False))]
|
|
res_ids = map(int, Field.search(m2o_search, order=[]))
|
|
domain[i] = (domain[i][0], 'in', res_ids, table)
|
|
else:
|
|
domain[i] += (table,)
|
|
i += 1
|
|
else:
|
|
if getattr(field, 'translate', False):
|
|
if cls.__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 = %%s)' % \
|
|
domain[i][0]
|
|
elif cls.__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.lang = %%s ' \
|
|
'AND ir_translation.type = \'%s\' ' \
|
|
'AND ir_translation.fuzzy = %%s)' % \
|
|
(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 = %%s)' % \
|
|
(table._table, table.__name__, domain[i][0])
|
|
table_join_args = [Transaction().language, False]
|
|
|
|
table_query = ''
|
|
table_args = []
|
|
if table.table_query():
|
|
table_query, table_args = table.table_query()
|
|
table_query = '(' + table_query + ') AS '
|
|
|
|
Translation = pool.get('ir.translation')
|
|
|
|
qu1, qu2, tables, table_args = \
|
|
Translation.search_domain([
|
|
('value', domain[i][1], domain[i][2]),
|
|
])
|
|
qu1 = qu1.replace('"ir_translation"."value"',
|
|
'COALESCE(NULLIF("ir_translation"."value", \'\'), '
|
|
'"%s"."%s")' % (table._table, domain[i][0]))
|
|
query1 = 'SELECT "' + table._table + '".id ' \
|
|
'FROM ' + table_query + '"' + table._table + '" ' \
|
|
+ table_join + ' WHERE ' + qu1
|
|
query2 = table_args + table_join_args + qu2
|
|
|
|
domain[i] = ('id', 'inselect', (query1, query2), table)
|
|
else:
|
|
domain[i] += (table,)
|
|
i += 1
|
|
domain.extend(joins)
|
|
|
|
qu1, qu2 = [], []
|
|
for arg in domain:
|
|
table = get_model_for_field(cls, arg[0])
|
|
if table == cls and len(arg) > 3:
|
|
table = arg[3]
|
|
column = column_get(table, arg[0])
|
|
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 = []
|
|
if column._type != 'boolean':
|
|
for xitem in range(len(arg[2])):
|
|
if (arg[2][xitem] is False
|
|
or arg[2][xitem] is None):
|
|
todel.append(xitem)
|
|
arg2 = arg[2][:]
|
|
for xitem in todel[::-1]:
|
|
del arg2[xitem]
|
|
arg2 = [FIELDS[column._type].sql_format(x)
|
|
for x in arg2]
|
|
if len(arg2):
|
|
if reduce(lambda x, y: (x
|
|
and isinstance(y, (int, long))
|
|
and not isinstance(y, bool)),
|
|
arg2, True):
|
|
red_sql, red_ids = reduce_ids('"%s"."%s"' % \
|
|
(table._table, arg[0]), arg2)
|
|
if arg[1] == 'not in':
|
|
red_sql = '(NOT(' + red_sql + '))'
|
|
qu1.append(red_sql)
|
|
qu2 += red_ids
|
|
else:
|
|
qu1.append(('("%s"."%s" ' + arg[1] + ' (%s))') % \
|
|
(table._table, arg[0], ','.join(
|
|
('%s',) * len(arg2))))
|
|
qu2 += arg2
|
|
if todel:
|
|
if column._type == 'boolean':
|
|
if arg[1] == 'in':
|
|
qu1[-1] = '(' + qu1[-1] + ' OR ' \
|
|
'"%s"."%s" = %%s)' % \
|
|
(table._table, arg[0])
|
|
qu2.append(False)
|
|
else:
|
|
qu1[-1] = '(' + qu1[-1] + ' AND ' \
|
|
'"%s"."%s" != %%s)' % \
|
|
(table._table, arg[0])
|
|
qu2.append(False)
|
|
else:
|
|
if arg[1] == 'in':
|
|
qu1[-1] = '(' + qu1[-1] + ' OR ' \
|
|
'"%s"."%s" IS NULL)' % \
|
|
(table._table, arg[0])
|
|
else:
|
|
qu1[-1] = '(' + qu1[-1] + ' AND ' \
|
|
'"%s"."%s" IS NOT NULL)' % \
|
|
(table._table, arg[0])
|
|
elif todel:
|
|
if column._type == 'boolean':
|
|
if arg[1] == 'in':
|
|
qu1.append('("%s"."%s" = %%s)' % \
|
|
(table._table, arg[0]))
|
|
qu2.append(False)
|
|
else:
|
|
qu1.append('("%s"."%s" != %%s)' % \
|
|
(table._table, arg[0]))
|
|
qu2.append(False)
|
|
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(' %s')
|
|
qu2.append(False)
|
|
else:
|
|
qu1.append(' %s')
|
|
qu2.append(True)
|
|
else:
|
|
if (arg[2] is False or arg[2] is None) and (arg[1] == '='):
|
|
if column._type == 'boolean':
|
|
qu1.append('(("%s"."%s" = %%s) OR ("%s"."%s" IS NULL))'
|
|
% (table._table, arg[0], table._table, arg[0]))
|
|
qu2.append(False)
|
|
else:
|
|
qu1.append('("%s"."%s" IS NULL)' % \
|
|
(table._table, arg[0]))
|
|
elif (arg[2] is False or arg[2] is None) and (arg[1] == '!='):
|
|
if column._type == 'boolean':
|
|
qu1.append('(("%s"."%s" != %%s) '
|
|
'AND ("%s"."%s" IS NOT NULL))'
|
|
% (table._table, arg[0], table._table, arg[0]))
|
|
qu2.append(False)
|
|
else:
|
|
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(FIELDS[column._type].\
|
|
sql_format(arg[2]))
|
|
else:
|
|
add_null = False
|
|
if arg[1] in ('like', 'ilike'):
|
|
if not arg[2]:
|
|
qu2.append('%')
|
|
add_null = True
|
|
else:
|
|
qu2.append(FIELDS[
|
|
column._type
|
|
].sql_format(arg[2]))
|
|
elif arg[1] in ('not like', 'not ilike'):
|
|
if not arg[2]:
|
|
qu2.append('')
|
|
else:
|
|
qu2.append(FIELDS[
|
|
column._type
|
|
].sql_format(arg[2]))
|
|
add_null = True
|
|
else:
|
|
qu2.append(FIELDS[
|
|
column._type
|
|
].sql_format(arg[2]))
|
|
qu1.append('("%s"."%s" %s %%s)' % (table._table,
|
|
arg[0], arg[1]))
|
|
if add_null:
|
|
qu1[-1] = '(' + qu1[-1] + ' OR ' \
|
|
'"' + table._table + '"."' + arg[0] + '"' \
|
|
' IS NULL)'
|
|
|
|
return qu1, qu2
|
|
|
|
@classmethod
|
|
def _order_calc(cls, field, otype):
|
|
pool = Pool()
|
|
order_by = []
|
|
tables = []
|
|
tables_args = {}
|
|
field_name = None
|
|
table_name = None
|
|
link_field = None
|
|
|
|
if field in cls._fields:
|
|
table_name = cls._table
|
|
|
|
if not hasattr(cls._fields[field], 'set'):
|
|
field_name = field
|
|
|
|
if cls._fields[field].order_field:
|
|
field_name = cls._fields[field].order_field
|
|
|
|
if isinstance(cls._fields[field], fields.Many2One):
|
|
obj = pool.get(cls._fields[field].model_name)
|
|
table_name = obj._table
|
|
link_field = field
|
|
field_name = None
|
|
|
|
if obj._rec_name in obj._fields:
|
|
field_name = obj._rec_name
|
|
|
|
if obj._order_name in obj._fields:
|
|
field_name = obj._order_name
|
|
|
|
if field_name:
|
|
order_by, tables, tables_args = obj._order_calc(field_name,
|
|
otype)
|
|
table_join = 'LEFT JOIN "' + table_name + '" AS ' \
|
|
'"' + table_name + '.' + link_field + '" ON ' \
|
|
'"%s.%s".id = "%s"."%s"' % (table_name, link_field,
|
|
cls._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)
|
|
for i in range(len(tables)):
|
|
if table_name in tables[i]:
|
|
args = tables_args.pop(tables[i], [])
|
|
tables[i] = tables[i].replace(table_name,
|
|
table_name + '.' + link_field)
|
|
tables_args[tables[i]] = args
|
|
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 = pool.get(
|
|
obj._inherit_fields[obj._rec_name][0])
|
|
field_name = obj._rec_name
|
|
|
|
if obj._order_name in obj._inherit_fields.keys():
|
|
obj2 = 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(field_name, otype)
|
|
|
|
table_join = 'LEFT JOIN "' + table_name + '" AS ' \
|
|
'"' + table_name + '.' + link_field + '" ON ' \
|
|
'"%s.%s".id = "%s"."%s"' % \
|
|
(table_name, link_field, cls._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)
|
|
for i in range(len(tables)):
|
|
if table_name in tables[i]:
|
|
args = tables_args.pop(tables[i], [])
|
|
tables[i] = tables[i].replace(table_name,
|
|
table_name + '.' + link_field)
|
|
tables_args[tables[i]] = args
|
|
if table_join not in tables:
|
|
tables.insert(0, table_join)
|
|
tables_args[table_join] = []
|
|
|
|
table_join2 = 'LEFT JOIN "' + table_name2 + '" AS ' \
|
|
'"' + table_name2 + '.' + link_field2 + '" ON ' \
|
|
'"%s.%s".id = "%s.%s"."%s"' % \
|
|
(table_name2, link_field2, table_name, link_field,
|
|
link_field2)
|
|
for i in range(len(order_by)):
|
|
if table_name2 in order_by[i]:
|
|
order_by[i] = order_by[i].replace(table_name2,
|
|
table_name2 + '.' + link_field2)
|
|
for i in range(1, len(tables)):
|
|
if table_name2 in tables[i]:
|
|
args = tables_args.pop(tables[i], [])
|
|
tables[i] = tables[i].replace(table_name2,
|
|
table_name2 + '.' + link_field2)
|
|
tables_args[tables[i]] = args
|
|
if table_join2 not in tables:
|
|
tables.insert(1, table_join2)
|
|
tables_args[table_join2] = []
|
|
return order_by, tables, tables_args
|
|
|
|
if field_name in cls._fields \
|
|
and getattr(cls._fields[field_name], 'translate', False):
|
|
translation_table = 'ir_translation_%s_%s' % \
|
|
(table_name, field_name)
|
|
if cls.__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 = %%s)' % \
|
|
(translation_table, translation_table, field_name,
|
|
translation_table, translation_table,
|
|
translation_table, translation_table)
|
|
elif cls.__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 = %%s)' % \
|
|
(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 = %%s)' % \
|
|
(translation_table, translation_table, table_name,
|
|
translation_table, cls.__name__, field_name,
|
|
translation_table, translation_table,
|
|
translation_table)
|
|
if table_join not in tables:
|
|
tables.append(table_join)
|
|
tables_args[table_join] = [Transaction().language, False]
|
|
order_by.append('COALESCE(NULLIF(' \
|
|
+ '"' + translation_table + '".value, \'\'), ' \
|
|
+ '"' + table_name + '".' + field_name + ') ' + otype)
|
|
return order_by, tables, tables_args
|
|
|
|
if field_name in cls._fields \
|
|
and cls._fields[field_name]._type == 'selection' \
|
|
and cls._fields[field_name].order_field is None:
|
|
selections = cls.fields_get([field_name]
|
|
)[field_name]['selection']
|
|
if not isinstance(selections, (tuple, list)):
|
|
selections = getattr(cls,
|
|
cls._fields[field_name].selection)()
|
|
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': '"%s"' % table_name,
|
|
'order': otype,
|
|
})
|
|
else:
|
|
order_by.append('"%s"."%s" %s'
|
|
% (table_name, field_name, otype))
|
|
return order_by, tables, tables_args
|
|
|
|
if field in cls._inherit_fields.keys():
|
|
obj = pool.get(cls._inherit_fields[field][0])
|
|
table_name = obj._table
|
|
link_field = cls._inherits[obj.__name__]
|
|
order_by, tables, tables_args = obj._order_calc(field, otype)
|
|
table_join = 'LEFT JOIN "' + table_name + '" ON ' \
|
|
'"%s".id = "%s"."%s"' % \
|
|
(table_name, cls._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) for %s in order!' %
|
|
(field, cls.__name__))
|
|
|
|
@classmethod
|
|
def _rebuild_tree(cls, parent, parent_id, left):
|
|
'''
|
|
Rebuild left, right value for the tree.
|
|
'''
|
|
cursor = Transaction().cursor
|
|
right = left + 1
|
|
|
|
with Transaction().set_user(0):
|
|
childs = cls.search([
|
|
(parent, '=', parent_id),
|
|
])
|
|
|
|
for child in childs:
|
|
right = cls._rebuild_tree(parent, child.id, right)
|
|
|
|
field = cls._fields[parent]
|
|
|
|
if parent_id:
|
|
cursor.execute('UPDATE "' + cls._table + '" ' \
|
|
'SET "' + field.left + '" = %s, ' \
|
|
'"' + field.right + '" = %s ' \
|
|
'WHERE id = %s', (left, right, parent_id))
|
|
return right + 1
|
|
|
|
@classmethod
|
|
def _update_tree(cls, 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 = Transaction().cursor
|
|
cursor.execute('SELECT "' + left + '", "' + right + '" ' \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE id = %s', (object_id,))
|
|
fetchone = cursor.fetchone()
|
|
if not fetchone:
|
|
return
|
|
old_left, old_right = fetchone
|
|
if old_left == old_right:
|
|
cursor.execute('UPDATE "' + cls._table + '" ' \
|
|
'SET "' + right + '" = "' + right + '" + 1 ' \
|
|
'WHERE id = %s', (object_id,))
|
|
old_right += 1
|
|
|
|
parent_right = 1
|
|
|
|
cursor.execute('SELECT "' + field_name + '" ' \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE id = %s', (object_id,))
|
|
parent_id = cursor.fetchone()[0] or False
|
|
|
|
if parent_id:
|
|
cursor.execute('SELECT "' + right + '" ' \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE id = %s', (parent_id,))
|
|
parent_right = cursor.fetchone()[0]
|
|
else:
|
|
cursor.execute('SELECT MAX("' + right + '") ' \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE "' + field_name + '" IS NULL')
|
|
fetchone = cursor.fetchone()
|
|
if fetchone:
|
|
parent_right = fetchone[0] + 1
|
|
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" ' \
|
|
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
|
|
(old_left, old_right))
|
|
child_ids = [x[0] for x in cursor.fetchall()]
|
|
|
|
if len(child_ids) > cursor.IN_MAX:
|
|
with Transaction().set_user(0):
|
|
return cls._rebuild_tree(field_name, False, 0)
|
|
|
|
red_child_sql, red_child_ids = reduce_ids('id', child_ids)
|
|
# ids for left update
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" ' \
|
|
'WHERE "' + left + '" >= %s ' \
|
|
'AND NOT ' + red_child_sql,
|
|
[parent_right] + red_child_ids)
|
|
left_ids = [x[0] for x in cursor.fetchall()]
|
|
|
|
# ids for right update
|
|
cursor.execute('SELECT id FROM "' + cls._table + '" ' \
|
|
'WHERE "' + right + '" >= %s ' \
|
|
'AND NOT ' + red_child_sql,
|
|
[parent_right] + red_child_ids)
|
|
right_ids = [x[0] for x in cursor.fetchall()]
|
|
|
|
if left_ids:
|
|
for i in range(0, len(left_ids), cursor.IN_MAX):
|
|
sub_ids = left_ids[i:i + cursor.IN_MAX]
|
|
red_sub_sql, red_sub_ids = reduce_ids('id', sub_ids)
|
|
cursor.execute('UPDATE "' + cls._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" + ' \
|
|
+ str(old_right - old_left + 1) + ' ' \
|
|
'WHERE ' + red_sub_sql, red_sub_ids)
|
|
if right_ids:
|
|
for i in range(0, len(right_ids), cursor.IN_MAX):
|
|
sub_ids = right_ids[i:i + cursor.IN_MAX]
|
|
red_sub_sql, red_sub_ids = reduce_ids('id', sub_ids)
|
|
cursor.execute('UPDATE "' + cls._table + '" ' \
|
|
'SET "' + right + '" = "' + right + '" + ' \
|
|
+ str(old_right - old_left + 1) + ' ' \
|
|
'WHERE ' + red_sub_sql, red_sub_ids)
|
|
|
|
cursor.execute('UPDATE "' + cls._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" + ' \
|
|
+ str(parent_right - old_left) + ', ' \
|
|
'"' + right + '" = "' + right + '" + ' \
|
|
+ str(parent_right - old_left) + ' ' \
|
|
'WHERE ' + red_child_sql, red_child_ids)
|
|
|
|
# Use root user to by-pass rules
|
|
with contextlib.nested(Transaction().set_user(0),
|
|
Transaction().set_context(active_test=False)):
|
|
brother_ids = map(int, cls.search([
|
|
(field_name, '=', parent_id),
|
|
]))
|
|
if brother_ids[-1] != object_id:
|
|
next_id = brother_ids[brother_ids.index(object_id) + 1]
|
|
cursor.execute('SELECT "' + left + '" ' \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE id = %s', (next_id,))
|
|
next_left = cursor.fetchone()[0]
|
|
cursor.execute('SELECT "' + left + '" '\
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE id = %s', (object_id,))
|
|
current_left = cursor.fetchone()[0]
|
|
|
|
cursor.execute('UPDATE "' + cls._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 "' + cls._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" - ' \
|
|
+ str(current_left - next_left) + ', ' \
|
|
'"' + right + '" = "' + right + '" - ' \
|
|
+ str(current_left - next_left) + ' ' \
|
|
'WHERE ' + red_child_sql, red_child_ids)
|
|
|
|
@classmethod
|
|
def _validate(cls, records):
|
|
super(ModelSQL, cls)._validate(records)
|
|
cursor = Transaction().cursor
|
|
if cursor.has_constraint():
|
|
return
|
|
# Works only for a single transaction
|
|
ids = map(int, records)
|
|
for _, sql, error in cls._sql_constraints:
|
|
match = _RE_UNIQUE.match(sql)
|
|
if match:
|
|
sql = match.group(1)
|
|
sql_clause = ' AND '.join('%s = %%s' % \
|
|
i for i in sql.split(','))
|
|
sql_clause = '(id != %s AND ' + sql_clause + ')'
|
|
|
|
for i in range(0, len(ids), cursor.IN_MAX):
|
|
sub_ids = ids[i:i + cursor.IN_MAX]
|
|
red_sql, red_ids = reduce_ids('id', sub_ids)
|
|
|
|
cursor.execute('SELECT id,' + sql + ' ' \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE ' + red_sql, red_ids)
|
|
|
|
fetchall = cursor.fetchall()
|
|
cursor.execute('SELECT id ' \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE ' + \
|
|
' OR '.join((sql_clause,) * len(fetchall)),
|
|
reduce(lambda x, y: x + list(y), fetchall, []))
|
|
|
|
if cursor.fetchone():
|
|
cls.raise_user_error(error)
|
|
continue
|
|
match = _RE_CHECK.match(sql)
|
|
if match:
|
|
sql = match.group(1)
|
|
for i in range(0, len(ids), cursor.IN_MAX):
|
|
sub_ids = ids[i:i + cursor.IN_MAX]
|
|
red_sql, red_ids = reduce_ids('id', sub_ids)
|
|
cursor.execute('SELECT id ' \
|
|
'FROM "' + cls._table + '" ' \
|
|
'WHERE NOT (' + sql + ') ' \
|
|
'AND ' + red_sql, red_ids)
|
|
if cursor.fetchone():
|
|
cls.raise_user_error(error)
|
|
continue
|