diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG
index ae1571f89d..15d9b0611a 100644
--- a/trytond/CHANGELOG
+++ b/trytond/CHANGELOG
@@ -1,3 +1,4 @@
+* Support getting many sequences at once
* Add sequence_nextval function to Database
* Retrieve groups of actions without checking access (#13506)
* Check read access of report records (#13505)
diff --git a/trytond/doc/ref/backend.rst b/trytond/doc/ref/backend.rst
index a8044e8be0..abc839394b 100644
--- a/trytond/doc/ref/backend.rst
+++ b/trytond/doc/ref/backend.rst
@@ -192,6 +192,10 @@ Database
Advances the named sequence and returns its next value.
+.. method:: Database.sequence_nextvals(connection, name, n)
+
+ Advances the named sequence n times and yields the next values.
+
.. method:: Database.sequence_next_number(connection, name)
Return the next number fo the named sequence using the ``connection``.
diff --git a/trytond/trytond/backend/database.py b/trytond/trytond/backend/database.py
index 9eb65ac093..623f5742ef 100644
--- a/trytond/trytond/backend/database.py
+++ b/trytond/trytond/backend/database.py
@@ -161,6 +161,11 @@ class DatabaseInterface(object):
return
raise NotImplementedError
+ def sequence_nextvals(self, connection, name, n):
+ if not self.has_sequence():
+ return
+ raise NotImplementedError
+
def sequence_next_number(self, connection, name):
if not self.has_sequence():
return
diff --git a/trytond/trytond/backend/postgresql/database.py b/trytond/trytond/backend/postgresql/database.py
index 90cd16dcf9..fe816e228c 100644
--- a/trytond/trytond/backend/postgresql/database.py
+++ b/trytond/trytond/backend/postgresql/database.py
@@ -738,6 +738,13 @@ class Database(DatabaseInterface):
cursor.execute('SELECT NEXTVAL(%s)', (name,))
return cursor.fetchone()[0]
+ def sequence_nextvals(self, connection, name, n):
+ cursor = connection.cursor()
+ cursor.execute(
+ 'SELECT NEXTVAL(%s) FROM generate_series(1, %s)', (name, n))
+ for val, in cursor:
+ yield val
+
def sequence_next_number(self, connection, name):
cursor = connection.cursor()
version = self.get_version(connection)
diff --git a/trytond/trytond/ir/message.xml b/trytond/trytond/ir/message.xml
index 0d816f54fb..02a8d5427a 100644
--- a/trytond/trytond/ir/message.xml
+++ b/trytond/trytond/ir/message.xml
@@ -285,9 +285,6 @@ this repository contains the full copyright notices and license terms. -->
The condition "%(condition)s" is not a valid PYSON expression for button rule "%(rule)s".
-
- Missing sequence.
-
Invalid prefix "%(affix)s" for sequence "%(sequence)s".
diff --git a/trytond/trytond/ir/sequence.py b/trytond/trytond/ir/sequence.py
index a36eb7e5ce..d8a01dc4d2 100644
--- a/trytond/trytond/ir/sequence.py
+++ b/trytond/trytond/ir/sequence.py
@@ -380,37 +380,40 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
return int(
(time.time() - self.timestamp_offset) / self.timestamp_rounding)
- @classmethod
- def _get_sequence(cls, sequence):
- if sequence.type == 'incremental':
- if sql_sequence and not cls._strict:
+ def _get_many(self, n=1):
+ if self.type == 'incremental':
+ if sql_sequence and not self._strict:
transaction = Transaction()
- number_next = transaction.database.sequence_nextval(
- transaction.connection, sequence._sql_sequence_name)
+ numbers = transaction.database.sequence_nextvals(
+ transaction.connection, self._sql_sequence_name, n)
# clean cache
transaction.counter += 1
- sequence._local_cache.pop(sequence.id, None)
+ self._local_cache.pop(self.id, None)
else:
- # Pre-fetch number_next
- number_next = sequence.number_next_internal
-
- cls.write([sequence], {
- 'number_next_internal': (number_next
- + sequence.number_increment),
- })
- return f'{number_next:0>{sequence.padding}d}'
- elif sequence.type in ('decimal timestamp', 'hexadecimal timestamp'):
- timestamp = sequence.last_timestamp
- while timestamp == sequence.last_timestamp:
- timestamp = sequence._timestamp()
- cls.write([sequence], {
- 'last_timestamp': timestamp,
- })
- if sequence.type == 'decimal timestamp':
- return f'{timestamp:d}'
+ # pre-fetch number_next
+ start = self.number_next_internal
+ end = start + n * self.number_increment
+ numbers = list(range(start, end, self.number_increment))
+ self.number_next_internal = end
+ self.save()
+ for number in numbers:
+ yield f'{number:0>{self.padding}d}'
+ elif self.type in {'decimal timestamp', 'hexadecimal timestamp'}:
+ timestamps = []
+ last_timestamp = timestamp = self.last_timestamp
+ for _ in range(n):
+ while timestamp == last_timestamp:
+ timestamp = self._timestamp()
+ timestamps.append(timestamp)
+ last_timestamp = timestamp
+ self.last_timestamp = last_timestamp
+ self.save()
+ if self.type == 'decimal timestamp':
+ for timestamp in timestamps:
+ yield f'{timestamp:d}'
else:
- return hex(timestamp)[2:].upper()
- return ''
+ for timestamp in timestamps:
+ yield hex(timestamp)[2:].upper()
@fields.depends('type', 'padding', 'number_next', methods=['_timestamp'])
def _get_preview_sequence(self):
@@ -426,23 +429,20 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
return hex(timestamp)[2:].upper()
return ''
+ def get(self):
+ "Returns the next sequence value"
+ return next(self.get_many(n=1))
+
@without_check_access
- def get(self, _lock=False):
- '''
- Return the next sequence value
- '''
- cls = self.__class__
- try:
- sequence = cls(self.id)
- except TypeError:
- raise MissingError(gettext('ir.msg_sequence_missing'))
+ def get_many(self, n=1, _lock=False):
+ "Yield the n next sequence values"
+ assert n > 0, n
if _lock:
self.lock()
- return '%s%s%s' % (
- cls._process(sequence.prefix),
- cls._get_sequence(sequence),
- cls._process(sequence.suffix),
- )
+ for value in self._get_many(n=n):
+ prefix = self._process(self.prefix)
+ suffix = self._process(self.suffix)
+ yield f'{prefix}{value}{suffix}'
@fields.depends('prefix', 'suffix', methods=['_get_preview_sequence'])
def on_change_with_preview(self, name=None):
@@ -459,5 +459,5 @@ class SequenceStrict(Sequence):
_table = None # Needed to reset Sequence._table
_strict = True
- def get(self, _lock=True):
- return super().get(_lock=True)
+ def get_many(self, n=1, _lock=True):
+ yield from super().get_many(n=n, _lock=True)
diff --git a/trytond/trytond/tests/test_ir.py b/trytond/trytond/tests/test_ir.py
index b7fb6ac011..c507e6e903 100644
--- a/trytond/trytond/tests/test_ir.py
+++ b/trytond/trytond/tests/test_ir.py
@@ -168,6 +168,45 @@ class IrTestCase(ModuleTestCase):
sequence.prefix = '${date_K}'
sequence.save()
+ @with_transaction()
+ def test_sequence_get(self):
+ "Test get sequences"
+ pool = Pool()
+ Sequence = pool.get('ir.sequence')
+ SequenceType = pool.get('ir.sequence.type')
+ try:
+ Group = pool.get('res.group')
+ groups = Group.search([])
+ except KeyError:
+ groups = []
+
+ sequence_type = SequenceType(name='Test', groups=groups)
+ sequence_type.save()
+ sequence = Sequence(name='Test Sequence', sequence_type=sequence_type)
+ sequence.save()
+
+ self.assertEqual(sequence.get(), '1')
+
+ @with_transaction()
+ def test_sequence_get_many(self):
+ "Test get many sequences"
+ pool = Pool()
+ Sequence = pool.get('ir.sequence')
+ SequenceType = pool.get('ir.sequence.type')
+ try:
+ Group = pool.get('res.group')
+ groups = Group.search([])
+ except KeyError:
+ groups = []
+
+ sequence_type = SequenceType(name='Test', groups=groups)
+ sequence_type.save()
+ sequence = Sequence(name='Test Sequence', sequence_type=sequence_type)
+ sequence.save()
+
+ self.assertEqual(
+ list(sequence.get_many(10)), list(map(str, range(1, 11))))
+
@with_transaction()
def test_global_search(self):
'Test Global Search'