diff --git a/CHANGELOG b/CHANGELOG index 0d1bbbfd48..24a735fe15 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Use dualmethod on ModelStorage.save * New API for on_change: instance changes * Add restore_history_before on ModelSQL * Remove img_{width,height} form attributes diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst index 972a09529b..c54a3ef14b 100644 --- a/doc/ref/models/models.rst +++ b/doc/ref/models/models.rst @@ -230,7 +230,7 @@ Static methods: Return the default value for :attr:`create_date`. -CLass methods: +Class methods: .. classmethod:: ModelStorage.create(vlist) @@ -336,6 +336,12 @@ CLass methods: method must be overridden to add validation and must raise an exception if validation fails. +Dual methods: + +.. classmethod:: ModelStorage.save(records) + + Save the modification made on the records. + Instance methods: .. method:: ModelStorage.get_rec_name(name) @@ -343,10 +349,6 @@ Instance methods: Getter for the :class:`trytond.model.fields.Function` field :attr:`rec_name`. -.. method:: ModelStorage.save() - - Save the modification made on the record instance. - ======== ModelSQL ======== diff --git a/trytond/ir/rule.py b/trytond/ir/rule.py index 84915140a4..c77ac0cf27 100644 --- a/trytond/ir/rule.py +++ b/trytond/ir/rule.py @@ -1,6 +1,6 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. -from ..model import ModelView, ModelSQL, fields +from ..model import ModelView, ModelSQL, fields, dualmethod from ..tools import safe_eval from ..transaction import Transaction from ..cache import Cache diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index b0c8a54d23..2a9b2c5289 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -780,7 +780,7 @@ class Translation(ModelSQL, ModelView): (model_data.db_id, model_data.noupdate) translations = set() - to_create = [] + to_save = [] pofile = polib.pofile(po_path) id2translation = {} @@ -872,26 +872,15 @@ class Translation(ModelSQL, ModelView): continue if not ids: - to_create.append(translation._save_values) - else: - to_write = [] + to_save.append(translation) + elif not noupdate: for translation_id in ids: old_translation = id2translation[translation_id] - if (old_translation.value != translation.value - or old_translation.fuzzy != - translation.fuzzy): - to_write.append(old_translation) - with Transaction().set_context(module=module): - if to_write and not noupdate: - cls.write(to_write, { - 'value': translation.value, - 'fuzzy': translation.fuzzy, - }) - translations |= set(cls.browse(ids)) + old_translation.value = translation.value + old_translation.fuzzy = translation.fuzzy + to_save.append(old_translation) - if to_create: - with Transaction().set_context(module=module): - translations |= set(cls.create(to_create)) + translations |= set(cls.save(to_save)) if translations: all_translations = set(cls.search([ diff --git a/trytond/model/__init__.py b/trytond/model/__init__.py index dae657d372..8dc7666807 100644 --- a/trytond/model/__init__.py +++ b/trytond/model/__init__.py @@ -9,6 +9,7 @@ from .workflow import Workflow from .dictschema import DictSchemaMixin from .match import MatchMixin from .union import UnionMixin +from .descriptors import dualmethod __all__ = ['Model', 'ModelView', 'ModelStorage', 'ModelSingleton', 'ModelSQL', - 'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin'] + 'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin', 'dualmethod'] diff --git a/trytond/model/descriptors.py b/trytond/model/descriptors.py new file mode 100644 index 0000000000..eace007e32 --- /dev/null +++ b/trytond/model/descriptors.py @@ -0,0 +1,34 @@ +# 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 functools + + +class dualmethod(object): + """Descriptor implementing combination of class and instance method + + When called on an instance, the class is passed as the first argument and a + list with the instance as the second. + When called on a class, the class itsefl is passed as the first argument. + + >>> class Example(object): + ... @dualmethod + ... def method(cls, instances): + ... print len(instances) + ... + >>> Example.method([Example()]) + 1 + >>> Example().method() + 1 + """ + def __init__(self, func): + self.func = func + + def __get__(self, instance, owner): + + @functools.wraps(self.func) + def newfunc(*args, **kwargs): + if instance: + return self.func(owner, [instance], *args, **kwargs) + else: + return self.func(owner, *args, **kwargs) + return newfunc diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index 4e145fee72..cea94a487f 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -27,6 +27,7 @@ from trytond.cache import LRUDict, freeze from trytond import backend from trytond.rpc import RPC from .modelview import ModelView +from .descriptors import dualmethod __all__ = ['ModelStorage'] @@ -1391,27 +1392,48 @@ class ModelStorage(Model): values[fname] = value return values - def save(self): - save_values = self._save_values - values = self._values - self._values = None - if save_values or self.id < 0: - try: - with Transaction().set_cursor(self._cursor), \ - Transaction().set_user(self._user), \ - Transaction().set_context(self._context): - if self.id < 0: - self._ids.remove(self.id) - try: - self.id = self.create([save_values])[0].id - finally: - self._ids.append(self.id) - else: - self.write([self], save_values) - except: - self._values = values - raise - self._init_values = None + @dualmethod + def save(cls, records): + if not records: + return + values = {} + save_values = {} + to_create = [] + to_write = [] + cursor = records[0]._cursor + user = records[0]._user + context = records[0]._context + for record in records: + assert cursor == record._cursor + assert user == record._user + assert context == record._context + save_values[record] = record._save_values + values[record] = record._values + record._values = None + if record.id is None or record.id < 0: + to_create.append(record) + elif save_values[record]: + to_write.append(record) + transaction = Transaction() + try: + with transaction.set_cursor(cursor), \ + transaction.set_user(user), \ + transaction.set_context(context): + if to_create: + news = cls.create([save_values[r] for r in to_create]) + for record, new in izip(to_create, news): + record._ids.remove(record.id) + record.id = new.id + record._ids.append(record.id) + if to_write: + cls.write(*sum( + (([r], save_values[r]) for r in to_write), ())) + except: + for record in records: + record._values = values[record] + raise + for record in records: + record._init_values = None class EvalEnvironment(dict): diff --git a/trytond/tests/test_descriptors.py b/trytond/tests/test_descriptors.py new file mode 100644 index 0000000000..0bcca2bce5 --- /dev/null +++ b/trytond/tests/test_descriptors.py @@ -0,0 +1,12 @@ +# 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 unittest +import doctest + +from trytond.model import descriptors + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(descriptors)) + return suite