Merge lp:~openerp-dev/openobject-server/trunk-replace-inherit_option_id-xmo into lp:openobject-server
- trunk-replace-inherit_option_id-xmo
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~openerp-dev/openobject-server/trunk-replace-inherit_option_id-xmo |
Merge into: | lp:openobject-server |
Diff against target: |
753 lines (+491/-42) 5 files modified
openerp/addons/base/ir/ir_ui_view.py (+95/-13) openerp/addons/base/tests/test_views.py (+370/-10) openerp/import_xml.rng (+16/-2) openerp/osv/orm.py (+3/-13) openerp/tools/convert.py (+7/-4) |
To merge this branch: | bzr merge lp:~openerp-dev/openobject-server/trunk-replace-inherit_option_id-xmo |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Olivier Dony (Odoo) | Pending | ||
Review via email: mp+218404@code.launchpad.net |
Commit message
Description of the change
Replaces inherit_option_id by a 3-state selection:
* always applied
* optional, currently enabled
* optional, currently disabled
Addons part (most of the work) at https:/
- 5209. By Xavier (Open ERP)
-
[MERGE] from extended view inheritance, add help for application field
Unmerged revisions
- 5209. By Xavier (Open ERP)
-
[MERGE] from extended view inheritance, add help for application field
- 5208. By Xavier (Open ERP)
-
[IMP] inherit_option_id -> application
- 5207. By Xavier (Open ERP)
-
[IMP] prevent changing a view from application: always to application: disabled
not sure that's actually useful, and can still go always -> enabled -> disabled...
- 5206. By Xavier (Open ERP)
-
[ADD] application field & check during inheriting views read
- 5205. By Xavier (Open ERP)
-
[MERGE] from trunk
- 5204. By Xavier (Open ERP)
-
[ADD] support for primary mode in <template>
should probably validate that there's an inherit_id (?)
- 5203. By Xavier (Open ERP)
-
[ADD] use of explicit primary mode in read_combined
- 5202. By Xavier (Open ERP)
-
[FIX] default_view should be based on mode=primary, not on inherit_id=False
- 5201. By Xavier (Open ERP)
-
[ADD] mode attribute to views
Not used yet, only defined its relation to inherit_id:
not inherit_id + primary -> ok
not inherit_id + extension -> error
inherit_id + primary -> ok
inherit_id + extension -> ok - 5200. By Xavier (Open ERP)
-
[IMP] simplify handling of callable _constraint message
Preview Diff
1 | === modified file 'openerp/addons/base/ir/ir_ui_view.py' | |||
2 | --- openerp/addons/base/ir/ir_ui_view.py 2014-05-01 15:26:04 +0000 | |||
3 | +++ openerp/addons/base/ir/ir_ui_view.py 2014-05-06 14:57:36 +0000 | |||
4 | @@ -117,8 +117,36 @@ | |||
5 | 117 | 'groups_id': fields.many2many('res.groups', 'ir_ui_view_group_rel', 'view_id', 'group_id', | 117 | 'groups_id': fields.many2many('res.groups', 'ir_ui_view_group_rel', 'view_id', 'group_id', |
6 | 118 | string='Groups', help="If this field is empty, the view applies to all users. Otherwise, the view applies to the users of those groups only."), | 118 | string='Groups', help="If this field is empty, the view applies to all users. Otherwise, the view applies to the users of those groups only."), |
7 | 119 | 'model_ids': fields.one2many('ir.model.data', 'res_id', domain=[('model','=','ir.ui.view')], auto_join=True), | 119 | 'model_ids': fields.one2many('ir.model.data', 'res_id', domain=[('model','=','ir.ui.view')], auto_join=True), |
8 | 120 | |||
9 | 121 | 'mode': fields.selection( | ||
10 | 122 | [('primary', "Base view"), ('extension', "Extension View")], | ||
11 | 123 | string="View inheritance mode", required=True, | ||
12 | 124 | help="""Only applies if this view inherits from an other one (inherit_id is not False/Null). | ||
13 | 125 | |||
14 | 126 | * if extension (default), if this view is requested the closest primary view | ||
15 | 127 | is looked up (via inherit_id), then all views inheriting from it with this | ||
16 | 128 | view's model are applied | ||
17 | 129 | * if primary, the closest primary view is fully resolved (even if it uses a | ||
18 | 130 | different model than this one), then this view's inheritance specs | ||
19 | 131 | (<xpath/>) are applied, and the result is used as if it were this view's | ||
20 | 132 | actual arch. | ||
21 | 133 | """), | ||
22 | 134 | |||
23 | 135 | 'application': fields.selection([ | ||
24 | 136 | ('always', "Always applied"), | ||
25 | 137 | ('enabled', "Optional, enabled"), | ||
26 | 138 | ('disabled', "Optional, disabled"), | ||
27 | 139 | ], | ||
28 | 140 | required=True, string="Application status", | ||
29 | 141 | help="""If this view is inherited, | ||
30 | 142 | * if always, the view always extends its parent | ||
31 | 143 | * if enabled, the view currently extends its parent but can be disabled | ||
32 | 144 | * if disabled, the view currently does not extend its parent but can be enabled | ||
33 | 145 | """), | ||
34 | 120 | } | 146 | } |
35 | 121 | _defaults = { | 147 | _defaults = { |
36 | 148 | 'mode': 'primary', | ||
37 | 149 | 'application': 'always', | ||
38 | 122 | 'priority': 16, | 150 | 'priority': 16, |
39 | 123 | } | 151 | } |
40 | 124 | _order = "priority,name" | 152 | _order = "priority,name" |
41 | @@ -167,8 +195,14 @@ | |||
42 | 167 | return False | 195 | return False |
43 | 168 | return True | 196 | return True |
44 | 169 | 197 | ||
45 | 198 | _sql_constraints = [ | ||
46 | 199 | ('inheritance_mode', | ||
47 | 200 | "CHECK (mode != 'extension' OR inherit_id IS NOT NULL)", | ||
48 | 201 | "Invalid inheritance mode: if the mode is 'extension', the view must" | ||
49 | 202 | " extend an other view"), | ||
50 | 203 | ] | ||
51 | 170 | _constraints = [ | 204 | _constraints = [ |
53 | 171 | (_check_xml, 'Invalid view definition', ['arch']) | 205 | (_check_xml, 'Invalid view definition', ['arch']), |
54 | 172 | ] | 206 | ] |
55 | 173 | 207 | ||
56 | 174 | def _auto_init(self, cr, context=None): | 208 | def _auto_init(self, cr, context=None): |
57 | @@ -177,6 +211,12 @@ | |||
58 | 177 | if not cr.fetchone(): | 211 | if not cr.fetchone(): |
59 | 178 | cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)') | 212 | cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)') |
60 | 179 | 213 | ||
61 | 214 | def _compute_defaults(self, cr, uid, values, context=None): | ||
62 | 215 | if 'inherit_id' in values: | ||
63 | 216 | values.setdefault( | ||
64 | 217 | 'mode', 'extension' if values['inherit_id'] else 'primary') | ||
65 | 218 | return values | ||
66 | 219 | |||
67 | 180 | def create(self, cr, uid, values, context=None): | 220 | def create(self, cr, uid, values, context=None): |
68 | 181 | if 'type' not in values: | 221 | if 'type' not in values: |
69 | 182 | if values.get('inherit_id'): | 222 | if values.get('inherit_id'): |
70 | @@ -185,10 +225,13 @@ | |||
71 | 185 | values['type'] = etree.fromstring(values['arch']).tag | 225 | values['type'] = etree.fromstring(values['arch']).tag |
72 | 186 | 226 | ||
73 | 187 | if not values.get('name'): | 227 | if not values.get('name'): |
75 | 188 | values['name'] = "%s %s" % (values['model'], values['type']) | 228 | values['name'] = "%s %s" % (values.get('model'), values['type']) |
76 | 189 | 229 | ||
77 | 190 | self.read_template.clear_cache(self) | 230 | self.read_template.clear_cache(self) |
79 | 191 | return super(view, self).create(cr, uid, values, context) | 231 | return super(view, self).create( |
80 | 232 | cr, uid, | ||
81 | 233 | self._compute_defaults(cr, uid, values, context=context), | ||
82 | 234 | context=context) | ||
83 | 192 | 235 | ||
84 | 193 | def write(self, cr, uid, ids, vals, context=None): | 236 | def write(self, cr, uid, ids, vals, context=None): |
85 | 194 | if not isinstance(ids, (list, tuple)): | 237 | if not isinstance(ids, (list, tuple)): |
86 | @@ -202,17 +245,43 @@ | |||
87 | 202 | if custom_view_ids: | 245 | if custom_view_ids: |
88 | 203 | self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids) | 246 | self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids) |
89 | 204 | 247 | ||
90 | 248 | if vals.get('application') == 'disabled': | ||
91 | 249 | from_always = self.search( | ||
92 | 250 | cr, uid, [('id', 'in', ids), ('application', '=', 'always')], context=context) | ||
93 | 251 | if from_always: | ||
94 | 252 | raise ValueError( | ||
95 | 253 | "Can't disable views %s marked as always applied" % ( | ||
96 | 254 | ', '.join(map(str, from_always)))) | ||
97 | 255 | |||
98 | 205 | self.read_template.clear_cache(self) | 256 | self.read_template.clear_cache(self) |
100 | 206 | ret = super(view, self).write(cr, uid, ids, vals, context) | 257 | ret = super(view, self).write( |
101 | 258 | cr, uid, ids, | ||
102 | 259 | self._compute_defaults(cr, uid, vals, context=context), | ||
103 | 260 | context) | ||
104 | 207 | 261 | ||
105 | 208 | # if arch is modified views become noupdatable | 262 | # if arch is modified views become noupdatable |
107 | 209 | if 'arch' in vals and not context.get('install_mode', False): | 263 | if 'arch' in vals and not context.get('install_mode'): |
108 | 210 | # TODO: should be doable in a read and a write | 264 | # TODO: should be doable in a read and a write |
109 | 211 | for view_ in self.browse(cr, uid, ids, context=context): | 265 | for view_ in self.browse(cr, uid, ids, context=context): |
110 | 212 | if view_.model_data_id: | 266 | if view_.model_data_id: |
111 | 213 | self.pool.get('ir.model.data').write(cr, openerp.SUPERUSER_ID, view_.model_data_id.id, {'noupdate': True}) | 267 | self.pool.get('ir.model.data').write(cr, openerp.SUPERUSER_ID, view_.model_data_id.id, {'noupdate': True}) |
112 | 214 | return ret | 268 | return ret |
113 | 215 | 269 | ||
114 | 270 | def toggle(self, cr, uid, ids, context=None): | ||
115 | 271 | """ Switches between enabled and disabled application statuses | ||
116 | 272 | """ | ||
117 | 273 | for view in self.browse(cr, uid, ids, context=context): | ||
118 | 274 | if view.application == 'enabled': | ||
119 | 275 | view.write({'application': 'disabled'}) | ||
120 | 276 | elif view.application == 'disabled': | ||
121 | 277 | view.write({'application': 'enabled'}) | ||
122 | 278 | else: | ||
123 | 279 | raise ValueError(_("Can't toggle view %d with application %r") % ( | ||
124 | 280 | view.id, | ||
125 | 281 | view.application, | ||
126 | 282 | )) | ||
127 | 283 | |||
128 | 284 | |||
129 | 216 | def copy(self, cr, uid, id, default=None, context=None): | 285 | def copy(self, cr, uid, id, default=None, context=None): |
130 | 217 | if not default: | 286 | if not default: |
131 | 218 | default = {} | 287 | default = {} |
132 | @@ -224,7 +293,7 @@ | |||
133 | 224 | # default view selection | 293 | # default view selection |
134 | 225 | def default_view(self, cr, uid, model, view_type, context=None): | 294 | def default_view(self, cr, uid, model, view_type, context=None): |
135 | 226 | """ Fetches the default view for the provided (model, view_type) pair: | 295 | """ Fetches the default view for the provided (model, view_type) pair: |
137 | 227 | view with no parent (inherit_id=Fase) with the lowest priority. | 296 | primary view with the lowest priority. |
138 | 228 | 297 | ||
139 | 229 | :param str model: | 298 | :param str model: |
140 | 230 | :param int view_type: | 299 | :param int view_type: |
141 | @@ -234,7 +303,7 @@ | |||
142 | 234 | domain = [ | 303 | domain = [ |
143 | 235 | ['model', '=', model], | 304 | ['model', '=', model], |
144 | 236 | ['type', '=', view_type], | 305 | ['type', '=', view_type], |
146 | 237 | ['inherit_id', '=', False], | 306 | ['mode', '=', 'primary'], |
147 | 238 | ] | 307 | ] |
148 | 239 | ids = self.search(cr, uid, domain, limit=1, order='priority', context=context) | 308 | ids = self.search(cr, uid, domain, limit=1, order='priority', context=context) |
149 | 240 | if not ids: | 309 | if not ids: |
150 | @@ -260,15 +329,19 @@ | |||
151 | 260 | 329 | ||
152 | 261 | user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id) | 330 | user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id) |
153 | 262 | 331 | ||
156 | 263 | check_view_ids = context and context.get('check_view_ids') or (0,) | 332 | conditions = [ |
157 | 264 | conditions = [['inherit_id', '=', view_id], ['model', '=', model]] | 333 | ['inherit_id', '=', view_id], |
158 | 334 | ['model', '=', model], | ||
159 | 335 | ['mode', '=', 'extension'], | ||
160 | 336 | ['application', 'in', ['always', 'enabled']], | ||
161 | 337 | ] | ||
162 | 265 | if self.pool._init: | 338 | if self.pool._init: |
163 | 266 | # Module init currently in progress, only consider views from | 339 | # Module init currently in progress, only consider views from |
164 | 267 | # modules whose code is already loaded | 340 | # modules whose code is already loaded |
165 | 268 | conditions.extend([ | 341 | conditions.extend([ |
166 | 269 | '|', | 342 | '|', |
167 | 270 | ['model_ids.module', 'in', tuple(self.pool._init_modules)], | 343 | ['model_ids.module', 'in', tuple(self.pool._init_modules)], |
169 | 271 | ['id', 'in', check_view_ids], | 344 | ['id', 'in', context and context.get('check_view_ids') or (0,)], |
170 | 272 | ]) | 345 | ]) |
171 | 273 | view_ids = self.search(cr, uid, conditions, context=context) | 346 | view_ids = self.search(cr, uid, conditions, context=context) |
172 | 274 | 347 | ||
173 | @@ -424,7 +497,7 @@ | |||
174 | 424 | if context is None: context = {} | 497 | if context is None: context = {} |
175 | 425 | if root_id is None: | 498 | if root_id is None: |
176 | 426 | root_id = source_id | 499 | root_id = source_id |
178 | 427 | sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, uid, source_id, model, context=context) | 500 | sql_inherit = self.pool['ir.ui.view'].get_inheriting_views_arch(cr, uid, source_id, model, context=context) |
179 | 428 | for (specs, view_id) in sql_inherit: | 501 | for (specs, view_id) in sql_inherit: |
180 | 429 | specs_tree = etree.fromstring(specs.encode('utf-8')) | 502 | specs_tree = etree.fromstring(specs.encode('utf-8')) |
181 | 430 | if context.get('inherit_branding'): | 503 | if context.get('inherit_branding'): |
182 | @@ -447,7 +520,7 @@ | |||
183 | 447 | 520 | ||
184 | 448 | # if view_id is not a root view, climb back to the top. | 521 | # if view_id is not a root view, climb back to the top. |
185 | 449 | base = v = self.browse(cr, uid, view_id, context=context) | 522 | base = v = self.browse(cr, uid, view_id, context=context) |
187 | 450 | while v.inherit_id: | 523 | while v.mode != 'primary': |
188 | 451 | v = v.inherit_id | 524 | v = v.inherit_id |
189 | 452 | root_id = v.id | 525 | root_id = v.id |
190 | 453 | 526 | ||
191 | @@ -457,7 +530,16 @@ | |||
192 | 457 | 530 | ||
193 | 458 | # read the view arch | 531 | # read the view arch |
194 | 459 | [view] = self.read(cr, uid, [root_id], fields=fields, context=context) | 532 | [view] = self.read(cr, uid, [root_id], fields=fields, context=context) |
196 | 460 | arch_tree = etree.fromstring(view['arch'].encode('utf-8')) | 533 | view_arch = etree.fromstring(view['arch'].encode('utf-8')) |
197 | 534 | if not v.inherit_id: | ||
198 | 535 | arch_tree = view_arch | ||
199 | 536 | else: | ||
200 | 537 | parent_view = self.read_combined( | ||
201 | 538 | cr, uid, v.inherit_id.id, fields=fields, context=context) | ||
202 | 539 | arch_tree = etree.fromstring(parent_view['arch']) | ||
203 | 540 | self.apply_inheritance_specs( | ||
204 | 541 | cr, uid, arch_tree, view_arch, parent_view['id'], context=context) | ||
205 | 542 | |||
206 | 461 | 543 | ||
207 | 462 | if context.get('inherit_branding'): | 544 | if context.get('inherit_branding'): |
208 | 463 | arch_tree.attrib.update({ | 545 | arch_tree.attrib.update({ |
209 | 464 | 546 | ||
210 | === modified file 'openerp/addons/base/tests/test_views.py' | |||
211 | --- openerp/addons/base/tests/test_views.py 2014-04-08 11:49:36 +0000 | |||
212 | +++ openerp/addons/base/tests/test_views.py 2014-05-06 14:57:36 +0000 | |||
213 | @@ -1,12 +1,16 @@ | |||
214 | 1 | # -*- encoding: utf-8 -*- | 1 | # -*- encoding: utf-8 -*- |
215 | 2 | from functools import partial | 2 | from functools import partial |
216 | 3 | import itertools | ||
217 | 3 | 4 | ||
218 | 4 | import unittest2 | 5 | import unittest2 |
219 | 5 | 6 | ||
220 | 6 | from lxml import etree as ET | 7 | from lxml import etree as ET |
221 | 7 | from lxml.builder import E | 8 | from lxml.builder import E |
222 | 8 | 9 | ||
223 | 10 | from psycopg2 import IntegrityError | ||
224 | 11 | |||
225 | 9 | from openerp.tests import common | 12 | from openerp.tests import common |
226 | 13 | import openerp.tools | ||
227 | 10 | 14 | ||
228 | 11 | Field = E.field | 15 | Field = E.field |
229 | 12 | 16 | ||
230 | @@ -14,9 +18,22 @@ | |||
231 | 14 | def setUp(self): | 18 | def setUp(self): |
232 | 15 | super(ViewCase, self).setUp() | 19 | super(ViewCase, self).setUp() |
233 | 16 | self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual) | 20 | self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual) |
234 | 21 | self.Views = self.registry('ir.ui.view') | ||
235 | 22 | |||
236 | 23 | def browse(self, id, context=None): | ||
237 | 24 | return self.Views.browse(self.cr, self.uid, id, context=context) | ||
238 | 25 | def create(self, value, context=None): | ||
239 | 26 | return self.Views.create(self.cr, self.uid, value, context=context) | ||
240 | 27 | |||
241 | 28 | def read_combined(self, id): | ||
242 | 29 | return self.Views.read_combined( | ||
243 | 30 | self.cr, self.uid, | ||
244 | 31 | id, ['arch'], | ||
245 | 32 | context={'check_view_ids': self.Views.search(self.cr, self.uid, [])} | ||
246 | 33 | ) | ||
247 | 17 | 34 | ||
248 | 18 | def assertTreesEqual(self, n1, n2, msg=None): | 35 | def assertTreesEqual(self, n1, n2, msg=None): |
250 | 19 | self.assertEqual(n1.tag, n2.tag) | 36 | self.assertEqual(n1.tag, n2.tag, msg) |
251 | 20 | self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg) | 37 | self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg) |
252 | 21 | self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg) | 38 | self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg) |
253 | 22 | 39 | ||
254 | @@ -24,8 +41,8 @@ | |||
255 | 24 | # equality (!?!?!?!) | 41 | # equality (!?!?!?!) |
256 | 25 | self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg) | 42 | self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg) |
257 | 26 | 43 | ||
260 | 27 | for c1, c2 in zip(n1, n2): | 44 | for c1, c2 in itertools.izip_longest(n1, n2): |
261 | 28 | self.assertTreesEqual(c1, c2, msg) | 45 | self.assertEqual(c1, c2, msg) |
262 | 29 | 46 | ||
263 | 30 | 47 | ||
264 | 31 | class TestNodeLocator(common.TransactionCase): | 48 | class TestNodeLocator(common.TransactionCase): |
265 | @@ -374,13 +391,6 @@ | |||
266 | 374 | """ Applies a sequence of modificator archs to a base view | 391 | """ Applies a sequence of modificator archs to a base view |
267 | 375 | """ | 392 | """ |
268 | 376 | 393 | ||
269 | 377 | class TestViewCombined(ViewCase): | ||
270 | 378 | """ | ||
271 | 379 | Test fallback operations of View.read_combined: | ||
272 | 380 | * defaults mapping | ||
273 | 381 | * ? | ||
274 | 382 | """ | ||
275 | 383 | |||
276 | 384 | class TestNoModel(ViewCase): | 394 | class TestNoModel(ViewCase): |
277 | 385 | def test_create_view_nomodel(self): | 395 | def test_create_view_nomodel(self): |
278 | 386 | View = self.registry('ir.ui.view') | 396 | View = self.registry('ir.ui.view') |
279 | @@ -628,6 +638,8 @@ | |||
280 | 628 | def _insert_view(self, **kw): | 638 | def _insert_view(self, **kw): |
281 | 629 | """Insert view into database via a query to passtrough validation""" | 639 | """Insert view into database via a query to passtrough validation""" |
282 | 630 | kw.pop('id', None) | 640 | kw.pop('id', None) |
283 | 641 | kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary') | ||
284 | 642 | kw.setdefault('application', 'always') | ||
285 | 631 | 643 | ||
286 | 632 | keys = sorted(kw.keys()) | 644 | keys = sorted(kw.keys()) |
287 | 633 | fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys) | 645 | fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys) |
288 | @@ -804,3 +816,351 @@ | |||
289 | 804 | E.button(name="action_next", type="object", string="New button")), | 816 | E.button(name="action_next", type="object", string="New button")), |
290 | 805 | string="Replacement title", version="7.0" | 817 | string="Replacement title", version="7.0" |
291 | 806 | )) | 818 | )) |
292 | 819 | |||
293 | 820 | class ViewModeField(ViewCase): | ||
294 | 821 | """ | ||
295 | 822 | This should probably, eventually, be folded back into other test case | ||
296 | 823 | classes, integrating the test (or not) of the mode field to regular cases | ||
297 | 824 | """ | ||
298 | 825 | |||
299 | 826 | def testModeImplicitValue(self): | ||
300 | 827 | """ mode is auto-generated from inherit_id: | ||
301 | 828 | * inherit_id -> mode=extension | ||
302 | 829 | * not inherit_id -> mode=primary | ||
303 | 830 | """ | ||
304 | 831 | view = self.browse(self.create({ | ||
305 | 832 | 'inherit_id': None, | ||
306 | 833 | 'arch': '<qweb/>' | ||
307 | 834 | })) | ||
308 | 835 | self.assertEqual(view.mode, 'primary') | ||
309 | 836 | |||
310 | 837 | view2 = self.browse(self.create({ | ||
311 | 838 | 'inherit_id': view.id, | ||
312 | 839 | 'arch': '<qweb/>' | ||
313 | 840 | })) | ||
314 | 841 | self.assertEqual(view2.mode, 'extension') | ||
315 | 842 | |||
316 | 843 | @openerp.tools.mute_logger('openerp.sql_db') | ||
317 | 844 | def testModeExplicit(self): | ||
318 | 845 | view = self.browse(self.create({ | ||
319 | 846 | 'inherit_id': None, | ||
320 | 847 | 'arch': '<qweb/>' | ||
321 | 848 | })) | ||
322 | 849 | view2 = self.browse(self.create({ | ||
323 | 850 | 'inherit_id': view.id, | ||
324 | 851 | 'mode': 'primary', | ||
325 | 852 | 'arch': '<qweb/>' | ||
326 | 853 | })) | ||
327 | 854 | self.assertEqual(view.mode, 'primary') | ||
328 | 855 | |||
329 | 856 | with self.assertRaises(IntegrityError): | ||
330 | 857 | self.create({ | ||
331 | 858 | 'inherit_id': None, | ||
332 | 859 | 'mode': 'extension', | ||
333 | 860 | 'arch': '<qweb/>' | ||
334 | 861 | }) | ||
335 | 862 | |||
336 | 863 | @openerp.tools.mute_logger('openerp.sql_db') | ||
337 | 864 | def testPurePrimaryToExtension(self): | ||
338 | 865 | """ | ||
339 | 866 | A primary view with inherit_id=None can't be converted to extension | ||
340 | 867 | """ | ||
341 | 868 | view_pure_primary = self.browse(self.create({ | ||
342 | 869 | 'inherit_id': None, | ||
343 | 870 | 'arch': '<qweb/>' | ||
344 | 871 | })) | ||
345 | 872 | with self.assertRaises(IntegrityError): | ||
346 | 873 | view_pure_primary.write({'mode': 'extension'}) | ||
347 | 874 | |||
348 | 875 | def testInheritPrimaryToExtension(self): | ||
349 | 876 | """ | ||
350 | 877 | A primary view with an inherit_id can be converted to extension | ||
351 | 878 | """ | ||
352 | 879 | base = self.create({'inherit_id': None, 'arch': '<qweb/>'}) | ||
353 | 880 | view = self.browse(self.create({ | ||
354 | 881 | 'inherit_id': base, | ||
355 | 882 | 'mode': 'primary', | ||
356 | 883 | 'arch': '<qweb/>' | ||
357 | 884 | })) | ||
358 | 885 | |||
359 | 886 | view.write({'mode': 'extension'}) | ||
360 | 887 | |||
361 | 888 | def testDefaultExtensionToPrimary(self): | ||
362 | 889 | """ | ||
363 | 890 | An extension view can be converted to primary | ||
364 | 891 | """ | ||
365 | 892 | base = self.create({'inherit_id': None, 'arch': '<qweb/>'}) | ||
366 | 893 | view = self.browse(self.create({ | ||
367 | 894 | 'inherit_id': base, | ||
368 | 895 | 'arch': '<qweb/>' | ||
369 | 896 | })) | ||
370 | 897 | |||
371 | 898 | view.write({'mode': 'primary'}) | ||
372 | 899 | |||
373 | 900 | class TestDefaultView(ViewCase): | ||
374 | 901 | def testDefaultViewBase(self): | ||
375 | 902 | self.create({ | ||
376 | 903 | 'inherit_id': False, | ||
377 | 904 | 'priority': 10, | ||
378 | 905 | 'mode': 'primary', | ||
379 | 906 | 'arch': '<qweb/>', | ||
380 | 907 | }) | ||
381 | 908 | v2 = self.create({ | ||
382 | 909 | 'inherit_id': False, | ||
383 | 910 | 'priority': 1, | ||
384 | 911 | 'mode': 'primary', | ||
385 | 912 | 'arch': '<qweb/>', | ||
386 | 913 | }) | ||
387 | 914 | |||
388 | 915 | default = self.Views.default_view(self.cr, self.uid, False, 'qweb') | ||
389 | 916 | self.assertEqual( | ||
390 | 917 | default, v2, | ||
391 | 918 | "default_view should get the view with the lowest priority for " | ||
392 | 919 | "a (model, view_type) pair" | ||
393 | 920 | ) | ||
394 | 921 | |||
395 | 922 | def testDefaultViewPrimary(self): | ||
396 | 923 | v1 = self.create({ | ||
397 | 924 | 'inherit_id': False, | ||
398 | 925 | 'priority': 10, | ||
399 | 926 | 'mode': 'primary', | ||
400 | 927 | 'arch': '<qweb/>', | ||
401 | 928 | }) | ||
402 | 929 | self.create({ | ||
403 | 930 | 'inherit_id': False, | ||
404 | 931 | 'priority': 5, | ||
405 | 932 | 'mode': 'primary', | ||
406 | 933 | 'arch': '<qweb/>', | ||
407 | 934 | }) | ||
408 | 935 | v3 = self.create({ | ||
409 | 936 | 'inherit_id': v1, | ||
410 | 937 | 'priority': 1, | ||
411 | 938 | 'mode': 'primary', | ||
412 | 939 | 'arch': '<qweb/>', | ||
413 | 940 | }) | ||
414 | 941 | |||
415 | 942 | default = self.Views.default_view(self.cr, self.uid, False, 'qweb') | ||
416 | 943 | self.assertEqual( | ||
417 | 944 | default, v3, | ||
418 | 945 | "default_view should get the view with the lowest priority for " | ||
419 | 946 | "a (model, view_type) pair in all the primary tables" | ||
420 | 947 | ) | ||
421 | 948 | |||
422 | 949 | class TestViewCombined(ViewCase): | ||
423 | 950 | """ | ||
424 | 951 | * When asked for a view, instead of looking for the closest parent with | ||
425 | 952 | inherit_id=False look for mode=primary | ||
426 | 953 | * If root.inherit_id, resolve the arch for root.inherit_id (?using which | ||
427 | 954 | model?), then apply root's inheritance specs to it | ||
428 | 955 | * Apply inheriting views on top | ||
429 | 956 | """ | ||
430 | 957 | |||
431 | 958 | def setUp(self): | ||
432 | 959 | super(TestViewCombined, self).setUp() | ||
433 | 960 | |||
434 | 961 | self.a1 = self.create({ | ||
435 | 962 | 'model': 'a', | ||
436 | 963 | 'arch': '<qweb><a1/></qweb>' | ||
437 | 964 | }) | ||
438 | 965 | self.a2 = self.create({ | ||
439 | 966 | 'model': 'a', | ||
440 | 967 | 'inherit_id': self.a1, | ||
441 | 968 | 'priority': 5, | ||
442 | 969 | 'arch': '<xpath expr="//a1" position="after"><a2/></xpath>' | ||
443 | 970 | }) | ||
444 | 971 | self.a3 = self.create({ | ||
445 | 972 | 'model': 'a', | ||
446 | 973 | 'inherit_id': self.a1, | ||
447 | 974 | 'arch': '<xpath expr="//a1" position="after"><a3/></xpath>' | ||
448 | 975 | }) | ||
449 | 976 | # mode=primary should be an inheritance boundary in both direction, | ||
450 | 977 | # even within a model it should not extend the parent | ||
451 | 978 | self.a4 = self.create({ | ||
452 | 979 | 'model': 'a', | ||
453 | 980 | 'inherit_id': self.a1, | ||
454 | 981 | 'mode': 'primary', | ||
455 | 982 | 'arch': '<xpath expr="//a1" position="after"><a4/></xpath>', | ||
456 | 983 | }) | ||
457 | 984 | |||
458 | 985 | self.b1 = self.create({ | ||
459 | 986 | 'model': 'b', | ||
460 | 987 | 'inherit_id': self.a3, | ||
461 | 988 | 'mode': 'primary', | ||
462 | 989 | 'arch': '<xpath expr="//a1" position="after"><b1/></xpath>' | ||
463 | 990 | }) | ||
464 | 991 | self.b2 = self.create({ | ||
465 | 992 | 'model': 'b', | ||
466 | 993 | 'inherit_id': self.b1, | ||
467 | 994 | 'arch': '<xpath expr="//a1" position="after"><b2/></xpath>' | ||
468 | 995 | }) | ||
469 | 996 | |||
470 | 997 | self.c1 = self.create({ | ||
471 | 998 | 'model': 'c', | ||
472 | 999 | 'inherit_id': self.a1, | ||
473 | 1000 | 'mode': 'primary', | ||
474 | 1001 | 'arch': '<xpath expr="//a1" position="after"><c1/></xpath>' | ||
475 | 1002 | }) | ||
476 | 1003 | self.c2 = self.create({ | ||
477 | 1004 | 'model': 'c', | ||
478 | 1005 | 'inherit_id': self.c1, | ||
479 | 1006 | 'priority': 5, | ||
480 | 1007 | 'arch': '<xpath expr="//a1" position="after"><c2/></xpath>' | ||
481 | 1008 | }) | ||
482 | 1009 | self.c3 = self.create({ | ||
483 | 1010 | 'model': 'c', | ||
484 | 1011 | 'inherit_id': self.c2, | ||
485 | 1012 | 'priority': 10, | ||
486 | 1013 | 'arch': '<xpath expr="//a1" position="after"><c3/></xpath>' | ||
487 | 1014 | }) | ||
488 | 1015 | |||
489 | 1016 | self.d1 = self.create({ | ||
490 | 1017 | 'model': 'd', | ||
491 | 1018 | 'inherit_id': self.b1, | ||
492 | 1019 | 'mode': 'primary', | ||
493 | 1020 | 'arch': '<xpath expr="//a1" position="after"><d1/></xpath>' | ||
494 | 1021 | }) | ||
495 | 1022 | |||
496 | 1023 | def test_basic_read(self): | ||
497 | 1024 | arch = self.read_combined(self.a1)['arch'] | ||
498 | 1025 | self.assertEqual( | ||
499 | 1026 | ET.fromstring(arch), | ||
500 | 1027 | E.qweb( | ||
501 | 1028 | E.a1(), | ||
502 | 1029 | E.a3(), | ||
503 | 1030 | E.a2(), | ||
504 | 1031 | ), arch) | ||
505 | 1032 | |||
506 | 1033 | def test_read_from_child(self): | ||
507 | 1034 | arch = self.read_combined(self.a3)['arch'] | ||
508 | 1035 | self.assertEqual( | ||
509 | 1036 | ET.fromstring(arch), | ||
510 | 1037 | E.qweb( | ||
511 | 1038 | E.a1(), | ||
512 | 1039 | E.a3(), | ||
513 | 1040 | E.a2(), | ||
514 | 1041 | ), arch) | ||
515 | 1042 | |||
516 | 1043 | def test_read_from_child_primary(self): | ||
517 | 1044 | arch = self.read_combined(self.a4)['arch'] | ||
518 | 1045 | self.assertEqual( | ||
519 | 1046 | ET.fromstring(arch), | ||
520 | 1047 | E.qweb( | ||
521 | 1048 | E.a1(), | ||
522 | 1049 | E.a4(), | ||
523 | 1050 | E.a3(), | ||
524 | 1051 | E.a2(), | ||
525 | 1052 | ), arch) | ||
526 | 1053 | |||
527 | 1054 | def test_cross_model_simple(self): | ||
528 | 1055 | arch = self.read_combined(self.c2)['arch'] | ||
529 | 1056 | self.assertEqual( | ||
530 | 1057 | ET.fromstring(arch), | ||
531 | 1058 | E.qweb( | ||
532 | 1059 | E.a1(), | ||
533 | 1060 | E.c3(), | ||
534 | 1061 | E.c2(), | ||
535 | 1062 | E.c1(), | ||
536 | 1063 | E.a3(), | ||
537 | 1064 | E.a2(), | ||
538 | 1065 | ), arch) | ||
539 | 1066 | |||
540 | 1067 | def test_cross_model_double(self): | ||
541 | 1068 | arch = self.read_combined(self.d1)['arch'] | ||
542 | 1069 | self.assertEqual( | ||
543 | 1070 | ET.fromstring(arch), | ||
544 | 1071 | E.qweb( | ||
545 | 1072 | E.a1(), | ||
546 | 1073 | E.d1(), | ||
547 | 1074 | E.b2(), | ||
548 | 1075 | E.b1(), | ||
549 | 1076 | E.a3(), | ||
550 | 1077 | E.a2(), | ||
551 | 1078 | ), arch) | ||
552 | 1079 | |||
553 | 1080 | class TestOptionalViews(ViewCase): | ||
554 | 1081 | """ | ||
555 | 1082 | Tests ability to enable/disable inherited views, formerly known as | ||
556 | 1083 | inherit_option_id | ||
557 | 1084 | """ | ||
558 | 1085 | |||
559 | 1086 | def setUp(self): | ||
560 | 1087 | super(TestOptionalViews, self).setUp() | ||
561 | 1088 | self.v0 = self.create({ | ||
562 | 1089 | 'model': 'a', | ||
563 | 1090 | 'arch': '<qweb><base/></qweb>', | ||
564 | 1091 | }) | ||
565 | 1092 | self.v1 = self.create({ | ||
566 | 1093 | 'model': 'a', | ||
567 | 1094 | 'inherit_id': self.v0, | ||
568 | 1095 | 'application': 'always', | ||
569 | 1096 | 'priority': 10, | ||
570 | 1097 | 'arch': '<xpath expr="//base" position="after"><v1/></xpath>', | ||
571 | 1098 | }) | ||
572 | 1099 | self.v2 = self.create({ | ||
573 | 1100 | 'model': 'a', | ||
574 | 1101 | 'inherit_id': self.v0, | ||
575 | 1102 | 'application': 'enabled', | ||
576 | 1103 | 'priority': 9, | ||
577 | 1104 | 'arch': '<xpath expr="//base" position="after"><v2/></xpath>', | ||
578 | 1105 | }) | ||
579 | 1106 | self.v3 = self.create({ | ||
580 | 1107 | 'model': 'a', | ||
581 | 1108 | 'inherit_id': self.v0, | ||
582 | 1109 | 'application': 'disabled', | ||
583 | 1110 | 'priority': 8, | ||
584 | 1111 | 'arch': '<xpath expr="//base" position="after"><v3/></xpath>' | ||
585 | 1112 | }) | ||
586 | 1113 | |||
587 | 1114 | def test_applied(self): | ||
588 | 1115 | """ mandatory and enabled views should be applied | ||
589 | 1116 | """ | ||
590 | 1117 | arch = self.read_combined(self.v0)['arch'] | ||
591 | 1118 | self.assertEqual( | ||
592 | 1119 | ET.fromstring(arch), | ||
593 | 1120 | E.qweb( | ||
594 | 1121 | E.base(), | ||
595 | 1122 | E.v1(), | ||
596 | 1123 | E.v2(), | ||
597 | 1124 | ) | ||
598 | 1125 | ) | ||
599 | 1126 | |||
600 | 1127 | def test_applied_state_toggle(self): | ||
601 | 1128 | """ Change application states of v2 and v3, check that the results | ||
602 | 1129 | are as expected | ||
603 | 1130 | """ | ||
604 | 1131 | self.browse(self.v2).write({'application': 'disabled'}) | ||
605 | 1132 | arch = self.read_combined(self.v0)['arch'] | ||
606 | 1133 | self.assertEqual( | ||
607 | 1134 | ET.fromstring(arch), | ||
608 | 1135 | E.qweb( | ||
609 | 1136 | E.base(), | ||
610 | 1137 | E.v1(), | ||
611 | 1138 | ) | ||
612 | 1139 | ) | ||
613 | 1140 | |||
614 | 1141 | self.browse(self.v3).write({'application': 'enabled'}) | ||
615 | 1142 | arch = self.read_combined(self.v0)['arch'] | ||
616 | 1143 | self.assertEqual( | ||
617 | 1144 | ET.fromstring(arch), | ||
618 | 1145 | E.qweb( | ||
619 | 1146 | E.base(), | ||
620 | 1147 | E.v1(), | ||
621 | 1148 | E.v3(), | ||
622 | 1149 | ) | ||
623 | 1150 | ) | ||
624 | 1151 | |||
625 | 1152 | self.browse(self.v2).write({'application': 'enabled'}) | ||
626 | 1153 | arch = self.read_combined(self.v0)['arch'] | ||
627 | 1154 | self.assertEqual( | ||
628 | 1155 | ET.fromstring(arch), | ||
629 | 1156 | E.qweb( | ||
630 | 1157 | E.base(), | ||
631 | 1158 | E.v1(), | ||
632 | 1159 | E.v2(), | ||
633 | 1160 | E.v3(), | ||
634 | 1161 | ) | ||
635 | 1162 | ) | ||
636 | 1163 | |||
637 | 1164 | def test_mandatory_no_disabled(self): | ||
638 | 1165 | with self.assertRaises(Exception): | ||
639 | 1166 | self.browse(self.v1).write({'application': 'disabled'}) | ||
640 | 807 | 1167 | ||
641 | === modified file 'openerp/import_xml.rng' | |||
642 | --- openerp/import_xml.rng 2014-01-16 09:17:16 +0000 | |||
643 | +++ openerp/import_xml.rng 2014-05-06 14:57:36 +0000 | |||
644 | @@ -217,9 +217,23 @@ | |||
645 | 217 | <rng:optional><rng:attribute name="priority"/></rng:optional> | 217 | <rng:optional><rng:attribute name="priority"/></rng:optional> |
646 | 218 | <rng:choice> | 218 | <rng:choice> |
647 | 219 | <rng:group> | 219 | <rng:group> |
650 | 220 | <rng:optional><rng:attribute name="inherit_id"/></rng:optional> | 220 | <rng:optional> |
651 | 221 | <rng:optional><rng:attribute name="inherit_option_id"/></rng:optional> | 221 | <rng:attribute name="inherit_id"/> |
652 | 222 | <rng:optional> | ||
653 | 223 | <rng:attribute name="primary"> | ||
654 | 224 | <rng:value>True</rng:value> | ||
655 | 225 | </rng:attribute> | ||
656 | 226 | </rng:optional> | ||
657 | 227 | </rng:optional> | ||
658 | 222 | <rng:optional><rng:attribute name="groups"/></rng:optional> | 228 | <rng:optional><rng:attribute name="groups"/></rng:optional> |
659 | 229 | <rng:optional> | ||
660 | 230 | <rng:attribute name="optional"> | ||
661 | 231 | <rng:choice> | ||
662 | 232 | <rng:value>enabled</rng:value> | ||
663 | 233 | <rng:value>disabled</rng:value> | ||
664 | 234 | </rng:choice> | ||
665 | 235 | </rng:attribute> | ||
666 | 236 | </rng:optional> | ||
667 | 223 | </rng:group> | 237 | </rng:group> |
668 | 224 | <rng:optional> | 238 | <rng:optional> |
669 | 225 | <rng:attribute name="page"><rng:value>True</rng:value></rng:attribute> | 239 | <rng:attribute name="page"><rng:value>True</rng:value></rng:attribute> |
670 | 226 | 240 | ||
671 | === modified file 'openerp/osv/orm.py' | |||
672 | --- openerp/osv/orm.py 2014-04-16 14:34:31 +0000 | |||
673 | +++ openerp/osv/orm.py 2014-05-06 14:57:36 +0000 | |||
674 | @@ -729,7 +729,6 @@ | |||
675 | 729 | _all_columns = {} | 729 | _all_columns = {} |
676 | 730 | 730 | ||
677 | 731 | _table = None | 731 | _table = None |
678 | 732 | _invalids = set() | ||
679 | 733 | _log_create = False | 732 | _log_create = False |
680 | 734 | _sql_constraints = [] | 733 | _sql_constraints = [] |
681 | 735 | _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists'] | 734 | _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists'] |
682 | @@ -1543,9 +1542,6 @@ | |||
683 | 1543 | 1542 | ||
684 | 1544 | yield dbid, xid, converted, dict(extras, record=stream.index) | 1543 | yield dbid, xid, converted, dict(extras, record=stream.index) |
685 | 1545 | 1544 | ||
686 | 1546 | def get_invalid_fields(self, cr, uid): | ||
687 | 1547 | return list(self._invalids) | ||
688 | 1548 | |||
689 | 1549 | def _validate(self, cr, uid, ids, context=None): | 1545 | def _validate(self, cr, uid, ids, context=None): |
690 | 1550 | context = context or {} | 1546 | context = context or {} |
691 | 1551 | lng = context.get('lang') | 1547 | lng = context.get('lang') |
692 | @@ -1566,12 +1562,9 @@ | |||
693 | 1566 | # Check presence of __call__ directly instead of using | 1562 | # Check presence of __call__ directly instead of using |
694 | 1567 | # callable() because it will be deprecated as of Python 3.0 | 1563 | # callable() because it will be deprecated as of Python 3.0 |
695 | 1568 | if hasattr(msg, '__call__'): | 1564 | if hasattr(msg, '__call__'): |
702 | 1569 | tmp_msg = msg(self, cr, uid, ids, context=context) | 1565 | translated_msg = msg(self, cr, uid, ids, context=context) |
703 | 1570 | if isinstance(tmp_msg, tuple): | 1566 | if isinstance(translated_msg, tuple): |
704 | 1571 | tmp_msg, params = tmp_msg | 1567 | translated_msg = translated_msg[0] % translated_msg[1] |
699 | 1572 | translated_msg = tmp_msg % params | ||
700 | 1573 | else: | ||
701 | 1574 | translated_msg = tmp_msg | ||
705 | 1575 | else: | 1568 | else: |
706 | 1576 | translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, msg) | 1569 | translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, msg) |
707 | 1577 | if extra_error: | 1570 | if extra_error: |
708 | @@ -1579,11 +1572,8 @@ | |||
709 | 1579 | error_msgs.append( | 1572 | error_msgs.append( |
710 | 1580 | _("The field(s) `%s` failed against a constraint: %s") % (', '.join(fields), translated_msg) | 1573 | _("The field(s) `%s` failed against a constraint: %s") % (', '.join(fields), translated_msg) |
711 | 1581 | ) | 1574 | ) |
712 | 1582 | self._invalids.update(fields) | ||
713 | 1583 | if error_msgs: | 1575 | if error_msgs: |
714 | 1584 | raise except_orm('ValidateError', '\n'.join(error_msgs)) | 1576 | raise except_orm('ValidateError', '\n'.join(error_msgs)) |
715 | 1585 | else: | ||
716 | 1586 | self._invalids.clear() | ||
717 | 1587 | 1577 | ||
718 | 1588 | def default_get(self, cr, uid, fields_list, context=None): | 1578 | def default_get(self, cr, uid, fields_list, context=None): |
719 | 1589 | """ | 1579 | """ |
720 | 1590 | 1580 | ||
721 | === modified file 'openerp/tools/convert.py' | |||
722 | --- openerp/tools/convert.py 2014-04-24 13:14:05 +0000 | |||
723 | +++ openerp/tools/convert.py 2014-05-06 14:57:36 +0000 | |||
724 | @@ -861,7 +861,7 @@ | |||
725 | 861 | if '.' not in full_tpl_id: | 861 | if '.' not in full_tpl_id: |
726 | 862 | full_tpl_id = '%s.%s' % (self.module, tpl_id) | 862 | full_tpl_id = '%s.%s' % (self.module, tpl_id) |
727 | 863 | # set the full template name for qweb <module>.<id> | 863 | # set the full template name for qweb <module>.<id> |
729 | 864 | if not (el.get('inherit_id') or el.get('inherit_option_id')): | 864 | if not el.get('inherit_id'): |
730 | 865 | el.set('t-name', full_tpl_id) | 865 | el.set('t-name', full_tpl_id) |
731 | 866 | el.tag = 't' | 866 | el.tag = 't' |
732 | 867 | else: | 867 | else: |
733 | @@ -884,15 +884,18 @@ | |||
734 | 884 | record.append(Field("qweb", name='type')) | 884 | record.append(Field("qweb", name='type')) |
735 | 885 | record.append(Field(el.get('priority', "16"), name='priority')) | 885 | record.append(Field(el.get('priority', "16"), name='priority')) |
736 | 886 | record.append(Field(el, name="arch", type="xml")) | 886 | record.append(Field(el, name="arch", type="xml")) |
740 | 887 | for field_name in ('inherit_id','inherit_option_id'): | 887 | if 'inherit_id' in el.attrib: |
741 | 888 | value = el.attrib.pop(field_name, None) | 888 | record.append(Field(name='inherit_id', ref=el.get('inherit_id'))) |
739 | 889 | if value: record.append(Field(name=field_name, ref=value)) | ||
742 | 890 | groups = el.attrib.pop('groups', None) | 889 | groups = el.attrib.pop('groups', None) |
743 | 891 | if groups: | 890 | if groups: |
744 | 892 | grp_lst = map(lambda x: "ref('%s')" % x, groups.split(',')) | 891 | grp_lst = map(lambda x: "ref('%s')" % x, groups.split(',')) |
745 | 893 | record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]")) | 892 | record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]")) |
746 | 894 | if el.attrib.pop('page', None) == 'True': | 893 | if el.attrib.pop('page', None) == 'True': |
747 | 895 | record.append(Field(name="page", eval="True")) | 894 | record.append(Field(name="page", eval="True")) |
748 | 895 | if el.get('primary') == 'True': | ||
749 | 896 | record.append(Field('primary', name='mode')) | ||
750 | 897 | if el.get('optional'): | ||
751 | 898 | record.append(Field(el.get('optional'), name='application')) | ||
752 | 896 | 899 | ||
753 | 897 | return self._tag_record(cr, record, data_node) | 900 | return self._tag_record(cr, record, data_node) |
754 | 898 | 901 |