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

Subscribers

People subscribed via source and target branches