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

Proposed by Ronald Portier (Therp)
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 Pending
Review via email: mp+136913@code.launchpad.net

Description of the change

Fixes lp960201

To post a comment you must log in.
Revision history for this message
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!

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