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