[IMP] base : Improved the typos.
[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
29 # for eval context:
30 import time
31 import openerp.release as release
32
33 import assertion_report
34
35 _logger = logging.getLogger(__name__)
36
37 try:
38     import pytz
39 except:
40     _logger.warning('could not find pytz library, please install it')
41     class pytzclass(object):
42         all_timezones=[]
43     pytz=pytzclass()
44
45
46 from datetime import datetime, timedelta
47 from lxml import etree
48 import misc
49 import openerp.loglevels as loglevels
50 import openerp.pooler as pooler
51 from config import config
52 from translate import _
53
54 # List of etree._Element subclasses that we choose to ignore when parsing XML.
55 from misc import SKIPPED_ELEMENT_TYPES
56
57 from misc import unquote
58
59 # Import of XML records requires the unsafe eval as well,
60 # almost everywhere, which is ok because it supposedly comes
61 # from trusted data, but at least we make it obvious now.
62 unsafe_eval = eval
63 from safe_eval import safe_eval as eval
64
65 class ConvertError(Exception):
66     def __init__(self, doc, orig_excpt):
67         self.d = doc
68         self.orig = orig_excpt
69
70     def __str__(self):
71         return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
72
73 def _ref(self, cr):
74     return lambda x: self.id_get(cr, x)
75
76 def _obj(pool, cr, uid, model_str, context=None):
77     model = pool.get(model_str)
78     return lambda x: model.browse(cr, uid, x, context=context)
79
80 def _get_idref(self, cr, uid, model_str, context, idref):
81     idref2 = dict(idref,
82                   time=time,
83                   DateTime=datetime,
84                   timedelta=timedelta,
85                   version=release.major_version,
86                   ref=_ref(self, cr),
87                   pytz=pytz)
88     if len(model_str):
89         idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context)
90     return idref2
91
92 def _fix_multiple_roots(node):
93     """
94     Surround the children of the ``node`` element of an XML field with a
95     single root "data" element, to prevent having a document with multiple
96     roots once parsed separately.
97
98     XML nodes should have one root only, but we'd like to support
99     direct multiple roots in our partial documents (like inherited view architectures).
100     As a convention we'll surround multiple root with a container "data" element, to be
101     ignored later when parsing.
102     """
103     real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)]
104     if len(real_nodes) > 1:
105         data_node = etree.Element("data")
106         for child in node:
107             data_node.append(child)
108         node.append(data_node)
109
110 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
111     if context is None:
112         context = {}
113     if node.tag in ('field','value'):
114         t = node.get('type','char')
115         f_model = node.get('model', '').encode('utf-8')
116         if node.get('search'):
117             f_search = node.get("search",'').encode('utf-8')
118             f_use = node.get("use",'id').encode('utf-8')
119             f_name = node.get("name",'').encode('utf-8')
120             idref2 = {}
121             if f_search:
122                 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
123             q = unsafe_eval(f_search, idref2)
124             ids = pool.get(f_model).search(cr, uid, q)
125             if f_use != 'id':
126                 ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
127             _cols = pool.get(f_model)._columns
128             if (f_name in _cols) and _cols[f_name]._type=='many2many':
129                 return ids
130             f_val = False
131             if len(ids):
132                 f_val = ids[0]
133                 if isinstance(f_val, tuple):
134                     f_val = f_val[0]
135             return f_val
136         a_eval = node.get('eval','')
137         idref2 = {}
138         if a_eval:
139             idref2 = _get_idref(self, cr, uid, f_model, context, idref)
140             try:
141                 return unsafe_eval(a_eval, idref2)
142             except Exception:
143                 _logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True)
144                 return ""
145         if t == 'xml':
146             def _process(s, idref):
147                 m = re.findall('[^%]%\((.*?)\)[ds]', s)
148                 for id in m:
149                     if not id in idref:
150                         idref[id]=self.id_get(cr, id)
151                 return s % idref
152             _fix_multiple_roots(node)
153             return '<?xml version="1.0"?>\n'\
154                 +_process("".join([etree.tostring(n, encoding='utf-8')
155                                    for n in node]),
156                           idref)
157         if t in ('char', 'int', 'float'):
158             d = node.text
159             if t == 'int':
160                 d = d.strip()
161                 if d == 'None':
162                     return None
163                 else:
164                     return int(d.strip())
165             elif t == 'float':
166                 return float(d.strip())
167             return d
168         elif t in ('list','tuple'):
169             res=[]
170             for n in node.findall('./value'):
171                 res.append(_eval_xml(self,n,pool,cr,uid,idref))
172             if t=='tuple':
173                 return tuple(res)
174             return res
175     elif node.tag == "getitem":
176         for n in node:
177             res=_eval_xml(self,n,pool,cr,uid,idref)
178         if not res:
179             raise LookupError
180         elif node.get('type') in ("int", "list"):
181             return res[int(node.get('index'))]
182         else:
183             return res[node.get('index','').encode("utf8")]
184     elif node.tag == "function":
185         args = []
186         a_eval = node.get('eval','')
187         if a_eval:
188             idref['ref'] = lambda x: self.id_get(cr, x)
189             args = unsafe_eval(a_eval, idref)
190         for n in node:
191             return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
192             if return_val is not None:
193                 args.append(return_val)
194         model = pool.get(node.get('model',''))
195         method = node.get('name','')
196         res = getattr(model, method)(cr, uid, *args)
197         return res
198     elif node.tag == "test":
199         return node.text
200
201 escape_re = re.compile(r'(?<!\\)/')
202 def escape(x):
203     return x.replace('\\/', '/')
204
205 class xml_import(object):
206     @staticmethod
207     def nodeattr2bool(node, attr, default=False):
208         if not node.get(attr):
209             return default
210         val = node.get(attr).strip()
211         if not val:
212             return default
213         return val.lower() not in ('0', 'false', 'off')
214
215     def isnoupdate(self, data_node=None):
216         return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
217
218     def get_context(self, data_node, node, eval_dict):
219         data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
220         node_context = node.get("context",'').encode('utf8')
221         context = {}
222         for ctx in (data_node_context, node_context):
223             if ctx:
224                 try:
225                     ctx_res = unsafe_eval(ctx, eval_dict)
226                     if isinstance(context, dict):
227                         context.update(ctx_res)
228                     else:
229                         context = ctx_res
230                 except NameError:
231                     # Some contexts contain references that are only valid at runtime at
232                     # client-side, so in that case we keep the original context string
233                     # as it is. We also log it, just in case.
234                     context = ctx
235                     _logger.debug('Context value (%s) for element with id "%s" or its data node does not parse '\
236                                                     'at server-side, keeping original string, in case it\'s meant for client side only',
237                                                     ctx, node.get('id','n/a'), exc_info=True)
238         return context
239
240     def get_uid(self, cr, uid, data_node, node):
241         node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
242         if node_uid:
243             return self.id_get(cr, node_uid)
244         return uid
245
246     def _test_xml_id(self, xml_id):
247         id = xml_id
248         if '.' in xml_id:
249             module, id = xml_id.split('.', 1)
250             assert '.' not in id, """The ID reference "%s" must contain
251 maximum one dot. They are used to refer to other modules ID, in the
252 form: module.record_id""" % (xml_id,)
253             if module != self.module:
254                 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
255                 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
256
257         if len(id) > 64:
258             _logger.error('id: %s is to long (max: 64)', id)
259
260     def _tag_delete(self, cr, rec, data_node=None):
261         d_model = rec.get("model",'')
262         d_search = rec.get("search",'').encode('utf-8')
263         d_id = rec.get("id",'')
264         ids = []
265
266         if d_search:
267             idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
268             ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search, idref))
269         if d_id:
270             try:
271                 ids.append(self.id_get(cr, d_id))
272             except:
273                 # d_id cannot be found. doesn't matter in this case
274                 pass
275         if ids:
276             self.pool.get(d_model).unlink(cr, self.uid, ids)
277
278     def _remove_ir_values(self, cr, name, value, model):
279         ir_values_obj = self.pool.get('ir.values')
280         ir_value_ids = ir_values_obj.search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
281         if ir_value_ids:
282             ir_values_obj.unlink(cr, self.uid, ir_value_ids)
283
284         return True
285
286     def _tag_report(self, cr, rec, data_node=None):
287         res = {}
288         for dest,f in (('name','string'),('model','model'),('report_name','name')):
289             res[dest] = rec.get(f,'').encode('utf8')
290             assert res[dest], "Attribute %s of report is empty !" % (f,)
291         for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),
292                            ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage')):
293             if rec.get(field):
294                 res[dest] = rec.get(field).encode('utf8')
295         if rec.get('auto'):
296             res['auto'] = eval(rec.get('auto','False'))
297         if rec.get('sxw'):
298             sxw_content = misc.file_open(rec.get('sxw')).read()
299             res['report_sxw_content'] = sxw_content
300         if rec.get('header'):
301             res['header'] = eval(rec.get('header','False'))
302         if rec.get('report_type'):
303             res['report_type'] = rec.get('report_type')
304
305         res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
306
307         xml_id = rec.get('id','').encode('utf8')
308         self._test_xml_id(xml_id)
309
310         if rec.get('groups'):
311             g_names = rec.get('groups','').split(',')
312             groups_value = []
313             for group in g_names:
314                 if group.startswith('-'):
315                     group_id = self.id_get(cr, group[1:])
316                     groups_value.append((3, group_id))
317                 else:
318                     group_id = self.id_get(cr, group)
319                     groups_value.append((4, group_id))
320             res['groups_id'] = groups_value
321
322         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)
323         self.idref[xml_id] = int(id)
324
325         if not rec.get('menu') or eval(rec.get('menu','False')):
326             keyword = str(rec.get('keyword', 'client_print_multi'))
327             value = 'ir.actions.report.xml,'+str(id)
328             replace = rec.get('replace', True)
329             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)
330         elif self.mode=='update' and eval(rec.get('menu','False'))==False:
331             # Special check for report having attribute menu=False on update
332             value = 'ir.actions.report.xml,'+str(id)
333             self._remove_ir_values(cr, res['name'], value, res['model'])
334         return id
335
336     def _tag_function(self, cr, rec, data_node=None):
337         if self.isnoupdate(data_node) and self.mode != 'init':
338             return
339         context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
340         uid = self.get_uid(cr, self.uid, data_node, rec)
341         _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
342         return
343
344     def _tag_wizard(self, cr, rec, data_node=None):
345         string = rec.get("string",'').encode('utf8')
346         model = rec.get("model",'').encode('utf8')
347         name = rec.get("name",'').encode('utf8')
348         xml_id = rec.get('id','').encode('utf8')
349         self._test_xml_id(xml_id)
350         multi = rec.get('multi','') and eval(rec.get('multi','False'))
351         res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
352
353         if rec.get('groups'):
354             g_names = rec.get('groups','').split(',')
355             groups_value = []
356             for group in g_names:
357                 if group.startswith('-'):
358                     group_id = self.id_get(cr, group[1:])
359                     groups_value.append((3, group_id))
360                 else:
361                     group_id = self.id_get(cr, group)
362                     groups_value.append((4, group_id))
363             res['groups_id'] = groups_value
364
365         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)
366         self.idref[xml_id] = int(id)
367         # ir_set
368         if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
369             keyword = str(rec.get('keyword','') or 'client_action_multi')
370             value = 'ir.actions.wizard,'+str(id)
371             replace = rec.get("replace",'') or True
372             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
373         elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
374             # Special check for wizard having attribute menu=False on update
375             value = 'ir.actions.wizard,'+str(id)
376             self._remove_ir_values(cr, string, value, model)
377
378     def _tag_url(self, cr, rec, data_node=None):
379         url = rec.get("url",'').encode('utf8')
380         target = rec.get("target",'').encode('utf8')
381         name = rec.get("name",'').encode('utf8')
382         xml_id = rec.get('id','').encode('utf8')
383         self._test_xml_id(xml_id)
384
385         res = {'name': name, 'url': url, 'target':target}
386
387         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)
388         self.idref[xml_id] = int(id)
389         # ir_set
390         if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
391             keyword = str(rec.get('keyword','') or 'client_action_multi')
392             value = 'ir.actions.url,'+str(id)
393             replace = rec.get("replace",'') or True
394             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)
395         elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
396             # Special check for URL having attribute menu=False on update
397             value = 'ir.actions.url,'+str(id)
398             self._remove_ir_values(cr, url, value, "ir.actions.url")
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'] = rec.get('multi', False)
497         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)
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.get('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.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))
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         import openerp.netsvc as netsvc
535         wf_service = netsvc.LocalService("workflow")
536         wf_service.trg_validate(uid, model,
537             id,
538             str(rec.get('action','')), cr)
539
540     #
541     # Support two types of notation:
542     #   name="Inventory Control/Sending Goods"
543     # or
544     #   action="action_id"
545     #   parent="parent_id"
546     #
547     def _tag_menuitem(self, cr, rec, data_node=None):
548         rec_id = rec.get("id",'').encode('ascii')
549         self._test_xml_id(rec_id)
550         m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
551
552         values = {'parent_id': False}
553         if rec.get('parent', False) is False and len(m_l) > 1:
554             # No parent attribute specified and the menu name has several menu components,
555             # try to determine the ID of the parent according to menu path
556             pid = False
557             res = None
558             values['name'] = m_l[-1]
559             m_l = m_l[:-1] # last part is our name, not a parent
560             for idx, menu_elem in enumerate(m_l):
561                 if pid:
562                     cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
563                 else:
564                     cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
565                 res = cr.fetchone()
566                 if res:
567                     pid = res[0]
568                 else:
569                     # the menuitem does't exist but we are in branch (not a leaf)
570                     _logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l))
571                     pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
572             values['parent_id'] = pid
573         else:
574             # The parent attribute was specified, if non-empty determine its ID, otherwise
575             # explicitly make a top-level menu
576             if rec.get('parent'):
577                 menu_parent_id = self.id_get(cr, rec.get('parent',''))
578             else:
579                 # we get here with <menuitem parent="">, explicit clear of parent, or
580                 # if no parent attribute at all but menu name is not a menu path
581                 menu_parent_id = False
582             values = {'parent_id': menu_parent_id}
583             if rec.get('name'):
584                 values['name'] = rec.get('name')
585             try:
586                 res = [ self.id_get(cr, rec.get('id','')) ]
587             except:
588                 res = None
589
590         if rec.get('action'):
591             a_action = rec.get('action','').encode('utf8')
592             a_type = rec.get('type','').encode('utf8') or 'act_window'
593             icons = {
594                 "act_window": 'STOCK_NEW',
595                 "report.xml": 'STOCK_PASTE',
596                 "wizard": 'STOCK_EXECUTE',
597                 "url": 'STOCK_JUMP_TO'
598             }
599             values['icon'] = icons.get(a_type,'STOCK_NEW')
600             if a_type=='act_window':
601                 a_id = self.id_get(cr, a_action)
602                 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
603                 rrres = cr.fetchone()
604                 assert rrres, "No window action defined for this id %s !\n" \
605                     "Verify that this is a window action or add a type argument." % (a_action,)
606                 action_type,action_mode,action_name,view_id,target = rrres
607                 if view_id:
608                     cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
609                     action_mode, = cr.fetchone()
610                 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
611                 if cr.rowcount:
612                     action_mode, = cr.fetchone()
613                 if action_type=='tree':
614                     values['icon'] = 'STOCK_INDENT'
615                 elif action_mode and action_mode.startswith(('tree','kanban','gantt')):
616                     values['icon'] = 'STOCK_JUSTIFY_FILL'
617                 elif action_mode and action_mode.startswith('graph'):
618                     values['icon'] = 'terp-graph'
619                 elif action_mode and action_mode.startswith('calendar'):
620                     values['icon'] = 'terp-calendar'
621                 if target=='new':
622                     values['icon'] = 'STOCK_EXECUTE'
623                 if not values.get('name', False):
624                     values['name'] = action_name
625             elif a_type=='wizard':
626                 a_id = self.id_get(cr, a_action)
627                 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
628                 resw = cr.fetchone()
629                 if (not values.get('name', False)) and resw:
630                     values['name'] = resw[0]
631             elif a_type=='url':
632                 a_id = self.id_get(cr, a_action)
633                 cr.execute('select name from ir_act_url where id=%s', (int(a_id),))
634                 resw = cr.fetchone()
635                 if (not values.get('name')) and resw:
636                     values['name'] = resw[0]
637         if rec.get('sequence'):
638             values['sequence'] = int(rec.get('sequence'))
639         if rec.get('icon'):
640             values['icon'] = str(rec.get('icon'))
641         if rec.get('web_icon'):
642             values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon')))
643         if rec.get('web_icon_hover'):
644             values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover')))
645
646         if rec.get('groups'):
647             g_names = rec.get('groups','').split(',')
648             groups_value = []
649             for group in g_names:
650                 if group.startswith('-'):
651                     group_id = self.id_get(cr, group[1:])
652                     groups_value.append((3, group_id))
653                 else:
654                     group_id = self.id_get(cr, group)
655                     groups_value.append((4, group_id))
656             values['groups_id'] = groups_value
657
658         xml_id = rec.get('id','').encode('utf8')
659         self._test_xml_id(xml_id)
660         pid = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.ui.menu', self.module, values, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode, res_id=res and res[0] or False)
661
662         if rec_id and pid:
663             self.idref[rec_id] = int(pid)
664
665         if rec.get('action') and pid:
666             a_action = rec.get('action').encode('utf8')
667             a_type = rec.get('type','').encode('utf8') or 'act_window'
668             a_id = self.id_get(cr, a_action)
669             action = "ir.actions.%s,%d" % (a_type, a_id)
670             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)
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.get(rec_model)
682         assert model, "The model %s does not exist !" % (rec_model,)
683         rec_id = rec.get("id",'').encode('ascii')
684         self._test_xml_id(rec_id)
685         rec_src = rec.get("search",'').encode('utf8')
686         rec_src_count = rec.get("count")
687
688         rec_string = rec.get("string",'').encode('utf8') or 'unknown'
689
690         ids = None
691         eval_dict = {'ref': _ref(self, cr)}
692         context = self.get_context(data_node, rec, eval_dict)
693         uid = self.get_uid(cr, self.uid, data_node, rec)
694         if rec_id:
695             ids = [self.id_get(cr, rec_id)]
696         elif rec_src:
697             q = unsafe_eval(rec_src, eval_dict)
698             ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
699             if rec_src_count:
700                 count = int(rec_src_count)
701                 if len(ids) != count:
702                     self.assertion_report.record_failure()
703                     msg = 'assertion "%s" failed!\n'    \
704                           ' Incorrect search count:\n'  \
705                           ' expected count: %d\n'       \
706                           ' obtained count: %d\n'       \
707                           % (rec_string, count, len(ids))
708                     _logger.error(msg)
709                     return
710
711         assert ids is not None,\
712             'You must give either an id or a search criteria'
713         ref = _ref(self, cr)
714         for id in ids:
715             brrec =  model.browse(cr, uid, id, context)
716             class d(dict):
717                 def __getitem__(self2, key):
718                     if key in brrec:
719                         return brrec[key]
720                     return dict.__getitem__(self2, key)
721             globals_dict = d()
722             globals_dict['floatEqual'] = self._assert_equals
723             globals_dict['ref'] = ref
724             globals_dict['_ref'] = ref
725             for test in rec.findall('./test'):
726                 f_expr = test.get("expr",'').encode('utf-8')
727                 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
728                 expression_value = unsafe_eval(f_expr, globals_dict)
729                 if expression_value != expected_value: # assertion failed
730                     self.assertion_report.record_failure()
731                     msg = 'assertion "%s" failed!\n'    \
732                           ' xmltag: %s\n'               \
733                           ' expected value: %r\n'       \
734                           ' obtained value: %r\n'       \
735                           % (rec_string, etree.tostring(test), expected_value, expression_value)
736                     _logger.error(msg)
737                     return
738         else: # all tests were successful for this assertion tag (no break)
739             self.assertion_report.record_success()
740
741     def _tag_record(self, cr, rec, data_node=None):
742         rec_model = rec.get("model").encode('ascii')
743         model = self.pool.get(rec_model)
744         assert model, "The model %s does not exist !" % (rec_model,)
745         rec_id = rec.get("id",'').encode('ascii')
746         rec_context = rec.get("context", None)
747         if rec_context:
748             rec_context = unsafe_eval(rec_context)
749         self._test_xml_id(rec_id)
750         if self.isnoupdate(data_node) and self.mode != 'init':
751             # check if the xml record has an id string
752             if rec_id:
753                 if '.' in rec_id:
754                     module,rec_id2 = rec_id.split('.')
755                 else:
756                     module = self.module
757                     rec_id2 = rec_id
758                 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
759                 # check if the resource already existed at the last update
760                 if id:
761                     # if it existed, we don't update the data, but we need to
762                     # know the id of the existing record anyway
763                     self.idref[rec_id] = int(id)
764                     return None
765                 else:
766                     # if the resource didn't exist
767                     if not self.nodeattr2bool(rec, 'forcecreate', True):
768                         # we don't want to create it, so we skip it
769                         return None
770                     # else, we let the record to be created
771
772             else:
773                 # otherwise it is skipped
774                 return None
775         res = {}
776         for field in rec.findall('./field'):
777 #TODO: most of this code is duplicated above (in _eval_xml)...
778             f_name = field.get("name",'').encode('utf-8')
779             f_ref = field.get("ref",'').encode('utf-8')
780             f_search = field.get("search",'').encode('utf-8')
781             f_model = field.get("model",'').encode('utf-8')
782             if not f_model and model._columns.get(f_name,False):
783                 f_model = model._columns[f_name]._obj
784             f_use = field.get("use",'').encode('utf-8') or 'id'
785             f_val = False
786
787             if f_search:
788                 q = unsafe_eval(f_search, self.idref)
789                 field = []
790                 assert f_model, 'Define an attribute model="..." in your .XML file !'
791                 f_obj = self.pool.get(f_model)
792                 # browse the objects searched
793                 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
794                 # column definitions of the "local" object
795                 _cols = self.pool.get(rec_model)._columns
796                 # if the current field is many2many
797                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
798                     f_val = [(6, 0, map(lambda x: x[f_use], s))]
799                 elif len(s):
800                     # otherwise (we are probably in a many2one field),
801                     # take the first element of the search
802                     f_val = s[0][f_use]
803             elif f_ref:
804                 if f_ref=="null":
805                     f_val = False
806                 else:
807                     if f_name in model._columns \
808                               and model._columns[f_name]._type == 'reference':
809                         val = self.model_id_get(cr, f_ref)
810                         f_val = val[0] + ',' + str(val[1])
811                     else:
812                         f_val = self.id_get(cr, f_ref)
813             else:
814                 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
815                 if model._columns.has_key(f_name):
816                     import openerp.osv as osv
817                     if isinstance(model._columns[f_name], osv.fields.integer):
818                         f_val = int(f_val)
819             res[f_name] = f_val
820
821         id = self.pool.get('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 )
822         if rec_id:
823             self.idref[rec_id] = int(id)
824         if config.get('import_partial', False):
825             cr.commit()
826         return rec_model, id
827
828     def id_get(self, cr, id_str):
829         if id_str in self.idref:
830             return self.idref[id_str]
831         res = self.model_id_get(cr, id_str)
832         if res and len(res)>1: res = res[1]
833         return res
834
835     def model_id_get(self, cr, id_str):
836         model_data_obj = self.pool.get('ir.model.data')
837         mod = self.module
838         if '.' in id_str:
839             mod,id_str = id_str.split('.')
840         return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
841
842     def parse(self, de):
843         if not de.tag in ['terp', 'openerp']:
844             _logger.error("Mismatch xml format")
845             raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
846
847         if de.tag == 'terp':
848             _logger.warning("The tag <terp/> is deprecated, use <openerp/>")
849
850         for n in de.findall('./data'):
851             for rec in n:
852                 if rec.tag in self._tags:
853                     try:
854                         self._tags[rec.tag](self.cr, rec, n)
855                     except:
856                         _logger.error('Parse error in %s:%d: \n%s',
857                                       rec.getroottree().docinfo.URL,
858                                       rec.sourceline,
859                                       etree.tostring(rec).strip(), exc_info=True)
860                         self.cr.rollback()
861                         raise
862         return True
863
864     def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
865
866         self.mode = mode
867         self.module = module
868         self.cr = cr
869         self.idref = idref
870         self.pool = pooler.get_pool(cr.dbname)
871         self.uid = 1
872         if report is None:
873             report = assertion_report.assertion_report()
874         self.assertion_report = report
875         self.noupdate = noupdate
876         self._tags = {
877             'menuitem': self._tag_menuitem,
878             'record': self._tag_record,
879             'assert': self._tag_assert,
880             'report': self._tag_report,
881             'wizard': self._tag_wizard,
882             'delete': self._tag_delete,
883             'ir_set': self._tag_ir_set,
884             'function': self._tag_function,
885             'workflow': self._tag_workflow,
886             'act_window': self._tag_act_window,
887             'url': self._tag_url
888         }
889
890 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
891         noupdate=False):
892     '''Import csv file :
893         quote: "
894         delimiter: ,
895         encoding: utf-8'''
896     if not idref:
897         idref={}
898     model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
899     #remove folder path from model
900     head, model = os.path.split(model)
901
902     pool = pooler.get_pool(cr.dbname)
903
904     input = cStringIO.StringIO(csvcontent) #FIXME
905     reader = csv.reader(input, quotechar='"', delimiter=',')
906     fields = reader.next()
907     fname_partial = ""
908     if config.get('import_partial'):
909         fname_partial = module + '/'+ fname
910         if not os.path.isfile(config.get('import_partial')):
911             pickle.dump({}, file(config.get('import_partial'),'w+'))
912         else:
913             data = pickle.load(file(config.get('import_partial')))
914             if fname_partial in data:
915                 if not data[fname_partial]:
916                     return
917                 else:
918                     for i in range(data[fname_partial]):
919                         reader.next()
920
921     if not (mode == 'init' or 'id' in fields):
922         _logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
923         return
924
925     uid = 1
926     datas = []
927     for line in reader:
928         if (not line) or not reduce(lambda x,y: x or y, line) :
929             continue
930         try:
931             datas.append(map(lambda x: misc.ustr(x), line))
932         except:
933             _logger.error("Cannot import the line: %s", line)
934     result, rows, warning_msg, dummy = pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
935     if result < 0:
936         # Report failed import and abort module install
937         raise Exception(_('Module loading failed: file %s/%s could not be processed:\n %s') % (module, fname, warning_msg))
938     if config.get('import_partial'):
939         data = pickle.load(file(config.get('import_partial')))
940         data[fname_partial] = 0
941         pickle.dump(data, file(config.get('import_partial'),'wb'))
942         cr.commit()
943
944 #
945 # xml import/export
946 #
947 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
948     doc = etree.parse(xmlfile)
949     relaxng = etree.RelaxNG(
950         etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
951     try:
952         relaxng.assert_(doc)
953     except Exception:
954         _logger.error('The XML file does not fit the required schema !')
955         _logger.error(misc.ustr(relaxng.error_log.last_error))
956         raise
957
958     if idref is None:
959         idref={}
960     obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
961     obj.parse(doc.getroot())
962     return True
963
964
965 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: