3195 lines
138 KiB
Python
3195 lines
138 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.
|
|
# -*- coding: utf-8 -*-
|
|
from trytond.netsvc import LocalService
|
|
import fields
|
|
from trytond.tools import Cache
|
|
import md5
|
|
import time
|
|
import traceback
|
|
import datetime
|
|
from lxml import etree
|
|
import copy
|
|
from trytond.sql_db import table_handler
|
|
from decimal import Decimal
|
|
import logging
|
|
|
|
OPERATORS = (
|
|
'child_of',
|
|
'not child_of',
|
|
'=',
|
|
'!=',
|
|
'like',
|
|
'not like',
|
|
'ilike',
|
|
'not ilike',
|
|
'in',
|
|
'not in',
|
|
'<=',
|
|
'>=',
|
|
'<',
|
|
'>',
|
|
)
|
|
|
|
def intersect(i, j):
|
|
return [x for x in j if x in i]
|
|
|
|
def exclude(i, j):
|
|
return [x for x in i if x not in j]
|
|
|
|
|
|
# TODO: execute an object method on BrowseRecordList
|
|
class BrowseRecordList(list):
|
|
'''
|
|
A list of BrowseRecord
|
|
'''
|
|
|
|
def __init__(self, lst, context=None):
|
|
super(BrowseRecordList, self).__init__(lst)
|
|
self.context = context
|
|
|
|
def get_eval(self):
|
|
res = []
|
|
for record in self:
|
|
res2 = {}
|
|
for field_name, field in record._table._columns.iteritems():
|
|
if not isinstance(record[field_name], BrowseRecordList):
|
|
res2[field_name] = record.get_eval(field_name)
|
|
res.append(res2)
|
|
return res
|
|
|
|
|
|
class BrowseRecordNull(object):
|
|
'''
|
|
An object that represents an empty record.
|
|
'''
|
|
|
|
def __init__(self):
|
|
self.id = False
|
|
|
|
def __getitem__(self, name):
|
|
return False
|
|
|
|
def __int__(self):
|
|
return False
|
|
|
|
def __str__(self):
|
|
return ''
|
|
|
|
def __nonzero__(self):
|
|
return False
|
|
|
|
|
|
class BrowseRecord(object):
|
|
'''
|
|
An object that represents record defined by a ORM object.
|
|
'''
|
|
|
|
def __init__(self, cursor, user, object_id, table, cache, context=None):
|
|
'''
|
|
table : the object (inherited from orm)
|
|
context : a dictionnary with an optionnal context
|
|
'''
|
|
self._cursor = cursor
|
|
self._user = user
|
|
self._id = object_id
|
|
self._table = table
|
|
self._table_name = self._table._name
|
|
self._context = context
|
|
self._language_cache = {}
|
|
|
|
cache.setdefault(table._name, {})
|
|
self._data = cache[table._name]
|
|
if not object_id in self._data:
|
|
self._data[object_id] = {'id': object_id}
|
|
self._cache = cache
|
|
|
|
def __getitem__(self, name):
|
|
if name == 'id':
|
|
return self._id
|
|
if name == 'setLang':
|
|
return self.setLang
|
|
if not self._data[self._id].has_key(name):
|
|
# build the list of fields we will fetch
|
|
|
|
# fetch the definition of the field which was asked for
|
|
if name in self._table._columns:
|
|
col = self._table._columns[name]
|
|
elif name in self._table._inherit_fields:
|
|
col = self._table._inherit_fields[name][2]
|
|
elif hasattr(self._table, name):
|
|
return getattr(self._table, name)
|
|
else:
|
|
raise Exception('Error', 'Programming error: field "%s" ' \
|
|
'does not exist in object "%s"!' \
|
|
% (name, self._table._name))
|
|
|
|
# if the field is a classic one or a many2one,
|
|
# we'll fetch all classic and many2one fields
|
|
if col._classic_write:
|
|
# gen the list of "local" (ie not inherited)
|
|
# fields which are classic or many2one
|
|
ffields = [x for x in self._table._columns.items() \
|
|
if x[1]._classic_write]
|
|
# gen the list of inherited fields
|
|
inherits = [(x[0], x[1][2]) for x in \
|
|
self._table._inherit_fields.items()]
|
|
# complete the field list with the inherited fields
|
|
# which are classic or many2one
|
|
ffields += [x for x in inherits if x[1]._classic_write]
|
|
# otherwise we fetch only that field
|
|
else:
|
|
ffields = [(name, col)]
|
|
ids = [x for x in self._data.keys() \
|
|
if not self._data[x].has_key(name)]
|
|
# read the data
|
|
datas = self._table.read(self._cursor, self._user, ids,
|
|
[x[0] for x in ffields], context=self._context,
|
|
load="_classic_write")
|
|
|
|
# create browse records for 'remote' objects
|
|
for data in datas:
|
|
for i, j in ffields:
|
|
if not j._obj in self._table.pool.object_name_list():
|
|
continue
|
|
obj = self._table.pool.get(j._obj)
|
|
if j._type in ('many2one',):
|
|
if not j._classic_write and data[i]:
|
|
if isinstance(data[i][0], (list, tuple)):
|
|
ids2 = data[i][0][0]
|
|
else:
|
|
ids2 = data[i][0]
|
|
else:
|
|
ids2 = data[i]
|
|
if ids2 is False:
|
|
data[i] = BrowseRecordNull()
|
|
else:
|
|
data[i] = BrowseRecord(self._cursor, self._user,
|
|
ids2, obj, self._cache,
|
|
context=self._context)
|
|
elif j._type in ('one2many', 'many2many') and len(data[i]):
|
|
data[i] = BrowseRecordList([BrowseRecord(self._cursor,
|
|
self._user, x, obj,
|
|
self._cache, context=self._context) for x in data[i]],
|
|
self._context)
|
|
self._data[data['id']].update(data)
|
|
return self._data[self._id][name]
|
|
|
|
def __getattr__(self, name):
|
|
# TODO raise an AttributeError exception
|
|
return self[name]
|
|
|
|
def __contains__(self, name):
|
|
return (name in self._table._columns) \
|
|
or (name in self._table._inherit_fields) \
|
|
or hasattr(self._table, name)
|
|
|
|
def __hasattr__(self, name):
|
|
return name in self
|
|
|
|
def __int__(self):
|
|
return self._id
|
|
|
|
def __str__(self):
|
|
return "BrowseRecord(%s, %d)" % (self._table_name, self._id)
|
|
|
|
def __eq__(self, other):
|
|
return (self._table_name, self._id) == (other._table_name, other._id)
|
|
|
|
def __ne__(self, other):
|
|
return (self._table_name, self._id) != (other._table_name, other._id)
|
|
|
|
# we need to define __unicode__ even though we've already defined __str__
|
|
# because we have overridden __getattr__
|
|
def __unicode__(self):
|
|
return unicode(str(self))
|
|
|
|
def __hash__(self):
|
|
return hash((self._table_name, self._id))
|
|
|
|
def __nonzero__(self):
|
|
return bool(self._id)
|
|
|
|
__repr__ = __str__
|
|
|
|
def setLang(self, lang):
|
|
self._context = self._context.copy()
|
|
prev_lang = self._context.get('language', 'en_US')
|
|
self._context['language'] = lang
|
|
for table in self._cache:
|
|
for obj_id in self._cache[table]:
|
|
self._language_cache.setdefault(prev_lang,
|
|
{}).setdefault(table, {}).update(
|
|
self._cache[table][obj_id])
|
|
if lang in self._language_cache \
|
|
and table in self._language_cache[lang] \
|
|
and obj_id in self._language_cache[lang][table]:
|
|
self._cache[table][obj_id] = \
|
|
self._language_cache[lang][table][obj_id]
|
|
else:
|
|
self._cache[table][obj_id] = {'id': obj_id}
|
|
|
|
def get_eval(self, name):
|
|
res = self[name]
|
|
if isinstance(res, BrowseRecord):
|
|
res = res.id
|
|
if isinstance(res, BrowseRecordList):
|
|
res = res.get_eval()
|
|
if isinstance(res, BrowseRecordNull):
|
|
res = False
|
|
return res
|
|
|
|
|
|
class EvalEnvironment(dict):
|
|
|
|
def __init__(self, record, obj):
|
|
super(EvalEnvironment, self).__init__()
|
|
self.record = record
|
|
self.obj = obj
|
|
|
|
def __getitem__(self, item):
|
|
if item.startswith('_parent_'):
|
|
field = item[8:]
|
|
obj = self.obj.pool.get(self.obj._columns[field]._obj)
|
|
return EvalEnvironment(self.record[field], obj)
|
|
if item in self.obj._columns:
|
|
return self.record.get_eval(item)
|
|
return super(EvalEnvironment, self).__getitem__(item)
|
|
|
|
def __getattr__(self, item):
|
|
return self.__getitem__(item)
|
|
|
|
|
|
class ORM(object):
|
|
"""
|
|
Object relationnal mapping to postgresql module
|
|
. Hierarchical structure
|
|
. Constraints consistency, validations
|
|
. Object meta Data depends on its status
|
|
. Optimised processing by complex query (multiple actions at once)
|
|
. Default fields value
|
|
. Permissions optimisation
|
|
. Persistant object: DB postgresql
|
|
. Datas conversions
|
|
. Multi-level caching system
|
|
. 2 different inheritancies
|
|
. Fields:
|
|
- classicals (varchar, integer, boolean, ...)
|
|
- relations (one2many, many2one, many2many)
|
|
- functions
|
|
"""
|
|
_table = None
|
|
_name = None
|
|
_rec_name = 'name'
|
|
_order_name = None # Use to force order field when sorting on Many2One
|
|
_date_name = 'date'
|
|
_order = None
|
|
_inherits = {} #XXX remove from class instance
|
|
_sequence = None
|
|
_description = ''
|
|
_auto = True
|
|
_sql = ''
|
|
pool = None
|
|
__columns = None
|
|
__defaults = None
|
|
|
|
def _reset_columns(self):
|
|
self.__columns = None
|
|
|
|
def _getcolumns(self):
|
|
if self.__columns:
|
|
return self.__columns
|
|
res = {}
|
|
for attr in dir(self):
|
|
if attr in ('_columns', '_defaults'):
|
|
continue
|
|
if isinstance(getattr(self, attr), fields.Column):
|
|
res[attr] = getattr(self, attr)
|
|
self.__columns = res
|
|
return res
|
|
|
|
_columns = property(fget=_getcolumns)
|
|
|
|
def _reset_defaults(self):
|
|
self.__defaults = None
|
|
|
|
def _getdefaults(self):
|
|
if self.__defaults:
|
|
return self.__defaults
|
|
res = {}
|
|
columns = self._columns.keys()
|
|
columns += self._inherit_fields.keys()
|
|
for column in columns:
|
|
if getattr(self, 'default_' + column, False):
|
|
res[column] = getattr(self, 'default_' + column)
|
|
self.__defaults = res
|
|
return res
|
|
|
|
_defaults = property(fget=_getdefaults)
|
|
|
|
def _field_create(self, cursor, module_name):
|
|
cursor.execute("SELECT id FROM ir_model WHERE model = %s",
|
|
(self._name,))
|
|
if not cursor.rowcount:
|
|
# reference model in order to have a description
|
|
# of its fonctionnality in custom_report
|
|
cursor.execute("INSERT INTO ir_model " \
|
|
"(model, name, info, module) VALUES (%s, %s, %s, %s)",
|
|
(self._name, self._description, self.__doc__,
|
|
module_name))
|
|
cursor.execute("SELECT id FROM ir_model WHERE model = %s",
|
|
(self._name,))
|
|
(model_id,) = cursor.fetchone()
|
|
else:
|
|
(model_id,) = cursor.fetchone()
|
|
cursor.execute('UPDATE ir_model ' \
|
|
'SET name = %s, ' \
|
|
'info = %s ' \
|
|
'WHERE id = %s',
|
|
(self._description, self.__doc__, model_id))
|
|
|
|
cursor.execute('SELECT f.id AS id, f.name AS name, ' \
|
|
'f.field_description AS field_description, ' \
|
|
'f.ttype AS ttype, f.relation AS relation, ' \
|
|
'f.module as module, f.help AS help '\
|
|
'FROM ir_model_field AS f, ir_model AS m ' \
|
|
'WHERE f.model = m.id ' \
|
|
'AND m.model = %s ',
|
|
(self._name,))
|
|
columns = {}
|
|
for column in cursor.dictfetchall():
|
|
columns[column['name']] = column
|
|
cursor.execute('SELECT id, name, src, type FROM ir_translation ' \
|
|
'WHERE lang = %s ' \
|
|
'AND type IN (%s, %s, %s) ' \
|
|
'AND name IN ' \
|
|
'(' + ','.join(['%s' for x in self._columns]) + ')',
|
|
('en_US', 'field', 'help', 'selection') + \
|
|
tuple([self._name + ',' + x \
|
|
for x in self._columns]))
|
|
trans_columns = {}
|
|
trans_help = {}
|
|
trans_selection = {}
|
|
for trans in cursor.dictfetchall():
|
|
if trans['type'] == 'field':
|
|
trans_columns[trans['name']] = trans
|
|
elif trans['type'] == 'help':
|
|
trans_help[trans['name']] = trans
|
|
elif trans['type'] == 'selection':
|
|
trans_selection.setdefault(trans['name'], {})
|
|
trans_selection[trans['name']][trans['src']] = trans
|
|
for k in self._columns:
|
|
field = self._columns[k]
|
|
if k not in columns:
|
|
cursor.execute("INSERT INTO ir_model_field " \
|
|
"(model, name, field_description, ttype, " \
|
|
"relation, help, module) " \
|
|
"VALUES (%s, %s, %s, %s, %s, %s, %s)",
|
|
(model_id, k, field.string, field._type,
|
|
field._obj or '', field.help, module_name))
|
|
elif columns[k]['field_description'] != field.string \
|
|
or columns[k]['ttype'] != field._type \
|
|
or columns[k]['relation'] != (field._obj or '') \
|
|
or columns[k]['help'] != field.help:
|
|
cursor.execute('UPDATE ir_model_field ' \
|
|
'SET field_description = %s, ' \
|
|
'ttype = %s, ' \
|
|
'relation = %s, ' \
|
|
'help = %s ' \
|
|
'WHERE id = %s ',
|
|
(field.string, field._type, field._obj or '',
|
|
field.help, columns[k]['id']))
|
|
trans_name = self._name + ',' + k
|
|
if trans_name not in trans_columns:
|
|
if k not in ('create_uid', 'create_date',
|
|
'write_uid', 'write_date', 'id'):
|
|
cursor.execute('INSERT INTO ir_translation ' \
|
|
'(name, lang, type, src, value, module, fuzzy) ' \
|
|
'VALUES (%s, %s, %s, %s, %s, %s, false)',
|
|
(trans_name, 'en_US', 'field',
|
|
field.string, '', module_name))
|
|
elif trans_columns[trans_name]['src'] != field.string:
|
|
cursor.execute('UPDATE ir_translation ' \
|
|
'SET src = %s ' \
|
|
'WHERE id = %s ',
|
|
(field.string, trans_columns[trans_name]['id']))
|
|
if trans_name not in trans_help:
|
|
if field.help:
|
|
cursor.execute('INSERT INTO ir_translation ' \
|
|
'(name, lang, type, src, value, module, fuzzy) ' \
|
|
'VALUES (%s, %s, %s, %s, %s, %s, false)',
|
|
(trans_name, 'en_US', 'help',
|
|
field.help, '', module_name))
|
|
elif trans_help[trans_name]['src'] != field.help:
|
|
cursor.execute('UPDATE ir_translation ' \
|
|
'SET src = %s ' \
|
|
'WHERE id = %s ',
|
|
(field.help, trans_help[trans_name]['id']))
|
|
if hasattr(field, 'selection') \
|
|
and isinstance(field.selection, (tuple, list)):
|
|
for (key, val) in field.selection:
|
|
if trans_name not in trans_selection \
|
|
or val not in trans_selection[trans_name]:
|
|
cursor.execute('INSERT INTO ir_translation ' \
|
|
'(name, lang, type, src, value, ' \
|
|
'module, fuzzy) ' \
|
|
'VALUES (%s, %s, %s, %s, %s, %s, false)',
|
|
(trans_name, 'en_US', 'selection', val, '',
|
|
module_name))
|
|
# Clean ir_model_field from field that are no more existing.
|
|
for k in columns:
|
|
if columns[k]['module'] == module_name and k not in self._columns:
|
|
# XXX This delete field even when it is defined later in the module
|
|
cursor.execute('DELETE FROM ir_model_field '\
|
|
'WHERE id = %s',
|
|
(columns[k]['id'],))
|
|
|
|
def auto_init(self, cursor, module_name):
|
|
self.init(cursor, module_name)
|
|
self._auto_init(cursor, module_name)
|
|
|
|
def init(self, cursor, module_name):
|
|
cursor.execute('SELECT id, src FROM ir_translation ' \
|
|
'WHERE lang = %s ' \
|
|
'AND type = %s ' \
|
|
'AND name = %s',
|
|
('en_US', 'error', self._name))
|
|
trans_error = {}
|
|
for trans in cursor.dictfetchall():
|
|
trans_error[trans['src']] = trans
|
|
|
|
errors = self._error_messages.values() + self._sql_error_messages.values()
|
|
for _, _, error in self._sql_constraints:
|
|
errors.append(error)
|
|
for error in set(errors):
|
|
if error not in trans_error:
|
|
cursor.execute('INSERT INTO ir_translation ' \
|
|
'(name, lang, type, src, value, module, fuzzy) ' \
|
|
'VALUES (%s, %s, %s, %s, %s, %s, false)',
|
|
(self._name, 'en_US', 'error', error, '', module_name))
|
|
|
|
def _auto_init(self, cursor, module_name):
|
|
self._field_create(cursor, module_name)
|
|
if not self._auto or self.table_query():
|
|
# No db table for this object.
|
|
return
|
|
|
|
table = table_handler(cursor, self._table, self._name, module_name)
|
|
logs = (
|
|
('create_date', 'timestamp', 'TIMESTAMP',
|
|
fields.DateTime._symbol_set, lambda *a: datetime.datetime.now()),
|
|
('write_date', 'timestamp', 'TIMESTAMP',
|
|
fields.DateTime._symbol_set, None),
|
|
('create_uid', 'int4',
|
|
'INTEGER REFERENCES res_user ON DELETE SET NULL',
|
|
fields.Integer._symbol_set, lambda *a: 0),
|
|
('write_uid', 'int4',
|
|
'INTEGER REFERENCES res_user ON DELETE SET NULL',
|
|
fields.Integer._symbol_set, 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 field._classic_write:
|
|
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, field.sql_type(),
|
|
field._symbol_set, default_fun, field.size)
|
|
|
|
if isinstance(field, (fields.Integer, fields.Float)):
|
|
table.db_default(field_name, 0)
|
|
|
|
if isinstance(field, fields.Many2One):
|
|
if field._obj in ('res.user', 'res.group'):
|
|
ref = field._obj.replace('.','_')
|
|
else:
|
|
ref = self.pool.get(field._obj)._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._obj in ('res.user', 'res.group'):
|
|
ref = field._obj.replace('.','_')
|
|
else:
|
|
ref = self.pool.get(field._obj)._table
|
|
table.add_m2m(field_name, ref, field._rel, 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._obj == self._name \
|
|
and field.left and field.right:
|
|
cursor.execute('SELECT id FROM "%s" ' \
|
|
'WHERE "%s" IS NULL OR "%s" IS NULL '\
|
|
'OR "%s" = 0 OR "%s" = 0'% \
|
|
(self._table, field.left, field.right,
|
|
field.left, field.right))
|
|
if cursor.rowcount:
|
|
self._rebuild_tree(cursor, 0, field_name, False, 0)
|
|
|
|
|
|
for ident, constraint, msg in self._sql_constraints:
|
|
table.add_constraint(ident, constraint)
|
|
|
|
def __init__(self):
|
|
self._rpc_allowed = [
|
|
'read',
|
|
'write',
|
|
'create',
|
|
'default_get',
|
|
'delete',
|
|
'fields_get',
|
|
'fields_view_get',
|
|
'search',
|
|
'name_get',
|
|
'name_search',
|
|
'copy',
|
|
'import_data',
|
|
'export_data',
|
|
'search_count',
|
|
'search_read',
|
|
]
|
|
self._sql_constraints = []
|
|
self._constraints = []
|
|
self._inherit_fields = []
|
|
self._order = [('id', 'ASC')]
|
|
self._error_messages = {}
|
|
self._sql_error_messages = {}
|
|
# reinit the cache on _columns and _defaults
|
|
self.__columns = None
|
|
self.__defaults = None
|
|
|
|
if not self._table:
|
|
self._table = self._name.replace('.', '_')
|
|
if not self._description:
|
|
self._description = self._name
|
|
|
|
self._inherits_reload()
|
|
if not self._sequence:
|
|
self._sequence = self._table+'_id_seq'
|
|
|
|
self.create_uid = fields.Many2One('res.user',
|
|
'Create User', required=True, readonly=True)
|
|
self.create_date = fields.DateTime('Create Date',
|
|
required=True, readonly=True)
|
|
self.write_uid = fields.Many2One('res.user',
|
|
'Write User', readonly=True)
|
|
self.write_date = fields.DateTime(
|
|
'Write Date', readonly=True)
|
|
self.id = fields.Integer('ID', readonly=True)
|
|
# reinit the cache on _columns
|
|
self.__columns = None
|
|
|
|
for name in self._columns.keys() + self._inherit_fields.keys():
|
|
if name in self._columns:
|
|
field = self._columns[name]
|
|
else:
|
|
field = self._inherit_fields[name][2]
|
|
if isinstance(field, (fields.Selection, fields.Reference)) \
|
|
and not isinstance(field.selection, (list, tuple)) \
|
|
and field.selection not in self._rpc_allowed:
|
|
self._rpc_allowed.append(field.selection)
|
|
if field.on_change:
|
|
on_change = 'on_change_' + name
|
|
if on_change not in self._rpc_allowed:
|
|
self._rpc_allowed.append(on_change)
|
|
if field.on_change_with:
|
|
on_change_with = 'on_change_with_' + name
|
|
if on_change_with not in self._rpc_allowed:
|
|
self._rpc_allowed.append(on_change_with)
|
|
|
|
for k in self._defaults:
|
|
assert (k in self._columns) or (k in self._inherit_fields), \
|
|
'Default function defined in %s but field %s does not exist!' % \
|
|
(self._name, k,)
|
|
|
|
def _inherits_reload_src(self):
|
|
"Update objects that uses this one to update their _inherits fields"
|
|
for obj in self.pool.object_name_pool.values():
|
|
if self._name in obj._inherits:
|
|
obj._inherits_reload()
|
|
|
|
def _inherits_reload(self):
|
|
res = {}
|
|
for table in self._inherits:
|
|
res.update(self.pool.get(table)._inherit_fields)
|
|
for col in self.pool.get(table)._columns.keys():
|
|
res[col] = (table, self._inherits[table],
|
|
self.pool.get(table)._columns[col])
|
|
for col in self.pool.get(table)._inherit_fields.keys():
|
|
res[col] = (table, self._inherits[table],
|
|
self.pool.get(table)._inherit_fields[col][2])
|
|
self._inherit_fields = res
|
|
self._inherits_reload_src()
|
|
|
|
def default_create_uid(self, cursor, user, context=None):
|
|
"Default value for uid field"
|
|
return user
|
|
|
|
def default_create_date(self, cursor, user, context=None):
|
|
"Default value for create_date field"
|
|
return datetime.datetime.today()
|
|
|
|
def table_query(self, context=None):
|
|
'''
|
|
Return None if the table object is a real table in the database
|
|
or return a tuple wiht the query for the table object and the arguments
|
|
'''
|
|
return None
|
|
|
|
def raise_user_error(self, cursor, error, error_args=None,
|
|
error_description='', error_description_args=None,
|
|
raise_exception=True, context=None):
|
|
'''
|
|
Raise an exception that will be display as an error message
|
|
in the client.
|
|
|
|
:param cursor: the database cursor
|
|
:param error: the key of the dictionary _error_messages used
|
|
for error message
|
|
:param error_args: the arguments that will be used
|
|
for "%"-based substitution
|
|
:param error_description: the key of the dictionary
|
|
_error_messages used for error description
|
|
:param error_description_args: the arguments that will be used
|
|
for "%"-based substitution
|
|
:param raise_exception: if set to False return the error string
|
|
(or tuple if error_description is not empty) instead of raising an
|
|
exception.
|
|
:param context: the context in which the language key will
|
|
be used for translation
|
|
'''
|
|
translation_obj = self.pool.get('ir.translation')
|
|
|
|
if context is None:
|
|
context = {}
|
|
|
|
error = self._error_messages.get(error, error)
|
|
|
|
res = translation_obj._get_source(cursor, self._name, 'error',
|
|
context.get('language', 'en_US'), error)
|
|
if not res:
|
|
res = translation_obj._get_source(cursor, error, 'error',
|
|
context.get('language', 'en_US'))
|
|
if not res:
|
|
res = translation_obj._get_source(cursor, error, 'error',
|
|
'en_US')
|
|
|
|
if res:
|
|
error = res
|
|
|
|
if error_args:
|
|
try:
|
|
error = error % error_args
|
|
except TypeError:
|
|
pass
|
|
|
|
if error_description:
|
|
error_description = self._error_messages.get(error_description,
|
|
error_description)
|
|
|
|
res = translation_obj._get_source(cursor, self._name, 'error',
|
|
context.get('language', 'en_US'), error_description)
|
|
if not res:
|
|
res = translation_obj._get_source(cursor, error_description,
|
|
'error', context.get('language', 'en_US'))
|
|
if not res:
|
|
res = translation_obj._get_source(cursor, error_description,
|
|
'error', 'en_US')
|
|
|
|
if res:
|
|
error_description = res
|
|
|
|
if error_description_args:
|
|
try:
|
|
error_description = error_description % \
|
|
error_description_args
|
|
except TypeError:
|
|
pass
|
|
if raise_exception:
|
|
raise Exception('UserError', error, error_description)
|
|
else:
|
|
return (error, error_description)
|
|
if raise_exception:
|
|
raise Exception('UserError', error)
|
|
else:
|
|
return error
|
|
|
|
def browse(self, cursor, user, ids, context=None):
|
|
'''
|
|
Return a browse a BrowseRecordList for the ids
|
|
or BrowseRecord if ids is a integer.
|
|
'''
|
|
cache = {}
|
|
# need to accepts ints and longs because ids coming from a method
|
|
# launched by button in the interface have a type long...
|
|
if isinstance(ids, (int, long)):
|
|
return BrowseRecord(cursor, user, ids, self, cache,
|
|
context=context)
|
|
return BrowseRecordList([BrowseRecord(cursor, user, x, self, cache,
|
|
context=context) for x in ids], context)
|
|
|
|
def __export_row(self, cursor, user, row, fields_names, context=None):
|
|
lines = []
|
|
data = ['' for x in range(len(fields_names))]
|
|
done = []
|
|
for fpos in range(len(fields_names)):
|
|
field = fields_names[fpos]
|
|
if field:
|
|
row2 = row
|
|
i = 0
|
|
while i < len(field):
|
|
row2 = row2[field[i]]
|
|
if not row2:
|
|
break
|
|
if isinstance(row2, (BrowseRecordList, list)):
|
|
first = True
|
|
fields2 = [(x[:i+1]==field[:i+1] and x[i+1:]) \
|
|
or [] for x in fields_names]
|
|
if fields2 in done:
|
|
break
|
|
done.append(fields2)
|
|
for row2 in row2:
|
|
lines2 = self.__export_row(cursor, user, row2,
|
|
fields2, context)
|
|
if first:
|
|
for fpos2 in range(len(fields_names)):
|
|
if lines2 and lines2[0][fpos2]:
|
|
data[fpos2] = lines2[0][fpos2]
|
|
lines += lines2[1:]
|
|
first = False
|
|
else:
|
|
lines += lines2
|
|
break
|
|
i += 1
|
|
if i == len(field):
|
|
data[fpos] = row2 or ''
|
|
return [data] + lines
|
|
|
|
def export_data(self, cursor, user, ids, fields_names, context=None):
|
|
'''
|
|
Return list of list of values for each ids.
|
|
The list of values follow the fields_names.
|
|
Relational fields are defined with '/' at any deep.
|
|
'''
|
|
fields_names = [x.split('/') for x in fields_names]
|
|
datas = []
|
|
for row in self.browse(cursor, user, ids, context):
|
|
datas += self.__export_row(cursor, user, row, fields_names, context)
|
|
return datas
|
|
|
|
# TODO: Send a request with the result and multi-thread !
|
|
def import_data(self, cursor, user, fields_names, datas, context=None):
|
|
'''
|
|
Create record for each values in datas.
|
|
The fields name of values must be defined in fields_names.
|
|
'''
|
|
if context is None:
|
|
context = {}
|
|
fields_names = [x.split('/') for x in fields_names]
|
|
logger = logging.getLogger('import')
|
|
|
|
def process_liness(self, datas, prefix, fields_def, position=0):
|
|
line = datas[position]
|
|
row = {}
|
|
translate = {}
|
|
todo = []
|
|
warning = ''
|
|
|
|
# Import normal fields_names
|
|
for i in range(len(fields_names)):
|
|
if i >= len(line):
|
|
raise Exception('ImportError',
|
|
'Please check that all your lines have %d cols.' % \
|
|
(len(fields_names),))
|
|
field = fields_names[i]
|
|
if (len(field) == len(prefix) + 1) \
|
|
and field[len(prefix)].endswith(':id'):
|
|
res_id = False
|
|
if line[i]:
|
|
if fields_def[field[len(prefix)][:-3]]['type'] \
|
|
== 'many2many':
|
|
res_id = []
|
|
for word in line[i].split(','):
|
|
module, xml_id = word.rsplit('.', 1)
|
|
ir_model_data_obj = \
|
|
self.pool.get('ir.model.data')
|
|
new_id = ir_model_data_obj._get_id(cursor,
|
|
user, module, xml_id)
|
|
res_id2 = ir_model_data_obj.read(cursor, user,
|
|
[new_id], ['res_id'])[0]['res_id']
|
|
if res_id2:
|
|
res_id.append(res_id2)
|
|
if len(res_id):
|
|
res_id = [('set', res_id)]
|
|
else:
|
|
module, xml_id = line[i].rsplit('.', 1)
|
|
ir_model_data_obj = self.pool.get('ir.model.data')
|
|
new_id = ir_model_data_obj._get_id(cursor, user,
|
|
module, xml_id)
|
|
res_id = ir_model_data_obj.read(cursor, user,
|
|
[new_id], ['res_id'])[0]['res_id']
|
|
row[field[0][:-3]] = res_id or False
|
|
continue
|
|
if (len(field) == len(prefix)+1) and \
|
|
len(field[len(prefix)].split(':lang=')) == 2:
|
|
field, lang = field[len(prefix)].split(':lang=')
|
|
translate.setdefault(lang, {})[field]=line[i] or False
|
|
continue
|
|
if (len(field) == len(prefix)+1) and \
|
|
(prefix == field[0:len(prefix)]):
|
|
if fields_def[field[len(prefix)]]['type'] == 'integer':
|
|
res = line[i] and int(line[i])
|
|
elif fields_def[field[len(prefix)]]['type'] == 'float':
|
|
res = line[i] and float(line[i])
|
|
elif fields_def[field[len(prefix)]]['type'] == 'selection':
|
|
res = False
|
|
if isinstance(
|
|
fields_def[field[len(prefix)]]['selection'],
|
|
(tuple, list)):
|
|
sel = fields_def[field[len(prefix)]]['selection']
|
|
else:
|
|
sel = getattr(self, fields_def[field[len(prefix)]]\
|
|
['selection'])(cursor, user, context)
|
|
for key, val in sel:
|
|
if str(key) == line[i]:
|
|
res = key
|
|
if line[i] and not res:
|
|
logger.warning("key '%s' not found " \
|
|
"in selection field '%s'" % \
|
|
(line[i], field[len(prefix)]))
|
|
elif fields_def[field[len(prefix)]]['type'] == 'many2one':
|
|
res = False
|
|
if line[i]:
|
|
relation = \
|
|
fields_def[field[len(prefix)]]['relation']
|
|
res2 = self.pool.get(relation).name_search(cursor,
|
|
user, line[i], [], operator='=')
|
|
res = (res2 and res2[0][0]) or False
|
|
if not res:
|
|
warning += ('Relation not found: ' + line[i] + \
|
|
' on ' + relation + ' !\n')
|
|
logger.warning(
|
|
'Relation not found: ' + line[i] + \
|
|
' on ' + relation + ' !\n')
|
|
elif fields_def[field[len(prefix)]]['type'] == 'many2many':
|
|
res = []
|
|
if line[i]:
|
|
relation = \
|
|
fields_def[field[len(prefix)]]['relation']
|
|
for word in line[i].split(','):
|
|
res2 = self.pool.get(relation).name_search(
|
|
cursor, user, word, [], operator='=')
|
|
res3 = (res2 and res2[0][0]) or False
|
|
if not res3:
|
|
warning += ('Relation not found: ' + \
|
|
line[i] + ' on '+relation + ' !\n')
|
|
logger.warning(
|
|
'Relation not found: ' + line[i] + \
|
|
' on '+relation + ' !\n')
|
|
else:
|
|
res.append(res3)
|
|
if len(res):
|
|
res = [('set', res)]
|
|
else:
|
|
res = line[i] or False
|
|
row[field[len(prefix)]] = res
|
|
elif (prefix==field[0:len(prefix)]):
|
|
if field[0] not in todo:
|
|
todo.append(field[len(prefix)])
|
|
|
|
# Import one2many fields
|
|
nbrmax = 1
|
|
for field in todo:
|
|
newfd = self.pool.get(fields_def[field]['relation']).fields_get(
|
|
cursor, user, context=context)
|
|
res = process_liness(self, datas, prefix + [field], newfd,
|
|
position)
|
|
(newrow, max2, warning2, translate2) = res
|
|
nbrmax = max(nbrmax, max2)
|
|
warning = warning + warning2
|
|
reduce(lambda x, y: x and y, newrow)
|
|
row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
|
|
[('create', newrow)]) or []
|
|
i = max2
|
|
while (position+i)<len(datas):
|
|
test = True
|
|
for j in range(len(fields_names)):
|
|
field2 = fields_names[j]
|
|
if (len(field2) <= (len(prefix)+1)) \
|
|
and datas[position+i][j]:
|
|
test = False
|
|
if not test:
|
|
break
|
|
|
|
(newrow, max2, warning2, translate2) = \
|
|
process_liness(self, datas, prefix+[field], newfd,
|
|
position + i)
|
|
warning = warning + warning2
|
|
if reduce(lambda x, y: x or y, newrow.values()):
|
|
row[field].append(('create', newrow))
|
|
i += max2
|
|
nbrmax = max(nbrmax, i)
|
|
|
|
if len(prefix) == 0:
|
|
for i in range(max(nbrmax, 1)):
|
|
datas.pop(0)
|
|
result = (row, nbrmax, warning, translate)
|
|
return result
|
|
|
|
fields_def = self.fields_get(cursor, user, context=context)
|
|
done = 0
|
|
|
|
while len(datas):
|
|
res = {}
|
|
try:
|
|
(res, other, warning, translate) = \
|
|
process_liness(self, datas, [], fields_def)
|
|
if warning:
|
|
cursor.rollback()
|
|
return (-1, res, warning, '')
|
|
new_id = self.create(cursor, user, res, context=context)
|
|
for lang in translate:
|
|
context2 = context.copy()
|
|
context2['language'] = lang
|
|
self.write(cursor, user, new_id, translate[lang],
|
|
context=context2)
|
|
except Exception, exp:
|
|
logger.error(exp)
|
|
cursor.rollback()
|
|
return (-1, res, exp[0], warning)
|
|
done += 1
|
|
return (done, 0, 0, 0)
|
|
|
|
def read(self, cursor, user, ids, fields_names=None, context=None,
|
|
load='_classic_read'):
|
|
'''
|
|
Return list of a dict for each ids or just a dict if ids is an integer.
|
|
The dict have fields_names as keys.
|
|
'''
|
|
self.pool.get('ir.model.access').check(cursor, user, self._name, 'read',
|
|
context=context)
|
|
if not fields_names:
|
|
fields_names = self._columns.keys() + \
|
|
exclude(self._inherit_fields.keys(), self._columns.keys())
|
|
select = ids
|
|
if isinstance(ids, (int, long)):
|
|
select = [ids]
|
|
result = self._read_flat(cursor, user, select, fields_names, context,
|
|
load)
|
|
for i in result:
|
|
for key, j in i.items():
|
|
if j is None:
|
|
i[key] = False
|
|
if isinstance(ids, (int, long)):
|
|
return result[0]
|
|
return result
|
|
|
|
def _read_flat(self, cursor, user, ids, fields_names, context=None,
|
|
load='_classic_read'):
|
|
if context is None:
|
|
context = {}
|
|
if not ids:
|
|
return []
|
|
|
|
if fields_names is None:
|
|
fields_names = self._columns.keys()
|
|
|
|
# construct a clause for the rules :
|
|
domain1, domain2 = self.pool.get('ir.rule').domain_get(cursor, user,
|
|
self._name, context=context)
|
|
|
|
# all inherited fields + all non inherited fields
|
|
# for which the attribute whose name is in load is True
|
|
fields_pre = [x for x in fields_names if (x in self._columns \
|
|
and getattr(self._columns[x], '_classic_write')) 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 = self.pool.get('ir.translation')._get_ids(cursor,
|
|
self._name + ',' + field, 'model',
|
|
context.get('language', False) or 'en_US', ids)
|
|
for i in res:
|
|
i[field] = res_trans.get(i['id'], False) or i[field]
|
|
|
|
for table in self._inherits:
|
|
col = self._inherits[table]
|
|
cols = intersect(self._inherit_fields.keys(), fields_names)
|
|
cols = exclude(cols, self._columns.keys())
|
|
if not cols:
|
|
continue
|
|
res2 = self.pool.get(table).read(cursor, user,
|
|
[x[col] for x in res], cols, context, load)
|
|
|
|
res3 = {}
|
|
for i in res2:
|
|
res3[i['id']] = i
|
|
del i['id']
|
|
|
|
for record in res:
|
|
record.update(res3[record[col]])
|
|
if col not in fields_names:
|
|
del record[col]
|
|
|
|
# all fields which need to be post-processed
|
|
# by a simple function (symbol_get)
|
|
fields_post = [x for x in fields_names if x in self._columns \
|
|
and self._columns[x]._symbol_get]
|
|
if fields_post:
|
|
# maybe it would be faster to iterate on the fields_names then
|
|
# on res, so that we wouldn't need to get the _symbol_get
|
|
# in each occurence
|
|
for i in res:
|
|
for field in fields_post:
|
|
i[field] = self._columns[field]._symbol_get(i[field])
|
|
ids = [x['id'] for x in res]
|
|
|
|
# all non inherited fields for which the attribute
|
|
# whose name is in load is False
|
|
fields_post = [x for x in fields_names if x in self._columns \
|
|
and not getattr(self._columns[x], load)]
|
|
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, self, ids, field, user,
|
|
context=context, values=res)
|
|
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, self, ids, field_list, user,
|
|
context=context, values=res)
|
|
for field in res2:
|
|
for record in res:
|
|
record[field] = res2[field][record['id']]
|
|
return res
|
|
|
|
def _validate(self, cursor, user, ids, context=None):
|
|
if context is None:
|
|
context = {}
|
|
context = context.copy()
|
|
field_error = []
|
|
field_err_str = []
|
|
for field in self._constraints:
|
|
if not getattr(self, field[0])(cursor, user, ids):
|
|
self.raise_user_error(cursor, field[1], context=context)
|
|
|
|
if not 'res.user' in self.pool.object_name_list():
|
|
ctx_pref = {
|
|
}
|
|
else:
|
|
user_obj = self.pool.get('res.user')
|
|
ctx_pref = user_obj.get_preferences(cursor, user,
|
|
context_only=True, context=context)
|
|
|
|
def get_error_args(field_name):
|
|
model_field_obj = self.pool.get('ir.model.field')
|
|
error_args = (field_name, self._name)
|
|
if model_field_obj:
|
|
model_field_ids = model_field_obj.search(cursor,
|
|
user, [
|
|
('name', '=', field_name),
|
|
('model.model', '=', self._name),
|
|
], context=context, limit=1)
|
|
if model_field_ids:
|
|
model_field = model_field_obj.browse(cursor,
|
|
user, model_field_ids[0],
|
|
context=context)
|
|
error_args = (model_field.field_description,
|
|
model_field.model.name)
|
|
return error_args
|
|
|
|
context.update(ctx_pref)
|
|
records = self.browse(cursor, user, ids, context=context)
|
|
for field_name, field in self._columns.iteritems():
|
|
# validate domain
|
|
if field._type in ('many2one', 'many2many', 'one2many') \
|
|
and field._domain:
|
|
relation_obj = self.pool.get(field._obj)
|
|
if isinstance(field._domain, basestring):
|
|
ctx = context.copy()
|
|
ctx.update(ctx_pref)
|
|
for record in records:
|
|
env = EvalEnvironment(record, self)
|
|
env.update(ctx)
|
|
env['current_date'] = datetime.datetime.today()
|
|
env['time'] = time
|
|
env['context'] = context
|
|
env['active_id'] = record.id
|
|
domain = eval(field._domain, env)
|
|
relation_ids = []
|
|
if record[field_name]:
|
|
if field._type in ('many2one',):
|
|
relation_ids.append(record[field_name].id)
|
|
else:
|
|
relation_ids.extend(
|
|
[x.id for x in record[field_name]])
|
|
if relation_ids and not relation_obj.search(cursor,
|
|
user, [
|
|
'AND',
|
|
[('id', 'in', relation_ids)],
|
|
domain,
|
|
], context=context):
|
|
self.raise_user_error(cursor,
|
|
'domain_validation_record',
|
|
error_args=get_error_args(field_name),
|
|
context=context)
|
|
else:
|
|
relation_ids = []
|
|
for record in records:
|
|
if record[field_name]:
|
|
if field._type in ('many2one',):
|
|
relation_ids.append(record[field_name].id)
|
|
else:
|
|
relation_ids.extend(
|
|
[x.id for x in record[field_name]])
|
|
if relation_ids:
|
|
find_ids = relation_obj.search(cursor, user, [
|
|
'AND',
|
|
[('id', 'in', relation_ids)],
|
|
field._domain,
|
|
], context=context)
|
|
if not set(relation_ids) == set(find_ids):
|
|
self.raise_user_error(cursor,
|
|
'domain_validation_record',
|
|
error_args=get_error_args(field_name),
|
|
context=context)
|
|
# validate states required
|
|
if field.states and 'required' in field.states:
|
|
if isinstance(field.states['required'], basestring):
|
|
ctx = context.copy()
|
|
ctx.update(ctx_pref)
|
|
for record in records:
|
|
env = EvalEnvironment(record, self)
|
|
env.update(ctx)
|
|
env['current_date'] = datetime.datetime.today()
|
|
env['time'] = time
|
|
env['context'] = context
|
|
env['active_id'] = record.id
|
|
required = eval(field.states['required'], env)
|
|
if required and not record[field_name]:
|
|
self.raise_user_error(cursor,
|
|
'required_validation_record',
|
|
error_args=get_error_args(field_name),
|
|
context=context)
|
|
else:
|
|
if field.states['required']:
|
|
for record in records:
|
|
if not record[field_name]:
|
|
self.raise_user_error(cursor,
|
|
'required_validation_record',
|
|
error_args=get_error_args(field_name),
|
|
context=context)
|
|
|
|
def default_get(self, cursor, user, fields_names, context=None):
|
|
'''
|
|
Return a dict with the default values for each fields_names.
|
|
'''
|
|
value = {}
|
|
# get the default values for the inherited fields
|
|
for i in self._inherits.keys():
|
|
value.update(self.pool.get(i).default_get(cursor, user,
|
|
fields_names, context=context))
|
|
|
|
# get the default values defined in the object
|
|
for field in fields_names:
|
|
if field in self._defaults:
|
|
value[field] = self._defaults[field](cursor, user, context)
|
|
if field in self._columns:
|
|
if isinstance(self._columns[field], fields.Property):
|
|
property_obj = self.pool.get('ir.property')
|
|
value[field] = property_obj.get(cursor, user, field,
|
|
self._name)
|
|
if self._columns[field]._type in ('many2one',) \
|
|
and value[field]:
|
|
obj = self.pool.get(self._columns[field]._obj)
|
|
if isinstance(value[field], (int, long)):
|
|
value[field] = obj.name_get(cursor, user,
|
|
value[field], context=context)[0]
|
|
|
|
# get the default values set by the user and override the default
|
|
# values defined in the object
|
|
ir_default_obj = self.pool.get('ir.default')
|
|
defaults = ir_default_obj.get_default(cursor, user,
|
|
self._name, False, context=context)
|
|
for field, field_value in defaults.items():
|
|
if field in fields_names:
|
|
fld_def = (field in self._columns) and self._columns[field] \
|
|
or self._inherit_fields[field][2]
|
|
if fld_def._type in ('many2one',):
|
|
obj = self.pool.get(fld_def._obj)
|
|
if not obj.search(cursor, user, [('id', '=', field_value)]):
|
|
continue
|
|
if isinstance(field_value, (int, long)):
|
|
field_value = obj.name_get(cursor, user, field_value,
|
|
context=context)[0]
|
|
if fld_def._type in ('many2many'):
|
|
obj = self.pool.get(fld_def._obj)
|
|
field_value2 = []
|
|
for i in range(len(field_value)):
|
|
if not obj.search(cursor, user, [('id', '=',
|
|
field_value[i])]):
|
|
continue
|
|
field_value2.append(field_value[i])
|
|
field_value = field_value2
|
|
if fld_def._type in ('one2many'):
|
|
obj = self.pool.get(fld_def._obj)
|
|
field_value2 = []
|
|
for i in range(len(field_value or [])):
|
|
field_value2.append({})
|
|
for field2 in field_value[i]:
|
|
if obj._columns[field2]._type \
|
|
in ('many2one',):
|
|
obj2 = self.pool.get(obj._columns[field2]._obj)
|
|
if not obj2.search(cursor, user,
|
|
[('id', '=', field_value[i][field2])]):
|
|
continue
|
|
if isinstance(field_value[i][field2],
|
|
(int, long)):
|
|
field_value[i][field2] = obj2.name_get(
|
|
cursor, user,
|
|
field_value[i][field2],
|
|
context=context)[0]
|
|
# TODO add test for many2many and one2many
|
|
field_value2[i][field2] = field_value[i][field2]
|
|
field_value = field_value2
|
|
value[field] = field_value
|
|
value = self._default_on_change(cursor, user, value, context=context)
|
|
return value
|
|
|
|
def _default_on_change(self, cursor, user, value, context=None):
|
|
res = value.copy()
|
|
val = {}
|
|
for i in self._inherits.keys():
|
|
val.update(self.pool.get(i)._default_on_change(cursor, user,
|
|
value, context=context))
|
|
for field in value.keys():
|
|
if field in self._columns:
|
|
if self._columns[field].on_change:
|
|
args = {}
|
|
for arg in self._columns[field].on_change:
|
|
args[arg] = value.get(arg, False)
|
|
if arg in self._columns \
|
|
and self._columns[arg]._type == 'many2one':
|
|
if isinstance(args[arg], (list, tuple)):
|
|
args[arg] = args[arg][0]
|
|
val.update(getattr(self, 'on_change_' + field)(cursor, user,
|
|
[], args, context=context))
|
|
if self._columns[field]._type in ('one2many',):
|
|
obj = self.pool.get(self._columns[field]._obj)
|
|
for val2 in res[field]:
|
|
val2.update(obj._default_on_change(cursor, user,
|
|
val2, context=context))
|
|
res.update(val)
|
|
return res
|
|
|
|
def delete(self, cursor, user, ids, context=None):
|
|
'''
|
|
Remove the ids.
|
|
'''
|
|
if context is None:
|
|
context = {}
|
|
context = context.copy()
|
|
if not ids:
|
|
return True
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
if self.table_query(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(i):
|
|
args.append(i)
|
|
args.append(context['_timestamp'][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')
|
|
del context['_timestamp']
|
|
|
|
self.pool.get('ir.model.access').check(cursor, user, self._name,
|
|
'delete', context=context)
|
|
|
|
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)
|
|
|
|
if not self.check_xml_record(cursor, user, ids, None, context=context):
|
|
self.raise_user_error(cursor, 'delete_xml_record',
|
|
error_description='xml_record_desc',
|
|
context=context)
|
|
|
|
tree_ids = {}
|
|
for k in self._columns:
|
|
field = self._columns[k]
|
|
if isinstance(field, fields.Many2One) \
|
|
and field._obj == 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]
|
|
for object_id in tree_ids[k]:
|
|
self._update_tree(cursor, user, object_id, k,
|
|
field.left, field.right)
|
|
|
|
return True
|
|
|
|
def check_xml_record(self, cursor, user, ids, values, context=None):
|
|
"""
|
|
Check if a list of records and their corresponding fields are
|
|
originating from xml data. This is used by write and delete
|
|
functions: if the return value is True the records can be
|
|
written/deleted, False otherwise. The default behaviour is to
|
|
forbid all modification on records/fields originating from
|
|
xml. Values is the dictionary of written values. If values is
|
|
equal to None, no field by field check is performed, False is
|
|
return has soon has one of the record comes from the xml.
|
|
"""
|
|
# Allow root user to update/delete
|
|
if user == 0:
|
|
return True
|
|
cursor.execute('SELECT values ' \
|
|
'FROM ir_model_data ' \
|
|
'WHERE model = %s ' \
|
|
'AND db_id in (' + ','.join('%s' for x in ids)+ ') ',
|
|
[self._name] + ids)
|
|
if cursor.rowcount == 0:
|
|
return True
|
|
if values == None:
|
|
return False
|
|
for line in cursor.fetchall():
|
|
xml_values = eval(line[0], {
|
|
'Decimal': Decimal,
|
|
'datetime': datetime,
|
|
})
|
|
for key, val in values.iteritems():
|
|
if key in xml_values and val != xml_values[key]:
|
|
return False
|
|
return True
|
|
|
|
def write(self, cursor, user, ids, vals, context=None):
|
|
'''
|
|
Update ids with the content of vals.
|
|
vals is a dict with fields name as keys.
|
|
'''
|
|
if context is None:
|
|
context = {}
|
|
context = context.copy()
|
|
if not ids:
|
|
return True
|
|
if self.table_query(context):
|
|
return True
|
|
|
|
vals = vals.copy()
|
|
|
|
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(i):
|
|
args.append(i)
|
|
args.append(context['_timestamp'][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')
|
|
del context['_timestamp']
|
|
|
|
self.pool.get('ir.model.access').check(cursor, user, self._name,
|
|
'write', context=context)
|
|
|
|
if 'write_uid' in vals:
|
|
del vals['write_uid']
|
|
if 'write_date' in vals:
|
|
del vals['write_date']
|
|
if 'id' in vals:
|
|
del vals['id']
|
|
|
|
#for v in self._inherits.values():
|
|
# assert v not in vals, (v, vals)
|
|
upd0 = []
|
|
upd1 = []
|
|
upd_todo = []
|
|
updend = []
|
|
direct = []
|
|
for field in vals:
|
|
if field in self._columns:
|
|
if self._columns[field]._classic_write:
|
|
if (not self._columns[field].translate) \
|
|
or context.get('language', 'en_US') == 'en_US':
|
|
upd0.append('"' + field + '"=' + \
|
|
self._columns[field]._symbol_set[0])
|
|
upd1.append(self._columns[field]._symbol_set[1](
|
|
vals[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 vals[field]:
|
|
if self._columns[field]._type == 'reference':
|
|
val = vals[field].split(',')[0]
|
|
else:
|
|
val = vals[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))
|
|
|
|
if not self.check_xml_record(cursor, user, ids, vals, context=context):
|
|
self.raise_user_error(cursor, 'write_xml_record',
|
|
error_description='xml_record_desc',
|
|
context=context)
|
|
|
|
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','en_US'), ids, vals[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, self, select_id, field,
|
|
vals[field], user, 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()])
|
|
|
|
vals2 = {}
|
|
for val in updend:
|
|
if self._inherit_fields[val][0] == table:
|
|
vals2[val] = vals[val]
|
|
self.pool.get(table).write(cursor, user, nids, vals2,
|
|
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._obj == self._name \
|
|
and field.left and field.right:
|
|
if field.left in vals or field.right in vals:
|
|
raise Exception('ValidateError', 'You can not update fields: ' \
|
|
'"%s", "%s"' % (field.left, field.right))
|
|
if k in vals:
|
|
for object_id in ids:
|
|
self._update_tree(cursor, user, object_id, k,
|
|
field.left, field.right)
|
|
|
|
# Restart rule cache
|
|
if self.pool.get('ir.rule.group').search(cursor, 0, [
|
|
('model.model', '=', self._name),
|
|
], context=context):
|
|
self.pool.get('ir.rule').domain_get(cursor.dbname)
|
|
|
|
wf_service = LocalService("workflow")
|
|
for obj_id in ids:
|
|
wf_service.trg_write(user, self._name, obj_id, cursor,
|
|
context=context)
|
|
return True
|
|
|
|
def __clean_defaults(self, defaults):
|
|
vals = {}
|
|
for field in defaults.keys():
|
|
fld_def = (field in self._columns) and self._columns[field] \
|
|
or self._inherit_fields[field][2]
|
|
if fld_def._type in ('many2one',):
|
|
if isinstance(defaults[field], (list, tuple)):
|
|
vals[field] = defaults[field][0]
|
|
else:
|
|
vals[field] = defaults[field]
|
|
elif fld_def._type in ('one2many',):
|
|
obj = self.pool.get(self._columns[field]._obj)
|
|
vals[field] = []
|
|
for defaults2 in defaults[field]:
|
|
vals2 = obj.__clean_defaults(defaults2)
|
|
vals[field].append(('create', vals2))
|
|
elif fld_def._type in ('many2many',):
|
|
vals[field] = [('set', defaults[field])]
|
|
elif fld_def._type in ('boolean',):
|
|
vals[field] = bool(defaults[field])
|
|
else:
|
|
vals[field] = defaults[field]
|
|
return vals
|
|
|
|
def create(self, cursor, user, vals, context=None):
|
|
"""
|
|
Create a record with the content of vals.
|
|
vals is a dict with fields name as key.
|
|
"""
|
|
if self.table_query(context):
|
|
return False
|
|
|
|
vals = vals.copy()
|
|
|
|
self.pool.get('ir.model.access').check(cursor, user, self._name,
|
|
'create', context=context)
|
|
|
|
if 'create_uid' in vals:
|
|
del vals['create_uid']
|
|
if 'create_date' in vals:
|
|
del vals['create_date']
|
|
if 'id' in vals:
|
|
del vals['id']
|
|
|
|
default = []
|
|
avoid_table = []
|
|
for (i, j) in self._inherits.items():
|
|
if j in vals:
|
|
avoid_table.append(i)
|
|
for i in self._columns.keys(): # + self._inherit_fields.keys():
|
|
if not i in vals \
|
|
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 vals) \
|
|
and (not self._inherit_fields[i][0] in avoid_table):
|
|
default.append(i)
|
|
|
|
if len(default):
|
|
defaults = self.default_get(cursor, user, default, context)
|
|
vals.update(self.__clean_defaults(defaults))
|
|
|
|
tocreate = {}
|
|
for i in self._inherits:
|
|
if self._inherits[i] not in vals:
|
|
tocreate[i] = {}
|
|
|
|
(upd0, upd1, upd2) = ('', '', [])
|
|
upd_todo = []
|
|
|
|
for i in vals.keys():
|
|
if i in self._inherit_fields:
|
|
(table, col, col_detail) = self._inherit_fields[i]
|
|
tocreate[table][i] = vals[i]
|
|
del vals[i]
|
|
|
|
cursor.execute("SELECT NEXTVAL('" + self._sequence + "')")
|
|
(id_new,) = cursor.fetchone()
|
|
for table in tocreate:
|
|
new_id = self.pool.get(table).create(cursor, user, tocreate[table],
|
|
context=context)
|
|
upd0 += ',' + self._inherits[table]
|
|
upd1 += ',%s'
|
|
upd2.append(new_id)
|
|
|
|
for field in vals:
|
|
if self._columns[field]._classic_write:
|
|
upd0 = upd0 + ',"' + field + '"'
|
|
upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
|
|
upd2.append(self._columns[field]._symbol_set[1](vals[field]))
|
|
else:
|
|
upd_todo.append(field)
|
|
if field in self._columns \
|
|
and hasattr(self._columns[field], 'selection') \
|
|
and vals[field]:
|
|
if self._columns[field]._type == 'reference':
|
|
val = vals[field].split(',')[0]
|
|
else:
|
|
val = vals[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, self, id_new, field, vals[field],
|
|
user=user, 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._obj == 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 fields_get(self, cursor, user, fields_names=None, context=None):
|
|
"""
|
|
Returns the definition of each field in the object
|
|
the optional fields_names parameter can limit the result to some fields
|
|
"""
|
|
if context is None:
|
|
context = {}
|
|
res = {}
|
|
translation_obj = self.pool.get('ir.translation')
|
|
model_access_obj = self.pool.get('ir.model.access')
|
|
for parent in self._inherits:
|
|
res.update(self.pool.get(parent).fields_get(cursor, user,
|
|
fields_names, context))
|
|
write_access = model_access_obj.check(cursor, user, self._name, 'write',
|
|
raise_exception=False, context=context)
|
|
if self.table_query(context):
|
|
write_access = False
|
|
|
|
if context.get('language'):
|
|
#Add translation to cache
|
|
trans_args = []
|
|
for field in self._columns.keys():
|
|
trans_args.append((self._name + ',' + field, 'field',
|
|
context['language'], None))
|
|
trans_args.append((self._name + ',' + field, 'help',
|
|
context['language'], None))
|
|
if hasattr(self._columns[field], 'selection'):
|
|
if isinstance(self._columns[field].selection, (tuple, list)):
|
|
sel = self._columns[field].selection
|
|
for (key, val) in sel:
|
|
trans_args.append((self._name + ',' + field,
|
|
'selection', context['language'], val))
|
|
translation_obj._get_sources(cursor, trans_args)
|
|
|
|
for field in self._columns.keys():
|
|
res[field] = {'type': self._columns[field]._type}
|
|
for arg in (
|
|
'string',
|
|
'readonly',
|
|
'states',
|
|
'size',
|
|
'required',
|
|
'change_default',
|
|
'translate',
|
|
'help',
|
|
'select',
|
|
'on_change',
|
|
'add_remove',
|
|
'on_change_with',
|
|
'sort',
|
|
):
|
|
if getattr(self._columns[field], arg, None) != None:
|
|
res[field][arg] = getattr(self._columns[field], arg)
|
|
if not write_access:
|
|
res[field]['readonly'] = True
|
|
if res[field].get('states') and \
|
|
'readonly' in res[field]['states']:
|
|
res[field]['states'] = res[field]['states'].copy()
|
|
del res[field]['states']['readonly']
|
|
for arg in ('digits', 'invisible'):
|
|
if hasattr(self._columns[field], arg) \
|
|
and getattr(self._columns[field], arg):
|
|
res[field][arg] = getattr(self._columns[field], arg)
|
|
if isinstance(self._columns[field], fields.Function) \
|
|
and not self._columns[field].order_field:
|
|
res[field]['sortable'] = False
|
|
|
|
if context.get('language'):
|
|
# translate the field label
|
|
res_trans = translation_obj._get_source(cursor,
|
|
self._name + ',' + field, 'field',
|
|
context['language'])
|
|
if res_trans:
|
|
res[field]['string'] = res_trans
|
|
help_trans = translation_obj._get_source(cursor,
|
|
self._name + ',' + field, 'help',
|
|
context['language'])
|
|
if help_trans:
|
|
res[field]['help'] = help_trans
|
|
|
|
if hasattr(self._columns[field], 'selection'):
|
|
if isinstance(self._columns[field].selection, (tuple, list)):
|
|
sel = self._columns[field].selection
|
|
if context.get('language'):
|
|
# translate each selection option
|
|
sel2 = []
|
|
for (key, val) in sel:
|
|
val2 = translation_obj._get_source(cursor,
|
|
self._name + ',' + field, 'selection',
|
|
context.get('language', 'en_US'), val)
|
|
sel2.append((key, val2 or val))
|
|
sel = sel2
|
|
res[field]['selection'] = sel
|
|
else:
|
|
# call the 'dynamic selection' function
|
|
res[field]['selection'] = self._columns[field].selection
|
|
if res[field]['type'] in (
|
|
'one2many',
|
|
'many2many',
|
|
'many2one',
|
|
):
|
|
res[field]['relation'] = self._columns[field]._obj
|
|
res[field]['domain'] = self._columns[field]._domain
|
|
res[field]['context'] = self._columns[field]._context
|
|
if res[field]['type'] == 'one2many':
|
|
res[field]['relation_field'] = self._columns[field]._field
|
|
|
|
if fields_names:
|
|
# filter out fields which aren't in the fields_names list
|
|
for i in res.keys():
|
|
if i not in fields_names:
|
|
del res[i]
|
|
return res
|
|
|
|
def view_header_get(self, cursor, user, value, view_type='form',
|
|
context=None):
|
|
"""
|
|
Overload this method if you need a window title
|
|
which depends on the context
|
|
"""
|
|
return False
|
|
|
|
def __view_look_dom(self, cursor, user, element, type, fields_width=None,
|
|
context=None):
|
|
translation_obj = self.pool.get('ir.translation')
|
|
|
|
if fields_width is None:
|
|
fields_width = {}
|
|
if context is None:
|
|
context = {}
|
|
result = False
|
|
fields_attrs = {}
|
|
childs = True
|
|
|
|
if element.tag in ('field', 'label', 'separator', 'group'):
|
|
for attr in ('name', 'icon'):
|
|
if element.get(attr):
|
|
attrs = {}
|
|
try:
|
|
if element.get(attr) in self._columns:
|
|
relation = self._columns[element.get(attr)]._obj
|
|
else:
|
|
relation = self._inherit_fields[element.get(attr)][2]._obj
|
|
except:
|
|
relation = False
|
|
if relation and element.tag == 'field':
|
|
childs = False
|
|
views = {}
|
|
for field in element:
|
|
if field.tag in ('form', 'tree', 'graph'):
|
|
field2 = copy.copy(field)
|
|
|
|
def _translate_field(field):
|
|
if field.get('string'):
|
|
trans = translation_obj._get_source(
|
|
cursor, self._name, 'view',
|
|
context['language'],
|
|
field.get('string'))
|
|
if trans:
|
|
field.set('string', trans)
|
|
if field.get('sum'):
|
|
trans = translation_obj._get_source(
|
|
cursor, self._name, 'view',
|
|
context['language'],
|
|
field.get('sum'))
|
|
if trans:
|
|
field.set('sum', trans)
|
|
for field_child in field:
|
|
_translate_field(field_child)
|
|
if 'language' in context:
|
|
_translate_field(field2)
|
|
|
|
xarch, xfields = self.pool.get(relation
|
|
)._view_look_dom_arch(cursor, user,
|
|
field2, field.tag,
|
|
context=context)
|
|
views[field.tag] = {
|
|
'arch': xarch,
|
|
'fields': xfields
|
|
}
|
|
element.remove(field)
|
|
attrs = {'views': views}
|
|
fields_attrs[element.get(attr)] = attrs
|
|
if element.get('name') in fields_width:
|
|
element.set('width', str(fields_width[element.get('name')]))
|
|
|
|
# translate view
|
|
if ('language' in context) and not result:
|
|
if element.get('string'):
|
|
trans = translation_obj._get_source(cursor,
|
|
self._name, 'view', context['language'],
|
|
element.get('string'))
|
|
if trans:
|
|
element.set('string', trans)
|
|
if element.get('sum'):
|
|
trans = translation_obj._get_source(cursor,
|
|
self._name, 'view', context['language'],
|
|
element.get('sum'))
|
|
if trans:
|
|
element.set('sum', trans)
|
|
|
|
if element.tag in ('form', 'tree', 'graph'):
|
|
value = ''
|
|
if element.get('string'):
|
|
value = element.get('string')
|
|
result = self.view_header_get(cursor, user, value, element.tag,
|
|
context)
|
|
if result:
|
|
element.set('string', result)
|
|
|
|
if childs:
|
|
for field in element:
|
|
fields_attrs.update(self.__view_look_dom(cursor, user, field,
|
|
type, fields_width=fields_width, context=context))
|
|
return fields_attrs
|
|
|
|
def _view_look_dom_arch(self, cursor, user, tree, type, context=None):
|
|
fields_width = {}
|
|
tree_root = tree.getroottree().getroot()
|
|
|
|
if type == 'tree':
|
|
viewtreewidth_obj = self.pool.get('ir.ui.view_tree_width')
|
|
viewtreewidth_ids = viewtreewidth_obj.search(cursor, user, [
|
|
('model', '=', self._name),
|
|
('user', '=', user),
|
|
], context=context)
|
|
for viewtreewidth in viewtreewidth_obj.browse(cursor, user,
|
|
viewtreewidth_ids, context=context):
|
|
if viewtreewidth.width > 0:
|
|
fields_width[viewtreewidth.field] = viewtreewidth.width
|
|
|
|
fields_def = self.__view_look_dom(cursor, user, tree_root, type,
|
|
fields_width=fields_width, context=context)
|
|
arch = etree.tostring(tree, encoding='utf-8', pretty_print=False)
|
|
fields2 = self.fields_get(cursor, user, fields_def.keys(), context)
|
|
for field in fields_def:
|
|
if field in fields2:
|
|
fields2[field].update(fields_def[field])
|
|
return arch, fields2
|
|
|
|
def fields_view_get(self, cursor, user, view_id=None, view_type='form',
|
|
context=None, toolbar=False, hexmd5=None):
|
|
'''
|
|
Return a dict with keys:
|
|
- arch: the xml description of the view.
|
|
- fields: a dict with the definition of each fields.
|
|
- toolbar: if toolbar is True, a dict with 'print', 'action', 'relate'
|
|
keyword action defintion for the view.
|
|
- md5: the check sum of the above dict that will be used for caching.
|
|
view_id can specify the id of the view, if empty the system
|
|
will select the first view
|
|
view_type specify the type of the view if view_id is empty
|
|
If hexmd5 is fill, the function will return True if the view have the same
|
|
md5 or the dict.
|
|
'''
|
|
|
|
def _inherit_apply(src, inherit):
|
|
|
|
def _find(tree, element):
|
|
if element.tag == 'xpath':
|
|
res = tree.xpath(element.get('expr'))
|
|
if res:
|
|
return res[0]
|
|
return None
|
|
|
|
tree_src = etree.fromstring(src)
|
|
tree_inherit = etree.fromstring(inherit)
|
|
root_inherit = tree_inherit.getroottree().getroot()
|
|
for element2 in root_inherit:
|
|
element = _find(tree_src, element2)
|
|
if element is not None:
|
|
pos = element2.get('position', 'inside')
|
|
if pos == 'replace':
|
|
parent = element.getparent()
|
|
parent.remove(element)
|
|
parent.extend(element2.getchildren())
|
|
elif pos == 'inside':
|
|
element.extend(element2.getchildren())
|
|
elif pos == 'after':
|
|
parent = element.getparent()
|
|
next = element.getnext()
|
|
if next is not None:
|
|
for child in element2:
|
|
index = parent.index(next)
|
|
parent.insert(index, child)
|
|
else:
|
|
parent.extend(element2.getchildren())
|
|
elif pos == 'before':
|
|
parent = element.getparent()
|
|
for child in element2:
|
|
index = parent.index(element)
|
|
parent.insert(index, child)
|
|
else:
|
|
raise AttributeError('Unknown position ' \
|
|
'in inherited view %s!' % pos)
|
|
else:
|
|
raise AttributeError('Couldn\'t find tag (%s: %s) in parent view!' % (element2.tag, element2.get('expr')))
|
|
return etree.tostring(tree_src, encoding='utf-8')
|
|
|
|
result = {'type': view_type, 'model': self._name}
|
|
|
|
test = True
|
|
model = True
|
|
sql_res = False
|
|
while test:
|
|
if view_id:
|
|
where = (model and (" and model='%s'" % (self._name,))) or ''
|
|
cursor.execute('SELECT arch, field_childs, id, type, ' \
|
|
'inherit ' \
|
|
'FROM ir_ui_view WHERE id = %s ' + where, (view_id,))
|
|
else:
|
|
cursor.execute('SELECT arch, field_childs, id, type, ' \
|
|
'inherit ' \
|
|
'FROM ir_ui_view ' \
|
|
'WHERE model = %s AND type = %s AND inherit IS NULL ' \
|
|
'ORDER BY priority',
|
|
(self._name,view_type))
|
|
sql_res = cursor.fetchone()
|
|
if not sql_res:
|
|
break
|
|
test = sql_res[4]
|
|
view_id = test or sql_res[2]
|
|
model = False
|
|
|
|
# if a view was found
|
|
if sql_res:
|
|
result['type'] = sql_res[3]
|
|
result['view_id'] = sql_res[2]
|
|
result['arch'] = sql_res[0]
|
|
|
|
def _inherit_apply_rec(result, inherit_id):
|
|
# get all views which inherit from (ie modify) this view
|
|
cursor.execute('SELECT arch, id FROM ir_ui_view ' \
|
|
'WHERE inherit = %s AND model = %s ' \
|
|
'ORDER BY priority', (inherit_id, self._name))
|
|
sql_inherit = cursor.fetchall()
|
|
for (inherit, view_id) in sql_inherit:
|
|
result = _inherit_apply(result, inherit)
|
|
result = _inherit_apply_rec(result, view_id)
|
|
return result
|
|
|
|
result['arch'] = _inherit_apply_rec(result['arch'], sql_res[2])
|
|
|
|
result['field_childs'] = sql_res[1] or False
|
|
# otherwise, build some kind of default view
|
|
else:
|
|
if view_type == 'form':
|
|
res = self.fields_get(cursor, user, context=context)
|
|
xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
|
|
'''<form string="%s">''' % (self._description,)
|
|
for i in res:
|
|
if i in ('create_uid', 'create_date',
|
|
'write_uid', 'write_date', 'id'):
|
|
continue
|
|
if res[i]['type'] not in ('one2many', 'many2many'):
|
|
xml += '<label name="%s"/>' % (i,)
|
|
xml += '<field name="%s"/>' % (i,)
|
|
if res[i]['type'] == 'text':
|
|
xml += "<newline/>"
|
|
xml += "</form>"
|
|
elif view_type == 'tree':
|
|
field = 'id'
|
|
if self._rec_name in self._columns:
|
|
field = self._rec_name
|
|
xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
|
|
'''<tree string="%s"><field name="%s"/></tree>''' \
|
|
% (self._description, field)
|
|
elif view_type == 'calendar':
|
|
xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
|
|
'''<calendar string="%s" date_start="%s">''' \
|
|
'''<field name="%s"/></calendar>''' \
|
|
% (self._description, self._date_name, self._rec_name)
|
|
else:
|
|
xml = ''
|
|
result['type'] = view_type
|
|
result['arch'] = xml
|
|
result['field_childs'] = False
|
|
result['view_id'] = 0
|
|
|
|
parser = etree.XMLParser(remove_blank_text=True)
|
|
tree = etree.fromstring(result['arch'], parser)
|
|
xarch, xfields = self._view_look_dom_arch(cursor, user, tree,
|
|
result['type'], context=context)
|
|
result['arch'] = xarch
|
|
result['fields'] = xfields
|
|
if toolbar:
|
|
action_obj = self.pool.get('ir.action.keyword')
|
|
prints = action_obj.get_keyword(cursor, user, 'form_print',
|
|
(self._name, 0), context=context)
|
|
actions = action_obj.get_keyword(cursor, user, 'form_action',
|
|
(self._name, 0), context=context)
|
|
relates = action_obj.get_keyword(cursor, user, 'form_relate',
|
|
(self._name, 0), context=context)
|
|
result['toolbar'] = {
|
|
'print': prints,
|
|
'action': actions,
|
|
'relate': relates,
|
|
}
|
|
result['md5'] = md5.new(str(result)).hexdigest()
|
|
if hexmd5 == result['md5']:
|
|
return True
|
|
return result
|
|
|
|
fields_view_get = Cache('orm.fields_view_get')(fields_view_get)
|
|
|
|
def _where_calc(self, cursor, user, args, active_test=True, context=None):
|
|
if context is None:
|
|
context = {}
|
|
args = args[:]
|
|
# if the object has a field named 'active', filter out all inactive
|
|
# records unless they were explicitely asked for
|
|
if ('active' in self._columns or \
|
|
'active' in self._inherit_fields.keys()) \
|
|
and (active_test and context.get('active_test', True)):
|
|
def process_args(args):
|
|
i = 0
|
|
active_found = False
|
|
while i < len(args):
|
|
if isinstance(args[i], list):
|
|
args[i] = process_args(args[i])
|
|
if isinstance(args[i], tuple):
|
|
if args[i][0] == 'active':
|
|
active_found = True
|
|
i += 1
|
|
if not active_found:
|
|
if args and ((isinstance(args[0], basestring) \
|
|
and args[0] == 'AND') \
|
|
or (not isinstance(args[0], basestring))):
|
|
args.append(('active', '=', 1))
|
|
else:
|
|
args = ['AND', args, ('active', '=', 1)]
|
|
return args
|
|
args = process_args(args)
|
|
|
|
table_query = ''
|
|
table_args = []
|
|
if self.table_query(context):
|
|
table_query, table_args = self.table_query(context)
|
|
table_query = '(' + table_query + ') AS '
|
|
|
|
tables = [table_query + '"' + self._table + '"']
|
|
tables_args = table_args
|
|
|
|
qu1, qu2 = self.__where_calc_oper(cursor, user, args, tables,
|
|
tables_args, context=context)
|
|
return qu1, qu2, tables, tables_args
|
|
|
|
|
|
def __where_calc_oper(self, cursor, user, args, tables, tables_args,
|
|
context=None):
|
|
operator = 'AND'
|
|
if len(args) and isinstance(args[0], basestring):
|
|
if args[0] not in ('AND', 'OR'):
|
|
raise Exception('ValidateError', 'Operator "%s" not supported' \
|
|
% args[0])
|
|
operator = args[0]
|
|
args = args[1:]
|
|
tuple_args = []
|
|
list_args = []
|
|
for arg in args:
|
|
#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.__where_calc(cursor, user,
|
|
tuple_args, tables, tables_args, context=context)
|
|
if len(qu1):
|
|
qu1 = (' ' + operator + ' ').join(qu1)
|
|
else:
|
|
qu1 = ''
|
|
|
|
for args2 in list_args:
|
|
qu1b, qu2b = self.__where_calc_oper(cursor,
|
|
user, args2, 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 __where_calc(self, cursor, user, args, tables, tables_args,
|
|
context=None):
|
|
if context is None:
|
|
context = {}
|
|
i = 0
|
|
joins = []
|
|
while i < len(args):
|
|
if args[i][1] not in OPERATORS:
|
|
raise Exception('ValidateError', 'Argument "%s" not supported' \
|
|
% args[i][1])
|
|
|
|
table = self
|
|
if args[i][0] in self._inherit_fields:
|
|
itable = self.pool.get(self._inherit_fields[args[i][0]][0])
|
|
table_query = ''
|
|
table_arg = []
|
|
if itable.table_query(context):
|
|
table_query, table_args = self.table_query(context)
|
|
table_query = '(' + table_query + ') AS '
|
|
if (table_query + '"' + itable._table + '"' not in tables):
|
|
tables.append(table_query + '"' + itable._table + '"')
|
|
tables_args += table_arg
|
|
joins.append(('id', 'join', '%s.%s' % \
|
|
(self._table, self._inherits[itable._name]), itable))
|
|
fargs = args[i][0].split('.', 1)
|
|
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[args[i][0]][0])
|
|
field = table._columns.get(fargs[0], False)
|
|
if len(fargs) > 1:
|
|
if field._type == 'many2one':
|
|
args[i] = (fargs[0], 'inselect',
|
|
self.pool.get(field._obj).search(cursor, user,
|
|
[(fargs[1], args[i][1], args[i][2])],
|
|
context=context, query_string=True))
|
|
i += 1
|
|
continue
|
|
else:
|
|
raise Exception('ValidateError', 'Clause on field "%s" ' \
|
|
'doesn\'t work on "%s"' % (args[i][0], self._name))
|
|
if field._properties:
|
|
arg = [args.pop(i)]
|
|
j = i
|
|
while j < len(args):
|
|
if args[j][0] == arg[0][0]:
|
|
arg.append(args.pop(j))
|
|
else:
|
|
j += 1
|
|
if field._fnct_search:
|
|
args.extend(field.search(cursor, user, self,
|
|
arg[0][0], arg, context=context))
|
|
elif field._type == 'one2many':
|
|
field_obj = self.pool.get(field._obj)
|
|
|
|
if isinstance(args[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,
|
|
args[i][2], [], args[i][1], context=context)]
|
|
else:
|
|
ids2 = args[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'
|
|
args[i] = ('id', clause, (query1, query2))
|
|
elif not ids2:
|
|
args[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
|
|
args[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()])
|
|
|
|
args[i] = ('id', 'in', ids3)
|
|
i += 1
|
|
elif field._type == 'many2many':
|
|
# XXX must find a solution for long id list
|
|
if args[i][1] in ('child_of', 'not child_of'):
|
|
if isinstance(args[i][2], basestring):
|
|
ids2 = [x[0] for x in self.pool.get(
|
|
field._obj).name_search(cursor, user, args[i][2], [],
|
|
'like', context=context)]
|
|
elif isinstance(args[i][2], (int, long)):
|
|
ids2 = [args[i][2]]
|
|
else:
|
|
ids2 = args[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._obj != table._name:
|
|
if len(args[i]) != 4:
|
|
raise Exception('Error', 'Programming error: ' \
|
|
'child_of on field "%s" is not allowed!' % \
|
|
(args[i][0],))
|
|
ids2 = self.pool.get(field._obj).search(cursor, user,
|
|
[(args[i][3], 'child_of', ids2)],
|
|
context=context)
|
|
query1 = 'SELECT "' + field.origin + '" ' \
|
|
'FROM "' + field._rel + '" ' \
|
|
'WHERE "' + field.target + '" IN (' + \
|
|
','.join(['%s' for x in ids2]) + ') ' \
|
|
'AND "' + field.origin + '" IS NOT NULL'
|
|
query2 = [str(x) for x in ids2]
|
|
if args[i][1] == 'child_of':
|
|
args[i] = ('id', 'inselect', (query1, query2))
|
|
else:
|
|
args[i] = ('id', 'notinselect', (query1, query2))
|
|
else:
|
|
if args[i][1] == 'child_of':
|
|
args[i] = ('id', 'in', ids2 + _rec_get(ids2,
|
|
table, args[i][0]))
|
|
else:
|
|
args[i] = ('id', 'not in', ids2 + _rec_get(ids2,
|
|
table, args[i][0]))
|
|
else:
|
|
if isinstance(args[i][2], basestring):
|
|
res_ids = [x[0] for x in self.pool.get(field._obj
|
|
).name_search(cursor, user, args[i][2], [],
|
|
args[i][1], context=context)]
|
|
else:
|
|
res_ids = args[i][2]
|
|
if res_ids == True or res_ids == False:
|
|
query1 = 'SELECT "' + field.origin + '" ' \
|
|
'FROM "' + field._rel + '" '\
|
|
'WHERE "' + field.origin + '" IS NOT NULL'
|
|
query2 = []
|
|
clause = 'inselect'
|
|
if res_ids == False:
|
|
clause = 'notinselect'
|
|
args[i] = ('id', clause, (query1, query2))
|
|
elif not res_ids:
|
|
args[i] = ('id', '=', '0')
|
|
else:
|
|
query1 = 'SELECT "' + field.origin + '" ' \
|
|
'FROM "' + field._rel + '" ' \
|
|
'WHERE "' + field.target + '" IN (' + \
|
|
','.join(['%s' for x in res_ids]) + ')'
|
|
query2 = [str(x) for x in res_ids]
|
|
args[i] = ('id', 'inselect', (query1, query2))
|
|
i += 1
|
|
|
|
elif field._type == 'many2one':
|
|
# XXX must find a solution for long id list
|
|
if args[i][1] in ('child_of', 'not child_of'):
|
|
if isinstance(args[i][2], basestring):
|
|
ids2 = [x[0] for x in self.pool.get(
|
|
field._obj).name_search(cursor, user, args[i][2],
|
|
[], 'like', context=context)]
|
|
elif isinstance(args[i][2], (int, long)):
|
|
ids2 = [args[i][2]]
|
|
else:
|
|
ids2 = args[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._obj != table._name:
|
|
if len(args[i]) != 4:
|
|
raise Exception('Error', 'Programming error: ' \
|
|
'child_of on field "%s" is not allowed!' % \
|
|
(args[i][0],))
|
|
ids2 = self.pool.get(field._obj).search(cursor, user,
|
|
[(args[i][3], 'child_of', ids2)],
|
|
context=context)
|
|
if args[i][1] == 'child_of':
|
|
args[i] = (args[i][0], 'in', ids2, table)
|
|
else:
|
|
args[i] = (args[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 = ''
|
|
for left, right in cursor.fetchall():
|
|
if clause:
|
|
clause += 'OR '
|
|
clause += '( "' + field.left + '" >= ' + \
|
|
str(left) + ' ' + \
|
|
'AND "' + field.right + '" <= ' + \
|
|
str(right) + ')'
|
|
|
|
query = 'SELECT id FROM "' + self._table + '" ' + \
|
|
'WHERE ' + clause
|
|
if args[i][1] == 'child_of':
|
|
args[i] = ('id', 'inselect', (query, []))
|
|
else:
|
|
args[i] = ('id', 'notinselect', (query, []))
|
|
else:
|
|
if args[i][1] == 'child_of':
|
|
args[i] = ('id', 'in', ids2 + _rec_get(
|
|
ids2, table, args[i][0]), table)
|
|
else:
|
|
args[i] = ('id', 'not in', ids2 + _rec_get(
|
|
ids2, table, args[i][0]), table)
|
|
else:
|
|
if isinstance(args[i][2], basestring):
|
|
res_ids = self.pool.get(field._obj).name_search(cursor,
|
|
user, args[i][2], [], args[i][1],
|
|
context=context)
|
|
args[i] = (args[i][0], 'in', [x[0] for x in res_ids],
|
|
table)
|
|
else:
|
|
args[i] += (table,)
|
|
i += 1
|
|
else:
|
|
if field.translate:
|
|
exprs = ['%s', '%s']
|
|
if args[i][1] in ('like', 'ilike', 'not like', 'not ilike'):
|
|
exprs = ['%% %s%%', '%s%%']
|
|
oper = 'OR'
|
|
if args[i][1] in ('not like', 'not ilike', '!='):
|
|
oper = 'AND'
|
|
query1 = '(SELECT res_id FROM ir_translation ' \
|
|
'WHERE name = %s AND lang = %s ' \
|
|
'AND type = %s ' \
|
|
'AND (value ' + args[i][1] + ' %s ' \
|
|
+ oper +' value ' + args[i][1] + ' %s))'
|
|
query2 = [table._name + ',' + args[i][0],
|
|
context.get('language', False) or 'en_US',
|
|
'model', exprs[0] % args[i][2],
|
|
exprs[1] % args[i][2]]
|
|
query1 += ' UNION '
|
|
table_query = ''
|
|
table_args = []
|
|
if table.table_query(context):
|
|
table_query, table_args = table.table_query(context)
|
|
table_query = '(' + table_query + ') AS '
|
|
query1 += '(SELECT id FROM ' + table_query + \
|
|
'"' + table._table + '" ' \
|
|
'WHERE ("' + args[i][0] + '" ' + \
|
|
args[i][1] + ' %s ' + oper + ' "' + args[i][0] + '" ' + \
|
|
args[i][1] + ' %s))'
|
|
query2 += table_args + [exprs[0] % args[i][2],
|
|
exprs[1] % args[i][2]]
|
|
args[i] = ('id', 'inselect', (query1, query2), table)
|
|
else:
|
|
args[i] += (table,)
|
|
i += 1
|
|
args.extend(joins)
|
|
|
|
qu1, qu2 = [], []
|
|
for arg in args:
|
|
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)
|
|
for xitem in todel[::-1]:
|
|
del arg[2][xitem]
|
|
#TODO fix max_stack_depth
|
|
if len(arg[2]):
|
|
if arg[0] == 'id':
|
|
qu1.append(('(%s.id ' + arg[1] + ' (%s))') % \
|
|
(table._table,
|
|
','.join(['%s'] * len(arg[2])),))
|
|
else:
|
|
qu1.append(('(%s.%s ' + arg[1] + ' (%s))') % \
|
|
(table._table, arg[0], ','.join(
|
|
[table._columns[arg[0]].\
|
|
_symbol_set[0]] * len(arg[2]))))
|
|
if todel:
|
|
if arg[1] == 'in':
|
|
qu1[-1] = '(' + qu1[-1] + ' or ' \
|
|
+ arg[0] + ' is null)'
|
|
else:
|
|
qu1[-1] = '(' + qu1[-1] + ' or ' \
|
|
+ arg[0] + ' is not null)'
|
|
qu2 += arg[2]
|
|
elif todel:
|
|
qu1.append('(' + arg[0] + ' IS NULL)')
|
|
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':
|
|
if arg[1] == 'join':
|
|
qu1.append('(%s.%s = %s)' % \
|
|
(table._table, arg[0], arg[2]))
|
|
else:
|
|
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(table._columns[arg[0]].\
|
|
_symbol_set[1](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],
|
|
table._columns[arg[0]]._symbol_set[0]))
|
|
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 search_count(self, cursor, user, args, context=None):
|
|
'''
|
|
Return the number of record that match the clause defined in args.
|
|
see function search
|
|
'''
|
|
res = self.search(cursor, user, args, context=context, count=True)
|
|
if isinstance(res, list):
|
|
return len(res)
|
|
return res
|
|
|
|
def _order_calc(self, cursor, user, field, otype, context=None):
|
|
order_by = []
|
|
tables = []
|
|
field_name = None
|
|
table_name = None
|
|
link_field = None
|
|
clause = ''
|
|
|
|
if field in self._columns:
|
|
table_name = self._table
|
|
|
|
if self._columns[field]._classic_write:
|
|
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]._obj)
|
|
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, clause = obj._order_calc(cursor, user,
|
|
field_name, otype, context=context)
|
|
if '"' + table_name + '"' not in tables:
|
|
tables.append('"' + table_name + '"')
|
|
if clause:
|
|
clause += ' AND '
|
|
clause += ' %s.%s = %s.id' % (self._table, link_field,
|
|
table_name)
|
|
return order_by, tables, clause
|
|
|
|
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, clause = obj2._order_calc(cursor, user,
|
|
field_name, otype, context=context)
|
|
|
|
if '"' + table_name + '"' not in tables:
|
|
tables.append('"' + table_name + '"')
|
|
if clause:
|
|
clause += ' AND '
|
|
clause += ' %s.%s = %s.id' % (self._table, link_field,
|
|
table_name)
|
|
|
|
if '"' + table_name2 + '"' not in tables:
|
|
tables.append('"' + table_name2 + '"')
|
|
if clause:
|
|
clause += ' AND '
|
|
clause += ' %s.%s = %s.id' % (obj._table, link_field2,
|
|
table_name2)
|
|
return order_by, tables, clause
|
|
|
|
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, clause
|
|
|
|
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, clause = obj._order_calc(cursor, user, field,
|
|
otype, context=context)
|
|
if '"' + table_name + '"' not in tables:
|
|
tables.append('"' + table_name + '"')
|
|
if clause:
|
|
clause += ' AND '
|
|
clause += ' %s.%s = %s.id' % (self._table, link_field,
|
|
table_name)
|
|
return order_by, tables, clause
|
|
|
|
raise Exception('Error', 'Wrong field name (%s) in order!' \
|
|
% field)
|
|
|
|
def search(self, cursor, user, args, offset=0, limit=None, order=None,
|
|
context=None, count=False, query_string=False):
|
|
'''
|
|
Return a list of id that match the clause defined in args.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param args: a list of tuples or lists
|
|
lists are construct like this:
|
|
['operator', args, args, ...]
|
|
operator can be 'AND' or 'OR', if it is missing the
|
|
default value will be 'AND'
|
|
tuples are construct like this:
|
|
('field name', 'operator', value)
|
|
field name: is the name of a field of the object
|
|
or a relational field by using '.' as separator.
|
|
operator must be in OPERATORS
|
|
:param offset: an integer to specify the offset in the result
|
|
:param limit: an integer to limit the number of ids return
|
|
:param order: a list of tupe that are construct like this:
|
|
('field name', 'DESC|ASC')
|
|
it allow to specify the order of the ids in the return list
|
|
:param count: a boolean to return just the len of the list
|
|
:param query_string: a boolean to return a tuple with
|
|
the SQL query string and the arguments.
|
|
:return: a list of ids
|
|
'''
|
|
(qu1, qu2, tables, tables_args) = self._where_calc(cursor, user, args,
|
|
context=context)
|
|
|
|
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, clause = 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 clause:
|
|
if qu1:
|
|
qu1 += ' AND ' + clause
|
|
else:
|
|
qu1 = clause
|
|
|
|
order_by = ','.join(order_by)
|
|
|
|
limit_str = limit and (type(limit) in (float, int, long))\
|
|
and ' LIMIT %d' % limit or ''
|
|
offset_str = offset and (type(offset) in (float, int, long))\
|
|
and ' OFFSET %d' % offset or ''
|
|
|
|
|
|
# construct a clause for the rules :
|
|
domain1, domain2 = self.pool.get('ir.rule').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 name_get(self, cursor, user, ids, context=None):
|
|
'''
|
|
Return a list of tuple for each ids.
|
|
The tuple contains the id and the name of the record.
|
|
'''
|
|
if not ids:
|
|
return []
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
return [(r['id'], unicode(r[self._rec_name])) for r in self.read(cursor,
|
|
user, ids, [self._rec_name], context, load='_classic_write')]
|
|
|
|
def name_search(self, cursor, user, name='', args=None, operator='ilike',
|
|
context=None, limit=None):
|
|
'''
|
|
Return a list of ids where the name and the args clause matches.
|
|
args is a clause like in the function search.
|
|
operator is the operator used to compare the name.
|
|
limit can be used to limit the number of id.
|
|
'''
|
|
if args is None:
|
|
args = []
|
|
args = args[:]
|
|
if name:
|
|
args = ['AND', args, (self._rec_name, operator, name)]
|
|
ids = self.search(cursor, user, args, limit=limit, context=context)
|
|
res = self.name_get(cursor, user, ids, context=context)
|
|
return res
|
|
|
|
def copy(self, cursor, user, object_id, default=None, context=None):
|
|
'''
|
|
Duplicate the object_id record.
|
|
default can be a dict with field name as keys,
|
|
it will replace the value of the record.
|
|
'''
|
|
lang_obj = self.pool.get('ir.lang')
|
|
if default is None:
|
|
default = {}
|
|
if context is None:
|
|
context = {}
|
|
if 'state' not in default:
|
|
if 'state' in self._defaults:
|
|
default['state'] = self._defaults['state'](cursor, user,
|
|
context)
|
|
|
|
def convert_data(fields, data):
|
|
for field_name in fields:
|
|
ftype = fields[field_name]['type']
|
|
|
|
if field_name in (
|
|
'create_date',
|
|
'create_uid',
|
|
'write_date',
|
|
'write_uid',
|
|
):
|
|
del data[field_name]
|
|
|
|
if field_name in default:
|
|
data[field_name] = default[field_name]
|
|
elif ftype == 'function':
|
|
del data[field_name]
|
|
elif ftype == 'many2one':
|
|
try:
|
|
data[field_name] = data[field_name] and \
|
|
data[field_name][0]
|
|
except:
|
|
pass
|
|
elif ftype in ('one2many',):
|
|
res = []
|
|
rel = self.pool.get(fields[field_name]['relation'])
|
|
for rel_id in data[field_name] or []:
|
|
# the lines are first duplicated using the wrong (old)
|
|
# parent but then are reassigned to the correct one thanks
|
|
# to the ('add', ...)
|
|
res.append(('add', rel.copy(cursor, user, rel_id,
|
|
context=context)))
|
|
data[field_name] = res
|
|
elif ftype == 'many2many':
|
|
if data[field_name]:
|
|
data[field_name] = [('set', data[field_name])]
|
|
if 'id' in data:
|
|
del data['id']
|
|
for i in self._inherits:
|
|
if self._inherits[i] in data:
|
|
del data[self._inherits[i]]
|
|
|
|
data = self.read(cursor, user, object_id, context=context)
|
|
fields = self.fields_get(cursor, user)
|
|
convert_data(fields, data)
|
|
new_id = self.create(cursor, user, data, context=context)
|
|
|
|
fields_translate = {}
|
|
for field_name, field in fields.iteritems():
|
|
if field_name in self._columns and \
|
|
self._columns[field_name].translate:
|
|
fields_translate[field_name] = field
|
|
elif field_name in self._inherit_fields and \
|
|
self._inherit_fields[field_name][2].translate:
|
|
fields_translate[field_name] = field
|
|
|
|
if fields_translate:
|
|
lang_ids = lang_obj.search(cursor, user, [
|
|
('translatable', '=', True),
|
|
], context=context)
|
|
if lang_ids:
|
|
lang_ids += lang_obj.search(cursor, user, [
|
|
('code', '=', 'en_US'),
|
|
], context=context)
|
|
langs = lang_obj.browse(cursor, user, lang_ids, context=context)
|
|
for lang in langs:
|
|
ctx = context.copy()
|
|
ctx['language'] = lang.code
|
|
data = self.read(cursor, user, object_id,
|
|
fields_names=fields_translate.keys(), context=ctx)
|
|
convert_data(fields_translate, data)
|
|
self.write(cursor, user, new_id, data, context=ctx)
|
|
return new_id
|
|
|
|
def search_read(self, cursor, user, args, offset=0, limit=None, order=None,
|
|
context=None, fields_names=None, load='_classic_read'):
|
|
'''
|
|
Call search function and read in once.
|
|
Usefull for the client to reduce the number of calls.
|
|
'''
|
|
ids = self.search(cursor, user, args, offset=offset, limit=limit,
|
|
order=order, context=context)
|
|
if limit == 1:
|
|
ids = ids[0]
|
|
return self.read(cursor, user, ids, fields_names=fields_names,
|
|
context=context, load=load)
|
|
|
|
def check_recursion(self, cursor, user, ids, parent='parent'):
|
|
'''
|
|
Function that check if there is no recursion in the tree
|
|
composed with parent as parent field name.
|
|
'''
|
|
ids_parent = ids[:]
|
|
while len(ids_parent):
|
|
ids_parent2 = []
|
|
for i in range(0, len(ids_parent), cursor.IN_MAX):
|
|
sub_ids_parent = ids_parent[i:i + cursor.IN_MAX]
|
|
cursor.execute('SELECT distinct "' + parent + '" ' +
|
|
'FROM "' + self._table + '" ' +
|
|
'WHERE id IN ' \
|
|
'(' + ','.join(['%s' for x in sub_ids_parent]) + ')',
|
|
sub_ids_parent)
|
|
ids_parent2.extend(filter(None,
|
|
[x[0] for x in cursor.fetchall()]))
|
|
ids_parent = ids_parent2
|
|
for i in ids_parent:
|
|
if i in ids:
|
|
return False
|
|
return True
|
|
|
|
def default_sequence(self, cursor, user, context=None):
|
|
'''
|
|
Return the default value for sequence field.
|
|
'''
|
|
cursor.execute('SELECT MAX(sequence) ' \
|
|
'FROM "' + self._table + '"')
|
|
res = cursor.fetchone()
|
|
if res:
|
|
return res[0]
|
|
return 0
|
|
|
|
def _rebuild_tree(self, cursor, user, parent, parent_id, left):
|
|
'''
|
|
Rebuild left, right value for the tree.
|
|
'''
|
|
right = left + 1
|
|
|
|
if not parent_id:
|
|
cursor.execute('SELECT id FROM "' + self._table + '" ' \
|
|
'WHERE "' + parent + '" IS NULL')
|
|
else:
|
|
cursor.execute('SELECT id FROM "' + self._table + '" ' \
|
|
'WHERE "' + parent + '" = %s', (parent_id,))
|
|
child_ids = [x[0] for x in cursor.fetchall()]
|
|
|
|
for child_id in child_ids:
|
|
right = self._rebuild_tree(cursor, user, parent, child_id, right)
|
|
|
|
field = self._columns[parent]
|
|
|
|
if parent_id:
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + field.left + '" = %s, ' \
|
|
'"' + field.right + '" = %s ' \
|
|
'WHERE id = %s', (left, right, parent_id))
|
|
return right + 1
|
|
|
|
def _update_tree(self, cursor, user, object_id, field_name, left, right):
|
|
'''
|
|
Update left, right values for the tree.
|
|
Remarks: the value (right - left - 1) / 2 will not give
|
|
the number of children node
|
|
'''
|
|
cursor.execute('SELECT "' + left + '", "' + right + '" ' \
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE id = %s', (object_id,))
|
|
if not cursor.rowcount:
|
|
return
|
|
old_left, old_right = cursor.fetchone()
|
|
|
|
cursor.execute('SELECT "' + right + '" ' \
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE id IN (' \
|
|
'SELECT "' + field_name + '" FROM "' + self._table + '" ' \
|
|
'WHERE id = %s)', (object_id,))
|
|
if cursor.rowcount:
|
|
parent_right = cursor.fetchone()[0]
|
|
|
|
cursor.execute('SELECT count(1) FROM "' + self._table + '" ' \
|
|
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
|
|
(old_left, old_right))
|
|
child_number = cursor.fetchone()[0]
|
|
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" + ' \
|
|
+ str(2 * child_number) + ' ' \
|
|
'WHERE "' + left + '" >= %s ' \
|
|
'AND NOT ("' + left + '" >= %s AND ' \
|
|
'"' + right + '" <= %s)',
|
|
(parent_right, old_left, old_right))
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + right + '" = "' + right + '" + ' \
|
|
+ str(2 * child_number) + ' ' \
|
|
'WHERE "' + right + '" >= %s ' \
|
|
'AND NOT ("' + left + '" >= %s AND ' \
|
|
'"' + right + '" <= %s)',
|
|
(parent_right, old_left, old_right))
|
|
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" + ' \
|
|
+ str(parent_right - old_left) + ', ' \
|
|
'"' + right + '" = "' + right + '" + ' \
|
|
+ str(parent_right - (old_left or -1)) + ' ' \
|
|
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
|
|
(old_left, old_right))
|
|
else:
|
|
max_right = 0
|
|
cursor.execute('SELECT MAX("' + right + '") ' \
|
|
'FROM "' + self._table + '" ' \
|
|
'WHERE "' + field_name + '" IS NULL')
|
|
if cursor.rowcount:
|
|
max_right = cursor.fetchone()[0]
|
|
|
|
cursor.execute('UPDATE "' + self._table + '" ' \
|
|
'SET "' + left + '" = "' + left + '" + ' \
|
|
+ str(max_right + 1 - old_left) + ', ' \
|
|
'"' + right + '" = "' + right + '" + ' \
|
|
+ str(max_right + 1 - (old_left or -1)) + ' ' \
|
|
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
|
|
(old_left, old_right))
|