From fa4708c3e9268b2b2297e678203ed82687b4703f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 23 Oct 2014 20:19:55 +0200 Subject: [PATCH] Manage some methods as class and instance method issue4242 review9771002 --- CHANGELOG | 2 + proteus/__init__.py | 133 ++++++++++++++++++++++++++++-------- proteus/tests/test_model.py | 9 +++ 3 files changed, 115 insertions(+), 29 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f46bf35ea6..48edaea5d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Manage reload, save, delete, duplicate and click as class and instance method + Version 3.4.0 - 2014-10-20 * Bug fixes (see mercurial logs for details) * New configuration syntax diff --git a/proteus/__init__.py b/proteus/__init__.py index 190276aeca..812e0ebb9b 100644 --- a/proteus/__init__.py +++ b/proteus/__init__.py @@ -16,6 +16,7 @@ except ImportError: sys.modules['cdecimal'] = decimal import threading import datetime +import functools from decimal import Decimal from types import NoneType @@ -73,6 +74,37 @@ class _EvalEnvironment(dict): return item in self.parent._fields +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 itself 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 + + class FieldDescriptor(object): default = None @@ -660,6 +692,11 @@ class Model(object): 'The unique ID' return self.__id + @id.setter + def id(self, value): + assert self.__id < 0 + self.__id = int(value) + @classmethod def find(cls, condition=None, offset=0, limit=None, order=None): 'Return records matching condition' @@ -669,46 +706,84 @@ class Model(object): cls._config.context) return [cls(id) for id in ids] - def reload(self): + @dualmethod + def reload(cls, records): 'Reload record' - self._values = {} - self._changed = set() + for record in records: + record._values = {} + record._changed = set() - def save(self): - 'Save the record' - context = self._config.context - if self.id < 0: - values = self._get_values() - self.__id, = self._proxy.create([values], context) - else: - if not self._changed: - return - values = self._get_values(fields=self._changed) - context['_timestamp'] = self._get_timestamp() - self._proxy.write([self.id], values, context) - self.reload() + @dualmethod + def save(cls, records): + 'Save records' + if not records: + return + proxy = records[0]._proxy + config = records[0]._config + context = config.context + create, write = [], [] + for record in records: + assert proxy == record._proxy + assert config == record._config + if record.id < 0: + create.append(record) + elif record._changed: + write.append(record) - def delete(self): - 'Delete the record' - if self.id > 0: - context = self._config.context - context['_timestamp'] = self._get_timestamp() - return self._proxy.delete([self.id], context) - self.reload() - return True + if create: + values = [r._get_values() for r in create] + ids = proxy.create(values, context) + for record, id_ in zip(create, ids): + record.id = id_ + if write: + values = [] + context['_timestamp'] = {} + for record in write: + values.append([record.id]) + values.append(record._get_values(fields=record._changed)) + context['_timestamp'].update(record._get_timestamp()) + values.append(context) + proxy.write(*values) + for record in records: + record.reload() - @classmethod + @dualmethod + def delete(cls, records): + 'Delete records' + if not records: + return + proxy = records[0]._proxy + config = records[0]._config + context = config.context + context['_timestamp'] = {} + delete = [] + for record in records: + assert proxy == record._proxy + assert config == record._config + if record.id > 0: + context['_timestamp'].update(record._get_timestamp()) + delete.append(record.id) + if delete: + proxy.delete(delete, context) + cls.reload(records) + + @dualmethod def duplicate(cls, records, default=None): 'Duplicate the record' ids = cls._proxy.copy([r.id for r in records], default, cls._config.context) return [cls(id) for id in ids] - def click(self, button): + @dualmethod + def click(cls, records, button): 'Click on button' - self.save() - self.reload() # Force reload because save doesn't always - return getattr(self._proxy, button)([self.id], self._config.context) + if not records: + return + cls.save(records) + cls.reload(records) # Force reload because save doesn't always + proxy = records[0]._proxy + context = records[0]._config.context + return getattr(proxy, button)([r.id for r in records], context) def _get_values(self, fields=None): 'Return dictionary values' diff --git a/proteus/tests/test_model.py b/proteus/tests/test_model.py index f6aa197ab4..fe4ac7d605 100644 --- a/proteus/tests/test_model.py +++ b/proteus/tests/test_model.py @@ -126,6 +126,15 @@ class TestModel(ProteusTestCase): self.assertEqual(test2.name, 'Test 2') self.assertEqual(test2.login, 'test2') + test.signature = 'classmethod save' + test2.name = 'Test 2 classmethod' + test3 = User(name='Test 3', login='test3') + User.save([test, test2, test3]) + self.assertEqual(test.signature, 'classmethod save') + self.assertEqual(test2.name, 'Test 2 classmethod') + self.assert_(test3.id > 0) + self.assertEqual(test3.name, 'Test 3') + def test_save_many2one(self): User = Model.get('res.user') test = User()