Add timestamp sequence
This commit is contained in:
1 parent
e147a1cc2e
commit
56e8ebe5d0
7 files changed
+261
-27
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Add timestamp sequence
|
||||
* New transaction management
|
||||
* Make _timestamp numeric instead of datetime to work with XML-RPC and JSON-RPC
|
||||
* Add ir.trigger to trigger ModelStorage change
|
||||
|
||||
+92
-20
@@ -3,9 +3,10 @@
|
||||
from __future__ import with_statement
|
||||
from string import Template
|
||||
import datetime
|
||||
import time
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.tools import datetime_strftime
|
||||
from trytond.pyson import In, Eval
|
||||
from trytond.pyson import In, Eval, Not
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
@@ -24,30 +25,64 @@ class Sequence(ModelSQL, ModelView):
|
||||
_name = 'ir.sequence'
|
||||
_description = __doc__
|
||||
name = fields.Char('Sequence Name', required=True, translate=True)
|
||||
code = fields.Selection('code_get', 'Sequence Type', required=True,
|
||||
code = fields.Selection('code_get', 'Sequence Code', required=True,
|
||||
states={
|
||||
'readonly': In('code', Eval('context', {})),
|
||||
})
|
||||
active = fields.Boolean('Active')
|
||||
prefix = fields.Char('Prefix')
|
||||
suffix = fields.Char('Suffix')
|
||||
number_next = fields.Integer('Next Number')
|
||||
number_increment = fields.Integer('Increment Number')
|
||||
padding = fields.Integer('Number padding')
|
||||
type = fields.Selection([
|
||||
('incremental', 'Incremental'),
|
||||
('decimal timestamp', 'Decimal Timestamp'),
|
||||
('hexadecimal timestamp', 'Hexadecimal Timestamp'),
|
||||
], 'Type')
|
||||
number_next = fields.Integer('Next Number',
|
||||
states={
|
||||
'invisible': Not(In(Eval('type'), ['incremental'])),
|
||||
}, depends=['type'])
|
||||
number_increment = fields.Integer('Increment Number',
|
||||
states={
|
||||
'invisible': Not(In(Eval('type'), ['incremental'])),
|
||||
}, depends=['type'])
|
||||
padding = fields.Integer('Number padding',
|
||||
states={
|
||||
'invisible': Not(In(Eval('type'), ['incremental'])),
|
||||
}, depends=['type'])
|
||||
timestamp_rounding = fields.Integer('Timestamp Rounding', required=True,
|
||||
states={
|
||||
'invisible': Not(In(Eval('type'),
|
||||
['decimal timestamp', 'hexadecimal timestamp'])),
|
||||
}, depends=['type'])
|
||||
timestamp_offset = fields.Float('Timestamp Offset',
|
||||
states={
|
||||
'invisible': Not(In(Eval('type'),
|
||||
['decimal timestamp', 'hexadecimal timestamp'])),
|
||||
}, depends=['type'])
|
||||
last_timestamp = fields.Integer('Last Timestamp',
|
||||
states={
|
||||
'invisible': Not(In(Eval('type'),
|
||||
['decimal timestamp', 'hexadecimal timestamp'])),
|
||||
}, depends=['type'])
|
||||
|
||||
def __init__(self):
|
||||
super(Sequence, self).__init__()
|
||||
self._constraints += [
|
||||
('check_prefix_suffix', 'invalid_prefix_suffix'),
|
||||
('check_last_timestamp', 'future_last_timestamp'),
|
||||
]
|
||||
self._error_messages.update({
|
||||
'missing': 'Missing sequence!',
|
||||
'invalid_prefix_suffix': 'Invalid prefix/suffix!',
|
||||
'future_last_timestamp': 'Last Timestamp could not be in future!',
|
||||
})
|
||||
|
||||
def default_active(self):
|
||||
return True
|
||||
|
||||
def default_type(self):
|
||||
return 'incremental'
|
||||
|
||||
def default_number_increment(self):
|
||||
return 1
|
||||
|
||||
@@ -57,6 +92,15 @@ class Sequence(ModelSQL, ModelView):
|
||||
def default_padding(self):
|
||||
return 0
|
||||
|
||||
def default_timestamp_rounding(self):
|
||||
return 1.0
|
||||
|
||||
def default_timestamp_offset(self):
|
||||
return 946681200.0 # Offset for 2000-01-01
|
||||
|
||||
def default_last_timestamp(self):
|
||||
return 0.0
|
||||
|
||||
def default_code(self):
|
||||
return Transaction().context.get('code', False)
|
||||
|
||||
@@ -77,6 +121,15 @@ class Sequence(ModelSQL, ModelView):
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_last_timestamp(self, ids):
|
||||
"Check last_timestamp"
|
||||
|
||||
for sequence in self.browse(ids):
|
||||
next_timestamp = self._timestamp(sequence)
|
||||
if sequence.last_timestamp > next_timestamp:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _process(self, string, date=None):
|
||||
date_obj = self.pool.get('ir.date')
|
||||
if not date:
|
||||
@@ -90,6 +143,35 @@ class Sequence(ModelSQL, ModelView):
|
||||
day=day,
|
||||
)
|
||||
|
||||
def _timestamp(self, sequence):
|
||||
return int((time.time() - sequence.timestamp_offset)
|
||||
/ sequence.timestamp_rounding)
|
||||
|
||||
def _get_sequence(self, sequence):
|
||||
if sequence.type == 'incremental':
|
||||
#Pre-fetch number_next
|
||||
number_next = sequence.number_next or 0
|
||||
|
||||
with Transaction().set_user(0):
|
||||
self.write(sequence.id, {
|
||||
'number_next': number_next + sequence.number_increment,
|
||||
})
|
||||
return '%%0%sd' % sequence.padding % number_next
|
||||
elif sequence.type in ('decimal timestamp', 'hexadecimal timestamp'):
|
||||
timestamp = sequence.last_timestamp
|
||||
while timestamp == sequence.last_timestamp:
|
||||
timestamp = self._timestamp(sequence)
|
||||
with Transaction().set_user(0):
|
||||
self.write(sequence.id, {
|
||||
'last_timestamp': timestamp,
|
||||
})
|
||||
if sequence.type == 'decimal timestamp':
|
||||
return '%d' % timestamp
|
||||
else:
|
||||
return hex(timestamp)[2:].upper()
|
||||
return ''
|
||||
|
||||
|
||||
def get_id(self, domain):
|
||||
'''
|
||||
Return sequence value for the domain
|
||||
@@ -108,21 +190,11 @@ class Sequence(ModelSQL, ModelView):
|
||||
if sequence_ids:
|
||||
with Transaction().set_user(0):
|
||||
sequence = self.browse(sequence_ids[0])
|
||||
#Pre-fetch number_next
|
||||
number_next = sequence.number_next
|
||||
|
||||
self.write(sequence.id, {
|
||||
'number_next': (number_next +
|
||||
sequence.number_increment),
|
||||
})
|
||||
|
||||
if number_next:
|
||||
return (self._process(sequence.prefix, date=date) +
|
||||
'%%0%sd' % sequence.padding % number_next +
|
||||
self._process(sequence.suffix, date=date))
|
||||
else:
|
||||
return (self._process(sequence.prefix, date=date) +
|
||||
self._process(sequence.suffix, date=date))
|
||||
return '%s%s%s' % (
|
||||
self._process(sequence.prefix, date=date),
|
||||
self._get_sequence(sequence),
|
||||
self._process(sequence.suffix, date=date),
|
||||
)
|
||||
self.raise_user_error('missing')
|
||||
|
||||
def get(self, code):
|
||||
|
||||
+21
-7
@@ -22,12 +22,25 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="prefix"/>
|
||||
<label name="suffix"/>
|
||||
<field name="suffix"/>
|
||||
<label name="padding"/>
|
||||
<field name="padding" />
|
||||
<label name="number_increment"/>
|
||||
<field name="number_increment"/>
|
||||
<label name="number_next"/>
|
||||
<field name="number_next"/>
|
||||
<label name="type"/>
|
||||
<field name="type"/>
|
||||
<group string="Incremental" id="incremental" colspan="4"
|
||||
states="{'invisible': Not(In(Eval('type'), ['incremental']))}">
|
||||
<label name="padding"/>
|
||||
<field name="padding" />
|
||||
<label name="number_increment"/>
|
||||
<field name="number_increment"/>
|
||||
<label name="number_next"/>
|
||||
<field name="number_next"/>
|
||||
</group>
|
||||
<group string="Timestamp" id="timestamp" colspan="4"
|
||||
states="{'invisible': Not(In(Eval('type'), ['decimal timestamp', 'hexadecimal timestamp']))}">
|
||||
<label name="timestamp_rounding"/>
|
||||
<field name="timestamp_rounding"/>
|
||||
<label name="timestamp_offset"/>
|
||||
<field name="timestamp_offset"/>
|
||||
<field name="last_timestamp"/>
|
||||
</group>
|
||||
<separator string="Legend (Placeholders for prefix, suffix)" colspan="4"
|
||||
id="legend"/>
|
||||
<label string="Year:" id="label_year"/>
|
||||
@@ -48,7 +61,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<tree string="Sequences">
|
||||
<field name="name" select="1"/>
|
||||
<field name="code" select="1"/>
|
||||
<field name="active" select="1"/>
|
||||
<field name="type" select="1"/>
|
||||
<field name="active" select="2"/>
|
||||
</tree>
|
||||
]]>
|
||||
</field>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
'depends': ['ir', 'res'],
|
||||
'xml': [
|
||||
'import_data.xml',
|
||||
'sequence.xml',
|
||||
],
|
||||
'translation': [
|
||||
],
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<record model="ir.sequence.type" id="sequence_type_test">
|
||||
<field name="name">Test</field>
|
||||
<field name="code">test</field>
|
||||
<field name="groups" eval="[('add', ref('res.group_admin'))]"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
from __future__ import with_statement
|
||||
import unittest
|
||||
import datetime
|
||||
from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
|
||||
install_module
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
class SequenceTestCase(unittest.TestCase):
|
||||
'''
|
||||
Test Sequence
|
||||
'''
|
||||
|
||||
def setUp(self):
|
||||
install_module('test')
|
||||
self.sequence = POOL.get('ir.sequence')
|
||||
|
||||
def test0010incremental(self):
|
||||
'''
|
||||
Test incremental
|
||||
'''
|
||||
with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
|
||||
sequence_id = self.sequence.create({
|
||||
'name': 'Test incremental',
|
||||
'code': 'test',
|
||||
'prefix': '',
|
||||
'suffix': '',
|
||||
'type': 'incremental',
|
||||
})
|
||||
self.assertEqual(self.sequence.get_id(sequence_id), '1')
|
||||
|
||||
self.sequence.write(sequence_id, {
|
||||
'number_increment': 10,
|
||||
})
|
||||
self.assertEqual(self.sequence.get_id(sequence_id), '2')
|
||||
self.assertEqual(self.sequence.get_id(sequence_id), '12')
|
||||
|
||||
self.sequence.write(sequence_id, {
|
||||
'padding': 3,
|
||||
})
|
||||
self.assertEqual(self.sequence.get_id(sequence_id), '022')
|
||||
|
||||
transaction.cursor.rollback()
|
||||
|
||||
def test0020decimal_timestamp(self):
|
||||
'''
|
||||
Test Decimal Timestamp
|
||||
'''
|
||||
with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
|
||||
sequence_id = self.sequence.create({
|
||||
'name': 'Test decimal timestamp',
|
||||
'code': 'test',
|
||||
'prefix': '',
|
||||
'suffix': '',
|
||||
'type': 'decimal timestamp',
|
||||
})
|
||||
timestamp = self.sequence.get_id(sequence_id)
|
||||
sequence = self.sequence.read(sequence_id, ['last_timestamp'])
|
||||
self.assertEqual(timestamp, str(sequence['last_timestamp']))
|
||||
|
||||
self.assertNotEqual(self.sequence.get_id(sequence_id), timestamp)
|
||||
|
||||
sequence = self.sequence.browse(sequence_id)
|
||||
next_timestamp = self.sequence._timestamp(sequence)
|
||||
self.assertRaises(Exception, self.sequence.write, sequence_id, {
|
||||
'last_timestamp': next_timestamp + 100,
|
||||
})
|
||||
|
||||
transaction.cursor.rollback()
|
||||
|
||||
def test0030hexadecimal_timestamp(self):
|
||||
'''
|
||||
Test Hexadecimal Timestamp
|
||||
'''
|
||||
with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
|
||||
sequence_id = self.sequence.create({
|
||||
'name': 'Test hexadecimal timestamp',
|
||||
'code': 'test',
|
||||
'prefix': '',
|
||||
'suffix': '',
|
||||
'type': 'hexadecimal timestamp',
|
||||
})
|
||||
timestamp = self.sequence.get_id(sequence_id)
|
||||
sequence = self.sequence.read(sequence_id, ['last_timestamp'])
|
||||
self.assertEqual(timestamp, hex(sequence['last_timestamp'])[2:].upper())
|
||||
|
||||
self.assertNotEqual(self.sequence.get_id(sequence_id), timestamp)
|
||||
|
||||
sequence = self.sequence.browse(sequence_id)
|
||||
next_timestamp = self.sequence._timestamp(sequence)
|
||||
self.assertRaises(Exception, self.sequence.write, sequence_id, {
|
||||
'last_timestamp': next_timestamp + 100,
|
||||
})
|
||||
|
||||
transaction.cursor.rollback()
|
||||
|
||||
def test0040prefix_suffix(self):
|
||||
'''
|
||||
Test prefix/suffix
|
||||
'''
|
||||
with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
|
||||
sequence_id = self.sequence.create({
|
||||
'name': 'Test incremental',
|
||||
'code': 'test',
|
||||
'prefix': 'prefix/',
|
||||
'suffix': '/suffix',
|
||||
'type': 'incremental',
|
||||
})
|
||||
self.assertEqual(self.sequence.get_id(sequence_id),
|
||||
'prefix/1/suffix')
|
||||
|
||||
self.sequence.write(sequence_id, {
|
||||
'prefix': '${year}-${month}-${day}/',
|
||||
'suffix': '/${day}.${month}.${year}',
|
||||
})
|
||||
context = CONTEXT.copy()
|
||||
with Transaction().set_context(date=datetime.date(2010, 8, 15)):
|
||||
self.assertEqual(self.sequence.get_id(sequence_id),
|
||||
'2010-08-15/2/15.08.2010')
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromTestCase(SequenceTestCase)
|
||||
|
||||
if __name__ == '__main__':
|
||||
suite = suite()
|
||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
@@ -184,6 +184,8 @@ def all_suite():
|
||||
suite_.addTests(test_trigger.suite())
|
||||
import trytond.tests.test_protocols_datatype as test_protocols_datatype
|
||||
suite_.addTests(test_protocols_datatype.suite())
|
||||
import trytond.tests.test_sequence as test_sequence
|
||||
suite_.addTests(test_sequence.suite())
|
||||
return suite_
|
||||
|
||||
def modules_suite():
|
||||
|
||||
Reference in new issue
Block a user