@@ -1,3 +1,4 @@
|
||||
* Allow date formating in sequences substitutions
|
||||
* Make ModelSQL based on table query read only by default
|
||||
* Add email validation tools
|
||||
* Remove overlapping indexes
|
||||
|
||||
@@ -60,8 +60,12 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
|
||||
'readonly': Eval('id', -1) >= 0,
|
||||
},
|
||||
depends=['id'])
|
||||
prefix = fields.Char('Prefix', strip='leading')
|
||||
suffix = fields.Char('Suffix', strip='trailing')
|
||||
prefix = fields.Char('Prefix', strip='leading',
|
||||
help="The current date can be used formatted using strftime format "
|
||||
"suffixed with underscores: i.e: ${date_Y}")
|
||||
suffix = fields.Char('Suffix', strip='trailing',
|
||||
help="The current date can be used formatted using strftime format "
|
||||
"suffixed with underscores: i.e: ${date_Y}")
|
||||
type = fields.Selection([
|
||||
('incremental', 'Incremental'),
|
||||
('decimal timestamp', 'Decimal Timestamp'),
|
||||
@@ -319,11 +323,14 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
|
||||
|
||||
@classmethod
|
||||
def _process(cls, string, date=None):
|
||||
return Template(string or '').substitute(
|
||||
**cls._get_substitutions(date))
|
||||
if not string:
|
||||
return ''
|
||||
|
||||
substitutions = cls._get_substitutions(date)
|
||||
return Template(string or '').safe_substitute(substitutions)
|
||||
|
||||
@classmethod
|
||||
def _get_substitutions(cls, date):
|
||||
def _get_substitutions(cls, date=None):
|
||||
'''
|
||||
Returns a dictionary with the keys and values of the substitutions
|
||||
available to format the sequence
|
||||
@@ -332,11 +339,39 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
|
||||
Date = pool.get('ir.date')
|
||||
if not date:
|
||||
date = Date.today()
|
||||
return {
|
||||
'year': date.strftime('%Y'),
|
||||
'month': date.strftime('%m'),
|
||||
'day': date.strftime('%d'),
|
||||
}
|
||||
|
||||
class CustomFormatter(dict):
|
||||
|
||||
def __getitem__(self, name):
|
||||
try:
|
||||
value = super().__getitem__(name)
|
||||
except KeyError:
|
||||
value = cls._convert_substitution_key(self, name)
|
||||
return value
|
||||
|
||||
return CustomFormatter({
|
||||
'date': date,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _convert_substitution_key(cls, substitutions, key):
|
||||
"""
|
||||
Converts a substitution key into a different (i.e: formated) value
|
||||
Returns the updated value
|
||||
"""
|
||||
# Compatibilty with previous keywords
|
||||
key = {
|
||||
'year': 'date_Y',
|
||||
'month': 'date_m',
|
||||
'day': 'date_d',
|
||||
}.get(key, key)
|
||||
if key.startswith('date_'):
|
||||
format_ = key[len('date'):].replace('_', '%')
|
||||
value = substitutions['date'].strftime(format_)
|
||||
if value == format_:
|
||||
raise ValueError(
|
||||
f"Unknown substitution format {format_}")
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def _timestamp(sequence):
|
||||
|
||||
@@ -30,12 +30,4 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<label name="last_timestamp"/>
|
||||
<field name="last_timestamp"/>
|
||||
</group>
|
||||
<separator string="Legend (Placeholders for prefix, suffix)" colspan="4"
|
||||
id="legend"/>
|
||||
<label string="Year:" id="label_year"/>
|
||||
<label string="${year}" colspan="3" xalign="0.0" xexpand="1" id="year"/>
|
||||
<label string="Month:" id="label_month"/>
|
||||
<label string="${month}" colspan="3" xalign="0.0" xexpand="1" id="month"/>
|
||||
<label string="Day:" id="label_day"/>
|
||||
<label string="${day}" colspan="3" xalign="0.0" xexpand="1" id="day"/>
|
||||
</form>
|
||||
|
||||
@@ -13,6 +13,7 @@ except ImportError:
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.ir.exceptions import SequenceAffixError
|
||||
from trytond.ir.lang import _replace
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval, If, PYSONEncoder
|
||||
@@ -118,6 +119,51 @@ class IrTestCase(ModuleTestCase):
|
||||
with Transaction().set_context(date=next_year):
|
||||
self.assertEqual(sequence.get(), '%s3' % str(next_year.year))
|
||||
|
||||
@with_transaction()
|
||||
def test_sequence_format(self):
|
||||
'Test Sequence Format'
|
||||
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()
|
||||
sequence.prefix = '${date_y}-'
|
||||
sequence.save()
|
||||
|
||||
today = datetime.date(2023, 1, 1)
|
||||
|
||||
with Transaction().set_context(date=today):
|
||||
self.assertEqual(sequence.get(), '23-1')
|
||||
|
||||
@with_transaction()
|
||||
def test_sequence_wrong_format(self):
|
||||
'Test Sequence Wrong Format'
|
||||
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()
|
||||
|
||||
with self.assertRaises(SequenceAffixError):
|
||||
sequence.prefix = '${date_K}'
|
||||
sequence.save()
|
||||
|
||||
@with_transaction()
|
||||
def test_global_search(self):
|
||||
'Test Global Search'
|
||||
|
||||
Reference in New Issue
Block a user