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