[MERGE] ir_sequence standard and no_gap implementation by Thu (vmt)

standard uses postgresql sequences, no locks, gaps are allowed.
no_gap uses an SELECT FOR UPDATE NO WAIT, no gaps are allowed but an exception could be raised if the lock is unavailable.
we also deprecated the infamous get()/get_id() api in favor of next_by_code()/next_by_id()
the specs of this change were inspired by openlabs sequence-postgres module

bzr revid: al@openerp.com-20110930190053-ovd4qlwt7hll02ch
This commit is contained in:
Antony Lesuisse 2011-09-30 21:00:53 +02:00
commit ff1ca3c4fb
68 changed files with 566 additions and 180 deletions

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5430,7 +5430,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5217,7 +5217,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5572,7 +5572,7 @@ msgstr "Преводи"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Брой на остъпката"
#. module: base

View File

@ -5439,7 +5439,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5709,7 +5709,7 @@ msgstr "Traduccions"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Omplenat del número"
#. module: base

View File

@ -5529,7 +5529,7 @@ msgstr "Překlady"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Zarovnání čísla"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr "Oversættelser"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5702,7 +5702,7 @@ msgstr "Übersetzungen"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Stellenanzahl"
#. module: base

View File

@ -5614,7 +5614,7 @@ msgstr "Μεταφράσεις"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Συμπλήρωση αριθμών"
#. module: base

View File

@ -5669,8 +5669,8 @@ msgstr "Translations"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgstr "Number padding"
msgid "Number Padding"
msgstr "Number Padding"
#. module: base
#: view:ir.actions.report.xml:0

View File

@ -5723,7 +5723,7 @@ msgstr "Traducciones"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Relleno del número"
#. module: base

View File

@ -4778,7 +4778,7 @@ msgstr "Traducciones"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Relleno del número"
#. module: base

View File

@ -5725,7 +5725,7 @@ msgstr "Traducciones"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Relleno del número"
#. module: base

View File

@ -5712,7 +5712,7 @@ msgstr "Traducciones"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Relleno del número"
#. module: base

View File

@ -5488,7 +5488,7 @@ msgstr "Tõlked"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Arvu täidistus"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5496,7 +5496,7 @@ msgstr "برگردان‌ها"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "پیمایش شمارگانی"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5664,7 +5664,7 @@ msgstr "Käännökset"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Täytenumerot"
#. module: base

View File

@ -5717,7 +5717,7 @@ msgstr "Traductions"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Remplissage"
#. module: base

View File

@ -5695,7 +5695,7 @@ msgstr "Traducións"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Número de enchido"
#. module: base

View File

@ -5472,7 +5472,7 @@ msgstr "תרגומים"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5553,7 +5553,7 @@ msgstr "Prijevodi"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Dopunjavanje na duljinu"
#. module: base

View File

@ -5666,8 +5666,8 @@ msgstr "Fordítások"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgstr "Number padding"
msgid "Number Padding"
msgstr "Number Padding"
#. module: base
#: view:ir.actions.report.xml:0

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5435,7 +5435,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5713,7 +5713,7 @@ msgstr "Traduzioni"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Riempimento Numero"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5457,7 +5457,7 @@ msgstr "번역"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5464,7 +5464,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -4618,7 +4618,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5514,7 +5514,7 @@ msgstr "Tulkojumi"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Ciparu vietas"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5533,7 +5533,7 @@ msgstr "Орчуулгууд"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Тоон дугаарын урт"
#. module: base

View File

@ -5530,7 +5530,7 @@ msgstr "Oversettelser"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Bunnfyll"
#. module: base

View File

@ -5702,7 +5702,7 @@ msgstr "Vertalingen"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Nummer verspringing"
#. module: base

View File

@ -5447,7 +5447,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -4796,7 +4796,7 @@ msgstr "Vertalingen"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Nummer verspringing"
#. module: base

View File

@ -5612,7 +5612,7 @@ msgstr "Tłumaczenia"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Liczba cyfr"
#. module: base

View File

@ -5591,7 +5591,7 @@ msgstr "Traduções"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Dígitos do número"
#. module: base

View File

@ -5687,7 +5687,7 @@ msgstr "Traduções"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Preencher número"
#. module: base

View File

@ -5555,7 +5555,7 @@ msgstr "Traduceri"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5689,7 +5689,7 @@ msgstr "Переводы"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Выравнивание чисел"
#. module: base

View File

@ -5598,7 +5598,7 @@ msgstr "Preklady"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Zarovanie čísla"
#. module: base

View File

@ -5650,7 +5650,7 @@ msgstr "Prevodi"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Zapolnitev številke"
#. module: base

View File

@ -5430,7 +5430,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5575,7 +5575,7 @@ msgstr "Prevodi"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Dopunjavanje brojeva"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5624,7 +5624,7 @@ msgstr "Översättningar"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Numerisk utfyllnad"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5430,7 +5430,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5637,7 +5637,7 @@ msgstr "Çeviriler"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Numara dolgusu"
#. module: base

View File

@ -5495,7 +5495,7 @@ msgstr "Переклади"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Вирівнювання номерів"
#. module: base

View File

@ -4636,7 +4636,7 @@ msgstr "Переклади"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "Вирівнювання номерів"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5468,7 +5468,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5471,7 +5471,7 @@ msgstr "翻译"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr "数字位数"
#. module: base

View File

@ -5431,7 +5431,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -5447,7 +5447,7 @@ msgstr "翻譯"
#. module: base
#: field:ir.sequence,padding:0
msgid "Number padding"
msgid "Number Padding"
msgstr ""
#. module: base

View File

@ -176,6 +176,7 @@
<field name="padding"/>
<field name="number_increment"/>
<field name="number_next"/>
<field name="implementation"/>
<separator colspan="4" string="Legend (for prefix, suffix)"/>
<group col="8" colspan="4">
<group>
@ -215,6 +216,7 @@
<field name="company_id" groups="base.group_multi_company"/>
<field name="number_next"/>
<field name="number_increment"/>
<field name="implementation"/>
</tree>
</field>
</record>

View File

@ -19,83 +19,252 @@
#
##############################################################################
import logging
import time
from osv import fields,osv
import pooler
class ir_sequence_type(osv.osv):
import openerp
_logger = logging.getLogger('ir_sequence')
class ir_sequence_type(openerp.osv.osv.osv):
_name = 'ir.sequence.type'
_order = 'name'
_columns = {
'name': fields.char('Name',size=64, required=True),
'code': fields.char('Code',size=32, required=True),
'name': openerp.osv.fields.char('Name', size=64, required=True),
'code': openerp.osv.fields.char('Code', size=32, required=True),
}
ir_sequence_type()
_sql_constraints = [
('code_unique', 'unique(code)', '`code` must be unique.'),
]
def _code_get(self, cr, uid, context={}):
cr.execute('select code, name from ir_sequence_type')
return cr.fetchall()
class ir_sequence(osv.osv):
class ir_sequence(openerp.osv.osv.osv):
""" Sequence model.
The sequence model allows to define and use so-called sequence objects.
Such objects are used to generate unique identifiers in a transaction-safe
way.
"""
_name = 'ir.sequence'
_order = 'name'
_columns = {
'name': fields.char('Name',size=64, required=True),
'code': fields.selection(_code_get, 'Code',size=64, required=True),
'active': fields.boolean('Active'),
'prefix': fields.char('Prefix',size=64, help="Prefix value of the record for the sequence"),
'suffix': fields.char('Suffix',size=64, help="Suffix value of the record for the sequence"),
'number_next': fields.integer('Next Number', required=True, help="Next number of this sequence"),
'number_increment': fields.integer('Increment Number', required=True, help="The next number of the sequence will be incremented by this number"),
'padding' : fields.integer('Number padding', required=True, help="OpenERP will automatically adds some '0' on the left of the 'Next Number' to get the required padding size."),
'company_id': fields.many2one('res.company', 'Company'),
'name': openerp.osv.fields.char('Name', size=64, required=True),
'code': openerp.osv.fields.selection(_code_get, 'Code', size=64, required=True),
'implementation': openerp.osv.fields.selection( # TODO update the view
[('standard', 'Standard'), ('no_gap', 'No gap')],
'Implementation', required=True,
help="Two sequence object implementations are offered: Standard "
"and 'No gap'. The later is slower than the former but forbids any"
" gap in the sequence (while they are possible in the former)."),
'active': openerp.osv.fields.boolean('Active'),
'prefix': openerp.osv.fields.char('Prefix', size=64, help="Prefix value of the record for the sequence"),
'suffix': openerp.osv.fields.char('Suffix', size=64, help="Suffix value of the record for the sequence"),
'number_next': openerp.osv.fields.integer('Next Number', required=True, help="Next number of this sequence"),
'number_increment': openerp.osv.fields.integer('Increment Number', required=True, help="The next number of the sequence will be incremented by this number"),
'padding' : openerp.osv.fields.integer('Number Padding', required=True, help="OpenERP will automatically adds some '0' on the left of the 'Next Number' to get the required padding size."),
'company_id': openerp.osv.fields.many2one('res.company', 'Company'),
}
_defaults = {
'active': lambda *a: True,
'implementation': 'standard',
'active': True,
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.sequence', context=c),
'number_increment': lambda *a: 1,
'number_next': lambda *a: 1,
'padding' : lambda *a : 0,
'number_increment': 1,
'number_next': 1,
'padding' : 0,
}
def _process(self, s):
return (s or '') % {
'year':time.strftime('%Y'),
'month': time.strftime('%m'),
'day':time.strftime('%d'),
'y': time.strftime('%y'),
'doy': time.strftime('%j'),
'woy': time.strftime('%W'),
'weekday': time.strftime('%w'),
'h24': time.strftime('%H'),
'h12': time.strftime('%I'),
'min': time.strftime('%M'),
'sec': time.strftime('%S'),
def init(self, cr):
return # Don't do the following index yet.
# CONSTRAINT/UNIQUE INDEX on (code, company_id)
# /!\ The unique constraint 'unique_name_company_id' is not sufficient, because SQL92
# only support field names in constraint definitions, and we need a function here:
# we need to special-case company_id to treat all NULL company_id as equal, otherwise
# we would allow duplicate (code, NULL) ir_sequences.
cr.execute("""
SELECT indexname FROM pg_indexes WHERE indexname =
'ir_sequence_unique_code_company_id_idx'""")
if not cr.fetchone():
cr.execute("""
CREATE UNIQUE INDEX ir_sequence_unique_code_company_id_idx
ON ir_sequence (code, (COALESCE(company_id,-1)))""")
def _create_sequence(self, cr, id, number_increment, number_next):
""" Create a PostreSQL sequence.
There is no access rights check.
"""
assert isinstance(id, (int, long))
sql = "CREATE SEQUENCE ir_sequence_%03d INCREMENT BY %%s START WITH %%s" % id
cr.execute(sql, (number_increment, number_next))
def _drop_sequence(self, cr, ids):
""" Drop the PostreSQL sequence if it exists.
There is no access rights check.
"""
ids = ids if isinstance(ids, (list, tuple)) else [ids]
assert all(isinstance(i, (int, long)) for i in ids), \
"Only ids in (int, long) allowed."
names = ','.join('ir_sequence_%03d' % i for i in ids)
# RESTRICT is the default; it prevents dropping the sequence if an
# object depends on it.
cr.execute("DROP SEQUENCE IF EXISTS %s RESTRICT " % names)
def _alter_sequence(self, cr, id, number_increment, number_next):
""" Alter a PostreSQL sequence.
There is no access rights check.
"""
assert isinstance(id, (int, long))
cr.execute("""
ALTER SEQUENCE ir_sequence_%03d INCREMENT BY %%s RESTART WITH %%s
""" % id, (number_increment, number_next))
def create(self, cr, uid, values, context=None):
""" Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
"""
values = self._add_missing_default_values(cr, uid, values, context)
values['id'] = super(ir_sequence, self).create(cr, uid, values, context)
if values['implementation'] == 'standard':
f = self._create_sequence(cr, values['id'], values['number_increment'], values['number_next'])
return values['id']
def unlink(self, cr, uid, ids, context=None):
super(ir_sequence, self).unlink(cr, uid, ids, context)
self._drop_sequence(cr, ids)
return True
def write(self, cr, uid, ids, values, context=None):
if not isinstance(ids, (list, tuple)):
ids = [ids]
new_implementation = values.get('implementation')
rows = self.read(cr, uid, ids, ['implementation', 'number_increment', 'number_next'], context)
super(ir_sequence, self).write(cr, uid, ids, values, context)
for row in rows:
# 4 cases: we test the previous impl. against the new one.
if row['implementation'] == 'standard':
i = values.get('number_increment', row['number_increment'])
n = values.get('number_next', row['number_next'])
if new_implementation in ('standard', None):
self._alter_sequence(cr, row['id'], i, n)
else:
self._drop_sequence(cr, row['id'])
else:
if new_implementation in ('no_gap', None):
pass
else:
self._create_sequence(cr, row['id'], i, n)
return True
def _interpolate(self, s, d):
if s:
return s % d
return ''
def _interpolation_dict(self):
t = time.localtime() # Actually, the server is always in UTC.
return {
'year': time.strftime('%Y', t),
'month': time.strftime('%m', t),
'day': time.strftime('%d', t),
'y': time.strftime('%y', t),
'doy': time.strftime('%j', t),
'woy': time.strftime('%W', t),
'weekday': time.strftime('%w', t),
'h24': time.strftime('%H', t),
'h12': time.strftime('%I', t),
'min': time.strftime('%M', t),
'sec': time.strftime('%S', t),
}
def get_id(self, cr, uid, sequence_id, test='id', context=None):
assert test in ('code','id')
company_ids = self.pool.get('res.company').search(cr, uid, [], context=context)
cr.execute('''SELECT id, number_next, prefix, suffix, padding
FROM ir_sequence
WHERE %s=%%s
AND active=true
AND (company_id in %%s or company_id is NULL)
ORDER BY company_id, id
FOR UPDATE NOWAIT''' % test,
(sequence_id, tuple(company_ids)))
res = cr.dictfetchone()
if res:
cr.execute('UPDATE ir_sequence SET number_next=number_next+number_increment WHERE id=%s AND active=true', (res['id'],))
if res['number_next']:
return self._process(res['prefix']) + '%%0%sd' % res['padding'] % res['number_next'] + self._process(res['suffix'])
else:
return self._process(res['prefix']) + self._process(res['suffix'])
return False
def _select_by_code_or_id(self, cr, uid, sequence_code_or_id, code_or_id, for_update_no_wait, context=None):
""" Read a sequence object.
def get(self, cr, uid, code):
return self.get_id(cr, uid, code, test='code')
ir_sequence()
There is no access rights check on the sequence itself.
"""
assert code_or_id in ('code', 'id')
res_company = self.pool.get('res.company')
company_ids = res_company.search(cr, uid, [], context=context)
sql = """
SELECT id, number_next, prefix, suffix, padding, implementation
FROM ir_sequence
WHERE %s=%%s
AND active=true
AND (company_id in %%s or company_id is NULL)
""" % code_or_id
if for_update_no_wait:
sql += 'FOR UPDATE NOWAIT'
cr.execute(sql, (sequence_code_or_id, tuple(company_ids)))
return cr.dictfetchone()
def _next(self, cr, uid, sequence, context=None):
if not sequence:
return False
if sequence['implementation'] == 'standard':
cr.execute("SELECT nextval('ir_sequence_%03d')" % sequence['id'])
sequence['number_next'] = cr.fetchone()
else:
# Read again with FOR UPDATE NO WAIT.
sequence = self._select_by_code_or_id(cr, uid, sequence['id'], 'id', True, context)
cr.execute("""
UPDATE ir_sequence
SET number_next=number_next+number_increment
WHERE id=%s
""", (sequence['id'],))
d = self._interpolation_dict()
interpolated_prefix = self._interpolate(sequence['prefix'], d)
interpolated_suffix = self._interpolate(sequence['suffix'], d)
if sequence['number_next']:
return interpolated_prefix + '%%0%sd' % sequence['padding'] % \
sequence['number_next'] + interpolated_suffix
else:
# TODO what is this case used for ?
return interpolated_prefix + interpolated_suffix
def next_by_id(self, cr, uid, sequence_id, context=None):
""" Draw an interpolated string using the specified sequence."""
self.check_read(cr, uid)
res = self._select_by_code_or_id(cr, uid, sequence_id, 'id', False, context)
return self._next(cr, uid, res, context)
def next_by_code(self, cr, uid, sequence_code, context=None):
""" Draw an interpolated string using the specified sequence."""
self.check_read(cr, uid)
res = self._select_by_code_or_id(cr, uid, sequence_code, 'code', False, context)
return self._next(cr, uid, res, context)
def get_id(self, cr, uid, sequence_code_or_id, code_or_id='id', context=None):
""" Draw an interpolated string using the specified sequence.
The sequence to use is specified by the ``sequence_code_or_id``
argument, which can be a code or an id (as controlled by the
``code_or_id`` argument. This method is deprecated.
"""
_logger.warning("ir_sequence.get() and ir_sequence.get_id() are deprecated. "
"Please use ir_sequence.next_by_code() or ir_sequence.next_by_id().")
if code_or_id == 'id':
return self.next_by_id(cr, uid, sequence_code_or_id, context)
else:
return self.next_by_code(cr, uid, sequence_code_or_id, context)
def get(self, cr, uid, code, context=None):
""" Draw an interpolated string using the specified sequence.
The sequence to use is specified by its code. This method is
deprecated.
"""
return self.get_id(cr, uid, code, 'code', context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

62
tests/common.py Normal file
View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
import os
import time
import unittest2
import xmlrpclib
import openerp
ADDONS_PATH = os.environ['OPENERP_ADDONS_PATH']
PORT = int(os.environ['OPENERP_PORT'])
DB = os.environ['OPENERP_DATABASE']
HOST = '127.0.0.1'
ADMIN_USER = 'admin'
ADMIN_USER_ID = 1
ADMIN_PASSWORD = 'admin'
common_proxy_60 = None
db_proxy_60 = None
object_proxy_60 = None
common_proxy_61 = None
db_proxy_61 = None
model_proxy_61 = None
def setUpModule():
"""
Start the OpenERP server similary to the openerp-server script and
setup some xmlrpclib proxies.
"""
openerp.tools.config['addons_path'] = ADDONS_PATH
openerp.tools.config['xmlrpc_port'] = PORT
openerp.service.start_services()
global common_proxy_60
global db_proxy_60
global object_proxy_60
# Use the old (pre 6.1) API.
url = 'http://%s:%d/xmlrpc/' % (HOST, PORT)
common_proxy_60 = xmlrpclib.ServerProxy(url + 'common')
db_proxy_60 = xmlrpclib.ServerProxy(url + 'db')
object_proxy_60 = xmlrpclib.ServerProxy(url + 'object')
global common_proxy_61
global db_proxy_61
global model_proxy_61
# Use the new (6.1) API.
url = 'http://%s:%d/openerp/6.1/xmlrpc/' % (HOST, PORT)
common_proxy_61 = xmlrpclib.ServerProxy(url + 'common')
db_proxy_61 = xmlrpclib.ServerProxy(url + 'db')
model_proxy_61 = xmlrpclib.ServerProxy(url + 'model/' + DB)
# Ugly way to ensure the server is listening.
time.sleep(2)
def tearDownModule():
""" Shutdown the OpenERP server similarly to a single ctrl-c. """
openerp.service.stop_services()

198
tests/test_ir_sequence.py Normal file
View File

@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
# Run with one of these commands:
# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \
# OPENERP_DATABASE=yy PYTHONPATH=. python tests/test_ir_sequence.py
# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \
# OPENERP_DATABASE=yy nosetests tests/test_ir_sequence.py
# > OPENERP_ADDONS_PATH='../../../addons/trunk' OPENERP_PORT=8069 \
# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence
# This assume an existing database.
import os
import psycopg2
import time
import unittest2
import xmlrpclib
import openerp
import common
DB = common.DB
ADMIN_USER_ID = common.ADMIN_USER_ID
setUpModule = common.setUpModule
tearDownModule = common.tearDownModule
def registry(model):
return openerp.modules.registry.RegistryManager.get(DB)[model]
def cursor():
return openerp.modules.registry.RegistryManager.get(DB).db.cursor()
class test_ir_sequence_standard(unittest2.TestCase):
""" A few tests for a 'Standard' (i.e. PostgreSQL) sequence. """
def test_ir_sequence_create(self):
""" Try to create a sequence object. """
cr = cursor()
d = dict(code='test_sequence_type', name='Test sequence type')
c = registry('ir.sequence.type').create(cr, ADMIN_USER_ID, d, {})
assert c
d = dict(code='test_sequence_type', name='Test sequence')
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
assert c
cr.commit()
cr.close()
def test_ir_sequence_search(self):
""" Try a search. """
cr = cursor()
ids = registry('ir.sequence').search(cr, ADMIN_USER_ID, [], {})
assert ids
cr.commit()
cr.close()
def test_ir_sequence_draw(self):
""" Try to draw a number. """
cr = cursor()
n = registry('ir.sequence').get(cr, ADMIN_USER_ID, 'test_sequence_type', {})
assert n
cr.commit()
cr.close()
def test_ir_sequence_draw_twice(self):
""" Try to draw a number from two transactions. """
cr0 = cursor()
cr1 = cursor()
n0 = registry('ir.sequence').get(cr0, ADMIN_USER_ID, 'test_sequence_type', {})
assert n0
n1 = registry('ir.sequence').get(cr1, ADMIN_USER_ID, 'test_sequence_type', {})
assert n1
cr0.commit()
cr1.commit()
cr0.close()
cr1.close()
class test_ir_sequence_no_gap(unittest2.TestCase):
""" Copy of the previous tests for a 'No gap' sequence. """
def test_ir_sequence_create_no_gap(self):
""" Try to create a sequence object. """
cr = cursor()
d = dict(code='test_sequence_type_2', name='Test sequence type')
c = registry('ir.sequence.type').create(cr, ADMIN_USER_ID, d, {})
assert c
d = dict(code='test_sequence_type_2', name='Test sequence',
implementation='no_gap')
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
assert c
cr.commit()
cr.close()
def test_ir_sequence_draw_no_gap(self):
""" Try to draw a number. """
cr = cursor()
n = registry('ir.sequence').get(cr, ADMIN_USER_ID, 'test_sequence_type_2', {})
assert n
cr.commit()
cr.close()
def test_ir_sequence_draw_twice_no_gap(self):
""" Try to draw a number from two transactions.
This is expected to not work.
"""
cr0 = cursor()
cr1 = cursor()
msg_re = '^could not obtain lock on row in relation "ir_sequence"$'
with self.assertRaisesRegexp(psycopg2.OperationalError, msg_re):
n0 = registry('ir.sequence').get(cr0, ADMIN_USER_ID, 'test_sequence_type_2', {})
assert n0
n1 = registry('ir.sequence').get(cr1, ADMIN_USER_ID, 'test_sequence_type_2', {})
cr0.close()
cr1.close()
class test_ir_sequence_change_implementation(unittest2.TestCase):
""" Create sequence objects and change their ``implementation`` field. """
def test_ir_sequence_1_create(self):
""" Try to create a sequence object. """
cr = cursor()
d = dict(code='test_sequence_type_3', name='Test sequence type')
c = registry('ir.sequence.type').create(cr, ADMIN_USER_ID, d, {})
assert c
d = dict(code='test_sequence_type_3', name='Test sequence')
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
assert c
d = dict(code='test_sequence_type_4', name='Test sequence type')
c = registry('ir.sequence.type').create(cr, ADMIN_USER_ID, d, {})
assert c
d = dict(code='test_sequence_type_4', name='Test sequence',
implementation='no_gap')
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
assert c
cr.commit()
cr.close()
def test_ir_sequence_2_write(self):
cr = cursor()
ids = registry('ir.sequence').search(cr, ADMIN_USER_ID,
[('code', 'in', ['test_sequence_type_3', 'test_sequence_type_4'])], {})
registry('ir.sequence').write(cr, ADMIN_USER_ID, ids,
{'implementation': 'standard'}, {})
registry('ir.sequence').write(cr, ADMIN_USER_ID, ids,
{'implementation': 'no_gap'}, {})
cr.commit()
cr.close()
def test_ir_sequence_3_unlink(self):
cr = cursor()
ids = registry('ir.sequence').search(cr, ADMIN_USER_ID,
[('code', 'in', ['test_sequence_type_3', 'test_sequence_type_4'])], {})
registry('ir.sequence').unlink(cr, ADMIN_USER_ID, ids, {})
cr.commit()
cr.close()
class test_ir_sequence_generate(unittest2.TestCase):
""" Create sequence objects and generate some values. """
def test_ir_sequence_create(self):
""" Try to create a sequence object. """
cr = cursor()
d = dict(code='test_sequence_type_5', name='Test sequence type')
c = registry('ir.sequence.type').create(cr, ADMIN_USER_ID, d, {})
assert c
d = dict(code='test_sequence_type_5', name='Test sequence')
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
assert c
cr.commit()
cr.close()
cr = cursor()
f = lambda *a: registry('ir.sequence').get(cr, ADMIN_USER_ID, 'test_sequence_type_5', {})
assert all(str(x) == f() for x in xrange(1,1000))
cr.commit()
cr.close()
def test_ir_sequence_create_no_gap(self):
""" Try to create a sequence object. """
cr = cursor()
d = dict(code='test_sequence_type_6', name='Test sequence type',
implementation='no_gap')
c = registry('ir.sequence.type').create(cr, ADMIN_USER_ID, d, {})
assert c
d = dict(code='test_sequence_type_6', name='Test sequence')
c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {})
assert c
cr.commit()
cr.close()
cr = cursor()
f = lambda *a: registry('ir.sequence').get(cr, ADMIN_USER_ID, 'test_sequence_type_6', {})
assert all(str(x) == f() for x in xrange(1,1000))
cr.commit()
cr.close()
if __name__ == '__main__':
unittest2.main()

View File

@ -12,60 +12,15 @@ import unittest2
import xmlrpclib
import openerp
import common
ADDONS_PATH = os.environ['OPENERP_ADDONS_PATH']
PORT = int(os.environ['OPENERP_PORT'])
DB = os.environ['OPENERP_DATABASE']
DB = common.DB
ADMIN_USER = common.ADMIN_USER
ADMIN_USER_ID = common.ADMIN_USER_ID
ADMIN_PASSWORD = common.ADMIN_PASSWORD
HOST = '127.0.0.1'
ADMIN_USER = 'admin'
ADMIN_USER_ID = 1
ADMIN_PASSWORD = 'admin'
common_proxy_60 = None
db_proxy_60 = None
object_proxy_60 = None
common_proxy_61 = None
db_proxy_61 = None
model_proxy_61 = None
def setUpModule():
"""
Start the OpenERP server similary to the openerp-server script and
setup some xmlrpclib proxies.
"""
openerp.tools.config['addons_path'] = ADDONS_PATH
openerp.tools.config['xmlrpc_port'] = PORT
openerp.service.start_services()
global common_proxy_60
global db_proxy_60
global object_proxy_60
global common_proxy_61
global db_proxy_61
global model_proxy_61
# Use the old (pre 6.1) API.
url = 'http://%s:%d/xmlrpc/' % (HOST, PORT)
common_proxy_60 = xmlrpclib.ServerProxy(url + 'common')
db_proxy_60 = xmlrpclib.ServerProxy(url + 'db')
object_proxy_60 = xmlrpclib.ServerProxy(url + 'object')
# Use the new (6.1) API.
url = 'http://%s:%d/openerp/6.1/xmlrpc/' % (HOST, PORT)
common_proxy_61 = xmlrpclib.ServerProxy(url + 'common')
db_proxy_61 = xmlrpclib.ServerProxy(url + 'db')
model_proxy_61 = xmlrpclib.ServerProxy(url + 'model/' + DB)
# Mmm need to make sure the server is listening for XML-RPC requests.
time.sleep(10)
def tearDownModule():
""" Shutdown the OpenERP server similarly to a single ctrl-c. """
openerp.service.stop_services()
setUpModule = common.setUpModule
tearDownModule = common.tearDownModule
class test_xmlrpc(unittest2.TestCase):
@ -74,34 +29,34 @@ class test_xmlrpc(unittest2.TestCase):
Simulate a OpenERP client requesting the creation of a database and
polling the server until the creation is complete.
"""
progress_id = db_proxy_60.create(ADMIN_PASSWORD, DB, True, False,
ADMIN_PASSWORD)
progress_id = common.db_proxy_60.create(ADMIN_PASSWORD, DB, True,
False, ADMIN_PASSWORD)
while True:
time.sleep(1)
progress, users = db_proxy_60.get_progress(ADMIN_PASSWORD,
progress, users = common.db_proxy_60.get_progress(ADMIN_PASSWORD,
progress_id)
if progress == 1.0:
break
def test_xmlrpc_login(self):
""" Try to login on the common service. """
uid = common_proxy_60.login(DB, ADMIN_USER, ADMIN_PASSWORD)
uid = common.common_proxy_60.login(DB, ADMIN_USER, ADMIN_PASSWORD)
assert uid == ADMIN_USER_ID
def test_xmlrpc_ir_model_search(self):
""" Try a search on the object service. """
ids = object_proxy_60.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD,
ids = common.object_proxy_60.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD,
'ir.model', 'search', [])
assert ids
ids = object_proxy_60.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD,
ids = common.object_proxy_60.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD,
'ir.model', 'search', [], {})
assert ids
def test_xmlrpc_61_ir_model_search(self):
""" Try a search on the object service. """
ids = model_proxy_61.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [])
ids = common.model_proxy_61.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [])
assert ids
ids = model_proxy_61.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [], {})
ids = common.model_proxy_61.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [], {})
assert ids
if __name__ == '__main__':