Store directly sequence type to sequence
issue10122 review351381002
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -36,8 +36,6 @@ def register():
|
||||
ir.Lang,
|
||||
ir.SequenceType,
|
||||
ir.SequenceTypeGroup,
|
||||
ir.Sequence,
|
||||
ir.SequenceStrict,
|
||||
ir.ModuleConfigWizardItem,
|
||||
ir.Export,
|
||||
ir.Export_Group,
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user