Merge lp:~openerp-dev/openobject-server/trunk-import-parsers-refactor-thu into lp:openobject-server
- trunk-import-parsers-refactor-thu
- Merge into trunk
Proposed by
Vo Minh Thu
Status: | Work in progress |
---|---|
Proposed branch: | lp:~openerp-dev/openobject-server/trunk-import-parsers-refactor-thu |
Merge into: | lp:openobject-server |
Diff against target: |
853 lines (+840/-0) 2 files modified
openerp/tools/convert.py (+16/-0) parser_xml.py (+824/-0) |
To merge this branch: | bzr merge lp:~openerp-dev/openobject-server/trunk-import-parsers-refactor-thu |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenERP Core Team | Pending | ||
Review via email: mp+150525@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
- 4761. By Vo Minh Thu
-
[IMP] Started to refactor openerp/
tools/convert. A parser_xml.py file has been added. It is a copy of refactor openerp/
tools/convert
where the result of parsing is just a list of records ((string, dict) pairs for now).
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'openerp/tools/convert.py' |
2 | --- openerp/tools/convert.py 2013-02-25 16:40:28 +0000 |
3 | +++ openerp/tools/convert.py 2013-02-26 11:04:52 +0000 |
4 | @@ -952,4 +952,20 @@ |
5 | return True |
6 | |
7 | |
8 | +if __name__ == '__main__': |
9 | + import sys |
10 | + import openerp |
11 | + |
12 | + assert len(sys.argv) == 4 |
13 | + database_name = sys.argv[1] |
14 | + module_name = sys.argv[2] |
15 | + filename = sys.argv[3] |
16 | + |
17 | + xml = etree.parse(filename) |
18 | + |
19 | + registry = openerp.modules.registry.RegistryManager.get(database_name) |
20 | + with registry.cursor() as cr: |
21 | + xml_import(cr, 'base', {}, 'init', None, False).parse(xml.getroot()) |
22 | + |
23 | + |
24 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
25 | |
26 | === added file 'parser_xml.py' |
27 | --- parser_xml.py 1970-01-01 00:00:00 +0000 |
28 | +++ parser_xml.py 2013-02-26 11:04:52 +0000 |
29 | @@ -0,0 +1,824 @@ |
30 | +# -*- coding: utf-8 -*- |
31 | +############################################################################## |
32 | +# |
33 | +# OpenERP, Open Source Management Solution |
34 | +# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). |
35 | +# |
36 | +# This program is free software: you can redistribute it and/or modify |
37 | +# it under the terms of the GNU Affero General Public License as |
38 | +# published by the Free Software Foundation, either version 3 of the |
39 | +# License, or (at your option) any later version. |
40 | +# |
41 | +# This program is distributed in the hope that it will be useful, |
42 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
43 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
44 | +# GNU Affero General Public License for more details. |
45 | +# |
46 | +# You should have received a copy of the GNU Affero General Public License |
47 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
48 | +# |
49 | +############################################################################## |
50 | + |
51 | +import cStringIO |
52 | +import csv |
53 | +import logging |
54 | +import os.path |
55 | +import pickle |
56 | +import re |
57 | + |
58 | +# for eval context: |
59 | +import time |
60 | +import openerp.release as release |
61 | + |
62 | +_logger = logging.getLogger(__name__) |
63 | + |
64 | +try: |
65 | + import pytz |
66 | +except: |
67 | + _logger.warning('could not find pytz library, please install it') |
68 | + class pytzclass(object): |
69 | + all_timezones=[] |
70 | + pytz=pytzclass() |
71 | + |
72 | + |
73 | +from datetime import datetime, timedelta |
74 | +from lxml import etree |
75 | + |
76 | +import openerp.tools.misc as misc |
77 | +import openerp.loglevels as loglevels |
78 | +import openerp.pooler as pooler |
79 | + |
80 | +# List of etree._Element subclasses that we choose to ignore when parsing XML. |
81 | +from openerp.tools.misc import SKIPPED_ELEMENT_TYPES |
82 | + |
83 | +from openerp.tools.misc import unquote |
84 | + |
85 | +# Import of XML records requires the unsafe eval as well, |
86 | +# almost everywhere, which is ok because it supposedly comes |
87 | +# from trusted data, but at least we make it obvious now. |
88 | +unsafe_eval = eval |
89 | +from openerp.tools.safe_eval import safe_eval as eval |
90 | + |
91 | +class ConvertError(Exception): |
92 | + def __init__(self, doc, orig_excpt): |
93 | + self.d = doc |
94 | + self.orig = orig_excpt |
95 | + |
96 | + def __str__(self): |
97 | + return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d) |
98 | + |
99 | +def _ref(self, cr): |
100 | + return lambda x: self.id_get(cr, x) |
101 | + |
102 | +def _obj(pool, cr, uid, model_str, context=None): |
103 | + model = pool.get(model_str) |
104 | + return lambda x: model.browse(cr, uid, x, context=context) |
105 | + |
106 | +def _get_idref(self, cr, uid, model_str, context, idref): |
107 | + idref2 = dict(idref, |
108 | + time=time, |
109 | + DateTime=datetime, |
110 | + timedelta=timedelta, |
111 | + version=release.major_version, |
112 | + ref=_ref(self, cr), |
113 | + pytz=pytz) |
114 | + if len(model_str): |
115 | + idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context) |
116 | + return idref2 |
117 | + |
118 | +def _fix_multiple_roots(node): |
119 | + """ |
120 | + Surround the children of the ``node`` element of an XML field with a |
121 | + single root "data" element, to prevent having a document with multiple |
122 | + roots once parsed separately. |
123 | + |
124 | + XML nodes should have one root only, but we'd like to support |
125 | + direct multiple roots in our partial documents (like inherited view architectures). |
126 | + As a convention we'll surround multiple root with a container "data" element, to be |
127 | + ignored later when parsing. |
128 | + """ |
129 | + real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)] |
130 | + if len(real_nodes) > 1: |
131 | + data_node = etree.Element("data") |
132 | + for child in node: |
133 | + data_node.append(child) |
134 | + node.append(data_node) |
135 | + |
136 | +def node_content(node): |
137 | + """Implement a little hack""" |
138 | + if node.get('type', 'char') == 'xml': |
139 | + _fix_multiple_roots(node) |
140 | + return '<?xml version="1.0"?>\n'\ |
141 | + + "".join([etree.tostring(n, encoding='utf-8') for n in node]) |
142 | + else: |
143 | + return node.text |
144 | + |
145 | +def _eval_xml(self, node, pool, cr, uid, idref, context=None): |
146 | + if context is None: |
147 | + context = {} |
148 | + if node.tag in ('field','value'): |
149 | + t = node.get('type','char') |
150 | + f_model = node.get('model', '').encode('utf-8') |
151 | + if node.get('search'): |
152 | + f_search = node.get("search",'').encode('utf-8') |
153 | + f_use = node.get("use",'id').encode('utf-8') |
154 | + f_name = node.get("name",'').encode('utf-8') |
155 | + idref2 = {} |
156 | + if f_search: |
157 | + idref2 = _get_idref(self, cr, uid, f_model, context, idref) |
158 | + q = unsafe_eval(f_search, idref2) |
159 | + ids = pool.get(f_model).search(cr, uid, q) |
160 | + if f_use != 'id': |
161 | + ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use])) |
162 | + _cols = pool.get(f_model)._columns |
163 | + if (f_name in _cols) and _cols[f_name]._type=='many2many': |
164 | + return ids |
165 | + f_val = False |
166 | + if len(ids): |
167 | + f_val = ids[0] |
168 | + if isinstance(f_val, tuple): |
169 | + f_val = f_val[0] |
170 | + return f_val |
171 | + a_eval = node.get('eval','') |
172 | + idref2 = {} |
173 | + if a_eval: |
174 | + idref2 = _get_idref(self, cr, uid, f_model, context, idref) |
175 | + try: |
176 | + return unsafe_eval(a_eval, idref2) |
177 | + except Exception: |
178 | + _logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True) |
179 | + return "" |
180 | + def _process(s, idref): |
181 | + m = re.findall('[^%]%\((.*?)\)[ds]', s) |
182 | + for id in m: |
183 | + if not id in idref: |
184 | + idref[id]=self.id_get(cr, id) |
185 | + return s % idref |
186 | + if t == 'xml': |
187 | + _fix_multiple_roots(node) |
188 | + return '<?xml version="1.0"?>\n'\ |
189 | + +_process("".join([etree.tostring(n, encoding='utf-8') |
190 | + for n in node]), idref) |
191 | + if t == 'html': |
192 | + return _process("".join([etree.tostring(n, encoding='utf-8') |
193 | + for n in node]), idref) |
194 | + if t in ('char', 'int', 'float'): |
195 | + d = node.text |
196 | + if t == 'int': |
197 | + d = d.strip() |
198 | + if d == 'None': |
199 | + return None |
200 | + else: |
201 | + return int(d.strip()) |
202 | + elif t == 'float': |
203 | + return float(d.strip()) |
204 | + return d |
205 | + elif t in ('list','tuple'): |
206 | + res=[] |
207 | + for n in node.findall('./value'): |
208 | + res.append(_eval_xml(self,n,pool,cr,uid,idref)) |
209 | + if t=='tuple': |
210 | + return tuple(res) |
211 | + return res |
212 | + elif node.tag == "getitem": |
213 | + for n in node: |
214 | + res=_eval_xml(self,n,pool,cr,uid,idref) |
215 | + if not res: |
216 | + raise LookupError |
217 | + elif node.get('type') in ("int", "list"): |
218 | + return res[int(node.get('index'))] |
219 | + else: |
220 | + return res[node.get('index','').encode("utf8")] |
221 | + elif node.tag == "function": |
222 | + args = [] |
223 | + a_eval = node.get('eval','') |
224 | + if a_eval: |
225 | + idref['ref'] = lambda x: self.id_get(cr, x) |
226 | + args = unsafe_eval(a_eval, idref) |
227 | + for n in node: |
228 | + return_val = _eval_xml(self,n, pool, cr, uid, idref, context) |
229 | + if return_val is not None: |
230 | + args.append(return_val) |
231 | + model = pool.get(node.get('model','')) |
232 | + method = node.get('name','') |
233 | + res = getattr(model, method)(cr, uid, *args) |
234 | + return res |
235 | + elif node.tag == "test": |
236 | + return node.text |
237 | + |
238 | +escape_re = re.compile(r'(?<!\\)/') |
239 | +def escape(x): |
240 | + return x.replace('\\/', '/') |
241 | + |
242 | +class parser(object): |
243 | + @staticmethod |
244 | + def nodeattr2bool(node, attr, default=False): |
245 | + val = node.get(attr, '').strip().lower() |
246 | + if not val: |
247 | + return default |
248 | + if val in ('0', 'false', 'off'): |
249 | + return False |
250 | + if val in ('1', 'true', 'on'): |
251 | + return True |
252 | + print "Oh noes" |
253 | + return True |
254 | + |
255 | + def isnoupdate(self, data_node): |
256 | + # TODO is taking the lenght of data_node necessary? |
257 | + return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False)) |
258 | + |
259 | + def get_context(self, data_node, node, eval_dict): |
260 | + data_node_context = (len(data_node) and data_node.get('context','').encode('utf8')) |
261 | + node_context = node.get("context",'').encode('utf8') |
262 | + context = {} |
263 | + for ctx in (data_node_context, node_context): |
264 | + if ctx: |
265 | + try: |
266 | + ctx_res = unsafe_eval(ctx, eval_dict) |
267 | + if isinstance(context, dict): |
268 | + context.update(ctx_res) |
269 | + else: |
270 | + context = ctx_res |
271 | + except NameError: |
272 | + # TODO is this still true ? |
273 | + # Some contexts contain references that are only valid at runtime at |
274 | + # client-side, so in that case we keep the original context string |
275 | + # as it is. We also log it, just in case. |
276 | + context = ctx |
277 | + _logger.debug('Context value (%s) for element with id "%s" or its data node does not parse '\ |
278 | + 'at server-side, keeping original string, in case it\'s meant for client side only', |
279 | + ctx, node.get('id','n/a'), exc_info=True) |
280 | + return context |
281 | + |
282 | + def get_uid(self, cr, uid, data_node, node): |
283 | + node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid','')) |
284 | + if node_uid: |
285 | + return self.id_get(cr, node_uid) |
286 | + return uid |
287 | + |
288 | + def _test_xml_id(self, xml_id): |
289 | + id = xml_id |
290 | + if '.' in xml_id: |
291 | + module, id = xml_id.split('.', 1) |
292 | + assert '.' not in id, """The ID reference "%s" must contain |
293 | +maximum one dot. They are used to refer to other modules ID, in the |
294 | +form: module.record_id""" % (xml_id,) |
295 | + if module != self.module: |
296 | + modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])]) |
297 | + assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,) |
298 | + |
299 | + if len(id) > 64: |
300 | + _logger.error('id: %s is to long (max: 64)', id) |
301 | + |
302 | + def _tag_delete(self, cr, rec, data_node=None): |
303 | + d_model = rec.get("model",'') |
304 | + d_search = rec.get("search",'').encode('utf-8') |
305 | + d_id = rec.get("id",'') |
306 | + ids = [] |
307 | + |
308 | + if d_search: |
309 | + idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={}) |
310 | + ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search, idref)) |
311 | + if d_id: |
312 | + try: |
313 | + ids.append(self.id_get(cr, d_id)) |
314 | + except: |
315 | + # d_id cannot be found. doesn't matter in this case |
316 | + pass |
317 | + if ids: |
318 | + self.pool.get(d_model).unlink(cr, self.uid, ids) |
319 | + |
320 | + def _remove_ir_values(self, cr, name, value, model): |
321 | + ir_values_obj = self.pool.get('ir.values') |
322 | + ir_value_ids = ir_values_obj.search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)]) |
323 | + if ir_value_ids: |
324 | + ir_values_obj.unlink(cr, self.uid, ir_value_ids) |
325 | + |
326 | + return True |
327 | + |
328 | + def _tag_report(self, cr, rec, data_node=None): |
329 | + res = {} |
330 | + for dest,f in (('name','string'),('model','model'),('report_name','name')): |
331 | + res[dest] = rec.get(f,'').encode('utf8') |
332 | + assert res[dest], "Attribute %s of report is empty !" % (f,) |
333 | + for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'), |
334 | + ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage')): |
335 | + if rec.get(field): |
336 | + res[dest] = rec.get(field).encode('utf8') |
337 | + if rec.get('auto'): |
338 | + res['auto'] = eval(rec.get('auto','False')) |
339 | + if rec.get('sxw'): |
340 | + sxw_content = misc.file_open(rec.get('sxw')).read() |
341 | + res['report_sxw_content'] = sxw_content |
342 | + if rec.get('header'): |
343 | + res['header'] = eval(rec.get('header','False')) |
344 | + if rec.get('report_type'): |
345 | + res['report_type'] = rec.get('report_type') |
346 | + |
347 | + res['multi'] = rec.get('multi') and eval(rec.get('multi','False')) |
348 | + |
349 | + xml_id = rec.get('id','').encode('utf8') |
350 | + self._test_xml_id(xml_id) |
351 | + |
352 | + if rec.get('groups'): |
353 | + g_names = rec.get('groups','').split(',') |
354 | + groups_value = [] |
355 | + for group in g_names: |
356 | + if group.startswith('-'): |
357 | + group_id = self.id_get(cr, group[1:]) |
358 | + groups_value.append((3, group_id)) |
359 | + else: |
360 | + group_id = self.id_get(cr, group) |
361 | + groups_value.append((4, group_id)) |
362 | + res['groups_id'] = groups_value |
363 | + |
364 | + id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.report.xml", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) |
365 | + self.idref[xml_id] = int(id) |
366 | + |
367 | + if not rec.get('menu') or eval(rec.get('menu','False')): |
368 | + keyword = str(rec.get('keyword', 'client_print_multi')) |
369 | + value = 'ir.actions.report.xml,'+str(id) |
370 | + replace = rec.get('replace', True) |
371 | + self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, res['name'], [res['model']], value, replace=replace, isobject=True, xml_id=xml_id) |
372 | + elif self.mode=='update' and eval(rec.get('menu','False'))==False: |
373 | + # Special check for report having attribute menu=False on update |
374 | + value = 'ir.actions.report.xml,'+str(id) |
375 | + self._remove_ir_values(cr, res['name'], value, res['model']) |
376 | + return id |
377 | + |
378 | + def _tag_function(self, rec): |
379 | + return ('TODO_TAG_FUNCTION', {}) |
380 | + |
381 | + def _tag_wizard(self, cr, rec, data_node=None): |
382 | + string = rec.get("string",'').encode('utf8') |
383 | + model = rec.get("model",'').encode('utf8') |
384 | + name = rec.get("name",'').encode('utf8') |
385 | + xml_id = rec.get('id','').encode('utf8') |
386 | + self._test_xml_id(xml_id) |
387 | + multi = rec.get('multi','') and eval(rec.get('multi','False')) |
388 | + res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model} |
389 | + |
390 | + if rec.get('groups'): |
391 | + g_names = rec.get('groups','').split(',') |
392 | + groups_value = [] |
393 | + for group in g_names: |
394 | + if group.startswith('-'): |
395 | + group_id = self.id_get(cr, group[1:]) |
396 | + groups_value.append((3, group_id)) |
397 | + else: |
398 | + group_id = self.id_get(cr, group) |
399 | + groups_value.append((4, group_id)) |
400 | + res['groups_id'] = groups_value |
401 | + |
402 | + id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.wizard", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) |
403 | + self.idref[xml_id] = int(id) |
404 | + # ir_set |
405 | + if (not rec.get('menu') or eval(rec.get('menu','False'))) and id: |
406 | + keyword = str(rec.get('keyword','') or 'client_action_multi') |
407 | + value = 'ir.actions.wizard,'+str(id) |
408 | + replace = rec.get("replace",'') or True |
409 | + self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id) |
410 | + elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False): |
411 | + # Special check for wizard having attribute menu=False on update |
412 | + value = 'ir.actions.wizard,'+str(id) |
413 | + self._remove_ir_values(cr, string, value, model) |
414 | + |
415 | + def _tag_url(self, cr, rec, data_node=None): |
416 | + url = rec.get("url",'').encode('utf8') |
417 | + target = rec.get("target",'').encode('utf8') |
418 | + name = rec.get("name",'').encode('utf8') |
419 | + xml_id = rec.get('id','').encode('utf8') |
420 | + self._test_xml_id(xml_id) |
421 | + |
422 | + res = {'name': name, 'url': url, 'target':target} |
423 | + |
424 | + id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) |
425 | + self.idref[xml_id] = int(id) |
426 | + # ir_set |
427 | + if (not rec.get('menu') or eval(rec.get('menu','False'))) and id: |
428 | + keyword = str(rec.get('keyword','') or 'client_action_multi') |
429 | + value = 'ir.actions.url,'+str(id) |
430 | + replace = rec.get("replace",'') or True |
431 | + self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, url, ["ir.actions.url"], value, replace=replace, isobject=True, xml_id=xml_id) |
432 | + elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False): |
433 | + # Special check for URL having attribute menu=False on update |
434 | + value = 'ir.actions.url,'+str(id) |
435 | + self._remove_ir_values(cr, url, value, "ir.actions.url") |
436 | + |
437 | + def _tag_act_window(self, cr, rec, data_node=None): |
438 | + name = rec.get('name','').encode('utf-8') |
439 | + xml_id = rec.get('id','').encode('utf8') |
440 | + self._test_xml_id(xml_id) |
441 | + type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window' |
442 | + view_id = False |
443 | + if rec.get('view_id'): |
444 | + view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8')) |
445 | + domain = rec.get('domain','').encode('utf-8') or '[]' |
446 | + res_model = rec.get('res_model','').encode('utf-8') |
447 | + src_model = rec.get('src_model','').encode('utf-8') |
448 | + view_type = rec.get('view_type','').encode('utf-8') or 'form' |
449 | + view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form' |
450 | + usage = rec.get('usage','').encode('utf-8') |
451 | + limit = rec.get('limit','').encode('utf-8') |
452 | + auto_refresh = rec.get('auto_refresh','').encode('utf-8') |
453 | + uid = self.uid |
454 | + |
455 | + # Act_window's 'domain' and 'context' contain mostly literals |
456 | + # but they can also refer to the variables provided below |
457 | + # in eval_context, so we need to eval() them before storing. |
458 | + # Among the context variables, 'active_id' refers to |
459 | + # the currently selected items in a list view, and only |
460 | + # takes meaning at runtime on the client side. For this |
461 | + # reason it must remain a bare variable in domain and context, |
462 | + # even after eval() at server-side. We use the special 'unquote' |
463 | + # class to achieve this effect: a string which has itself, unquoted, |
464 | + # as representation. |
465 | + active_id = unquote("active_id") |
466 | + active_ids = unquote("active_ids") |
467 | + active_model = unquote("active_model") |
468 | + |
469 | + def ref(str_id): |
470 | + return self.id_get(cr, str_id) |
471 | + |
472 | + # Include all locals() in eval_context, for backwards compatibility |
473 | + eval_context = { |
474 | + 'name': name, |
475 | + 'xml_id': xml_id, |
476 | + 'type': type, |
477 | + 'view_id': view_id, |
478 | + 'domain': domain, |
479 | + 'res_model': res_model, |
480 | + 'src_model': src_model, |
481 | + 'view_type': view_type, |
482 | + 'view_mode': view_mode, |
483 | + 'usage': usage, |
484 | + 'limit': limit, |
485 | + 'auto_refresh': auto_refresh, |
486 | + 'uid' : uid, |
487 | + 'active_id': active_id, |
488 | + 'active_ids': active_ids, |
489 | + 'active_model': active_model, |
490 | + 'ref' : ref, |
491 | + } |
492 | + context = self.get_context(data_node, rec, eval_context) |
493 | + |
494 | + try: |
495 | + domain = unsafe_eval(domain, eval_context) |
496 | + except NameError: |
497 | + # Some domains contain references that are only valid at runtime at |
498 | + # client-side, so in that case we keep the original domain string |
499 | + # as it is. We also log it, just in case. |
500 | + _logger.debug('Domain value (%s) for element with id "%s" does not parse '\ |
501 | + 'at server-side, keeping original string, in case it\'s meant for client side only', |
502 | + domain, xml_id or 'n/a', exc_info=True) |
503 | + res = { |
504 | + 'name': name, |
505 | + 'type': type, |
506 | + 'view_id': view_id, |
507 | + 'domain': domain, |
508 | + 'context': context, |
509 | + 'res_model': res_model, |
510 | + 'src_model': src_model, |
511 | + 'view_type': view_type, |
512 | + 'view_mode': view_mode, |
513 | + 'usage': usage, |
514 | + 'limit': limit, |
515 | + 'auto_refresh': auto_refresh, |
516 | + } |
517 | + |
518 | + if rec.get('groups'): |
519 | + g_names = rec.get('groups','').split(',') |
520 | + groups_value = [] |
521 | + for group in g_names: |
522 | + if group.startswith('-'): |
523 | + group_id = self.id_get(cr, group[1:]) |
524 | + groups_value.append((3, group_id)) |
525 | + else: |
526 | + group_id = self.id_get(cr, group) |
527 | + groups_value.append((4, group_id)) |
528 | + res['groups_id'] = groups_value |
529 | + |
530 | + if rec.get('target'): |
531 | + res['target'] = rec.get('target','') |
532 | + if rec.get('multi'): |
533 | + res['multi'] = rec.get('multi', False) |
534 | + id = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.actions.act_window', self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) |
535 | + self.idref[xml_id] = int(id) |
536 | + |
537 | + if src_model: |
538 | + #keyword = 'client_action_relate' |
539 | + keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate' |
540 | + value = 'ir.actions.act_window,'+str(id) |
541 | + replace = rec.get('replace','') or True |
542 | + self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, xml_id, [src_model], value, replace=replace, isobject=True, xml_id=xml_id) |
543 | + # TODO add remove ir.model.data |
544 | + |
545 | + def _tag_ir_set(self, cr, rec, data_node=None): |
546 | + if self.mode != 'init': |
547 | + return |
548 | + res = {} |
549 | + for field in rec.findall('./field'): |
550 | + f_name = field.get("name",'').encode('utf-8') |
551 | + f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref) |
552 | + res[f_name] = f_val |
553 | + self.pool.get('ir.model.data').ir_set(cr, self.uid, res['key'], res['key2'], res['name'], res['models'], res['value'], replace=res.get('replace',True), isobject=res.get('isobject', False), meta=res.get('meta',None)) |
554 | + |
555 | + def _tag_workflow(self, cr, rec, data_node=None): |
556 | + if self.isnoupdate(data_node) and self.mode != 'init': |
557 | + return |
558 | + model = str(rec.get('model','')) |
559 | + w_ref = rec.get('ref','') |
560 | + if w_ref: |
561 | + id = self.id_get(cr, w_ref) |
562 | + else: |
563 | + number_children = len(rec) |
564 | + assert number_children > 0,\ |
565 | + 'You must define a child node if you dont give a ref' |
566 | + assert number_children == 1,\ |
567 | + 'Only one child node is accepted (%d given)' % number_children |
568 | + id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref) |
569 | + |
570 | + uid = self.get_uid(cr, self.uid, data_node, rec) |
571 | + import openerp.netsvc as netsvc |
572 | + wf_service = netsvc.LocalService("workflow") |
573 | + wf_service.trg_validate(uid, model, |
574 | + id, |
575 | + str(rec.get('action','')), cr) |
576 | + |
577 | + # |
578 | + # Support two types of notation: |
579 | + # name="Inventory Control/Sending Goods" |
580 | + # or |
581 | + # action="action_id" |
582 | + # parent="parent_id" |
583 | + # |
584 | + def _tag_menuitem(self, cr, rec, data_node=None): |
585 | + rec_id = rec.get("id",'').encode('ascii') |
586 | + self._test_xml_id(rec_id) |
587 | + m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8'))) |
588 | + |
589 | + values = {'parent_id': False} |
590 | + if rec.get('parent', False) is False and len(m_l) > 1: |
591 | + # No parent attribute specified and the menu name has several menu components, |
592 | + # try to determine the ID of the parent according to menu path |
593 | + pid = False |
594 | + res = None |
595 | + values['name'] = m_l[-1] |
596 | + m_l = m_l[:-1] # last part is our name, not a parent |
597 | + for idx, menu_elem in enumerate(m_l): |
598 | + if pid: |
599 | + cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem)) |
600 | + else: |
601 | + cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,)) |
602 | + res = cr.fetchone() |
603 | + if res: |
604 | + pid = res[0] |
605 | + else: |
606 | + # the menuitem does't exist but we are in branch (not a leaf) |
607 | + _logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l)) |
608 | + pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem}) |
609 | + values['parent_id'] = pid |
610 | + else: |
611 | + # The parent attribute was specified, if non-empty determine its ID, otherwise |
612 | + # explicitly make a top-level menu |
613 | + if rec.get('parent'): |
614 | + menu_parent_id = self.id_get(cr, rec.get('parent','')) |
615 | + else: |
616 | + # we get here with <menuitem parent="">, explicit clear of parent, or |
617 | + # if no parent attribute at all but menu name is not a menu path |
618 | + menu_parent_id = False |
619 | + values = {'parent_id': menu_parent_id} |
620 | + if rec.get('name'): |
621 | + values['name'] = rec.get('name') |
622 | + try: |
623 | + res = [ self.id_get(cr, rec.get('id','')) ] |
624 | + except: |
625 | + res = None |
626 | + |
627 | + if rec.get('action'): |
628 | + a_action = rec.get('action','').encode('utf8') |
629 | + |
630 | + # determine the type of action |
631 | + a_type, a_id = self.model_id_get(cr, a_action) |
632 | + a_type = a_type.split('.')[-1] # keep only type part |
633 | + |
634 | + icons = { |
635 | + "act_window": 'STOCK_NEW', |
636 | + "report.xml": 'STOCK_PASTE', |
637 | + "wizard": 'STOCK_EXECUTE', |
638 | + "url": 'STOCK_JUMP_TO', |
639 | + "client": 'STOCK_EXECUTE', |
640 | + "server": 'STOCK_EXECUTE', |
641 | + } |
642 | + values['icon'] = icons.get(a_type,'STOCK_NEW') |
643 | + |
644 | + if a_type=='act_window': |
645 | + cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),)) |
646 | + rrres = cr.fetchone() |
647 | + assert rrres, "No window action defined for this id %s !\n" \ |
648 | + "Verify that this is a window action or add a type argument." % (a_action,) |
649 | + action_type,action_mode,action_name,view_id,target = rrres |
650 | + if view_id: |
651 | + cr.execute('SELECT arch FROM ir_ui_view WHERE id=%s', (int(view_id),)) |
652 | + arch, = cr.fetchone() |
653 | + action_mode = etree.fromstring(arch.encode('utf8')).tag |
654 | + cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),)) |
655 | + if cr.rowcount: |
656 | + action_mode, = cr.fetchone() |
657 | + if action_type=='tree': |
658 | + values['icon'] = 'STOCK_INDENT' |
659 | + elif action_mode and action_mode.startswith(('tree','kanban','gantt')): |
660 | + values['icon'] = 'STOCK_JUSTIFY_FILL' |
661 | + elif action_mode and action_mode.startswith('graph'): |
662 | + values['icon'] = 'terp-graph' |
663 | + elif action_mode and action_mode.startswith('calendar'): |
664 | + values['icon'] = 'terp-calendar' |
665 | + if target=='new': |
666 | + values['icon'] = 'STOCK_EXECUTE' |
667 | + if not values.get('name', False): |
668 | + values['name'] = action_name |
669 | + |
670 | + elif a_type in ['wizard', 'url', 'client', 'server'] and not values.get('name'): |
671 | + a_table = 'ir_act_%s' % a_type |
672 | + cr.execute('select name from %s where id=%%s' % a_table, (int(a_id),)) |
673 | + resw = cr.fetchone() |
674 | + if resw: |
675 | + values['name'] = resw[0] |
676 | + |
677 | + if not values.get('name'): |
678 | + # ensure menu has a name |
679 | + values['name'] = rec_id or '?' |
680 | + |
681 | + if rec.get('sequence'): |
682 | + values['sequence'] = int(rec.get('sequence')) |
683 | + if rec.get('icon'): |
684 | + values['icon'] = str(rec.get('icon')) |
685 | + if rec.get('web_icon'): |
686 | + values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon'))) |
687 | + if rec.get('web_icon_hover'): |
688 | + values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover'))) |
689 | + |
690 | + if rec.get('groups'): |
691 | + g_names = rec.get('groups','').split(',') |
692 | + groups_value = [] |
693 | + for group in g_names: |
694 | + if group.startswith('-'): |
695 | + group_id = self.id_get(cr, group[1:]) |
696 | + groups_value.append((3, group_id)) |
697 | + else: |
698 | + group_id = self.id_get(cr, group) |
699 | + groups_value.append((4, group_id)) |
700 | + values['groups_id'] = groups_value |
701 | + |
702 | + pid = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.ui.menu', self.module, values, rec_id, noupdate=self.isnoupdate(data_node), mode=self.mode, res_id=res and res[0] or False) |
703 | + |
704 | + if rec_id and pid: |
705 | + self.idref[rec_id] = int(pid) |
706 | + |
707 | + if rec.get('action') and pid: |
708 | + action = "ir.actions.%s,%d" % (a_type, a_id) |
709 | + self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(pid))], action, True, True, xml_id=rec_id) |
710 | + return ('ir.ui.menu', pid) |
711 | + |
712 | + def _assert_equals(self, f1, f2, prec=4): |
713 | + return not round(f1 - f2, prec) |
714 | + |
715 | + def _tag_assert(self, cr, rec, data_node=None): |
716 | + if self.isnoupdate(data_node) and self.mode != 'init': |
717 | + return |
718 | + |
719 | + rec_model = rec.get("model",'').encode('ascii') |
720 | + model = self.pool.get(rec_model) |
721 | + assert model, "The model %s does not exist !" % (rec_model,) |
722 | + rec_id = rec.get("id",'').encode('ascii') |
723 | + self._test_xml_id(rec_id) |
724 | + rec_src = rec.get("search",'').encode('utf8') |
725 | + rec_src_count = rec.get("count") |
726 | + |
727 | + rec_string = rec.get("string",'').encode('utf8') or 'unknown' |
728 | + |
729 | + ids = None |
730 | + eval_dict = {'ref': _ref(self, cr)} |
731 | + context = self.get_context(data_node, rec, eval_dict) |
732 | + uid = self.get_uid(cr, self.uid, data_node, rec) |
733 | + if rec_id: |
734 | + ids = [self.id_get(cr, rec_id)] |
735 | + elif rec_src: |
736 | + q = unsafe_eval(rec_src, eval_dict) |
737 | + ids = self.pool.get(rec_model).search(cr, uid, q, context=context) |
738 | + if rec_src_count: |
739 | + count = int(rec_src_count) |
740 | + if len(ids) != count: |
741 | + self.assertion_report.record_failure() |
742 | + msg = 'assertion "%s" failed!\n' \ |
743 | + ' Incorrect search count:\n' \ |
744 | + ' expected count: %d\n' \ |
745 | + ' obtained count: %d\n' \ |
746 | + % (rec_string, count, len(ids)) |
747 | + _logger.error(msg) |
748 | + return |
749 | + |
750 | + assert ids is not None,\ |
751 | + 'You must give either an id or a search criteria' |
752 | + ref = _ref(self, cr) |
753 | + for id in ids: |
754 | + brrec = model.browse(cr, uid, id, context) |
755 | + class d(dict): |
756 | + def __getitem__(self2, key): |
757 | + if key in brrec: |
758 | + return brrec[key] |
759 | + return dict.__getitem__(self2, key) |
760 | + globals_dict = d() |
761 | + globals_dict['floatEqual'] = self._assert_equals |
762 | + globals_dict['ref'] = ref |
763 | + globals_dict['_ref'] = ref |
764 | + for test in rec.findall('./test'): |
765 | + f_expr = test.get("expr",'').encode('utf-8') |
766 | + expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True |
767 | + expression_value = unsafe_eval(f_expr, globals_dict) |
768 | + if expression_value != expected_value: # assertion failed |
769 | + self.assertion_report.record_failure() |
770 | + msg = 'assertion "%s" failed!\n' \ |
771 | + ' xmltag: %s\n' \ |
772 | + ' expected value: %r\n' \ |
773 | + ' obtained value: %r\n' \ |
774 | + % (rec_string, etree.tostring(test), expected_value, expression_value) |
775 | + _logger.error(msg) |
776 | + return |
777 | + else: # all tests were successful for this assertion tag (no break) |
778 | + self.assertion_report.record_success() |
779 | + |
780 | + def _tag_record(self, rec): |
781 | + rec_model = rec.get('model').encode('ascii') |
782 | + rec_id = rec.get('id','').encode('ascii') |
783 | + rec_context = rec.get('context', None) |
784 | + |
785 | + rec_fields = {} |
786 | + for field in rec.findall('./field'): |
787 | + field_name = field.get('name').encode('utf-8') |
788 | + rec_fields[field_name] = {} |
789 | + for x in ('ref', 'search', 'model', 'use', 'eval'): |
790 | + x_ = field.get(x,'').encode('utf-8') |
791 | + if x_: rec_fields[field_name][x] = x_ |
792 | + if field.text is not None: |
793 | + rec_fields[field_name]['content'] = node_content(field) |
794 | + |
795 | + return ('RECORD', { |
796 | + 'model': rec_model, |
797 | + 'xid': rec_id, |
798 | + 'context': rec_context, |
799 | + 'fields': rec_fields, |
800 | + }) |
801 | + |
802 | + def __iter__(self): |
803 | + assert self.xml.tag == 'openerp' |
804 | + _tags = { |
805 | + 'menuitem': self._tag_menuitem, |
806 | + 'record': self._tag_record, |
807 | + 'assert': self._tag_assert, |
808 | + 'report': self._tag_report, |
809 | + 'wizard': self._tag_wizard, |
810 | + 'delete': self._tag_delete, |
811 | + 'ir_set': self._tag_ir_set, |
812 | + 'function': self._tag_function, |
813 | + 'workflow': self._tag_workflow, |
814 | + 'act_window': self._tag_act_window, |
815 | + 'url': self._tag_url |
816 | + } |
817 | + for data in self.xml.findall('./data'): |
818 | + for element in data: |
819 | + if element.tag in _tags: |
820 | + yield _tags[element.tag](element) |
821 | + else: |
822 | + yield ('UNKNOWN', {'tag': element.tag}) |
823 | + |
824 | + def __init__(self, module_name, xml): |
825 | + |
826 | + self.module_name = module_name |
827 | + self.xml = xml.getroot() |
828 | + |
829 | +def valid_xml(xml): |
830 | + schema_xml = etree.parse('openerp/import_xml.rng' ) |
831 | + schema = etree.RelaxNG(schema_xml) |
832 | + try: |
833 | + schema.assert_(xml) |
834 | + except Exception: |
835 | + print "Can't validate the file `%s` against the RelaxNG schema." % filename |
836 | + return False |
837 | + else: |
838 | + return True |
839 | + |
840 | +if __name__ == '__main__': |
841 | + import sys |
842 | + assert len(sys.argv) == 3 |
843 | + module_name = sys.argv[1] |
844 | + filename = sys.argv[2] |
845 | + |
846 | + xml = etree.parse(filename) |
847 | + if not valid_xml(xml): |
848 | + sys.exit(1) |
849 | + |
850 | + for x in parser(module_name, xml): |
851 | + print x |
852 | + |
853 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |