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