LEFT | RIGHT |
1 #This file is part of Tryton. The COPYRIGHT file at the top level of | 1 #This file is part of Tryton. The COPYRIGHT file at the top level of |
2 #this repository contains the full copyright notices and license terms. | 2 #this repository contains the full copyright notices and license terms. |
3 from lxml import etree | 3 from lxml import etree |
4 try: | 4 from functools import wraps |
5 import hashlib | |
6 except ImportError: | |
7 hashlib = None | |
8 import md5 | |
9 import copy | |
10 from trytond.model import Model | 5 from trytond.model import Model |
11 from trytond.tools import safe_eval | 6 from trytond.tools import safe_eval, ClassProperty |
12 from trytond.pyson import PYSONEncoder, CONTEXT | 7 from trytond.pyson import PYSONEncoder, CONTEXT |
13 from trytond.transaction import Transaction | 8 from trytond.transaction import Transaction |
14 from trytond.cache import Cache | 9 from trytond.cache import Cache |
| 10 from trytond.pool import Pool |
| 11 from trytond.exceptions import UserError |
| 12 from trytond.rpc import RPC |
| 13 |
| 14 __all__ = ['ModelView'] |
| 15 |
15 | 16 |
16 def _find(tree, element): | 17 def _find(tree, element): |
17 if element.tag == 'xpath': | 18 if element.tag == 'xpath': |
18 res = tree.xpath(element.get('expr')) | 19 res = tree.xpath(element.get('expr')) |
19 if res: | 20 if res: |
20 return res[0] | 21 return res[0] |
21 return None | 22 return None |
22 | 23 |
23 | 24 |
24 def _inherit_apply(src, inherit): | 25 def _inherit_apply(src, inherit): |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
68 raise AttributeError( | 69 raise AttributeError( |
69 'Couldn\'t find tag (%s: %s) in parent view!' % \ | 70 'Couldn\'t find tag (%s: %s) in parent view!' % \ |
70 (element2.tag, element2.get('expr'))) | 71 (element2.tag, element2.get('expr'))) |
71 return etree.tostring(tree_src, encoding='utf-8') | 72 return etree.tostring(tree_src, encoding='utf-8') |
72 | 73 |
73 | 74 |
74 class ModelView(Model): | 75 class ModelView(Model): |
75 """ | 76 """ |
76 Define a model with views in Tryton. | 77 Define a model with views in Tryton. |
77 """ | 78 """ |
78 __modules_list = None # Cache for the modules list sorted by dependency | 79 __modules_list = None # Cache for the modules list sorted by dependency |
| 80 _fields_view_get_cache = Cache('modelview.fields_view_get') |
| 81 _view_toolbar_get_cache = Cache('modelview.view_toolbar_get') |
79 | 82 |
80 @staticmethod | 83 @staticmethod |
81 def _reset_modules_list(): | 84 def _reset_modules_list(): |
82 ModelView.__modules_list = None | 85 ModelView.__modules_list = None |
83 | 86 |
84 def _get_modules_list(self): | 87 @ClassProperty |
| 88 @classmethod |
| 89 def _modules_list(cls): |
85 from trytond.modules import create_graph, get_module_list | 90 from trytond.modules import create_graph, get_module_list |
86 if ModelView.__modules_list: | 91 if ModelView.__modules_list: |
87 return ModelView.__modules_list | 92 return ModelView.__modules_list |
88 graph = create_graph(get_module_list())[0] | 93 graph = create_graph(get_module_list())[0] |
89 ModelView.__modules_list = [x.name for x in graph] + [None] | 94 ModelView.__modules_list = [x.name for x in graph] + [None] |
90 return ModelView.__modules_list | 95 return ModelView.__modules_list |
91 | 96 |
92 _modules_list = property(fget=_get_modules_list) | 97 @classmethod |
93 | 98 def __setup__(cls): |
94 def __init__(self): | 99 super(ModelView, cls).__setup__() |
95 super(ModelView, self).__init__() | 100 cls.__rpc__['fields_view_get'] = RPC() |
96 self._rpc['fields_view_get'] = False | 101 cls.__rpc__['view_toolbar_get'] = RPC() |
97 | 102 cls._buttons = {} |
98 @Cache('modelview.fields_view_get') | 103 |
99 def fields_view_get(self, view_id=None, view_type='form', toolbar=False, | 104 @classmethod |
100 hexmd5=None): | 105 def fields_view_get(cls, view_id=None, view_type='form'): |
101 ''' | 106 ''' |
102 Return a view definition. | 107 Return a view definition. |
103 | 108 If view_id is None the first one will be used of view_type. |
104 :param view_id: the id of the view, if None the first one will be used | 109 The definition is a dictionary with keys: |
105 :param view_type: the type of the view if view_id is None | |
106 :param toolbar: if True the result will contain a toolbar key with | |
107 keyword action definitions for the view | |
108 :param hexmd5: if filled, the function will return True if the result | |
109 has the same md5 | |
110 :return: a dictionary with keys: | |
111 - model: the model name | 110 - model: the model name |
112 - arch: the xml description of the view | 111 - arch: the xml description of the view |
113 - fields: a dictionary with the definition of each field in the view | 112 - fields: a dictionary with the definition of each field in the view |
114 - toolbar: a dictionary with the keyword action definitions | |
115 - md5: the check sum of the dictionary without this checksum | |
116 ''' | 113 ''' |
117 view_obj = self.pool.get('ir.ui.view') | 114 key = (cls.__name__, view_id, view_type) |
118 result = {'model': self._name} | 115 result = cls._fields_view_get_cache.get(key) |
| 116 if result: |
| 117 return result |
| 118 result = {'model': cls.__name__} |
| 119 pool = Pool() |
| 120 View = pool.get('ir.ui.view') |
119 | 121 |
120 test = True | 122 test = True |
121 model = True | 123 model = True |
122 view = None | 124 view = None |
123 inherit_view_id = False | 125 inherit_view_id = False |
124 cursor = Transaction().cursor | |
125 while test: | 126 while test: |
126 if view_id: | 127 if view_id: |
127 domain = [('id', '=', view_id)] | 128 domain = [('id', '=', view_id)] |
128 if model: | 129 if model: |
129 domain.append(('model', '=', self._name)) | 130 domain.append(('model', '=', cls.__name__)) |
130 view_ids = view_obj.search(domain, order=[]) | 131 views = View.search(domain, order=[]) |
131 else: | 132 else: |
132 domain = [ | 133 domain = [ |
133 ('model', '=', self._name), | 134 ('model', '=', cls.__name__), |
134 ('type', '=', view_type), | 135 ('type', '=', view_type), |
135 ] | 136 ] |
136 order = [ | 137 order = [ |
137 ('inherit', 'DESC'), | 138 ('inherit', 'DESC'), |
138 ('priority', 'ASC'), | 139 ('priority', 'ASC'), |
139 ('id', 'ASC'), | 140 ('id', 'ASC'), |
140 ] | 141 ] |
141 view_ids = view_obj.search(domain, order=order) | 142 views = View.search(domain, order=order) |
142 if not view_ids: | 143 if not views: |
143 break | 144 break |
144 view = view_obj.browse(view_ids[0]) | 145 view = views[0] |
145 test = view.inherit | 146 test = view.inherit |
146 if test: | 147 if test: |
147 inherit_view_id = view.id | 148 inherit_view_id = view.id |
148 view_id = test or view.id | 149 view_id = test.id if test else view.id |
149 model = False | 150 model = False |
150 | 151 |
151 # if a view was found | 152 # if a view was found |
152 if view: | 153 if view: |
153 result['type'] = view.type | 154 result['type'] = view.type |
154 result['view_id'] = view_id | 155 result['view_id'] = view_id |
155 result['arch'] = view.arch | 156 result['arch'] = view.arch |
156 result['field_childs'] = view.field_childs | 157 result['field_childs'] = view.field_childs |
157 | 158 |
158 # Check if view is not from an inherited model | 159 # Check if view is not from an inherited model |
159 if view.model != self._name: | 160 if view.model != cls.__name__: |
160 inherit_obj = self.pool.get(view.model) | 161 Inherit = pool.get(view.model) |
161 result['arch'] = inherit_obj.fields_view_get( | 162 result['arch'] = Inherit.fields_view_get( |
162 result['view_id'])['arch'] | 163 result['view_id'])['arch'] |
163 view_id = inherit_view_id | 164 view_id = inherit_view_id |
164 | 165 |
165 # get all views which inherit from (ie modify) this view | 166 # get all views which inherit from (ie modify) this view |
166 view_ids = view_obj.search([ | 167 views = View.search([ |
167 'OR', [ | 168 'OR', [ |
168 ('inherit', '=', view_id), | 169 ('inherit', '=', view_id), |
169 ('model', '=', self._name), | 170 ('model', '=', cls.__name__), |
170 ], [ | 171 ], [ |
171 ('id', '=', view_id), | 172 ('id', '=', view_id), |
172 ('inherit', '!=', False), | 173 ('inherit', '!=', None), |
173 ]], order=[('priority', 'ASC'), ('id', 'ASC')]) | 174 ], |
174 views = view_obj.browse(view_ids) | 175 ], |
| 176 order=[ |
| 177 ('priority', 'ASC'), |
| 178 ('id', 'ASC'), |
| 179 ]) |
175 raise_p = False | 180 raise_p = False |
176 while True: | 181 while True: |
177 try: | 182 try: |
178 views.sort(lambda x, y: | 183 views.sort(key=lambda x: |
179 cmp(self._modules_list.index(x.module or None), | 184 cls._modules_list.index(x.module or None)) |
180 self._modules_list.index(y.module or None))) | |
181 break | 185 break |
182 except ValueError: | 186 except ValueError: |
183 if raise_p: | 187 if raise_p: |
184 raise | 188 raise |
185 # There is perhaps a new module in the directory | 189 # There is perhaps a new module in the directory |
186 ModelView._reset_modules_list() | 190 ModelView._reset_modules_list() |
187 raise_p = True | 191 raise_p = True |
188 for view in views: | 192 for view in views: |
189 if view.domain: | 193 if view.domain: |
190 if not safe_eval(view.domain, | 194 if not safe_eval(view.domain, |
191 {'context': Transaction().context}): | 195 {'context': Transaction().context}): |
192 continue | 196 continue |
193 if not view.arch or not view.arch.strip(): | 197 if not view.arch or not view.arch.strip(): |
194 continue | 198 continue |
195 result['arch'] = _inherit_apply(result['arch'], view.arch) | 199 result['arch'] = _inherit_apply(result['arch'], view.arch) |
196 | 200 |
197 # otherwise, build some kind of default view | 201 # otherwise, build some kind of default view |
198 else: | 202 else: |
199 if view_type == 'form': | 203 if view_type == 'form': |
200 res = self.fields_get() | 204 res = cls.fields_get() |
201 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \ | 205 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \ |
202 '''<form string="%s">''' % (self._description,) | 206 '''<form string="%s" col="4">''' % (cls.__doc__,) |
203 for i in res: | 207 for i in res: |
204 if i in ('create_uid', 'create_date', | 208 if i in ('create_uid', 'create_date', |
205 'write_uid', 'write_date', 'id', 'rec_name'): | 209 'write_uid', 'write_date', 'id', 'rec_name'): |
206 continue | 210 continue |
207 if res[i]['type'] not in ('one2many', 'many2many'): | 211 if res[i]['type'] not in ('one2many', 'many2many'): |
208 xml += '<label name="%s"/>' % (i,) | 212 xml += '<label name="%s"/>' % (i,) |
209 xml += '<field name="%s"/>' % (i,) | 213 xml += '<field name="%s"/>' % (i,) |
210 if res[i]['type'] == 'text': | 214 if res[i]['type'] == 'text': |
211 xml += "<newline/>" | 215 xml += "<newline/>" |
| 216 else: |
| 217 xml += '<field name="%s" colspan="4"/>' % (i,) |
212 xml += "</form>" | 218 xml += "</form>" |
213 elif view_type == 'tree': | 219 elif view_type == 'tree': |
214 field = 'id' | 220 field = 'id' |
215 if self._rec_name in self._columns: | 221 if cls._rec_name in cls._fields: |
216 field = self._rec_name | 222 field = cls._rec_name |
217 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \ | 223 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \ |
218 '''<tree string="%s"><field name="%s"/></tree>''' \ | 224 '''<tree string="%s"><field name="%s"/></tree>''' \ |
219 % (self._description, field) | 225 % (cls.__doc__, field) |
220 else: | 226 else: |
221 xml = '' | 227 xml = '' |
222 result['type'] = view_type | 228 result['type'] = view_type |
223 result['arch'] = xml | 229 result['arch'] = xml |
224 result['field_childs'] = False | 230 result['field_childs'] = False |
225 result['view_id'] = 0 | 231 result['view_id'] = 0 |
226 | 232 |
227 # Update arch and compute fields from arch | 233 # Update arch and compute fields from arch |
228 parser = etree.XMLParser(remove_blank_text=True) | 234 parser = etree.XMLParser(remove_blank_text=True) |
229 tree = etree.fromstring(result['arch'], parser) | 235 tree = etree.fromstring(result['arch'], parser) |
230 xarch, xfields = self._view_look_dom_arch(tree, result['type']) | 236 xarch, xfields = cls._view_look_dom_arch(tree, result['type'], |
| 237 result['field_childs']) |
231 result['arch'] = xarch | 238 result['arch'] = xarch |
232 result['fields'] = xfields | 239 result['fields'] = xfields |
233 | 240 |
234 # Add toolbar | 241 cls._fields_view_get_cache.set(key, result) |
235 if toolbar: | 242 return result |
236 action_obj = self.pool.get('ir.action.keyword') | 243 |
237 prints = action_obj.get_keyword('form_print', (self._name, 0)) | 244 @classmethod |
238 actions = action_obj.get_keyword('form_action', (self._name, 0)) | 245 def view_toolbar_get(cls): |
239 relates = action_obj.get_keyword('form_relate', (self._name, 0)) | 246 """ |
240 result['toolbar'] = { | 247 Returns the model specific actions. |
241 'print': prints, | 248 A dictionary with keys: |
242 'action': actions, | 249 - print: a list of available reports |
243 'relate': relates, | 250 - action: a list of available actions |
| 251 - relate: a list of available relations |
| 252 """ |
| 253 Action = Pool().get('ir.action.keyword') |
| 254 key = (cls.__name__, repr(Transaction().context)) |
| 255 result = cls._view_toolbar_get_cache.get(key) |
| 256 if result: |
| 257 return result |
| 258 prints = Action.get_keyword('form_print', (cls.__name__, -1)) |
| 259 actions = Action.get_keyword('form_action', (cls.__name__, -1)) |
| 260 relates = Action.get_keyword('form_relate', (cls.__name__, -1)) |
| 261 result = { |
| 262 'print': prints, |
| 263 'action': actions, |
| 264 'relate': relates, |
244 } | 265 } |
245 | 266 cls._view_toolbar_get_cache.set(key, result) |
246 # Compute md5 | |
247 if hashlib: | |
248 result['md5'] = hashlib.md5(str(result)).hexdigest() | |
249 else: | |
250 result['md5'] = md5.new(str(result)).hexdigest() | |
251 if hexmd5 == result['md5']: | |
252 return True | |
253 return result | 267 return result |
254 | 268 |
255 def view_header_get(self, value, view_type='form'): | 269 @classmethod |
| 270 def view_header_get(cls, value, view_type='form'): |
256 """ | 271 """ |
257 Overload this method if you need a window title. | 272 Overload this method if you need a window title. |
258 which depends on the context | 273 which depends on the context |
259 | 274 |
260 :param value: the default header string | 275 :param value: the default header string |
261 :param view_type: the type of the view | 276 :param view_type: the type of the view |
262 :return: the header string of the view | 277 :return: the header string of the view |
263 """ | 278 """ |
264 return value | 279 return value |
265 | 280 |
266 def _view_look_dom_arch(self, tree, type): | 281 @classmethod |
| 282 def _view_look_dom_arch(cls, tree, type, field_children=None): |
| 283 pool = Pool() |
| 284 ModelAccess = pool.get('ir.model.access') |
| 285 FieldAccess = pool.get('ir.model.field.access') |
| 286 |
267 fields_width = {} | 287 fields_width = {} |
268 tree_root = tree.getroottree().getroot() | 288 tree_root = tree.getroottree().getroot() |
269 | 289 |
| 290 # Find field without read access |
| 291 fread_accesses = FieldAccess.check(cls.__name__, |
| 292 cls._fields.keys(), 'read', access=True) |
| 293 fields_to_remove = list(x for x, y in fread_accesses.iteritems() |
| 294 if not y) |
| 295 |
| 296 def check_relation(model, field): |
| 297 if field._type in ('one2many', 'many2one'): |
| 298 if not ModelAccess.check(field.model_name, mode='read', |
| 299 raise_exception=False): |
| 300 return False |
| 301 if field._type in ('many2many', 'one2one'): |
| 302 if not ModelAccess.check(field.target, mode='read', |
| 303 raise_exception=False): |
| 304 return False |
| 305 elif not ModelAccess.check(field.relation_name, |
| 306 mode='read', raise_exception=False): |
| 307 return False |
| 308 if field._type == 'reference': |
| 309 selection = field.selection |
| 310 if isinstance(selection, basestring): |
| 311 selection = getattr(model, field.selection)() |
| 312 for model_name, _ in selection: |
| 313 if not ModelAccess.check(model_name, mode='read', |
| 314 raise_exception=False): |
| 315 return False |
| 316 return True |
| 317 |
| 318 # Find relation field without read access |
| 319 for name, field in cls._fields.iteritems(): |
| 320 if not check_relation(cls, field): |
| 321 fields_to_remove.append(name) |
| 322 |
| 323 for name, field in cls._fields.iteritems(): |
| 324 for field_to_remove in fields_to_remove: |
| 325 if field_to_remove in field.depends: |
| 326 fields_to_remove.append(name) |
| 327 |
| 328 # Find field inherited without read access |
| 329 for inherit_name in cls._inherits: |
| 330 Inherit = pool.get(inherit_name) |
| 331 fread_accesses = FieldAccess.check(Inherit.__name__, |
| 332 Inherit._fields.keys(), 'read', access=True) |
| 333 fields_to_remove += list(x for x, y in fread_accesses.iteritems() |
| 334 if not y and x not in cls._fields.keys()) |
| 335 |
| 336 # Find relation field without read access |
| 337 for name, field in Inherit._fields.iteritems(): |
| 338 if not check_relation(Inherit, field): |
| 339 fields_to_remove.append(name) |
| 340 |
| 341 for name, field in Inherit._fields.iteritems(): |
| 342 for field_to_remove in fields_to_remove: |
| 343 if field_to_remove in field.depends: |
| 344 fields_to_remove.append(name) |
| 345 |
| 346 # Remove field without read access |
| 347 for field in fields_to_remove: |
| 348 for element in tree.xpath( |
| 349 '//field[@name="%s"] | //label[@name="%s"]' |
| 350 % (field, field)): |
| 351 if type == 'form': |
| 352 element.tag = 'label' |
| 353 element.attrib.clear() |
| 354 elif type == 'tree': |
| 355 parent = element.getparent() |
| 356 parent.remove(element) |
| 357 |
270 if type == 'tree': | 358 if type == 'tree': |
271 viewtreewidth_obj = self.pool.get('ir.ui.view_tree_width') | 359 ViewTreeWidth = pool.get('ir.ui.view_tree_width') |
272 viewtreewidth_ids = viewtreewidth_obj.search([ | 360 viewtreewidth_ids = ViewTreeWidth.search([ |
273 ('model', '=', self._name), | 361 ('model', '=', cls.__name__), |
274 ('user', '=', Transaction().user), | 362 ('user', '=', Transaction().user), |
275 ]) | 363 ]) |
276 for viewtreewidth in viewtreewidth_obj.browse(viewtreewidth_ids): | 364 for viewtreewidth in ViewTreeWidth.browse(viewtreewidth_ids): |
277 if viewtreewidth.width > 0: | 365 if viewtreewidth.width > 0: |
278 fields_width[viewtreewidth.field] = viewtreewidth.width | 366 fields_width[viewtreewidth.field] = viewtreewidth.width |
279 | 367 |
280 fields_def = self.__view_look_dom(tree_root, type, | 368 fields_def = cls.__view_look_dom(tree_root, type, |
281 fields_width=fields_width) | 369 fields_width=fields_width) |
282 | 370 |
| 371 if field_children: |
| 372 fields_def.setdefault(field_children, {'name': field_children}) |
| 373 model, field = None, None |
| 374 if field_children in cls._fields: |
| 375 model = cls |
| 376 field = cls._fields[field_children] |
| 377 elif field_children in cls._inherit_fields: |
| 378 model_name, model, field = cls._inherit_fields[field_children] |
| 379 if model and field and field.model_name == model.__name__: |
| 380 fields_def.setdefault(field.field, {'name': field.field}) |
| 381 |
283 for field_name in fields_def.keys(): | 382 for field_name in fields_def.keys(): |
284 if field_name in self._columns: | 383 if field_name in cls._fields: |
285 field = self._columns[field_name] | 384 field = cls._fields[field_name] |
286 elif field_name in self._inherit_fields: | 385 elif field_name in cls._inherit_fields: |
287 field = self._inherit_fields[field_name][2] | 386 field = cls._inherit_fields[field_name][2] |
288 else: | 387 else: |
289 continue | 388 continue |
290 for depend in field.depends: | 389 for depend in field.depends: |
291 fields_def.setdefault(depend, {'name': depend}) | 390 fields_def.setdefault(depend, {'name': depend}) |
292 | 391 |
293 if ('active' in self._columns) or ('active' in self._inherit_fields): | 392 if ('active' in cls._fields) or ('active' in cls._inherit_fields): |
294 fields_def.setdefault('active', {'name': 'active', 'select': "2"}) | 393 fields_def.setdefault('active', {'name': 'active'}) |
295 | 394 |
296 arch = etree.tostring(tree, encoding='utf-8', pretty_print=False) | 395 arch = etree.tostring(tree, encoding='utf-8', pretty_print=False) |
297 fields2 = self.fields_get(fields_def.keys()) | 396 fields2 = cls.fields_get(fields_def.keys()) |
298 for field in fields_def: | 397 for field in fields_def: |
299 if field in fields2: | 398 if field in fields2: |
300 fields2[field].update(fields_def[field]) | 399 fields2[field].update(fields_def[field]) |
301 return arch, fields2 | 400 return arch, fields2 |
302 | 401 |
303 def __view_look_dom(self, element, type, fields_width=None): | 402 @classmethod |
304 translation_obj = self.pool.get('ir.translation') | 403 def __view_look_dom(cls, element, type, fields_width=None): |
| 404 pool = Pool() |
| 405 Translation = pool.get('ir.translation') |
| 406 ModelData = pool.get('ir.model.data') |
| 407 Button = pool.get('ir.model.button') |
| 408 User = pool.get('res.user') |
305 | 409 |
306 if fields_width is None: | 410 if fields_width is None: |
307 fields_width = {} | 411 fields_width = {} |
308 result = False | |
309 fields_attrs = {} | 412 fields_attrs = {} |
310 childs = True | 413 childs = True |
311 | 414 |
312 if element.tag in ('field', 'label', 'separator', 'group'): | 415 if element.tag in ('field', 'label', 'separator', 'group'): |
313 for attr in ('name', 'icon'): | 416 for attr in ('name', 'icon'): |
314 if element.get(attr): | 417 if element.get(attr): |
315 attrs = {} | 418 attrs = {} |
316 try: | 419 try: |
317 if element.get(attr) in self._columns: | 420 if element.get(attr) in cls._fields: |
318 field = self._columns[element.get(attr)] | 421 field = cls._fields[element.get(attr)] |
319 else: | 422 else: |
320 field = self._inherit_fields[element.get( | 423 field = cls._inherit_fields[element.get( |
321 attr)][2] | 424 attr)][2] |
322 if hasattr(field, 'model_name'): | 425 if hasattr(field, 'model_name'): |
323 relation = field.model_name | 426 relation = field.model_name |
324 else: | 427 else: |
325 relation = field.get_target(self.pool)._name | 428 relation = field.get_target().__name__ |
326 except Exception: | 429 except Exception: |
327 relation = False | 430 relation = False |
328 if relation and element.tag == 'field': | 431 if relation and element.tag == 'field': |
329 childs = False | 432 childs = False |
330 views = {} | 433 views = {} |
331 for field in element: | 434 mode = (element.attrib.pop('mode', None) |
332 if field.tag in ('form', 'tree', 'graph'): | 435 or 'tree,form').split(',') |
333 field2 = copy.copy(field) | 436 view_ids = [] |
334 | 437 if element.get('view_ids'): |
335 def _translate_field(field): | 438 for view_id in element.get('view_ids').split(','): |
336 if field.get('string'): | 439 try: |
337 trans = translation_obj._get_source( | 440 view_ids.append(int(view_id)) |
338 self._name, 'view', | 441 except ValueError: |
339 Transaction().language, | 442 view_ids.append(ModelData.get_id( |
340 field.get('string')) | 443 *view_id.split('.'))) |
341 if trans: | 444 Relation = pool.get(relation) |
342 field.set('string', trans) | 445 if (not len(element) |
343 if field.get('sum'): | 446 and type == 'form' |
344 trans = translation_obj._get_source( | 447 and field._type in ('one2many', 'many2many')): |
345 self._name, 'view', | 448 # Prefetch only the first view to prevent infinite |
346 Transaction().language, | 449 # loop |
347 field.get('sum')) | 450 if view_ids: |
348 if trans: | 451 for view_id in view_ids: |
349 field.set('sum', trans) | 452 view = Relation.fields_view_get( |
350 for field_child in field: | 453 view_id=view_id) |
351 _translate_field(field_child) | 454 views[view['type']] = view |
352 if Transaction().language != 'en_US': | 455 break |
353 _translate_field(field2) | 456 else: |
354 | 457 for view_type in mode: |
355 relation_obj = self.pool.get(relation) | 458 views[view_type] = \ |
356 if hasattr(relation_obj, '_view_look_dom_arch'): | 459 Relation.fields_view_get( |
357 xarch, xfields = \ | 460 view_type=view_type) |
358 relation_obj._view_look_dom_arch( | 461 break |
359 field2, field.tag) | 462 element.attrib['mode'] = ','.join(mode) |
360 views[field.tag] = { | 463 element.attrib['view_ids'] = ','.join( |
361 'arch': xarch, | 464 map(str, view_ids)) |
362 'fields': xfields | 465 attrs = { |
363 } | 466 'views': views, |
364 element.remove(field) | 467 } |
365 attrs = {'views': views} | |
366 fields_attrs[element.get(attr)] = attrs | 468 fields_attrs[element.get(attr)] = attrs |
367 if element.get('name') in fields_width: | 469 if element.get('name') in fields_width: |
368 element.set('width', str(fields_width[element.get('name')])) | 470 element.set('width', str(fields_width[element.get('name')])) |
369 | 471 |
370 # convert attributes into pyson | 472 # convert attributes into pyson |
371 encoder = PYSONEncoder() | 473 encoder = PYSONEncoder() |
372 for attr in ('states', 'domain', 'context', 'digits', 'add_remove', | 474 for attr in ('states', 'domain', 'context', 'digits', 'add_remove', |
373 'spell', 'colors'): | 475 'spell', 'colors'): |
374 if element.get(attr): | 476 if element.get(attr): |
375 element.set(attr, encoder.encode(safe_eval(element.get(attr), | 477 element.set(attr, encoder.encode(safe_eval(element.get(attr), |
376 CONTEXT))) | 478 CONTEXT))) |
377 | 479 |
| 480 if element.tag == 'button': |
| 481 button_name = element.attrib['name'] |
| 482 if button_name in cls._buttons: |
| 483 states = cls._buttons[button_name] |
| 484 else: |
| 485 states = {} |
| 486 groups = set(User.get_groups()) |
| 487 button_groups = Button.get_groups(cls.__name__, button_name) |
| 488 if button_groups and not groups & button_groups: |
| 489 states = states.copy() |
| 490 states['readonly'] = True |
| 491 element.set('states', encoder.encode(states)) |
| 492 |
378 # translate view | 493 # translate view |
379 if Transaction().language != 'en_US' and not result: | 494 if Transaction().language != 'en_US': |
380 for attr in ('string', 'sum', 'confirm', 'help'): | 495 for attr in ('string', 'sum', 'confirm', 'help'): |
381 if element.get(attr): | 496 if element.get(attr): |
382 trans = translation_obj._get_source(self._name, 'view', | 497 trans = Translation.get_source(cls.__name__, 'view', |
383 Transaction().language, element.get(attr)) | 498 Transaction().language, element.get(attr)) |
384 if trans: | 499 if trans: |
385 element.set(attr, trans) | 500 element.set(attr, trans) |
386 | 501 |
387 # Set header string | 502 # Set header string |
388 if element.tag in ('form', 'tree', 'graph'): | 503 if element.tag in ('form', 'tree', 'graph'): |
389 element.set('string', self.view_header_get( | 504 element.set('string', cls.view_header_get( |
390 element.get('string') or '', view_type=element.tag)) | 505 element.get('string') or '', view_type=element.tag)) |
391 | 506 |
392 if element.tag == 'tree' and element.get('sequence'): | 507 if element.tag == 'tree' and element.get('sequence'): |
393 fields_attrs.setdefault(element.get('sequence'), {}) | 508 fields_attrs.setdefault(element.get('sequence'), {}) |
394 | 509 |
395 if childs: | 510 if childs: |
396 for field in element: | 511 for field in element: |
397 fields_attrs.update(self.__view_look_dom(field, type, | 512 fields_attrs.update(cls.__view_look_dom(field, type, |
398 fields_width=fields_width)) | 513 fields_width=fields_width)) |
399 return fields_attrs | 514 return fields_attrs |
| 515 |
| 516 @staticmethod |
| 517 def button(func): |
| 518 @wraps(func) |
| 519 def wrapper(cls, *args, **kwargs): |
| 520 pool = Pool() |
| 521 Button = pool.get('ir.model.button') |
| 522 User = pool.get('res.user') |
| 523 |
| 524 if Transaction().user != 0: |
| 525 groups = set(User.get_groups()) |
| 526 button_groups = Button.get_groups(cls.__name__, |
| 527 func.__name__) |
| 528 if button_groups and not groups & button_groups: |
| 529 raise UserError('Calling button %s on %s is not allowed!' |
| 530 % (func.__name__, cls.__name__)) |
| 531 return func(cls, *args, **kwargs) |
| 532 return wrapper |
| 533 |
| 534 @staticmethod |
| 535 def button_action(action): |
| 536 def decorator(func): |
| 537 func = ModelView.button(func) |
| 538 |
| 539 @wraps(func) |
| 540 def wrapper(*args, **kwargs): |
| 541 pool = Pool() |
| 542 ModelData = pool.get('ir.model.data') |
| 543 Action = pool.get('ir.action') |
| 544 |
| 545 func(*args, **kwargs) |
| 546 |
| 547 module, fs_id = action.split('.') |
| 548 action_id = Action.get_action_id( |
| 549 ModelData.get_id(module, fs_id)) |
| 550 return action_id |
| 551 return wrapper |
| 552 return decorator |
LEFT | RIGHT |