Store directly sequence type to sequence

issue10122
review351381002
This commit is contained in:
Cédric Krier
2021-03-11 23:00:10 +01:00
parent 2925c971b5
commit 3aec470d5b
14 changed files with 105 additions and 109 deletions
+2
View File
@@ -1,3 +1,5 @@
* Use instance method to get next sequence value
* Store the sequence type directly on sequence
* Allow sending email with record's attachments
* Add which records to use for actions
* Add parent to group to inherit accesses
+3
View File
@@ -264,6 +264,9 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.message" id="msg_sequence_last_timestamp_future">
<field name="text">The "Last Timestamp" cannot be in the future for sequence "%s".</field>
</record>
<record model="ir.message" id="msg_sequence_change_sequence_type">
<field name="text">You cannot change the sequence type of a sequence instead create a new sequence.</field>
</record>
<record model="ir.message" id="msg_language_invalid_grouping">
<field name="text">Invalid grouping "%(grouping)s" for language "%(language)s".</field>
+55 -35
View File
@@ -9,7 +9,7 @@ from trytond import backend
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.model import ModelView, ModelSQL, DeactivableMixin, fields, Check
from trytond.model.exceptions import ValidationError
from trytond.model.exceptions import ValidationError, AccessError
from trytond.pool import Pool
from trytond.pyson import Eval, And
from trytond.transaction import Transaction
@@ -34,7 +34,15 @@ class SequenceType(ModelSQL, ModelView):
__name__ = 'ir.sequence.type'
name = fields.Char('Sequence Name', required=True, translate=True)
code = fields.Char('Sequence Code', required=True)
@classmethod
def __register__(cls, module):
super().__register__(module)
table_h = cls.__table_handler__(module)
# Migration from 5.8: remove code
# We keep the column until ir.sequence has been migrated
table_h.not_null_action('code', action='remove')
class Sequence(DeactivableMixin, ModelSQL, ModelView):
@@ -43,10 +51,13 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
_strict = False
name = fields.Char('Sequence Name', required=True, translate=True)
code = fields.Selection('code_get', 'Sequence Code', required=True,
sequence_type = fields.Many2One(
'ir.sequence.type', "Sequence Type",
required=True, ondelete='RESTRICT',
states={
'readonly': Eval('context', {}).contains('code'),
})
'readonly': Eval('id', -1) >= 0,
},
depends=['id'])
prefix = fields.Char('Prefix')
suffix = fields.Char('Suffix')
type = fields.Selection([
@@ -100,6 +111,29 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
'Timestamp rounding should be greater than 0'),
]
@classmethod
def __register__(cls, module):
pool = Pool()
SequenceType = pool.get('ir.sequence.type')
cursor = Transaction().connection.cursor()
table = cls.__table__()
sequence_type = SequenceType.__table__()
super().__register__(module)
table_h = cls.__table_handler__(module)
# Migration from 5.8: replace code by sequence_type
if table_h.column_exist('code'):
cursor.execute(*table.update(
[table.sequence_type],
sequence_type.select(
sequence_type.id,
where=sequence_type.code == table.code)))
table_h.drop_column('code')
sequence_type_h = SequenceType.__table_handler__(module)
sequence_type_h.drop_column('code')
@staticmethod
def default_type():
return 'incremental'
@@ -128,10 +162,6 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
def default_last_timestamp():
return 0
@staticmethod
def default_code():
return Transaction().context.get('code')
def get_number_next(self, name):
if self.type != 'incremental':
return
@@ -171,10 +201,17 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
return sequences
@classmethod
def write(cls, sequences, values, *args):
super(Sequence, cls).write(sequences, values, *args)
def write(cls, *args):
transaction = Transaction()
if (transaction.user != 0
and transaction.context.get('_check_access')):
for values in args[1::2]:
if 'sequence_type' in values:
raise AccessError(gettext(
'ir.msg_sequence_change_sequence_type'))
super().write(*args)
if sql_sequence and not cls._strict:
actions = iter((sequences, values) + args)
actions = iter(args)
for sequences, values in zip(actions, actions):
for sequence in sequences:
sequence.update_sql_sequence(values.get('number_next'))
@@ -186,13 +223,6 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
sequence.delete_sql_sequence()
return super(Sequence, cls).delete(sequences)
@classmethod
def code_get(cls):
pool = Pool()
SequenceType = pool.get('ir.sequence.type')
sequence_types = SequenceType.search([])
return [(x.code, x.name) for x in sequence_types]
@classmethod
def validate(cls, sequences):
super(Sequence, cls).validate(sequences)
@@ -323,21 +353,16 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
return hex(timestamp)[2:].upper()
return ''
@classmethod
def get_id(cls, domain, _lock=False):
def get(self, _lock=False):
'''
Return sequence value for the domain
Return the next sequence value
'''
if isinstance(domain, cls):
domain = domain.id
if isinstance(domain, int):
domain = [('id', '=', domain)]
cls = self.__class__
# bypass rules on sequences
with Transaction().set_context(user=False, _check_access=False):
with Transaction().set_user(0):
try:
sequence, = cls.search(domain, limit=1)
sequence = cls(self.id)
except TypeError:
raise MissingError(gettext('ir.msg_sequence_missing'))
if _lock:
@@ -360,10 +385,6 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
cls._process(sequence.suffix, date=date),
)
@classmethod
def get(cls, code):
return cls.get_id([('code', '=', code)])
class SequenceStrict(Sequence):
"Sequence Strict"
@@ -371,6 +392,5 @@ class SequenceStrict(Sequence):
_table = None # Needed to reset Sequence._table
_strict = True
@classmethod
def get_id(cls, clause):
return super(SequenceStrict, cls).get_id(clause, _lock=True)
def get(self, _lock=True):
return super().get(_lock=True)
+2 -2
View File
@@ -4,8 +4,8 @@ this repository contains the full copyright notices and license terms. -->
<form>
<label name="name"/>
<field name="name" colspan="3"/>
<label name="code"/>
<field name="code"/>
<label name="sequence_type"/>
<field name="sequence_type" widget="selection"/>
<label name="active"/>
<field name="active"/>
<label name="prefix"/>
+1 -1
View File
@@ -3,6 +3,6 @@
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="name" expand="2"/>
<field name="code" expand="1"/>
<field name="sequence_type" expand="1"/>
<field name="type"/>
</tree>
-2
View File
@@ -4,6 +4,4 @@ this repository contains the full copyright notices and license terms. -->
<form>
<label name="name"/>
<field name="name"/>
<label name="code"/>
<field name="code" readonly="1"/>
</form>
-1
View File
@@ -3,5 +3,4 @@
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="name" expand="2"/>
<field name="code" expand="1"/>
</tree>
-2
View File
@@ -36,8 +36,6 @@ def register():
ir.Lang,
ir.SequenceType,
ir.SequenceTypeGroup,
ir.Sequence,
ir.SequenceStrict,
ir.ModuleConfigWizardItem,
ir.Export,
ir.Export_Group,
-38
View File
@@ -224,44 +224,6 @@ class SequenceTypeGroup(ModelSQL):
Rule._domain_get_cache.clear()
class Sequence(metaclass=PoolMeta):
__name__ = 'ir.sequence'
groups = fields.Function(fields.Many2Many('res.group', None, None,
'User Groups'), 'get_groups', searcher='search_groups')
@classmethod
def get_groups(cls, sequences, name):
SequenceType = Pool().get('ir.sequence.type')
code2seq = {}
for sequence in sequences:
code2seq.setdefault(sequence.code, []).append(sequence.id)
sequence_types = SequenceType.search([
('code', 'in', list(code2seq.keys())),
])
groups = {}
for sequence_type in sequence_types:
seq_ids = code2seq[sequence_type.code]
for seq_id in seq_ids:
groups.setdefault(seq_id, []).append(sequence_type.id)
return groups
@staticmethod
def search_groups(name, clause):
SequenceType = Pool().get('ir.sequence.type')
seq_types = SequenceType.search([clause], order=[])
codes = set(st.code for st in seq_types)
return [('code', 'in', list(codes))]
class SequenceStrict(Sequence):
# This empty class declaration is needed to inherit the groups
# field
__name__ = 'ir.sequence.strict'
class ModuleConfigWizardItem(metaclass=PoolMeta):
__name__ = 'ir.module.config_wizard.item'
+2 -2
View File
@@ -778,7 +778,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_sequence">
<field name="domain"
eval="[('groups', 'in', Eval('groups', []))]"
eval="[('sequence_type.groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_sequence"/>
</record>
@@ -791,7 +791,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_sequence_strict">
<field name="domain"
eval="[('groups', 'in', Eval('groups', []))]"
eval="[('sequence_type.groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_sequence_strict"/>
</record>
+1 -3
View File
@@ -2,9 +2,7 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath
expr="/form/field[@name='code']"
position="after">
<xpath expr="/form" position="inside">
<field name="groups" colspan="4"/>
</xpath>
</data>
-1
View File
@@ -6,7 +6,6 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.sequence.type" id="sequence_type_test">
<field name="name">Test</field>
<field name="code">test</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_test_group_admin">
+5 -7
View File
@@ -31,20 +31,18 @@ class IrTestCase(ModuleTestCase):
except KeyError:
groups = []
sequence_type = SequenceType(name='Test', code='test', groups=groups)
sequence_type = SequenceType(name='Test', groups=groups)
sequence_type.save()
sequence = Sequence(name='Test Sequence', code='test')
sequence = Sequence(name='Test Sequence', sequence_type=sequence_type)
sequence.save()
self.assertEqual(Sequence.get_id(sequence.id), '1')
self.assertEqual(sequence.get(), '1')
today = Date.today()
sequence.prefix = '${year}'
sequence.save()
self.assertEqual(Sequence.get_id(sequence.id),
'%s2' % str(today.year))
self.assertEqual(sequence.get(), '%s2' % str(today.year))
next_year = today + relativedelta(years=1)
with Transaction().set_context(date=next_year):
self.assertEqual(Sequence.get_id(sequence.id),
'%s3' % str(next_year.year))
self.assertEqual(sequence.get(), '%s3' % str(next_year.year))
@with_transaction()
def test_global_search(self):
+34 -15
View File
@@ -24,49 +24,59 @@ class SequenceTestCase(unittest.TestCase):
@with_transaction()
def test_incremental(self):
'Test incremental'
pool = Pool()
Sequence = self.get_model()
SequenceType = pool.get('ir.sequence.type')
sequence_type, = SequenceType.search([
('name', '=', "Test"),
], limit=1)
sequence, = Sequence.create([{
'name': 'Test incremental',
'code': 'test',
'sequence_type': sequence_type.id,
'prefix': '',
'suffix': '',
'type': 'incremental',
}])
self.assertEqual(sequence.number_next, 1)
self.assertEqual(Sequence.get_id(sequence), '1')
self.assertEqual(sequence.get(), '1')
Sequence.write([sequence], {
'number_increment': 10,
})
self.assertEqual(sequence.number_next, 2)
self.assertEqual(Sequence.get_id(sequence), '2')
self.assertEqual(Sequence.get_id(sequence), '12')
self.assertEqual(sequence.get(), '2')
self.assertEqual(sequence.get(), '12')
Sequence.write([sequence], {
'padding': 3,
})
self.assertEqual(sequence.number_next, 22)
self.assertEqual(Sequence.get_id(sequence), '022')
self.assertEqual(sequence.get(), '022')
@with_transaction()
def test_decimal_timestamp(self):
'Test Decimal Timestamp'
pool = Pool()
Sequence = self.get_model()
SequenceType = pool.get('ir.sequence.type')
sequence_type, = SequenceType.search([
('name', '=', "Test"),
], limit=1)
sequence, = Sequence.create([{
'name': 'Test decimal timestamp',
'code': 'test',
'sequence_type': sequence_type.id,
'prefix': '',
'suffix': '',
'type': 'decimal timestamp',
}])
timestamp = Sequence.get_id(sequence)
timestamp = sequence.get()
self.assertEqual(timestamp, str(sequence.last_timestamp))
self.assertEqual(sequence.number_next, None)
self.assertNotEqual(Sequence.get_id(sequence), timestamp)
self.assertNotEqual(sequence.get(), timestamp)
next_timestamp = Sequence._timestamp(sequence)
self.assertRaises(LastTimestampError, Sequence.write, [sequence], {
@@ -76,22 +86,27 @@ class SequenceTestCase(unittest.TestCase):
@with_transaction()
def test_hexadecimal_timestamp(self):
'Test Hexadecimal Timestamp'
pool = Pool()
Sequence = self.get_model()
SequenceType = pool.get('ir.sequence.type')
sequence_type, = SequenceType.search([
('name', '=', "Test"),
], limit=1)
sequence, = Sequence.create([{
'name': 'Test hexadecimal timestamp',
'code': 'test',
'sequence_type': sequence_type.id,
'prefix': '',
'suffix': '',
'type': 'hexadecimal timestamp',
}])
timestamp = Sequence.get_id(sequence)
timestamp = sequence.get()
self.assertEqual(timestamp,
hex(int(sequence.last_timestamp))[2:].upper())
self.assertEqual(sequence.number_next, None)
self.assertNotEqual(Sequence.get_id(sequence), timestamp)
self.assertNotEqual(sequence.get(), timestamp)
next_timestamp = Sequence._timestamp(sequence)
self.assertRaises(LastTimestampError, Sequence.write, [sequence], {
@@ -101,16 +116,21 @@ class SequenceTestCase(unittest.TestCase):
@with_transaction()
def test_prefix_suffix(self):
'Test prefix/suffix'
pool = Pool()
Sequence = self.get_model()
SequenceType = pool.get('ir.sequence.type')
sequence_type, = SequenceType.search([
('name', '=', "Test"),
], limit=1)
sequence, = Sequence.create([{
'name': 'Test incremental',
'code': 'test',
'sequence_type': sequence_type.id,
'prefix': 'prefix/',
'suffix': '/suffix',
'type': 'incremental',
}])
self.assertEqual(Sequence.get_id(sequence),
self.assertEqual(sequence.get(),
'prefix/1/suffix')
Sequence.write([sequence], {
@@ -118,8 +138,7 @@ class SequenceTestCase(unittest.TestCase):
'suffix': '/${day}.${month}.${year}',
})
with Transaction().set_context(date=datetime.date(2010, 8, 15)):
self.assertEqual(Sequence.get_id(sequence),
'2010-08-15/2/15.08.2010')
self.assertEqual(sequence.get(), '2010-08-15/2/15.08.2010')
class SequenceStrictTestCase(SequenceTestCase):