Merge lp:~therp-nl/openobject-server/ronald@therp.nl_fix_sequence_lp960201 into lp:openobject-server

Proposed by Ronald Portier (Therp) on 2012-11-29
Status: Merged
Merge reported by: Olivier Dony (Odoo)
Merged at revision: not available
Proposed branch: lp:~therp-nl/openobject-server/ronald@therp.nl_fix_sequence_lp960201
Merge into: lp:openobject-server
Diff against target: 213 lines (+108/-10) (has conflicts)
2 files modified
openerp/addons/base/ir/ir_sequence.py (+97/-9)
openerp/addons/base/ir/ir_sequence_view.xml (+11/-1)
Text conflict in openerp/addons/base/ir/ir_sequence.py
To merge this branch: bzr merge lp:~therp-nl/openobject-server/ronald@therp.nl_fix_sequence_lp960201
Reviewer Review Type Date Requested Status
OpenERP Core Team 2012-11-29 Pending
Review via email: mp+136913@code.launchpad.net

Description of the change

Fixes lp960201

To post a comment you must log in.
Olivier Dony (Odoo) (odo-openerp) wrote :

Hi Ronald,

Thanks a lot for the patch and for your patience, sorry it took so long to process it.

Globally I approve your fix, but some parts of it were not suitable for merging in a stable version, and we need a fix in stable versions too. In order to save you the trouble of updating your merge prop and rebasing it on 7.0, Cedric has done it for you in this merge proposal, based on yours:
   https://code.launchpad.net/~openerp-dev/openobject-server/7.0-sequence-next-fix-csn/+merge/164363

It's now been merged in 7.0 at revision 4978 rev-id: <email address hidden> (credited to you), with an extra patch at revision 4979.
As a result I'll mark this merge proposal as merged, even if it's not technically true. The essence of the patch is indeed merged in 7.0 and will be automatically forward-ported to trunk soon.

Here is a summary of the changes we did:
- we changed the view so that only the 'number_next_actual' is visible in all views. This required fixing the code to avoid crashing during creation but makes the UI simpler for end-users. This implied a small change to the field label and tooltip too.
- we removed the new boolean flags you added (prohibit_*), both because they could not be added to a stable version and because they were technically not necessary to fix the issue. These flags were also not very useful now that only the "number_next_actual" field was visible. The user would only alter or decrease the sequence number if they really meant it. Also the "prohibit_number_next_lower" is a nice-to-have that cannot be made reliable with "standard" sequences: PostgreSQL-backed sequences are cross-transactions so you would be subject to a dirty read phenomenon.
- we changed the default value for `number_next` passed to _alter_sequence() to None, to distinguish "no value" from 0. Otherwise trying to set the value to 0 would be silently ignored. Technically the default PostgreSQL settings forbid starting sequences at 0-, but this is something than can be changed for each sequence, so someone might really want to use it.

Thanks again for this excellent contribution!

Olivier Dony (Odoo) (odo-openerp) wrote :

Note: revision 4978 does not contain the fix for the creation of sequence, that part was applied in revision 4979

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openerp/addons/base/ir/ir_sequence.py'
2--- openerp/addons/base/ir/ir_sequence.py 2012-11-26 09:43:37 +0000
3+++ openerp/addons/base/ir/ir_sequence.py 2012-11-29 11:40:33 +0000
4@@ -22,9 +22,13 @@
5 import logging
6 import time
7
8+<<<<<<< TREE
9 from osv import osv, fields
10 from tools.translate import _
11
12+=======
13+from openerp.tools.translate import _
14+>>>>>>> MERGE-SOURCE
15 import openerp
16
17 _logger = logging.getLogger(__name__)
18@@ -40,7 +44,7 @@
19 _sql_constraints = [
20 ('code_unique', 'unique(code)', '`code` must be unique.'),
21 ]
22-
23+
24 def _code_get(self, cr, uid, context=None):
25 cr.execute('select code, name from ir_sequence_type')
26 return cr.fetchall()
27@@ -55,6 +59,32 @@
28 """
29 _name = 'ir.sequence'
30 _order = 'name'
31+
32+ def _get_number_next_actual(
33+ self, cr, user, ids, field_name, arg, context=None):
34+ '''Return number from ir_sequence row when no_gap implementation,
35+ and number from postgres sequence when standard implementation.'''
36+ res = dict.fromkeys(ids)
37+ for seq_id in ids:
38+ this_obj = self.browse(cr, user, seq_id, context=context)
39+ if this_obj.implementation != 'standard':
40+ res[seq_id] = this_obj.number_next
41+ else:
42+ # get number from postgres sequence. Cannot use
43+ # currval, because that might give an error when
44+ # not having used nextval before.
45+ statement = (
46+ "SELECT last_value, increment_by, is_called"
47+ " FROM ir_sequence_%03d"
48+ % seq_id)
49+ cr.execute(statement)
50+ (last_value, increment_by, is_called) = cr.fetchone()
51+ if is_called:
52+ res[seq_id] = last_value + increment_by
53+ else:
54+ res[seq_id] = last_value
55+ return res
56+
57 _columns = {
58 'name': openerp.osv.fields.char('Name', size=64, required=True),
59 'code': openerp.osv.fields.selection(_code_get, 'Code', size=64),
60@@ -67,11 +97,31 @@
61 'active': openerp.osv.fields.boolean('Active'),
62 'prefix': openerp.osv.fields.char('Prefix', size=64, help="Prefix value of the record for the sequence"),
63 'suffix': openerp.osv.fields.char('Suffix', size=64, help="Suffix value of the record for the sequence"),
64- 'number_next': openerp.osv.fields.integer('Next Number', required=True, help="Next number of this sequence"),
65+ 'number_next': openerp.osv.fields.integer(
66+ 'Next Number', required=True,
67+ help='Next number originally set or reset for this sequence'),
68+ 'number_next_actual': openerp.osv.fields.function(
69+ _get_number_next_actual, type='integer', required=True,
70+ string='Actual Next Number',
71+ help='Next number that will actually be used.'
72+ ' Will be zero for new sequence.'),
73+ 'prohibit_number_next_change': openerp.osv.fields.boolean(
74+ 'Prohibit change of Next Number',
75+ help='Changing next number can wreak havoc on sequences.'
76+ ' Checking this option will prevent accidental changes.'),
77+ 'prohibit_number_next_lower': openerp.osv.fields.boolean(
78+ 'Prohibit lower value for Next Number',
79+ help='When manually setting Next Number to a value lower than the'
80+ ' the current value, there is a big risk for duplicate key errors'
81+ ' when trying to add new sequences generated to the database.'
82+ ' This is especially the case when your prefix or suffix is not'
83+ ' changed at the same time, or has no dynamic part (formula) that'
84+ ' will also change.'),
85 'number_increment': openerp.osv.fields.integer('Increment Number', required=True, help="The next number of the sequence will be incremented by this number"),
86 '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."),
87 'company_id': openerp.osv.fields.many2one('res.company', 'Company'),
88 }
89+
90 _defaults = {
91 'implementation': 'standard',
92 'active': True,
93@@ -79,6 +129,8 @@
94 'number_increment': 1,
95 'number_next': 1,
96 'padding' : 0,
97+ 'prohibit_number_next_change': False,
98+ 'prohibit_number_next_lower': False,
99 }
100
101 def init(self, cr):
102@@ -122,7 +174,7 @@
103 # object depends on it.
104 cr.execute("DROP SEQUENCE IF EXISTS %s RESTRICT " % names)
105
106- def _alter_sequence(self, cr, id, number_increment, number_next):
107+ def _alter_sequence(self, cr, id, number_increment, number_next=0):
108 """ Alter a PostreSQL sequence.
109
110 There is no access rights check.
111@@ -130,10 +182,12 @@
112 if number_increment == 0:
113 raise osv.except_osv(_('Warning!'),_("Increment number must not be zero."))
114 assert isinstance(id, (int, long))
115- cr.execute("""
116- ALTER SEQUENCE ir_sequence_%03d INCREMENT BY %%s RESTART WITH %%s
117- """ % id, (number_increment, number_next))
118-
119+ statement = ("ALTER SEQUENCE ir_sequence_%03d INCREMENT BY %d" %
120+ (id, number_increment))
121+ if number_next:
122+ statement += " RESTART WITH %d" % (number_next, )
123+ cr.execute(statement)
124+
125 def create(self, cr, uid, values, context=None):
126 """ Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
127 """
128@@ -147,10 +201,37 @@
129 super(ir_sequence, self).unlink(cr, uid, ids, context)
130 self._drop_sequence(cr, ids)
131 return True
132+
133+ def _pre_validate(self, cr, uid, ids, values, context=None):
134+ '''If number_next changed, check wether change allowed.
135+If number is lower than actual next number, check wether this is allowed.
136+Raise error when invalid change requested.'''
137+ if not 'number_next' in values:
138+ return
139+ nn_new = values['number_next']
140+ for this_obj in self.browse(cr, uid, ids, context=context):
141+ # Only validate when changed.
142+ if this_obj.number_next != nn_new:
143+ prohibit_change = values.get(
144+ 'prohibit_number_next_change',
145+ this_obj.prohibit_number_next_change)
146+ if prohibit_change:
147+ raise openerp.osv.orm.except_orm(
148+ _('ValidationError'),
149+ _('Chance of Next Number has been prohibited'))
150+ prohibit_lower = values.get(
151+ 'prohibit_number_next_lower',
152+ this_obj.prohibit_number_next_lower)
153+ if prohibit_lower and (this_obj.number_next_actual > nn_new):
154+ raise openerp.osv.orm.except_orm(
155+ _('ValidationError'),
156+ _('Setting Next Number to a value lower than the'
157+ ' actual value has been prohibited'))
158
159 def write(self, cr, uid, ids, values, context=None):
160 if not isinstance(ids, (list, tuple)):
161 ids = [ids]
162+ self._pre_validate(cr, uid, ids, values, context=context)
163 new_implementation = values.get('implementation')
164 rows = self.read(cr, uid, ids, ['implementation', 'number_increment', 'number_next'], context)
165 super(ir_sequence, self).write(cr, uid, ids, values, context)
166@@ -161,7 +242,15 @@
167 n = values.get('number_next', row['number_next'])
168 if row['implementation'] == 'standard':
169 if new_implementation in ('standard', None):
170- self._alter_sequence(cr, row['id'], i, n)
171+ # Implementation has NOT changed.
172+ # Only change sequence if really requested.
173+ if (('number_next' in values)
174+ and (values['number_next'] != row['number_next'])):
175+ self._alter_sequence(cr, row['id'], i, n)
176+ else:
177+ # Just in case only increment changed
178+ if 'number_increment' in values:
179+ self._alter_sequence(cr, row['id'], i)
180 else:
181 self._drop_sequence(cr, row['id'])
182 else:
183@@ -169,7 +258,6 @@
184 pass
185 else:
186 self._create_sequence(cr, row['id'], i, n)
187-
188 return True
189
190 def _interpolate(self, s, d):
191
192=== modified file 'openerp/addons/base/ir/ir_sequence_view.xml'
193--- openerp/addons/base/ir/ir_sequence_view.xml 2012-10-13 12:09:24 +0000
194+++ openerp/addons/base/ir/ir_sequence_view.xml 2012-11-29 11:40:33 +0000
195@@ -20,7 +20,17 @@
196 <field name="suffix"/>
197 <field name="padding"/>
198 <field name="number_increment"/>
199- <field name="number_next"/>
200+ <!-- If number_next_actual is 0, we have a brand new record -->
201+ <field
202+ name="prohibit_number_next_change"
203+ attrs="{'invisible':[('number_next_actual','=',0)]}" />
204+ <field name="prohibit_number_next_lower"
205+ attrs="{'invisible':[('number_next_actual','=',0)]}" />
206+ <field name="number_next_actual"
207+ attrs="{'invisible':[('number_next_actual','=',0)]}" />
208+ <field
209+ name="number_next"
210+ attrs="{'readonly':[('prohibit_number_next_change','=',True),('number_next_actual','&gt;',0)]}" />
211 <field name="implementation"/>
212 </group>
213 <group col="3" string="Legend (for prefix, suffix)">