Merge lp:~akretion-team/openerp-product-attributes/openerp-product-attributes_limit_database_column_name into lp:~product-core-editors/openerp-product-attributes/7.0

Proposed by David BEAL (ak)
Status: Merged
Merged at revision: 224
Proposed branch: lp:~akretion-team/openerp-product-attributes/openerp-product-attributes_limit_database_column_name
Merge into: lp:~product-core-editors/openerp-product-attributes/7.0
Diff against target: 459 lines (+180/-86)
2 files modified
base_custom_attributes/custom_attributes.py (+171/-82)
base_custom_attributes/ir_model.py (+9/-4)
To merge this branch: bzr merge lp:~akretion-team/openerp-product-attributes/openerp-product-attributes_limit_database_column_name
Reviewer Review Type Date Requested Status
Guewen Baconnier @ Camptocamp code review Approve
Sébastien BEAU - http://www.akretion.com Pending
Quentin THEURET @Amaris code review Pending
Benoit Guillot - http://www.akretion.com Pending
Review via email: mp+194998@code.launchpad.net

This proposal supersedes a proposal from 2013-09-27.

Description of the change

[IMP] add a function to define allowed chars for database column name + cleaning code .py alignement

To post a comment you must log in.
Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote : Posted in a previous version of this proposal

LGTM,

code review, no test

review: Approve
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal

Hi,

The next time, can you avoid to mix a cleaning and a fix?
Because we can't know where is your change and what it does without parsing and searching through all the diffs.

Can I propose a better name for the function "set_column_name"? "safe_column_name" maybe?

Thanks!

review: Needs Fixing (code review)
Revision history for this message
David BEAL (ak) (davidbeal) wrote : Posted in a previous version of this proposal

Ok

Changes are done

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal

Thanks for the change!
Why not

re.sub(r'\W', '', string)

Instead of

filter((lambda x: re.search('[0-9a-z_]', x)), string)

?

Revision history for this message
David BEAL (ak) (davidbeal) wrote : Posted in a previous version of this proposal

attribute_code in magento doesn't support upper case

If you want to share attributes with openerp, magento and other plateforms you need to apply common rules with the more restrictif system

Revision history for this message
David BEAL (ak) (davidbeal) wrote : Posted in a previous version of this proposal

Hi

Is it OK for everybody ?

Thanks for your response

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal

You apply lower() on the string just before the filter, so anyway you can't
have any uppercase chars, that's why I put \W. However, your regexp is more
explicit.

My remark was more on the usage of filter() which is inefficient vs
re.sub().

Thanks for the changes. Approve (I don't remember how to change my review
state from mail, I will change it afterwards from launchpad)
Le 2 oct. 2013 11:35, "David BEAL" <email address hidden> a écrit :

> attribute_code in magento doesn't support upper case
>
> If you want to share attributes with openerp, magento and other plateforms
> you need to apply common rules with the more restrictif system
> --
>
> https://code.launchpad.net/~akretion-team/openerp-product-attributes/openerp-product-attributes_limit_database_column_name/+merge/188014
> You are reviewing the proposed merge of
> lp:~akretion-team/openerp-product-attributes/openerp-product-attributes_limit_database_column_name
> into lp:openerp-product-attributes.
>

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) : Posted in a previous version of this proposal
review: Approve (code review)
Revision history for this message
Quentin THEURET @Amaris (qtheuret) wrote : Posted in a previous version of this proposal

LGTM

review: Approve (code review)
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal

The merge gives a conflict. Can you rebase on the latest revision please?

review: Needs Resubmitting
219. By David BEAL (ak)

[MERGE] merge again from main branch

220. By David BEAL (ak)

[FIX] extract 'sep' unused variable

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

LGTM

review: Approve (code review)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'base_custom_attributes/custom_attributes.py'
2--- base_custom_attributes/custom_attributes.py 2013-11-12 08:30:11 +0000
3+++ base_custom_attributes/custom_attributes.py 2013-11-13 08:38:32 +0000
4@@ -1,9 +1,9 @@
5 # -*- encoding: utf-8 -*-
6 ###############################################################################
7 # #
8-# base_attribute.attributes for OpenERP #
9-# Copyright (C) 2011 Akretion Benoît GUILLOT <benoit.guillot@akretion.com> #
10-# Copyright (C) 2013 Akretion Raphaël VALYI <raphael.valyi@akretion.com> #
11+# base_attribute.attributes for OpenERP #
12+# Copyright (C) 2011 Akretion Benoît GUILLOT <benoit.guillot@akretion.com>
13+# Copyright (C) 2013 Akretion Raphaël VALYI <raphael.valyi@akretion.com>
14 # #
15 # This program is free software: you can redistribute it and/or modify #
16 # it under the terms of the GNU Affero General Public License as #
17@@ -26,6 +26,15 @@
18 from openerp.tools.translate import _
19 from lxml import etree
20 from unidecode import unidecode # Debian package python-unidecode
21+import re
22+
23+
24+def safe_column_name(string):
25+ """This function prevent portability problem in database column name
26+ with other DBMS system
27+ Use case : if you synchronise attributes with other applications """
28+ string = unidecode(string.replace(' ', '_').lower())
29+ return re.sub(r'[^0-9a-z_]','', string)
30
31
32 class attribute_option(orm.Model):
33@@ -40,15 +49,28 @@
34 return [(r['model'], r['name']) for r in res]
35
36 _columns = {
37- 'name': fields.char('Name', size=128, translate=True, required=True),
38- 'value_ref': fields.reference('Reference', selection=_get_model_list, size=128),
39- 'attribute_id': fields.many2one('attribute.attribute', 'Product Attribute', required=True),
40+ 'name': fields.char(
41+ 'Name',
42+ size=128,
43+ translate=True,
44+ required=True),
45+ 'value_ref': fields.reference(
46+ 'Reference',
47+ selection=_get_model_list,
48+ size=128),
49+ 'attribute_id': fields.many2one(
50+ 'attribute.attribute',
51+ 'Product Attribute',
52+ required=True),
53 'sequence': fields.integer('Sequence'),
54 }
55
56 def name_change(self, cr, uid, ids, name, relation_model_id, context=None):
57 if relation_model_id:
58- warning = {'title': _('Error!'), 'message': _("Use the 'Load Options' button instead to select appropriate model references.")}
59+ warning = {'title': _('Error!'),
60+ 'message': _("Use the 'Load Options' button "
61+ "instead to select appropriate "
62+ "model references'")}
63 return {"value": {"name": False}, "warning": warning}
64 else:
65 return True
66@@ -59,11 +81,15 @@
67 _rec_name = 'attribute_id'
68
69 _columns = {
70- 'attribute_id': fields.many2one('attribute.attribute', 'Attribute', required=True),
71+ 'attribute_id': fields.many2one(
72+ 'attribute.attribute',
73+ 'Product Attribute',
74+ required=True),
75 }
76
77 _defaults = {
78- 'attribute_id': lambda self, cr, uid, context: context.get('attribute_id', False)
79+ 'attribute_id': lambda self, cr, uid, context:
80+ context.get('attribute_id',False)
81 }
82
83 def validate(self, cr, uid, ids, context=None):
84@@ -86,12 +112,16 @@
85 res = super(attribute_option_wizard, self).create(cr, uid, vals, context)
86 return res
87
88- def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
89- res = super(attribute_option_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
90+ def fields_view_get(self, cr, uid, view_id=None, view_type='form',
91+ context=None, toolbar=False, submenu=False):
92+ res = super(attribute_option_wizard, self).fields_view_get(
93+ cr, uid, view_id, view_type, context, toolbar, submenu)
94 if view_type == 'form' and context and context.get("attribute_id"):
95 attr_obj = self.pool.get("attribute.attribute")
96- model_id = attr_obj.read(cr, uid, [context.get("attribute_id")], ['relation_model_id'])[0]['relation_model_id'][0]
97- relation = self.pool.get("ir.model").read(cr, uid, [model_id], ["model"])[0]["model"]
98+ model_id = attr_obj.read(cr, uid, [context.get("attribute_id")],
99+ ['relation_model_id'])[0]['relation_model_id'][0]
100+ relation = self.pool.get("ir.model").read(cr, uid, [model_id],
101+ ["model"])[0]["model"]
102 res['fields'].update({'option_ids': {
103 'domain': [],
104 'string': "Options",
105@@ -118,11 +148,10 @@
106 kwargs = {'name': "%s" % attribute.name}
107 if attribute.ttype in ['many2many', 'text']:
108 parent = etree.SubElement(parent, 'group', colspan="2", col="4")
109- sep = etree.SubElement(parent,
110+ etree.SubElement(parent,
111 'separator',
112 string="%s" % attribute.field_description,
113- colspan="4")
114-
115+ colspan="4")
116 kwargs['nolabel'] = "1"
117 if attribute.ttype in ['many2one', 'many2many']:
118 if attribute.relation_model_id:
119@@ -142,23 +171,30 @@
120 kwargs['required'] = str(attribute.required or
121 attribute.required_on_views)
122 field = etree.SubElement(parent, 'field', **kwargs)
123- orm.setup_modifiers(field, self.fields_get(cr, uid, attribute.name, context))
124+ orm.setup_modifiers(field, self.fields_get(cr, uid, attribute.name,
125+ context))
126 return parent
127
128- def _build_attributes_notebook(self, cr, uid, attribute_group_ids, context=None):
129- notebook = etree.Element('notebook', name="attributes_notebook", colspan="4")
130+ def _build_attributes_notebook(self, cr, uid, attribute_group_ids,
131+ context=None):
132+ notebook = etree.Element('notebook', name="attributes_notebook",
133+ colspan="4")
134 toupdate_fields = []
135 grp_obj = self.pool.get('attribute.group')
136- for group in grp_obj.browse(cr, uid, attribute_group_ids, context=context):
137- page = etree.SubElement(notebook, 'page', string=group.name.capitalize())
138+ for group in grp_obj.browse(cr, uid, attribute_group_ids,
139+ context=context):
140+ page = etree.SubElement(notebook, 'page',
141+ string=group.name.capitalize())
142 for attribute in group.attribute_ids:
143 if attribute.name not in toupdate_fields:
144 toupdate_fields.append(attribute.name)
145- self._build_attribute_field(cr, uid, page, attribute, context=context)
146+ self._build_attribute_field(cr, uid, page, attribute,
147+ context=context)
148 return notebook, toupdate_fields
149
150- def relation_model_id_change(self, cr, uid, ids, relation_model_id, option_ids, context=None):
151- "removed selected options as they would be inconsistent"
152+ def relation_model_id_change(self, cr, uid, ids, relation_model_id,
153+ option_ids, context=None):
154+ "removed selected options as they would be inconsistent"
155 return {'value': {'option_ids': [(2, i[1]) for i in option_ids]}}
156
157 def button_add_options(self, cr, uid, ids, context=None):
158@@ -173,24 +209,36 @@
159 }
160
161 _columns = {
162- 'field_id': fields.many2one('ir.model.fields', 'Ir Model Fields', required=True, ondelete="cascade"),
163- 'attribute_type': fields.selection([('char','Char'),
164- ('text','Text'),
165- ('select','Select'),
166- ('multiselect','Multiselect'),
167- ('boolean','Boolean'),
168- ('integer','Integer'),
169- ('date','Date'),
170- ('datetime','Datetime'),
171- ('binary','Binary'),
172- ('float','Float')],
173- 'Type', required=True),
174- 'serialized': fields.boolean('Field serialized',
175- help="If serialized, the field will be stocked in the serialized field: "
176- "attribute_custom_tmpl or attribute_custom_variant depending on the field based_on"),
177- 'option_ids': fields.one2many('attribute.option', 'attribute_id', 'Attribute Options'),
178+ 'field_id': fields.many2one(
179+ 'ir.model.fields',
180+ 'Ir Model Fields',
181+ required=True,
182+ ondelete="cascade"),
183+ 'attribute_type': fields.selection([
184+ ('char', 'Char'),
185+ ('text', 'Text'),
186+ ('select', 'Select'),
187+ ('multiselect', 'Multiselect'),
188+ ('boolean', 'Boolean'),
189+ ('integer', 'Integer'),
190+ ('date', 'Date'),
191+ ('datetime', 'Datetime'),
192+ ('binary', 'Binary'),
193+ ('float', 'Float')],
194+ 'Type', required=True),
195+ 'serialized': fields.boolean(
196+ 'Field serialized',
197+ help="If serialized, the field will be stocked in the serialized "
198+ "field: attribute_custom_tmpl or attribute_custom_variant "
199+ "depending on the field based_on"),
200+ 'option_ids': fields.one2many(
201+ 'attribute.option',
202+ 'attribute_id',
203+ 'Attribute Options'),
204 'create_date': fields.datetime('Created date', readonly=True),
205- 'relation_model_id': fields.many2one('ir.model', 'Model'),
206+ 'relation_model_id': fields.many2one(
207+ 'ir.model',
208+ 'Model'),
209 'required_on_views': fields.boolean(
210 'Required (on views)',
211 help="If activated, the attribute will be mandatory on the views, "
212@@ -199,11 +247,10 @@
213
214 def create(self, cr, uid, vals, context=None):
215 if vals.get('relation_model_id'):
216- relation = self.pool.get('ir.model').read(cr, uid,
217- [vals.get('relation_model_id')], ['model'])[0]['model']
218+ relation = self.pool.get('ir.model').read(
219+ cr, uid, [vals.get('relation_model_id')], ['model'])[0]['model']
220 else:
221 relation = 'attribute.option'
222-
223 if vals['attribute_type'] == 'select':
224 vals['ttype'] = 'many2one'
225 vals['relation'] = relation
226@@ -216,27 +263,31 @@
227
228 if vals.get('serialized'):
229 field_obj = self.pool.get('ir.model.fields')
230- serialized_ids = field_obj.search(cr, uid,
231- [('ttype', '=', 'serialized'), ('model_id', '=', vals['model_id']),
232- ('name', '=', 'x_custom_json_attrs')], context=context)
233+ serialized_ids = field_obj.search(cr, uid, [
234+ ('ttype', '=', 'serialized'),
235+ ('model_id', '=', vals['model_id']),
236+ ('name', '=', 'x_custom_json_attrs')],
237+ context=context)
238 if serialized_ids:
239 vals['serialization_field_id'] = serialized_ids[0]
240 else:
241 f_vals = {
242 'name': u'x_custom_json_attrs',
243- 'field_description': u'Serialized JSON Attributes',
244+ 'field_description': u'Serialized JSON Attributes',
245 'ttype': 'serialized',
246 'model_id': vals['model_id'],
247 }
248- vals['serialization_field_id'] = field_obj.create(cr, uid, f_vals, {'manual': True})
249+ vals['serialization_field_id'] = field_obj.create(
250+ cr, uid, f_vals, {'manual': True})
251 vals['state'] = 'manual'
252 return super(attribute_attribute, self).create(cr, uid, vals, context)
253
254- def onchange_field_description(self, cr, uid, ids, field_description, name, create_date, context=None):
255+ def onchange_field_description(self, cr, uid, ids, field_description,
256+ name, create_date, context=None):
257 name = name or u'x_'
258 if field_description and not create_date:
259- name = unidecode(u'x_%s' % field_description.replace(' ', '_').lower())
260- return {'value' : {'name' : name}}
261+ name = unicode('x_' + safe_column_name(field_description))
262+ return {'value': {'name': name}}
263
264 def onchange_name(self, cr, uid, ids, name, context=None):
265 res = {}
266@@ -244,15 +295,15 @@
267 name = u'x_%s' % name
268 else:
269 name = u'%s' % name
270- res = {'value' : {'name' : unidecode(name)}}
271+ res = {'value': {'name': unidecode(name)}}
272
273 #FILTER ON MODEL
274- model_domain = []
275 model_name = context.get('force_model')
276 if not model_name:
277 model_id = context.get('default_model_id')
278 if model_id:
279- model = self.pool['ir.model'].browse(cr, uid, model_id, context=context)
280+ model = self.pool['ir.model'].browse(cr, uid, model_id,
281+ context=context)
282 model_name = model.model
283 if model_name:
284 model_obj = self.pool[model_name]
285@@ -264,8 +315,8 @@
286 def _get_default_model(self, cr, uid, context=None):
287 if context and context.get('force_model'):
288 model_id = self.pool['ir.model'].search(cr, uid, [
289- ['model', '=', context['force_model']]
290- ], context=context)
291+ ('model', '=', context['force_model'])
292+ ], context=context)
293 if model_id:
294 return model_id[0]
295 return None
296@@ -276,29 +327,42 @@
297
298
299 class attribute_group(orm.Model):
300- _name= "attribute.group"
301+ _name = "attribute.group"
302 _description = "Attribute Group"
303- _order="sequence"
304+ _order ="sequence"
305
306 _columns = {
307- 'name': fields.char('Name', size=128, required=True, translate=True),
308+ 'name': fields.char(
309+ 'Name',
310+ size=128,
311+ required=True,
312+ translate=True),
313 'sequence': fields.integer('Sequence'),
314- 'attribute_set_id': fields.many2one('attribute.set', 'Attribute Set'),
315- 'attribute_ids': fields.one2many('attribute.location', 'attribute_group_id', 'Attributes'),
316- 'model_id': fields.many2one('ir.model', 'Model', required=True),
317+ 'attribute_set_id': fields.many2one(
318+ 'attribute.set',
319+ 'Attribute Set'),
320+ 'attribute_ids': fields.one2many(
321+ 'attribute.location',
322+ 'attribute_group_id',
323+ 'Attributes'),
324+ 'model_id': fields.many2one(
325+ 'ir.model',
326+ 'Model',
327+ required=True),
328 }
329
330 def create(self, cr, uid, vals, context=None):
331- for attribute in vals.get('attribute_ids', []):
332- if vals.get('attribute_set_id') and attribute[2] and not attribute[2].get('attribute_set_id'):
333+ for attribute in vals['attribute_ids']:
334+ if vals.get('attribute_set_id') and attribute[2] and \
335+ not attribute[2].get('attribute_set_id'):
336 attribute[2]['attribute_set_id'] = vals['attribute_set_id']
337 return super(attribute_group, self).create(cr, uid, vals, context)
338
339 def _get_default_model(self, cr, uid, context=None):
340 if context and context.get('force_model'):
341- model_id = self.pool['ir.model'].search(cr, uid, [
342- ['model', '=', context['force_model']]
343- ], context=context)
344+ model_id = self.pool['ir.model'].search(
345+ cr, uid, [['model', '=', context['force_model']]],
346+ context=context)
347 if model_id:
348 return model_id[0]
349 return None
350@@ -312,16 +376,26 @@
351 _name = "attribute.set"
352 _description = "Attribute Set"
353 _columns = {
354- 'name': fields.char('Name', size=128, required=True, translate=True),
355- 'attribute_group_ids': fields.one2many('attribute.group', 'attribute_set_id', 'Attribute Groups'),
356- 'model_id': fields.many2one('ir.model', 'Model', required=True),
357+ 'name': fields.char(
358+ 'Name',
359+ size=128,
360+ required=True,
361+ translate=True),
362+ 'attribute_group_ids': fields.one2many(
363+ 'attribute.group',
364+ 'attribute_set_id',
365+ 'Attribute Groups'),
366+ 'model_id': fields.many2one(
367+ 'ir.model',
368+ 'Model',
369+ required=True),
370 }
371
372 def _get_default_model(self, cr, uid, context=None):
373 if context and context.get('force_model'):
374- model_id = self.pool['ir.model'].search(cr, uid, [
375- ['model', '=', context['force_model']]
376- ], context=context)
377+ model_id = self.pool['ir.model'].search(
378+ cr, uid, [['model', '=', context['force_model']]],
379+ context=context)
380 if model_id:
381 return model_id[0]
382 return None
383@@ -330,22 +404,37 @@
384 'model_id': _get_default_model
385 }
386
387+
388 class attribute_location(orm.Model):
389 _name = "attribute.location"
390 _description = "Attribute Location"
391 _order="sequence"
392 _inherits = {'attribute.attribute': 'attribute_id'}
393
394-
395 def _get_attribute_loc_from_group(self, cr, uid, ids, context=None):
396- return self.pool.get('attribute.location').search(cr, uid, [('attribute_group_id', 'in', ids)], context=context)
397+ return self.pool.get('attribute.location').search(
398+ cr, uid, [('attribute_group_id', 'in', ids)], context=context)
399
400 _columns = {
401- 'attribute_id': fields.many2one('attribute.attribute', 'Attribute', required=True, ondelete="cascade"),
402- 'attribute_set_id': fields.related('attribute_group_id', 'attribute_set_id', type='many2one', relation='attribute.set', string='Attribute Set', readonly=True,
403-store={
404- 'attribute.group': (_get_attribute_loc_from_group, ['attribute_set_id'], 10),
405- }),
406- 'attribute_group_id': fields.many2one('attribute.group', 'Attribute Group', required=True),
407+ 'attribute_id': fields.many2one(
408+ 'attribute.attribute',
409+ 'Product Attribute',
410+ required=True,
411+ ondelete="cascade"),
412+ 'attribute_set_id': fields.related(
413+ 'attribute_group_id',
414+ 'attribute_set_id',
415+ type='many2one',
416+ relation='attribute.set',
417+ string='Attribute Set',
418+ readonly=True,
419+ store={
420+ 'attribute.group': (_get_attribute_loc_from_group,
421+ ['attribute_set_id'], 10),
422+ }),
423+ 'attribute_group_id': fields.many2one(
424+ 'attribute.group',
425+ 'Attribute Group',
426+ required=True),
427 'sequence': fields.integer('Sequence'),
428 }
429
430=== modified file 'base_custom_attributes/ir_model.py'
431--- base_custom_attributes/ir_model.py 2012-08-21 14:10:21 +0000
432+++ base_custom_attributes/ir_model.py 2013-11-13 08:38:32 +0000
433@@ -1,8 +1,8 @@
434 # -*- encoding: utf-8 -*-
435 ###############################################################################
436 # #
437-# product_custom_attributes for OpenERP #
438-# Copyright (C) 2011 Akretion Benoît GUILLOT <benoit.guillot@akretion.com> #
439+# product_custom_attributes for OpenERP
440+# Copyright (C) 2011 Akretion Benoît GUILLOT <benoit.guillot@akretion.com>
441 # #
442 # This program is free software: you can redistribute it and/or modify #
443 # it under the terms of the GNU Affero General Public License as #
444@@ -27,8 +27,13 @@
445
446 _inherit = "ir.model.fields"
447 _columns = {
448- 'field_description': fields.char('Field Label', required=True, size=256, translate=True),
449+ 'field_description': fields.char(
450+ 'Field Label',
451+ required=True,
452+ size=256,
453+ translate=True),
454 }
455 _sql_constraints = [
456- ('name_model_uniq', 'unique (name, model_id)', 'The name of the field has to be uniq for a given model !'),
457+ ('name_model_uniq', 'unique (name, model_id)',
458+ 'The name of the field has to be uniq for a given model !'),
459 ]

Subscribers

People subscribed via source and target branches