changeset 0ac9a6f5748b in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=0ac9a6f5748b
description:
Allow to lock records for update
issue8012
review45901002
diffstat:
CHANGELOG | 1 +
doc/ref/models/models.rst | 6 ++++++
trytond/model/modelsql.py | 20 +++++++++++++++++++-
trytond/tests/modelsql.py | 6 ++++++
trytond/tests/test_modelsql.py | 20 ++++++++++++++++++++
5 files changed, 52 insertions(+), 1 deletions(-)
diffs (119 lines):
diff -r e681112facef -r 0ac9a6f5748b CHANGELOG
--- a/CHANGELOG Wed Jan 30 09:36:21 2019 +0100
+++ b/CHANGELOG Wed Jan 30 09:45:59 2019 +0100
@@ -1,3 +1,4 @@
+* Allows to lock records for update
* Remove _nocache on Transaction
* Make Cache transactional
* Allow to search on key's value of Dict
diff -r e681112facef -r 0ac9a6f5748b doc/ref/models/models.rst
--- a/doc/ref/models/models.rst Wed Jan 30 09:36:21 2019 +0100
+++ b/doc/ref/models/models.rst Wed Jan 30 09:45:59 2019 +0100
@@ -463,6 +463,12 @@
},
}
+Dual methods:
+
+.. classmethod:: ModelSQL.lock(records)
+
+ Take a lock for update on the records.
+
Constraint
==========
diff -r e681112facef -r 0ac9a6f5748b trytond/model/modelsql.py
--- a/trytond/model/modelsql.py Wed Jan 30 09:36:21 2019 +0100
+++ b/trytond/model/modelsql.py Wed Jan 30 09:45:59 2019 +0100
@@ -6,7 +6,7 @@
from functools import wraps
from sql import (Table, Column, Literal, Desc, Asc, Expression, Null,
- NullsFirst, NullsLast)
+ NullsFirst, NullsLast, For)
from sql.functions import CurrentTimestamp, Extract
from sql.conditionals import Coalesce
from sql.operators import Or, And, Operator, Equal
@@ -26,6 +26,7 @@
from .modelstorage import (cache_size, is_leaf,
ValidationError, RequiredValidationError, AccessError)
+from .descriptors import dualmethod
class ForeignKeyError(ValidationError):
@@ -1552,6 +1553,23 @@
if cursor.fetchone():
raise SQLConstraintError(gettext(error))
+ @dualmethod
+ def lock(cls, records):
+ transaction = Transaction()
+ database = transaction.database
+ connection = transaction.connection
+ table = cls.__table__()
+
+ if database.has_select_for():
+ for sub_records in grouped_slice(records):
+ where = reduce_ids(table.id, sub_records)
+ query = table.select(
+ Literal(1), where=where, for_=For('UPDATE', nowait=True))
+ with connection.cursor() as cursor:
+ cursor.execute(*query)
+ else:
+ database.lock(connection, cls._table)
+
def convert_from(table, tables):
# Don't nested joins as SQLite doesn't support
diff -r e681112facef -r 0ac9a6f5748b trytond/tests/modelsql.py
--- a/trytond/tests/modelsql.py Wed Jan 30 09:36:21 2019 +0100
+++ b/trytond/tests/modelsql.py Wed Jan 30 09:45:59 2019 +0100
@@ -122,6 +122,11 @@
]
+class ModelLock(ModelSQL):
+ 'Model to test lock'
+ __name__ = 'test.modelsql.lock'
+
+
def register(module):
Pool.register(
ModelSQLRead,
@@ -136,4 +141,5 @@
ModelCheck,
ModelUnique,
ModelExclude,
+ ModelLock,
module=module, type_='model')
diff -r e681112facef -r 0ac9a6f5748b trytond/tests/test_modelsql.py
--- a/trytond/tests/test_modelsql.py Wed Jan 30 09:36:21 2019 +0100
+++ b/trytond/tests/test_modelsql.py Wed Jan 30 09:45:59 2019 +0100
@@ -475,6 +475,26 @@
with self.assertRaises(SQLConstraintError):
Model.create([{'value': 42}, {'value': 42}])
+ @unittest.skipIf(backend.name() == 'sqlite',
+ 'SQLite does not have lock at table level but on file')
+ @with_transaction()
+ def test_lock(self):
+ "Test lock"
+ pool = Pool()
+ Model = pool.get('test.modelsql.lock')
+ DatabaseOperationalError = backend.get('DatabaseOperationalError')
+ transaction = Transaction()
+ record_id = Model.create([{}])[0].id
+ transaction.commit()
+
+ with transaction.new_transaction():
+ record = Model(record_id)
+ record.lock()
+ with transaction.new_transaction():
+ record = Model(record_id)
+ with self.assertRaises(DatabaseOperationalError):
+ record.lock()
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(ModelSQLTestCase)