merge
This commit is contained in:
commit
7c9f5ffa89
10 files changed
+254
-270
No files matched your search
@@ -1,3 +1,5 @@
|
||||
* Remove the default order on MPTT
|
||||
* Add grouped attribute to data tag
|
||||
* Store selection tree state
|
||||
* Add order to Action Window
|
||||
* Add factor on number fields
|
||||
|
||||
@@ -110,8 +110,9 @@ Here is the list of the tags:
|
||||
* ``tryton``: The main tag of the xml
|
||||
|
||||
* ``data``: Define a set of data inside the file. It can have the
|
||||
attributes ``noupdate`` to prevent the framework to update the records
|
||||
and ``skiptest`` to prevent import of data when running tests.
|
||||
attributes ``noupdate`` to prevent the framework to update the records,
|
||||
``skiptest`` to prevent import of data when running tests and ``grouped``
|
||||
to create records at the end with a grouped call.
|
||||
|
||||
* ``record``: Create a record of the model defined by the attribute
|
||||
``model`` in the database. The ``id`` attribute can be used to refer to
|
||||
|
||||
+43
-19
@@ -8,6 +8,9 @@ import logging
|
||||
import traceback
|
||||
import sys
|
||||
import re
|
||||
from itertools import izip
|
||||
from collections import defaultdict
|
||||
|
||||
from trytond.version import VERSION
|
||||
from trytond.tools import safe_eval
|
||||
from trytond.transaction import Transaction
|
||||
@@ -384,6 +387,8 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
self.fs2db = Fs2bdAccessor(self.ModelData, pool)
|
||||
self.to_delete = self.populate_to_delete()
|
||||
self.noupdate = None
|
||||
self.grouped = None
|
||||
self.grouped_creations = defaultdict(dict)
|
||||
self.skip_data = False
|
||||
|
||||
# Tag handlders are used to delegate the processing
|
||||
@@ -430,6 +435,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
|
||||
elif name == "data":
|
||||
self.noupdate = bool(int(attributes.get("noupdate", '0')))
|
||||
self.grouped = bool(int(attributes.get('grouped', 0)))
|
||||
if self.pool.test and \
|
||||
bool(int(attributes.get("skiptest", '0'))):
|
||||
self.skip_data = True
|
||||
@@ -452,6 +458,11 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
|
||||
def endElement(self, name):
|
||||
|
||||
if name == 'data' and self.grouped:
|
||||
for model, values in self.grouped_creations.iteritems():
|
||||
self.create_records(model, values.values(), values.keys())
|
||||
self.grouped_creations.clear()
|
||||
|
||||
# Closing tag found, if we are in a delegation the handler
|
||||
# know what to do:
|
||||
if self.taghandler and not self.skip_data:
|
||||
@@ -675,30 +686,43 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
self.fs2db.reset_browsercord(module, Model.__name__, [record.id])
|
||||
|
||||
else:
|
||||
# this record is new, create it in the db:
|
||||
with Transaction().set_context(module=module, language='en_US'):
|
||||
record, = Model.create([values])
|
||||
if self.grouped:
|
||||
self.grouped_creations[model][fs_id] = values
|
||||
else:
|
||||
self.create_records(model, [values], [fs_id])
|
||||
|
||||
# re-read it: this ensure that we store the real value
|
||||
# in the model_data table:
|
||||
record = Model(record.id)
|
||||
def create_records(self, model, vlist, fs_ids):
|
||||
Model = self.pool.get(model)
|
||||
|
||||
with Transaction().set_context(module=self.module, language='en_US'):
|
||||
records = Model.create(vlist)
|
||||
|
||||
mdata_values = []
|
||||
for record, values, fs_id in izip(records, vlist, fs_ids):
|
||||
for key in values:
|
||||
values[key] = self._clean_value(key, record)
|
||||
|
||||
mdata, = self.ModelData.create([{
|
||||
'fs_id': fs_id,
|
||||
'model': model,
|
||||
'module': module,
|
||||
'db_id': record.id,
|
||||
'values': str(values),
|
||||
'noupdate': self.noupdate,
|
||||
}])
|
||||
mdata_values.append({
|
||||
'fs_id': fs_id,
|
||||
'model': model,
|
||||
'module': self.module,
|
||||
'db_id': record.id,
|
||||
'values': str(values),
|
||||
'noupdate': self.noupdate,
|
||||
})
|
||||
|
||||
# update fs2db:
|
||||
self.fs2db.set(module, fs_id, {
|
||||
"db_id": record.id, "model": model,
|
||||
"id": mdata.id, "values": str(values)})
|
||||
self.fs2db.reset_browsercord(module, model, [record.id])
|
||||
models_data = self.ModelData.create(mdata_values)
|
||||
|
||||
for record, values, fs_id, mdata in izip(
|
||||
records, vlist, fs_ids, models_data):
|
||||
self.fs2db.set(self.module, fs_id, {
|
||||
'db_id': record.id,
|
||||
'model': model,
|
||||
'id': mdata.id,
|
||||
'values': str(values),
|
||||
})
|
||||
self.fs2db.reset_browsercord(self.module, model,
|
||||
[r.id for r in records])
|
||||
|
||||
|
||||
def post_import(pool, module, to_delete):
|
||||
|
||||
+10
-1
@@ -204,26 +204,35 @@ class Lang(ModelSQL, ModelView):
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
pool = Pool()
|
||||
Translation = pool.get('ir.translation')
|
||||
# Clear cache
|
||||
cls._lang_cache.clear()
|
||||
return super(Lang, cls).create(vlist)
|
||||
languages = super(Lang, cls).create(vlist)
|
||||
Translation._get_language_cache.clear()
|
||||
return languages
|
||||
|
||||
@classmethod
|
||||
def write(cls, langs, vals):
|
||||
pool = Pool()
|
||||
Translation = pool.get('ir.translation')
|
||||
# Clear cache
|
||||
cls._lang_cache.clear()
|
||||
super(Lang, cls).write(langs, vals)
|
||||
Translation._get_language_cache.clear()
|
||||
|
||||
@classmethod
|
||||
def delete(cls, langs):
|
||||
pool = Pool()
|
||||
Config = pool.get('ir.configuration')
|
||||
Translation = pool.get('ir.translation')
|
||||
for lang in langs:
|
||||
if lang.code == Config.get_language():
|
||||
cls.raise_user_error('delete_default')
|
||||
# Clear cache
|
||||
cls._lang_cache.clear()
|
||||
super(Lang, cls).delete(langs)
|
||||
Translation._get_language_cache.clear()
|
||||
|
||||
@staticmethod
|
||||
def _group(lang, s, monetary=None):
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import datetime
|
||||
import re
|
||||
import heapq
|
||||
from collections import defaultdict
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..report import Report
|
||||
@@ -368,6 +369,10 @@ class ModelAccess(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def get_access(cls, models):
|
||||
'Return access for models'
|
||||
# root user above constraint
|
||||
if Transaction().user == 0:
|
||||
return defaultdict(lambda: defaultdict(lambda: True))
|
||||
|
||||
pool = Pool()
|
||||
Model = pool.get('ir.model')
|
||||
UserGroup = pool.get('res.user-res.group')
|
||||
@@ -503,6 +508,11 @@ class ModelFieldAccess(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def get_access(cls, models):
|
||||
'Return fields access for models'
|
||||
# root user above constraint
|
||||
if Transaction().user == 0:
|
||||
return defaultdict(lambda: defaultdict(
|
||||
lambda: defaultdict(lambda: True)))
|
||||
|
||||
pool = Pool()
|
||||
Model = pool.get('ir.model')
|
||||
ModelField = pool.get('ir.model.field')
|
||||
|
||||
+94
-54
@@ -16,6 +16,7 @@ except ImportError:
|
||||
from md5 import md5
|
||||
from functools import reduce
|
||||
from lxml import etree
|
||||
from itertools import izip
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..wizard import Wizard, StateView, StateTransition, StateAction, \
|
||||
@@ -71,6 +72,7 @@ class Translation(ModelSQL, ModelView):
|
||||
searcher='search_model')
|
||||
_translation_cache = Cache('ir.translation', size_limit=10240,
|
||||
context=False)
|
||||
_get_language_cache = Cache('ir.translation')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
@@ -153,11 +155,15 @@ class Translation(ModelSQL, ModelView):
|
||||
|
||||
@classmethod
|
||||
def get_language(cls):
|
||||
result = cls._get_language_cache.get(None)
|
||||
if result is not None:
|
||||
return result
|
||||
pool = Pool()
|
||||
Lang = pool.get('ir.lang')
|
||||
langs = Lang.search([])
|
||||
res = [(lang.code, lang.name) for lang in langs]
|
||||
return res
|
||||
result = [(lang.code, lang.name) for lang in langs]
|
||||
cls._get_language_cache.set(None, result)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_src_md5(cls, src):
|
||||
@@ -249,12 +255,21 @@ class Translation(ModelSQL, ModelView):
|
||||
return translations
|
||||
|
||||
@classmethod
|
||||
def set_ids(cls, name, ttype, lang, ids, value):
|
||||
def set_ids(cls, name, ttype, lang, ids, values):
|
||||
"Set translation for each id"
|
||||
pool = Pool()
|
||||
ModelFields = pool.get('ir.model.field')
|
||||
Model = pool.get('ir.model')
|
||||
Config = pool.get('ir.configuration')
|
||||
cursor = Transaction().cursor
|
||||
in_max = cursor.IN_MAX
|
||||
|
||||
if len(ids) > in_max:
|
||||
for i in range(0, len(ids), in_max):
|
||||
sub_ids = ids[i:i + in_max]
|
||||
sub_values = values[i:i + in_max]
|
||||
cls.set_ids(name, ttype, lang, sub_ids, sub_values)
|
||||
return
|
||||
|
||||
model_name, field_name = name.split(',')
|
||||
if model_name in ('ir.model.field', 'ir.model'):
|
||||
@@ -269,74 +284,99 @@ class Translation(ModelSQL, ModelView):
|
||||
ttype = 'model'
|
||||
with Transaction().set_context(language='en_US'):
|
||||
records = Model.browse(ids)
|
||||
for record in records:
|
||||
|
||||
def get_name(record):
|
||||
if ttype in ('field', 'help'):
|
||||
name = record.model.model + ',' + record.name
|
||||
return record.model.model + ',' + record.name
|
||||
else:
|
||||
name = record.model + ',' + field_name
|
||||
translation2 = cls.search([
|
||||
('lang', '=', lang),
|
||||
('type', '=', ttype),
|
||||
('name', '=', name),
|
||||
])
|
||||
with Transaction().set_user(0):
|
||||
if not translation2:
|
||||
cls.create([{
|
||||
'name': name,
|
||||
'lang': lang,
|
||||
'type': ttype,
|
||||
'src': getattr(record, field_name),
|
||||
'value': value,
|
||||
'fuzzy': False,
|
||||
}])
|
||||
else:
|
||||
cls.write(translation2, {
|
||||
return record.model + ',' + field_name
|
||||
|
||||
translations = {}
|
||||
for translation in cls.search([
|
||||
('lang', '=', lang),
|
||||
('type', '=', ttype),
|
||||
('name', 'in', [get_name(r) for r in records]),
|
||||
]):
|
||||
translations[translation.name] = translation
|
||||
|
||||
to_create = []
|
||||
for record, value in izip(records, values):
|
||||
translation = translations.get(get_name(record))
|
||||
if not translation:
|
||||
to_create.append({
|
||||
'name': name,
|
||||
'lang': lang,
|
||||
'type': ttype,
|
||||
'src': getattr(record, field_name),
|
||||
'value': value,
|
||||
'fuzzy': False,
|
||||
})
|
||||
return len(ids)
|
||||
else:
|
||||
with Transaction().set_user(0):
|
||||
cls.write([translation], {
|
||||
'src': getattr(record, field_name),
|
||||
'value': value,
|
||||
'fuzzy': False,
|
||||
})
|
||||
if to_create:
|
||||
with Transaction().set_user(0):
|
||||
cls.create(to_create)
|
||||
return
|
||||
|
||||
Model = pool.get(model_name)
|
||||
with Transaction().set_context(language=Config.get_language()):
|
||||
records = Model.browse(ids)
|
||||
for record in records:
|
||||
translation2 = cls.search([
|
||||
('lang', '=', lang),
|
||||
('type', '=', ttype),
|
||||
('name', '=', name),
|
||||
('res_id', '=', record.id),
|
||||
])
|
||||
with Transaction().set_user(0):
|
||||
if not translation2:
|
||||
cls.create([{
|
||||
'name': name,
|
||||
'lang': lang,
|
||||
'type': ttype,
|
||||
'res_id': record.id,
|
||||
'value': value,
|
||||
'src': getattr(record, field_name),
|
||||
'fuzzy': False,
|
||||
}])
|
||||
else:
|
||||
cls.write(translation2, {
|
||||
|
||||
translations = {}
|
||||
for translation in cls.search([
|
||||
('lang', '=', lang),
|
||||
('type', '=', ttype),
|
||||
('name', '=', name),
|
||||
('res_id', 'in', ids),
|
||||
]):
|
||||
translations[translation.res_id] = translation
|
||||
|
||||
other_translations = {}
|
||||
if (lang == Config.get_language()
|
||||
and Transaction().context.get('fuzzy_translation', True)):
|
||||
for translation in cls.search([
|
||||
('lang', '!=', lang),
|
||||
('type', '=', ttype),
|
||||
('name', '=', name),
|
||||
('res_id', 'in', ids),
|
||||
]):
|
||||
other_translations.setdefault(translation.res_id, []
|
||||
).append(translation)
|
||||
|
||||
to_create = []
|
||||
for record, value in izip(records, values):
|
||||
translation = translations.get(record.id)
|
||||
if not translation:
|
||||
to_create.append({
|
||||
'name': name,
|
||||
'lang': lang,
|
||||
'type': ttype,
|
||||
'res_id': record.id,
|
||||
'value': value,
|
||||
'src': getattr(record, field_name),
|
||||
'fuzzy': False,
|
||||
})
|
||||
if (lang == Config.get_language()
|
||||
and Transaction().context.get('fuzzy_translation',
|
||||
True)):
|
||||
other_langs = cls.search([
|
||||
('lang', '!=', lang),
|
||||
('type', '=', ttype),
|
||||
('name', '=', name),
|
||||
('res_id', '=', record.id),
|
||||
])
|
||||
else:
|
||||
with Transaction().set_user(0):
|
||||
cls.write([translation], {
|
||||
'value': value,
|
||||
'src': getattr(record, field_name),
|
||||
'fuzzy': False,
|
||||
})
|
||||
other_langs = other_translations.get(record.id)
|
||||
if other_langs:
|
||||
cls.write(other_langs, {
|
||||
'src': getattr(record, field_name),
|
||||
'fuzzy': True,
|
||||
})
|
||||
return len(ids)
|
||||
if to_create:
|
||||
with Transaction().set_user(0):
|
||||
cls.create(to_create)
|
||||
|
||||
@classmethod
|
||||
def delete_ids(cls, model, ttype, ids):
|
||||
|
||||
+77
-134
@@ -223,6 +223,7 @@ class ModelSQL(ModelStorage):
|
||||
super(ModelSQL, cls).create(vlist)
|
||||
cursor = Transaction().cursor
|
||||
pool = Pool()
|
||||
Translation = pool.get('ir.translation')
|
||||
|
||||
if cls.table_query():
|
||||
return False
|
||||
@@ -349,16 +350,21 @@ class ModelSQL(ModelStorage):
|
||||
create_records = Transaction().create_records.setdefault(cls.__name__,
|
||||
set()).update(new_ids)
|
||||
|
||||
translation_values = {}
|
||||
for values, new_id in izip(vlist, new_ids):
|
||||
for fname, value in values.iteritems():
|
||||
field = cls._fields[fname]
|
||||
if getattr(field, 'translate', False):
|
||||
pool.get('ir.translation').set_ids(
|
||||
cls.__name__ + ',' + fname, 'model',
|
||||
Transaction().language, [new_id], value)
|
||||
translation_values.setdefault(cls.__name__ + ',' + fname,
|
||||
{})[new_id] = value
|
||||
if hasattr(field, 'set'):
|
||||
field.set([new_id], cls, fname, value)
|
||||
|
||||
if translation_values:
|
||||
for name, translations in translation_values.iteritems():
|
||||
Translation.set_ids(name, 'model', Transaction().language,
|
||||
translations.keys(), translations.values())
|
||||
|
||||
if cls._history:
|
||||
columns = ['"' + str(x) + '"' for x in cls._fields
|
||||
if not hasattr(cls._fields[x], 'set')]
|
||||
@@ -375,17 +381,8 @@ class ModelSQL(ModelStorage):
|
||||
records = cls.browse(new_ids)
|
||||
cls._validate(records)
|
||||
|
||||
# Check for Modified Preorder Tree Traversal
|
||||
for k in cls._fields:
|
||||
field = cls._fields[k]
|
||||
if (isinstance(field, fields.Many2One)
|
||||
and field.model_name == cls.__name__
|
||||
and field.left and field.right):
|
||||
if len(new_ids) == 1:
|
||||
cls._update_tree(new_ids[0], k, field.left, field.right)
|
||||
else:
|
||||
with Transaction().set_user(0):
|
||||
cls._rebuild_tree(k, False, 0)
|
||||
field_names = cls._fields.keys()
|
||||
cls._update_mptt(field_names, [new_ids] * len(field_names))
|
||||
|
||||
cls.trigger_create(records)
|
||||
return records
|
||||
@@ -801,8 +798,8 @@ class ModelSQL(ModelStorage):
|
||||
for field in direct:
|
||||
if getattr(cls._fields[field], 'translate', False):
|
||||
Translation.set_ids(
|
||||
cls.__name__ + ',' + field, 'model',
|
||||
Transaction().language, ids, values[field])
|
||||
cls.__name__ + ',' + field, 'model',
|
||||
Transaction().language, ids, [values[field]] * len(ids))
|
||||
|
||||
# call the 'set' method of fields
|
||||
for field in upd_todo:
|
||||
@@ -820,21 +817,8 @@ class ModelSQL(ModelStorage):
|
||||
|
||||
cls._validate(records)
|
||||
|
||||
# Check for Modified Preorder Tree Traversal
|
||||
for k in cls._fields:
|
||||
field = cls._fields[k]
|
||||
if isinstance(field, fields.Many2One) \
|
||||
and field.model_name == cls.__name__ \
|
||||
and field.left and field.right:
|
||||
if field.left in values or field.right in values:
|
||||
raise Exception('ValidateError',
|
||||
'You can not update fields: "%s", "%s"' %
|
||||
(field.left, field.right))
|
||||
if len(ids) == 1:
|
||||
cls._update_tree(ids[0], k, field.left, field.right)
|
||||
else:
|
||||
with Transaction().set_user(0):
|
||||
cls._rebuild_tree(k, False, 0)
|
||||
field_names = cls._fields.keys()
|
||||
cls._update_mptt(field_names, [ids] * len(field_names), values)
|
||||
|
||||
cls.trigger_write(trigger_eligibles)
|
||||
|
||||
@@ -1011,13 +995,7 @@ class ModelSQL(ModelStorage):
|
||||
'(id, write_uid, write_date) VALUES (%s, %s, %s)',
|
||||
(obj_id, Transaction().user, datetime.datetime.now()))
|
||||
|
||||
for k in tree_ids.keys():
|
||||
field = cls._fields[k]
|
||||
if len(tree_ids[k]) == 1:
|
||||
cls._update_tree(tree_ids[k][0], k, field.left, field.right)
|
||||
else:
|
||||
with Transaction().set_user(0):
|
||||
cls._rebuild_tree(k, False, 0)
|
||||
cls._update_mptt(tree_ids.keys(), tree_ids.values())
|
||||
|
||||
@classmethod
|
||||
def search(cls, domain, offset=0, limit=None, order=None, count=False,
|
||||
@@ -1888,6 +1866,31 @@ class ModelSQL(ModelStorage):
|
||||
raise Exception('Error', 'Wrong field name (%s) for %s in order!' %
|
||||
(field, cls.__name__))
|
||||
|
||||
@classmethod
|
||||
def _update_mptt(cls, field_names, list_ids, values=None):
|
||||
cursor = Transaction().cursor
|
||||
count = None
|
||||
for field_name, ids in zip(field_names, list_ids):
|
||||
field = cls._fields[field_name]
|
||||
if (isinstance(field, fields.Many2One)
|
||||
and field.model_name == cls.__name__
|
||||
and field.left and field.right):
|
||||
if (values is not None
|
||||
and (field.left in values or field.right in values)):
|
||||
raise Exception('ValidateError',
|
||||
'You can not update fields: "%s", "%s"' %
|
||||
(field.left, field.right))
|
||||
if count is None:
|
||||
cursor.execute('SELECT COUNT(1) FROM "' + cls._table + '"')
|
||||
count, = cursor.fetchone()
|
||||
if len(ids) < count / 4:
|
||||
for id_ in ids:
|
||||
cls._update_tree(id_, field_name,
|
||||
field.left, field.right)
|
||||
else:
|
||||
with Transaction().set_user(0):
|
||||
cls._rebuild_tree(field_name, None, 0)
|
||||
|
||||
@classmethod
|
||||
def _rebuild_tree(cls, parent, parent_id, left):
|
||||
'''
|
||||
@@ -1896,10 +1899,9 @@ class ModelSQL(ModelStorage):
|
||||
cursor = Transaction().cursor
|
||||
right = left + 1
|
||||
|
||||
with Transaction().set_user(0):
|
||||
childs = cls.search([
|
||||
(parent, '=', parent_id),
|
||||
])
|
||||
childs = cls.search([
|
||||
(parent, '=', parent_id),
|
||||
], order=[])
|
||||
|
||||
for child in childs:
|
||||
right = cls._rebuild_tree(parent, child.id, right)
|
||||
@@ -1914,35 +1916,36 @@ class ModelSQL(ModelStorage):
|
||||
return right + 1
|
||||
|
||||
@classmethod
|
||||
def _update_tree(cls, object_id, field_name, left, right):
|
||||
def _update_tree(cls, record_id, field_name, left, right):
|
||||
'''
|
||||
Update left, right values for the tree.
|
||||
Remarks:
|
||||
- the value (right - left - 1) / 2 will not give
|
||||
the number of children node
|
||||
- the order of the tree respects the default _order
|
||||
'''
|
||||
cursor = Transaction().cursor
|
||||
cursor.execute('SELECT "' + left + '", "' + right + '" '
|
||||
cursor.execute('SELECT "' + left + '", "' + right + '", '
|
||||
'"' + field_name + '" '
|
||||
'FROM "' + cls._table + '" '
|
||||
'WHERE id = %s', (object_id,))
|
||||
'WHERE id = %s', (record_id,))
|
||||
fetchone = cursor.fetchone()
|
||||
if not fetchone:
|
||||
return
|
||||
old_left, old_right = fetchone
|
||||
if old_left == old_right:
|
||||
old_left, old_right, parent_id = fetchone
|
||||
if old_left == old_right == 0:
|
||||
cursor.execute('SELECT MAX("' + right + '") '
|
||||
'FROM "' + cls._table + '" '
|
||||
'WHERE "' + field_name + '" IS NULL')
|
||||
old_left, = cursor.fetchone()
|
||||
old_left += 1
|
||||
old_right = old_left + 1
|
||||
cursor.execute('UPDATE "' + cls._table + '" '
|
||||
'SET "' + right + '" = "' + right + '" + 1 '
|
||||
'WHERE id = %s', (object_id,))
|
||||
old_right += 1
|
||||
'SET "' + left + '" = %s, "' + right + '" = %s '
|
||||
'WHERE id = %s', (old_left, old_right, record_id))
|
||||
size = old_right - old_left + 1
|
||||
|
||||
parent_right = 1
|
||||
|
||||
cursor.execute('SELECT "' + field_name + '" '
|
||||
'FROM "' + cls._table + '" '
|
||||
'WHERE id = %s', (object_id,))
|
||||
parent_id = cursor.fetchone()[0] or False
|
||||
|
||||
if parent_id:
|
||||
cursor.execute('SELECT "' + right + '" '
|
||||
'FROM "' + cls._table + '" '
|
||||
@@ -1956,85 +1959,25 @@ class ModelSQL(ModelStorage):
|
||||
if fetchone:
|
||||
parent_right = fetchone[0] + 1
|
||||
|
||||
cursor.execute('SELECT id FROM "' + cls._table + '" '
|
||||
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
|
||||
(old_left, old_right))
|
||||
child_ids = [x[0] for x in cursor.fetchall()]
|
||||
|
||||
if len(child_ids) > cursor.IN_MAX:
|
||||
with Transaction().set_user(0):
|
||||
return cls._rebuild_tree(field_name, False, 0)
|
||||
|
||||
red_child_sql, red_child_ids = reduce_ids('id', child_ids)
|
||||
# ids for left update
|
||||
cursor.execute('SELECT id FROM "' + cls._table + '" '
|
||||
'WHERE "' + left + '" >= %s '
|
||||
'AND NOT ' + red_child_sql,
|
||||
[parent_right] + red_child_ids)
|
||||
left_ids = [x[0] for x in cursor.fetchall()]
|
||||
|
||||
# ids for right update
|
||||
cursor.execute('SELECT id FROM "' + cls._table + '" '
|
||||
'WHERE "' + right + '" >= %s '
|
||||
'AND NOT ' + red_child_sql,
|
||||
[parent_right] + red_child_ids)
|
||||
right_ids = [x[0] for x in cursor.fetchall()]
|
||||
|
||||
if left_ids:
|
||||
for i in range(0, len(left_ids), cursor.IN_MAX):
|
||||
sub_ids = left_ids[i:i + cursor.IN_MAX]
|
||||
red_sub_sql, red_sub_ids = reduce_ids('id', sub_ids)
|
||||
cursor.execute('UPDATE "' + cls._table + '" '
|
||||
'SET "' + left + '" = "' + left + '" + '
|
||||
+ str(old_right - old_left + 1) + ' '
|
||||
'WHERE ' + red_sub_sql, red_sub_ids)
|
||||
if right_ids:
|
||||
for i in range(0, len(right_ids), cursor.IN_MAX):
|
||||
sub_ids = right_ids[i:i + cursor.IN_MAX]
|
||||
red_sub_sql, red_sub_ids = reduce_ids('id', sub_ids)
|
||||
cursor.execute('UPDATE "' + cls._table + '" '
|
||||
'SET "' + right + '" = "' + right + '" + '
|
||||
+ str(old_right - old_left + 1) + ' '
|
||||
'WHERE ' + red_sub_sql, red_sub_ids)
|
||||
|
||||
cursor.execute('UPDATE "' + cls._table + '" '
|
||||
'SET "' + left + '" = "' + left + '" + '
|
||||
+ str(parent_right - old_left) + ', '
|
||||
'"' + right + '" = "' + right + '" + '
|
||||
+ str(parent_right - old_left) + ' '
|
||||
'WHERE ' + red_child_sql, red_child_ids)
|
||||
|
||||
# Use root user to by-pass rules
|
||||
with contextlib.nested(Transaction().set_user(0),
|
||||
Transaction().set_context(active_test=False)):
|
||||
brother_ids = map(int, cls.search([
|
||||
(field_name, '=', parent_id),
|
||||
]))
|
||||
if brother_ids[-1] != object_id:
|
||||
next_id = brother_ids[brother_ids.index(object_id) + 1]
|
||||
cursor.execute('SELECT "' + left + '" '
|
||||
'FROM "' + cls._table + '" '
|
||||
'WHERE id = %s', (next_id,))
|
||||
next_left = cursor.fetchone()[0]
|
||||
cursor.execute('SELECT "' + left + '" '
|
||||
'FROM "' + cls._table + '" '
|
||||
'WHERE id = %s', (object_id,))
|
||||
current_left = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute('UPDATE "' + cls._table + '" '
|
||||
'SET "' + left + '" = "' + left + '" + '
|
||||
+ str(old_right - old_left + 1) + ', '
|
||||
'"' + right + '" = "' + right + '" + '
|
||||
+ str(old_right - old_left + 1) + ' '
|
||||
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
|
||||
(next_left, current_left))
|
||||
|
||||
cursor.execute('UPDATE "' + cls._table + '" '
|
||||
'SET "' + left + '" = "' + left + '" - '
|
||||
+ str(current_left - next_left) + ', '
|
||||
'"' + right + '" = "' + right + '" - '
|
||||
+ str(current_left - next_left) + ' '
|
||||
'WHERE ' + red_child_sql, red_child_ids)
|
||||
'SET "' + left + '" = "' + left + '" + %s '
|
||||
'WHERE "' + left + '" >= %s', (size, parent_right))
|
||||
cursor.execute('UPDATE "' + cls._table + '" '
|
||||
'SET "' + right + '" = "' + right + '" + %s '
|
||||
'WHERE "' + right + '" >= %s', (size, parent_right))
|
||||
if old_left < parent_right:
|
||||
params = (parent_right - old_left,
|
||||
parent_right - old_left,
|
||||
old_left, old_right)
|
||||
else:
|
||||
params = (parent_right - old_left - size,
|
||||
parent_right - old_left - size,
|
||||
old_left + size, old_right + size)
|
||||
cursor.execute('UPDATE "' + cls._table + '" '
|
||||
'SET "' + left + '" = "' + left + '" + %s, '
|
||||
'"' + right + '" = "' + right + '" + %s '
|
||||
'WHERE "' + left + '" >= %s '
|
||||
'AND "' + right + '" <= %s', params)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, records):
|
||||
|
||||
@@ -12,9 +12,6 @@ class MPTT(ModelSQL, ModelView):
|
||||
'Modified Preorder Tree Traversal'
|
||||
__name__ = 'test.mptt'
|
||||
name = fields.Char('Name', required=True)
|
||||
sequence = fields.Integer('Sequence',
|
||||
order_field='(%(table)s.sequence IS NULL) %(order)s, '
|
||||
'%(table)s.sequence %(order)s')
|
||||
parent = fields.Many2One('test.mptt', "Parent", select=True,
|
||||
left="left", right="right")
|
||||
left = fields.Integer('Left', required=True, select=True)
|
||||
@@ -22,11 +19,6 @@ class MPTT(ModelSQL, ModelView):
|
||||
childs = fields.One2Many('test.mptt', 'parent', 'Children')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(MPTT, cls).__setup__()
|
||||
cls._order.insert(0, ('sequence', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def validate(cls, record):
|
||||
super(MPTT, cls).validate(record)
|
||||
|
||||
+13
-52
@@ -2,6 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
import sys
|
||||
import unittest
|
||||
from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
|
||||
install_module
|
||||
@@ -17,12 +18,10 @@ class MPTTTestCase(unittest.TestCase):
|
||||
install_module('test')
|
||||
self.mptt = POOL.get('test.mptt')
|
||||
|
||||
def CheckTree(self, parent_id=False, left=0, right=0):
|
||||
def CheckTree(self, parent_id=False, left=-1, right=sys.maxint):
|
||||
childs = self.mptt.search([
|
||||
('parent', '=', parent_id),
|
||||
])
|
||||
child_index = dict((x.id, i) for i, x in enumerate(childs))
|
||||
childs.sort(key=lambda x: child_index[x.id])
|
||||
], order=[('left', 'ASC')])
|
||||
for child in childs:
|
||||
assert child.left > left, \
|
||||
'%s: left %d <= parent left %d' % \
|
||||
@@ -30,21 +29,21 @@ class MPTTTestCase(unittest.TestCase):
|
||||
assert child.left < child.right, \
|
||||
'%s: left %d >= right %d' % \
|
||||
(child, child.left, child.right)
|
||||
assert right == 0 or child.right < right, \
|
||||
assert child.right < right, \
|
||||
'%s: right %d >= parent right %d' % \
|
||||
(child, child.right, right)
|
||||
self.CheckTree(child.id, left=child.left,
|
||||
right=child.right)
|
||||
next_left = 0
|
||||
next_left = -1
|
||||
for child in childs:
|
||||
assert child.left > next_left, \
|
||||
'%s: left %d <= next left %d' % \
|
||||
(child, child.left, next_left)
|
||||
next_left = child.right
|
||||
childs.reverse()
|
||||
previous_right = 0
|
||||
previous_right = sys.maxint
|
||||
for child in childs:
|
||||
assert previous_right == 0 or child.right < previous_right, \
|
||||
assert child.right < previous_right, \
|
||||
'%s: right %d >= previous right %d' \
|
||||
% (child, child.right, previous_right)
|
||||
previous_right = child.left
|
||||
@@ -65,62 +64,28 @@ class MPTTTestCase(unittest.TestCase):
|
||||
for record in records:
|
||||
self.ReParent(record)
|
||||
|
||||
def ReOrder(self, parent=None):
|
||||
records = self.mptt.search([
|
||||
('parent', '=', parent),
|
||||
])
|
||||
if not records:
|
||||
return
|
||||
i = len(records)
|
||||
for record in records:
|
||||
record.sequence = i
|
||||
record.save()
|
||||
i -= 1
|
||||
i = 0
|
||||
for record in records:
|
||||
record.sequence = i
|
||||
record.save()
|
||||
i += 1
|
||||
for record in records:
|
||||
self.ReOrder(record)
|
||||
|
||||
records = self.mptt.search([])
|
||||
self.mptt.write(records, {
|
||||
'sequence': 0,
|
||||
})
|
||||
|
||||
def test0010create(self):
|
||||
'''
|
||||
Create tree.
|
||||
'''
|
||||
with Transaction().start(DB_NAME, USER,
|
||||
context=CONTEXT) as transaction:
|
||||
new_records = [None]
|
||||
new_records = [None, None, None]
|
||||
for j in range(3):
|
||||
parent_records = new_records
|
||||
new_records = []
|
||||
k = 0
|
||||
for parent_record in parent_records:
|
||||
for i in range(3):
|
||||
new_records += self.mptt.create([{
|
||||
'name': 'Test %d %d %d' % (j, k, i),
|
||||
'parent': (parent_record.id
|
||||
if parent_record else None),
|
||||
}])
|
||||
new_records += self.mptt.create([{
|
||||
'name': 'Test %d %d %d' % (j, k, i),
|
||||
'parent': (parent_record.id
|
||||
if parent_record else None),
|
||||
} for i in range(3)])
|
||||
k += 1
|
||||
self.CheckTree()
|
||||
|
||||
transaction.cursor.commit()
|
||||
|
||||
def test0020reorder(self):
|
||||
'''
|
||||
Re-order.
|
||||
'''
|
||||
with Transaction().start(DB_NAME, USER,
|
||||
context=CONTEXT) as transaction:
|
||||
self.ReOrder()
|
||||
transaction.cursor.rollback()
|
||||
|
||||
def test0030reparent(self):
|
||||
'''
|
||||
Re-parent.
|
||||
@@ -141,8 +106,6 @@ class MPTTTestCase(unittest.TestCase):
|
||||
self.CheckTree()
|
||||
self.ReParent()
|
||||
self.CheckTree()
|
||||
self.ReOrder()
|
||||
self.CheckTree()
|
||||
|
||||
transaction.cursor.rollback()
|
||||
|
||||
@@ -164,8 +127,6 @@ class MPTTTestCase(unittest.TestCase):
|
||||
})
|
||||
self.ReParent()
|
||||
self.CheckTree()
|
||||
self.ReOrder()
|
||||
self.CheckTree()
|
||||
|
||||
transaction.cursor.rollback()
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ attlist.tryton &= empty
|
||||
data = element data { attlist.data, (record | menuitem)* }
|
||||
attlist.data &=
|
||||
[ a:defaultValue = "0" ] attribute noupdate { "0" | "1" }?
|
||||
attlist.data &=
|
||||
[ a:defaultValue = "0" ] attribute grouped { "0" | "1" }?
|
||||
record = element record { attlist.record, field* }
|
||||
attlist.record &= attribute model { text }
|
||||
attlist.record &= attribute id { text }
|
||||
|
||||
Reference in new issue
Block a user