From 59cee94c69c9319c1cf26d9431ee3448fbce8bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 2 Sep 2013 11:31:56 +0200 Subject: [PATCH 1/5] Skip model and field access for root review1049002 --- trytond/ir/model.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/trytond/ir/model.py b/trytond/ir/model.py index c517010bdb..ccf1b5e09b 100644 --- a/trytond/ir/model.py +++ b/trytond/ir/model.py @@ -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') From e136a7c266fffeb17d791f785cf5bfad46260a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 2 Sep 2013 17:34:46 +0200 Subject: [PATCH 2/5] Add new XML attribute "grouped" on data tag review1005002 --- CHANGELOG | 1 + doc/topics/modules/index.rst | 5 +-- trytond/convert.py | 62 +++++++++++++++++++++++++----------- trytond/tryton.rnc | 2 ++ 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d7ed712ed2..c9b127fcd4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add grouped attribute to data tag * Store selection tree state * Add order to Action Window * Add factor on number fields diff --git a/doc/topics/modules/index.rst b/doc/topics/modules/index.rst index a52cde30de..fe1a7e4443 100644 --- a/doc/topics/modules/index.rst +++ b/doc/topics/modules/index.rst @@ -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 diff --git a/trytond/convert.py b/trytond/convert.py index 852a252571..b3eea7771e 100644 --- a/trytond/convert.py +++ b/trytond/convert.py @@ -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): diff --git a/trytond/tryton.rnc b/trytond/tryton.rnc index cb4cff5c88..5eef7ca1ca 100644 --- a/trytond/tryton.rnc +++ b/trytond/tryton.rnc @@ -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 } From f55fbdaa33eb77a43ecc0484d334232c07245b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 2 Sep 2013 17:37:21 +0200 Subject: [PATCH 3/5] Add cache to Translation.get_language review1005002 --- trytond/ir/lang.py | 11 ++++++++++- trytond/ir/translation.py | 9 +++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py index 9a7728815c..90297515ed 100644 --- a/trytond/ir/lang.py +++ b/trytond/ir/lang.py @@ -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): diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index 5b92df8e30..d94d9da008 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -71,6 +71,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 +154,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): From a7e7b3b91ed4aeb998aaf2dc59485fce3555a721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 2 Sep 2013 17:38:11 +0200 Subject: [PATCH 4/5] Change Translation.set_ids to use a list of values review1005002 --- trytond/ir/translation.py | 139 ++++++++++++++++++++++++-------------- trytond/model/modelsql.py | 16 +++-- 2 files changed, 98 insertions(+), 57 deletions(-) diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index d94d9da008..a754bb62af 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -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, \ @@ -254,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'): @@ -274,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): diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index b71333dbe3..572b8ab297 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -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')] @@ -801,8 +807,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: From 021037057536f2919fb3ddfcddc05678b289f123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 3 Sep 2013 10:26:23 +0200 Subject: [PATCH 5/5] Improve MPTT - Remove the order on MPTT - Make room with queries using left/right instead of ids - Call _update_tree multiple times for small list instead of rebuild the all tree - Improve test for with many roots - Factorize the test to update MPTT issue1892 review1007002 --- CHANGELOG | 1 + trytond/model/modelsql.py | 195 +++++++++++++------------------------ trytond/test/mptt.py | 8 -- trytond/tests/test_mptt.py | 65 +++---------- 4 files changed, 80 insertions(+), 189 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c9b127fcd4..635c70f179 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Remove the default order on MPTT * Add grouped attribute to data tag * Store selection tree state * Add order to Action Window diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index 572b8ab297..1040916bcd 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -381,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 @@ -826,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) @@ -1017,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, @@ -1894,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): ''' @@ -1902,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) @@ -1920,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 + '" ' @@ -1962,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): diff --git a/trytond/test/mptt.py b/trytond/test/mptt.py index f4b67baab2..12e02bd2d4 100644 --- a/trytond/test/mptt.py +++ b/trytond/test/mptt.py @@ -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) diff --git a/trytond/tests/test_mptt.py b/trytond/tests/test_mptt.py index 62554b6e9c..4b2da8f370 100644 --- a/trytond/tests/test_mptt.py +++ b/trytond/tests/test_mptt.py @@ -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()