[IMP] Change in dashboard view api. @creatable is an action id, not a form view id
[odoo/odoo.git] / openerp / report / print_xml.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 os,types
23 from lxml import etree
24 import openerp.netsvc as netsvc
25 import openerp.tools as tools
26 from openerp.tools.safe_eval import safe_eval
27 import print_fnc
28 import copy
29 from openerp.osv.orm import browse_null, browse_record
30 import openerp.pooler as pooler
31
32 class InheritDict(dict):
33     # Might be usefull when we're doing name lookup for call or eval.
34
35     def __init__(self, parent=None):
36         self.parent = parent
37
38     def __getitem__(self, name):
39         if name in self:
40             return super(InheritDict, self).__getitem__(name)
41         else:
42             if not self.parent:
43                 raise KeyError
44             else:
45                 return self.parent[name]
46
47 def tounicode(val):
48     if isinstance(val, str):
49         unicode_val = unicode(val, 'utf-8')
50     elif isinstance(val, unicode):
51         unicode_val = val
52     else:
53         unicode_val = unicode(val)
54     return unicode_val
55
56 class document(object):
57     def __init__(self, cr, uid, datas, func=False):
58         # create a new document
59         self.cr = cr
60         self.pool = pooler.get_pool(cr.dbname)
61         self.func = func or {}
62         self.datas = datas
63         self.uid = uid
64         self.bin_datas = {}
65
66     def node_attrs_get(self, node):
67         if len(node.attrib):
68             return node.attrib
69         return {}
70
71     def get_value(self, browser, field_path):
72         fields = field_path.split('.')
73
74         if not len(fields):
75             return ''
76
77         value = browser
78
79         for f in fields:
80             if isinstance(value, list):
81                 if len(value)==0:
82                     return ''
83                 value = value[0]
84             if isinstance(value, browse_null):
85                 return ''
86             else:
87                 value = value[f]
88
89         if isinstance(value, browse_null) or (type(value)==bool and not value):
90             return ''
91         else:
92             return value
93
94     def get_value2(self, browser, field_path):
95         value = self.get_value(browser, field_path)
96         if isinstance(value, browse_record):
97             return value.id
98         elif isinstance(value, browse_null):
99             return False
100         else:
101             return value
102
103     def eval(self, record, expr):
104 #TODO: support remote variables (eg address.title) in expr
105 # how to do that: parse the string, find dots, replace those dotted variables by temporary
106 # "simple ones", fetch the value of those variables and add them (temporarily) to the _data
107 # dictionary passed to eval
108
109 #FIXME: it wont work if the data hasn't been fetched yet... this could
110 # happen if the eval node is the first one using this browse_record
111 # the next line is a workaround for the problem: it causes the resource to be loaded
112 #Pinky: Why not this ? eval(expr, browser) ?
113 #       name = browser.name
114 #       data_dict = browser._data[self.get_value(browser, 'id')]
115         return safe_eval(expr, {}, {'obj': record})
116
117     def parse_node(self, node, parent, browser, datas=None):
118             attrs = self.node_attrs_get(node)
119             if 'type' in attrs:
120                 if attrs['type']=='field':
121                     value = self.get_value(browser, attrs['name'])
122 #TODO: test this
123                     if value == '' and 'default' in attrs:
124                         value = attrs['default']
125                     el = etree.SubElement(parent, node.tag)
126                     el.text = tounicode(value)
127 #TODO: test this
128                     for key, value in attrs.iteritems():
129                         if key not in ('type', 'name', 'default'):
130                             el.set(key, value)
131
132                 elif attrs['type']=='attachment':
133                     if isinstance(browser, list):
134                         model = browser[0]._table_name
135                     else:
136                         model = browser._table_name
137
138                     value = self.get_value(browser, attrs['name'])
139
140                     ids = self.pool.get('ir.attachment').search(self.cr, self.uid, [('res_model','=',model),('res_id','=',int(value))])
141                     datas = self.pool.get('ir.attachment').read(self.cr, self.uid, ids)
142
143                     if len(datas):
144                         # if there are several, pick first
145                         datas = datas[0]
146                         fname = str(datas['datas_fname'])
147                         ext = fname.split('.')[-1].lower()
148                         if ext in ('jpg','jpeg', 'png'):
149                             import base64
150                             from StringIO import StringIO
151                             dt = base64.decodestring(datas['datas'])
152                             fp = StringIO()
153                             fp.write(dt)
154                             i = str(len(self.bin_datas))
155                             self.bin_datas[i] = fp
156                             el = etree.SubElement(parent, node.tag)
157                             el.text = i
158
159                 elif attrs['type']=='data':
160 #TODO: test this
161                     txt = self.datas.get('form', {}).get(attrs['name'], '')
162                     el = etree.SubElement(parent, node.tag)
163                     el.text = txt
164
165                 elif attrs['type']=='function':
166                     if attrs['name'] in self.func:
167                         txt = self.func[attrs['name']](node)
168                     else:
169                         txt = print_fnc.print_fnc(attrs['name'], node)
170                     el = etree.SubElement(parent, node.tag)
171                     el.text = txt
172
173                 elif attrs['type']=='eval':
174                     value = self.eval(browser, attrs['expr'])
175                     el = etree.SubElement(parent, node.tag)
176                     el.text = str(value)
177
178                 elif attrs['type']=='fields':
179                     fields = attrs['name'].split(',')
180                     vals = {}
181                     for b in browser:
182                         value = tuple([self.get_value2(b, f) for f in fields])
183                         if not value in vals:
184                             vals[value]=[]
185                         vals[value].append(b)
186                     keys = vals.keys()
187                     keys.sort()
188
189                     if 'order' in attrs and attrs['order']=='desc':
190                         keys.reverse()
191
192                     v_list = [vals[k] for k in keys]
193                     for v in v_list:
194                         el = etree.SubElement(parent, node.tag)
195                         for el_cld in node:
196                             self.parse_node(el_cld, el, v)
197
198                 elif attrs['type']=='call':
199                     if len(attrs['args']):
200 #TODO: test this
201                         # fetches the values of the variables which names where passed in the args attribute
202                         args = [self.eval(browser, arg) for arg in attrs['args'].split(',')]
203                     else:
204                         args = []
205                     # get the object
206                     if attrs.has_key('model'):
207                         obj = self.pool.get(attrs['model'])
208                     else:
209                         if isinstance(browser, list):
210                             obj = browser[0]._table
211                         else:
212                             obj = browser._table
213
214                     # get the ids
215                     if attrs.has_key('ids'):
216                         ids = self.eval(browser, attrs['ids'])
217                     else:
218                         if isinstance(browser, list):
219                             ids = [b.id for b in browser]
220                         else:
221                             ids = [browser.id]
222
223                     # call the method itself
224                     newdatas = getattr(obj, attrs['name'])(self.cr, self.uid, ids, *args)
225
226                     def parse_result_tree(node, parent, datas):
227                         if not node.tag == etree.Comment:
228                             el = etree.SubElement(parent, node.tag)
229                             atr = self.node_attrs_get(node)
230                             if 'value' in atr:
231                                 if not isinstance(datas[atr['value']], (str, unicode)):
232                                     txt = str(datas[atr['value']])
233                                 else:
234                                      txt = datas[atr['value']]
235                                 el.text = txt
236                             else:
237                                 for el_cld in node:
238                                     parse_result_tree(el_cld, el, datas)
239                     if not isinstance(newdatas, list):
240                         newdatas = [newdatas]
241                     for newdata in newdatas:
242                         parse_result_tree(node, parent, newdata)
243
244                 elif attrs['type']=='zoom':
245                     value = self.get_value(browser, attrs['name'])
246                     if value:
247                         if not isinstance(value, list):
248                             v_list = [value]
249                         else:
250                             v_list = value
251                         for v in v_list:
252                             el = etree.SubElement(parent, node.tag)
253                             for el_cld in node:
254                                 self.parse_node(el_cld, el, v)
255             else:
256                 # if there is no "type" attribute in the node, copy it to the xml data and parse its children
257                 if not node.tag == etree.Comment:
258                     if node.tag == parent.tag:
259                         el = parent
260                     else:
261                         el = etree.SubElement(parent, node.tag)
262                     for el_cld in node:
263                         self.parse_node(el_cld,el, browser)
264     def xml_get(self):
265         return etree.tostring(self.doc,encoding="utf-8",xml_declaration=True,pretty_print=True)
266
267     def parse_tree(self, ids, model, context=None):
268         if not context:
269             context={}
270         browser = self.pool.get(model).browse(self.cr, self.uid, ids, context)
271         self.parse_node(self.dom, self.doc, browser)
272
273     def parse_string(self, xml, ids, model, context=None):
274         if not context:
275             context={}
276         # parses the xml template to memory
277         self.dom = etree.XML(xml)
278         # create the xml data from the xml template
279         self.parse_tree(ids, model, context)
280
281     def parse(self, filename, ids, model, context=None):
282         if not context:
283             context={}
284         # parses the xml template to memory
285         src_file = tools.file_open(filename)
286         try:
287             self.dom = etree.XML(src_file.read())
288             self.doc = etree.Element(self.dom.tag)
289             self.parse_tree(ids, model, context)
290         finally:
291             src_file.close()
292
293     def close(self):
294         self.doc = None
295         self.dom = None
296
297
298 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
299