[FIX] Some improvement in exception handling.
[odoo/odoo.git] / addons / base / controllers / main.py
1 # -*- coding: utf-8 -*-
2 import base64
3 import glob, os
4 import pprint
5 from xml.etree import ElementTree
6 from cStringIO import StringIO
7
8 import simplejson
9
10 import openerpweb
11 import openerpweb.ast
12 import openerpweb.nonliterals
13
14 import cherrypy
15 import xmlrpclib
16
17 # Should move to openerpweb.Xml2Json
18 class Xml2Json:
19     # xml2json-direct
20     # Simple and straightforward XML-to-JSON converter in Python
21     # New BSD Licensed
22     #
23     # URL: http://code.google.com/p/xml2json-direct/
24     @staticmethod
25     def convert_to_json(s):
26         return simplejson.dumps(
27             Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
28
29     @staticmethod
30     def convert_to_structure(s):
31         root = ElementTree.fromstring(s)
32         return Xml2Json.convert_element(root)
33
34     @staticmethod
35     def convert_element(el, skip_whitespaces=True):
36         res = {}
37         if el.tag[0] == "{":
38             ns, name = el.tag.rsplit("}", 1)
39             res["tag"] = name
40             res["namespace"] = ns[1:]
41         else:
42             res["tag"] = el.tag
43         res["attrs"] = {}
44         for k, v in el.items():
45             res["attrs"][k] = v
46         kids = []
47         if el.text and (not skip_whitespaces or el.text.strip() != ''):
48             kids.append(el.text)
49         for kid in el:
50             kids.append(Xml2Json.convert_element(kid))
51             if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
52                 kids.append(kid.tail)
53         res["children"] = kids
54         return res
55
56 #----------------------------------------------------------
57 # OpenERP Web base Controllers
58 #----------------------------------------------------------
59
60 class Session(openerpweb.Controller):
61     _cp_path = "/base/session"
62
63     def manifest_glob(self, addons, key):
64         files = []
65         for addon in addons:
66             globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
67
68             files.extend([
69                 resource_path[len(openerpweb.path_addons):]
70                 for pattern in globlist
71                 for resource_path in glob.glob(os.path.join(
72                     openerpweb.path_addons, addon, pattern))
73             ])
74         return files
75
76     def concat_files(self, file_list):
77         """ Concatenate file content
78         return (concat,timestamp)
79         concat: concatenation of file content
80         timestamp: max(os.path.getmtime of file_list)
81         """
82         root = openerpweb.path_root
83         files_content = []
84         files_timestamp = 0
85         for i in file_list:
86             fname = os.path.join(root, i)
87             ftime = os.path.getmtime(fname)
88             if ftime > files_timestamp:
89                 files_timestamp = ftime
90             files_content = open(fname).read()
91         files_concat = "".join(files_content)
92         return files_concat
93
94     @openerpweb.jsonrequest
95     def login(self, req, db, login, password):
96         req.session.login(db, login, password)
97
98         return {
99             "session_id": req.session_id,
100             "uid": req.session._uid,
101         }
102
103     @openerpweb.jsonrequest
104     def sc_list(self, req):
105         return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
106                                                          req.session.eval_context(req.context))
107
108     @openerpweb.jsonrequest
109     def get_databases_list(self, req):
110         proxy = req.session.proxy("db")
111         dbs = proxy.list()
112         
113         return {"db_list": dbs}
114     
115     @openerpweb.jsonrequest
116     def get_lang_list(self, req):
117         lang_list = [('en_US', 'English (US)')]
118         try:
119             lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
120         except Exception, e:
121             pass
122         return {"lang_list": lang_list}
123     
124     @openerpweb.jsonrequest
125     def db_operation(self, req, flag, **kw):
126         
127         if flag == 'drop':
128             db = kw.get('db')
129             password = kw.get('password')
130             
131             return req.session.proxy("db").drop(password, db)
132         
133         elif flag == 'backup':
134             db = kw.get('db')
135             password = kw.get('password')
136             try:
137                 res = req.session.proxy("db").dump(password, db)
138                 if res:
139                     cherrypy.response.headers['Content-Type'] = "application/data"
140                     cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
141                     return base64.decodestring(res)
142             except Exception:
143                 return {'error': 'Could not create backup !'}
144             
145         elif flag == 'restore':
146             filename = kw.get('filename')
147             db = kw.get('db')
148             password = kw.get('password')
149             
150             try:
151                 if filename:
152                     data = base64.encodestring(filename.file.read())
153                     return req.session.proxy("db").restore(password, db, data)
154             except Exception:
155                 return {'error': 'Could not restore database !'}
156         
157         elif flag == 'change_password':
158             old_password = kw.get('old_password')
159             new_password = kw.get('new_password')
160             confirm_password = kw.get('confirm_password')
161             
162             if old_password and new_password and confirm_password:
163                 return req.session.proxy("db").change_admin_password(old_password, new_password)
164             
165     @openerpweb.jsonrequest
166     def modules(self, req):
167         return {"modules": [name
168             for name, manifest in openerpweb.addons_manifest.iteritems()
169             if manifest.get('active', True)]}
170
171     @openerpweb.jsonrequest
172     def csslist(self, req, mods='base'):
173         return {'files': self.manifest_glob(mods.split(','), 'css')}
174
175     @openerpweb.jsonrequest
176     def jslist(self, req, mods='base'):
177         return {'files': self.manifest_glob(mods.split(','), 'js')}
178
179     def css(self, req, mods='base,base_hello'):
180         files = self.manifest_glob(mods.split(','), 'css')
181         concat = self.concat_files(files)[0]
182         # TODO request set the Date of last modif and Etag
183         return concat
184     css.exposed = True
185
186     def js(self, req, mods='base,base_hello'):
187         files = self.manifest_glob(mods.split(','), 'js')
188         concat = self.concat_files(files)[0]
189         # TODO request set the Date of last modif and Etag
190         return concat
191     js.exposed = True
192
193     @openerpweb.jsonrequest
194     def eval_domain_and_context(self, req, contexts, domains,
195                                 group_by_seq=None):
196         """ Evaluates sequences of domains and contexts, composing them into
197         a single context, domain or group_by sequence.
198
199         :param list contexts: list of contexts to merge together. Contexts are
200                               evaluated in sequence, all previous contexts
201                               are part of their own evaluation context
202                               (starting at the session context).
203         :param list domains: list of domains to merge together. Domains are
204                              evaluated in sequence and appended to one another
205                              (implicit AND), their evaluation domain is the
206                              result of merging all contexts.
207         :param list group_by_seq: list of domains (which may be in a different
208                                   order than the ``contexts`` parameter),
209                                   evaluated in sequence, their ``'group_by'``
210                                   key is extracted if they have one.
211         :returns:
212             a 3-dict of:
213
214             context (``dict``)
215                 the global context created by merging all of
216                 ``contexts``
217
218             domain (``list``)
219                 the concatenation of all domains
220
221             group_by (``list``)
222                 a list of fields to group by, potentially empty (in which case
223                 no group by should be performed)
224         """
225         context, domain = eval_context_and_domain(req.session,
226                                                   openerpweb.nonliterals.CompoundContext(*(contexts or [])),
227                                                   openerpweb.nonliterals.CompoundDomain(*(domains or [])))
228         
229         group_by_sequence = []
230         for candidate in (group_by_seq or []):
231             ctx = req.session.eval_context(candidate, context)
232             group_by = ctx.get('group_by')
233             if not group_by:
234                 continue
235             elif isinstance(group_by, basestring):
236                 group_by_sequence.append(group_by)
237             else:
238                 group_by_sequence.extend(group_by)
239         
240         return {
241             'context': context,
242             'domain': domain,
243             'group_by': group_by_sequence
244         }
245
246     @openerpweb.jsonrequest
247     def save_session_action(self, req, the_action):
248         """
249         This method store an action object in the session object and returns an integer
250         identifying that action. The method get_session_action() can be used to get
251         back the action.
252         
253         :param the_action: The action to save in the session.
254         :type the_action: anything
255         :return: A key identifying the saved action.
256         :rtype: integer
257         """
258         saved_actions = cherrypy.session.get('saved_actions')
259         if not saved_actions:
260             saved_actions = {"next":0, "actions":{}}
261             cherrypy.session['saved_actions'] = saved_actions
262         # we don't allow more than 10 stored actions
263         if len(saved_actions["actions"]) >= 10:
264             del saved_actions["actions"][min(saved_actions["actions"].keys())]
265         key = saved_actions["next"]
266         saved_actions["actions"][key] = the_action
267         saved_actions["next"] = key + 1
268         return key
269
270     @openerpweb.jsonrequest
271     def get_session_action(self, req, key):
272         """
273         Gets back a previously saved action. This method can return None if the action
274         was saved since too much time (this case should be handled in a smart way).
275         
276         :param key: The key given by save_session_action()
277         :type key: integer
278         :return: The saved action or None.
279         :rtype: anything
280         """
281         saved_actions = cherrypy.session.get('saved_actions')
282         if not saved_actions:
283             return None
284         return saved_actions["actions"].get(key)
285         
286 def eval_context_and_domain(session, context, domain=None):
287     e_context = session.eval_context(context)
288     # should we give the evaluated context as an evaluation context to the domain?
289     e_domain = session.eval_domain(domain or [])
290
291     return e_context, e_domain
292         
293 def load_actions_from_ir_values(req, key, key2, models, meta, context):
294     Values = req.session.model('ir.values')
295     actions = Values.get(key, key2, models, meta, context)
296
297     return [(id, name, clean_action(action, req.session))
298             for id, name, action in actions]
299
300 def clean_action(action, session):
301     if action['type'] != 'ir.actions.act_window':
302         return action
303     # values come from the server, we can just eval them
304     if isinstance(action.get('context', None), basestring):
305         action['context'] = eval(
306             action['context'],
307             session.evaluation_context()) or {}
308
309     if isinstance(action.get('domain', None), basestring):
310         action['domain'] = eval(
311             action['domain'],
312             session.evaluation_context(
313                 action['context'])) or []
314     if 'flags' not in action:
315         # Set empty flags dictionary for web client.
316         action['flags'] = dict()
317     return fix_view_modes(action)
318
319 def generate_views(action):
320     """
321     While the server generates a sequence called "views" computing dependencies
322     between a bunch of stuff for views coming directly from the database
323     (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
324     to return custom view dictionaries generated on the fly.
325
326     In that case, there is no ``views`` key available on the action.
327
328     Since the web client relies on ``action['views']``, generate it here from
329     ``view_mode`` and ``view_id``.
330
331     Currently handles two different cases:
332
333     * no view_id, multiple view_mode
334     * single view_id, single view_mode
335
336     :param dict action: action descriptor dictionary to generate a views key for
337     """
338     view_id = action.get('view_id', False)
339     if isinstance(view_id, (list, tuple)):
340         view_id = view_id[0]
341
342     # providing at least one view mode is a requirement, not an option
343     view_modes = action['view_mode'].split(',')
344
345     if len(view_modes) > 1:
346         if view_id:
347             raise ValueError('Non-db action dictionaries should provide '
348                              'either multiple view modes or a single view '
349                              'mode and an optional view id.\n\n Got view '
350                              'modes %r and view id %r for action %r' % (
351                 view_modes, view_id, action))
352         action['views'] = [(False, mode) for mode in view_modes]
353         return
354     action['views'] = [(view_id, view_modes[0])]
355
356
357 def fix_view_modes(action):
358     """ For historical reasons, OpenERP has weird dealings in relation to
359     view_mode and the view_type attribute (on window actions):
360
361     * one of the view modes is ``tree``, which stands for both list views
362       and tree views
363     * the choice is made by checking ``view_type``, which is either
364       ``form`` for a list view or ``tree`` for an actual tree view
365
366     This methods simply folds the view_type into view_mode by adding a
367     new view mode ``list`` which is the result of the ``tree`` view_mode
368     in conjunction with the ``form`` view_type.
369
370     TODO: this should go into the doc, some kind of "peculiarities" section
371
372     :param dict action: an action descriptor
373     :returns: nothing, the action is modified in place
374     """
375     if 'views' not in action:
376         generate_views(action)
377
378     if action.pop('view_type') != 'form':
379         return
380
381     action['views'] = [
382         [id, mode if mode != 'tree' else 'list']
383         for id, mode in action['views']
384     ]
385
386     return action
387
388 class Menu(openerpweb.Controller):
389     _cp_path = "/base/menu"
390
391     @openerpweb.jsonrequest
392     def load(self, req):
393         return {'data': self.do_load(req)}
394
395     def do_load(self, req):
396         """ Loads all menu items (all applications and their sub-menus).
397
398         :param req: A request object, with an OpenERP session attribute
399         :type req: < session -> OpenERPSession >
400         :return: the menu root
401         :rtype: dict('children': menu_nodes)
402         """
403         Menus = req.session.model('ir.ui.menu')
404         # menus are loaded fully unlike a regular tree view, cause there are
405         # less than 512 items
406         context = req.session.eval_context(req.context)
407         menu_ids = Menus.search([], 0, False, False, context)
408         menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
409         menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
410         menu_items.append(menu_root)
411         
412         # make a tree using parent_id
413         menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
414         for menu_item in menu_items:
415             if menu_item['parent_id']:
416                 parent = menu_item['parent_id'][0]
417             else:
418                 parent = False
419             if parent in menu_items_map:
420                 menu_items_map[parent].setdefault(
421                     'children', []).append(menu_item)
422
423         # sort by sequence a tree using parent_id
424         for menu_item in menu_items:
425             menu_item.setdefault('children', []).sort(
426                 key=lambda x:x["sequence"])
427
428         return menu_root
429
430     @openerpweb.jsonrequest
431     def action(self, req, menu_id):
432         actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
433                                              [('ir.ui.menu', menu_id)], False,
434                                              req.session.eval_context(req.context))
435         return {"action": actions}
436
437 class DataSet(openerpweb.Controller):
438     _cp_path = "/base/dataset"
439
440     @openerpweb.jsonrequest
441     def fields(self, req, model):
442         return {'fields': req.session.model(model).fields_get(False,
443                                                               req.session.eval_context(req.context))}
444
445     @openerpweb.jsonrequest
446     def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
447         return self.do_search_read(request, model, fields, offset, limit, domain, context, sort)
448     def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
449         """ Performs a search() followed by a read() (if needed) using the
450         provided search criteria
451
452         :param request: a JSON-RPC request object
453         :type request: openerpweb.JsonRequest
454         :param str model: the name of the model to search on
455         :param fields: a list of the fields to return in the result records
456         :type fields: [str]
457         :param int offset: from which index should the results start being returned
458         :param int limit: the maximum number of records to return
459         :param list domain: the search domain for the query
460         :param list sort: sorting directives
461         :returns: a list of result records
462         :rtype: list
463         """
464         Model = request.session.model(model)
465         context, domain = eval_context_and_domain(request.session, request.context, domain)
466         
467         ids = Model.search(domain, offset or 0, limit or False,
468                            sort or False, context)
469
470         if fields and fields == ['id']:
471             # shortcut read if we only want the ids
472             return map(lambda id: {'id': id}, ids)
473
474         reads = Model.read(ids, fields or False, context)
475         reads.sort(key=lambda obj: ids.index(obj['id']))
476         return reads
477
478     @openerpweb.jsonrequest
479     def get(self, request, model, ids, fields=False):
480         return self.do_get(request, model, ids, fields)
481     def do_get(self, request, model, ids, fields=False):
482         """ Fetches and returns the records of the model ``model`` whose ids
483         are in ``ids``.
484
485         The results are in the same order as the inputs, but elements may be
486         missing (if there is no record left for the id)
487
488         :param request: the JSON-RPC2 request object
489         :type request: openerpweb.JsonRequest
490         :param model: the model to read from
491         :type model: str
492         :param ids: a list of identifiers
493         :type ids: list
494         :param fields: a list of fields to fetch, ``False`` or empty to fetch
495                        all fields in the model
496         :type fields: list | False
497         :returns: a list of records, in the same order as the list of ids
498         :rtype: list
499         """
500         Model = request.session.model(model)
501         records = Model.read(ids, fields, request.session.eval_context(request.context))
502
503         record_map = dict((record['id'], record) for record in records)
504
505         return [record_map[id] for id in ids if record_map.get(id)]
506     
507     @openerpweb.jsonrequest
508     def load(self, req, model, id, fields):
509         m = req.session.model(model)
510         value = {}
511         r = m.read([id], False, req.session.eval_context(req.context))
512         if r:
513             value = r[0]
514         return {'value': value}
515
516     @openerpweb.jsonrequest
517     def create(self, req, model, data):
518         m = req.session.model(model)
519         r = m.create(data, req.session.eval_context(req.context))
520         return {'result': r}
521
522     @openerpweb.jsonrequest
523     def save(self, req, model, id, data):
524         m = req.session.model(model)
525         r = m.write([id], data, req.session.eval_context(req.context))
526         return {'result': r}
527
528     @openerpweb.jsonrequest
529     def unlink(self, request, model, ids=()):
530         Model = request.session.model(model)
531         return Model.unlink(ids, request.session.eval_context(request.context))
532
533     def call_common(self, req, model, method, args, domain_id=None, context_id=None):
534         domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id  else []
535         context = args[context_id] if context_id and len(args) - 1 >= context_id  else {}
536         c, d = eval_context_and_domain(req.session, context, domain)
537         if domain_id and len(args) - 1 >= domain_id:
538             args[domain_id] = d
539         if context_id and len(args) - 1 >= context_id:
540             args[context_id] = c
541
542         return getattr(req.session.model(model), method)(*args)
543
544     @openerpweb.jsonrequest
545     def call(self, req, model, method, args, domain_id=None, context_id=None):
546         return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
547
548     @openerpweb.jsonrequest
549     def call_button(self, req, model, method, args, domain_id=None, context_id=None):
550         action = self.call_common(req, model, method, args, domain_id, context_id)
551         if isinstance(action, dict) and action.get('type') != '':
552             return {'result': clean_action(action, req.session)}
553         return {'result': False}
554
555     @openerpweb.jsonrequest
556     def exec_workflow(self, req, model, id, signal):
557         r = req.session.exec_workflow(model, id, signal)
558         return {'result': r}
559
560     @openerpweb.jsonrequest
561     def default_get(self, req, model, fields):
562         m = req.session.model(model)
563         r = m.default_get(fields, req.session.eval_context(req.context))
564         return {'result': r}
565
566 class DataGroup(openerpweb.Controller):
567     _cp_path = "/base/group"
568     @openerpweb.jsonrequest
569     def read(self, request, model, group_by_fields, domain=None):
570         Model = request.session.model(model)
571         context, domain = eval_context_and_domain(request.session, request.context, domain)
572
573         return Model.read_group(
574             domain or [], False, group_by_fields, 0, False,
575             dict(context, group_by=group_by_fields))
576
577 class View(openerpweb.Controller):
578     _cp_path = "/base/view"
579
580     def fields_view_get(self, request, model, view_id, view_type,
581                         transform=True, toolbar=False, submenu=False):
582         Model = request.session.model(model)
583         context = request.session.eval_context(request.context)
584         fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
585         # todo fme?: check that we should pass the evaluated context here
586         self.process_view(request.session, fvg, context, transform)
587         return fvg
588     
589     def process_view(self, session, fvg, context, transform):
590         # depending on how it feels, xmlrpclib.ServerProxy can translate
591         # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
592         # enjoy unicode strings which can not be trivially converted to
593         # strings, and it blows up during parsing.
594
595         # So ensure we fix this retardation by converting view xml back to
596         # bit strings.
597         if isinstance(fvg['arch'], unicode):
598             arch = fvg['arch'].encode('utf-8')
599         else:
600             arch = fvg['arch']
601
602         if transform:
603             evaluation_context = session.evaluation_context(context or {})
604             xml = self.transform_view(arch, session, evaluation_context)
605         else:
606             xml = ElementTree.fromstring(arch)
607         fvg['arch'] = Xml2Json.convert_element(xml)
608         for field in fvg['fields'].values():
609             if field.has_key('views') and field['views']:
610                 for view in field["views"].values():
611                     self.process_view(session, view, None, transform)
612
613     @openerpweb.jsonrequest
614     def add_custom(self, request, view_id, arch):
615         CustomView = request.session.model('ir.ui.view.custom')
616         CustomView.create({
617             'user_id': request.session._uid,
618             'ref_id': view_id,
619             'arch': arch
620         }, request.session.eval_context(request.context))
621         return {'result': True}
622
623     @openerpweb.jsonrequest
624     def undo_custom(self, request, view_id, reset=False):
625         CustomView = request.session.model('ir.ui.view.custom')
626         context = request.session.eval_context(request.context)
627         vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
628                                     0, False, False, context)
629         if vcustom:
630             if reset:
631                 CustomView.unlink(vcustom, context)
632             else:
633                 CustomView.unlink([vcustom[0]], context)
634             return {'result': True}
635         return {'result': False}
636
637     def normalize_attrs(self, elem, context):
638         """ Normalize @attrs, @invisible, @required, @readonly and @states, so
639         the client only has to deal with @attrs.
640
641         See `the discoveries pad <http://pad.openerp.com/discoveries>`_ for
642         the rationale.
643
644         :param elem: the current view node (Python object)
645         :type elem: xml.etree.ElementTree.Element
646         :param dict context: evaluation context
647         """
648         # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
649         attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
650         if 'states' in elem.attrib:
651             attrs.setdefault('invisible', [])\
652                 .append(('state', 'not in', elem.attrib.pop('states').split(',')))
653         if attrs:
654             elem.set('attrs', simplejson.dumps(attrs))
655         for a in ['invisible', 'readonly', 'required']:
656             if a in elem.attrib:
657                 # In the XML we trust
658                 avalue = bool(eval(elem.get(a, 'False'),
659                                    {'context': context or {}}))
660                 if not avalue:
661                     del elem.attrib[a]
662                 else:
663                     elem.attrib[a] = '1'
664                     if a == 'invisible' and 'attrs' in elem.attrib:
665                         del elem.attrib['attrs']
666
667     def transform_view(self, view_string, session, context=None):
668         # transform nodes on the fly via iterparse, instead of
669         # doing it statically on the parsing result
670         parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
671         root = None
672         for event, elem in parser:
673             if event == "start":
674                 if root is None:
675                     root = elem
676                 self.normalize_attrs(elem, context)
677                 self.parse_domains_and_contexts(elem, session)
678         return root
679
680     def parse_domain(self, elem, attr_name, session):
681         """ Parses an attribute of the provided name as a domain, transforms it
682         to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
683
684         :param elem: the node being parsed
685         :type param: xml.etree.ElementTree.Element
686         :param str attr_name: the name of the attribute which should be parsed
687         :param session: Current OpenERP session
688         :type session: openerpweb.openerpweb.OpenERPSession
689         """
690         domain = elem.get(attr_name, '').strip()
691         if domain:
692             try:
693                 elem.set(
694                     attr_name,
695                     openerpweb.ast.literal_eval(
696                         domain))
697             except ValueError:
698                 # not a literal
699                 elem.set(attr_name,
700                          openerpweb.nonliterals.Domain(session, domain))
701
702     def parse_domains_and_contexts(self, elem, session):
703         """ Converts domains and contexts from the view into Python objects,
704         either literals if they can be parsed by literal_eval or a special
705         placeholder object if the domain or context refers to free variables.
706
707         :param elem: the current node being parsed
708         :type param: xml.etree.ElementTree.Element
709         :param session: OpenERP session object, used to store and retrieve
710                         non-literal objects
711         :type session: openerpweb.openerpweb.OpenERPSession
712         """
713         self.parse_domain(elem, 'domain', session)
714         self.parse_domain(elem, 'filter_domain', session)
715         for el in ['context', 'default_get']:
716             context_string = elem.get(el, '').strip()
717             if context_string:
718                 try:
719                     elem.set(el,
720                              openerpweb.ast.literal_eval(context_string))
721                 except ValueError:
722                     elem.set(el,
723                              openerpweb.nonliterals.Context(
724                                  session, context_string))
725
726 class FormView(View):
727     _cp_path = "/base/formview"
728
729     @openerpweb.jsonrequest
730     def load(self, req, model, view_id, toolbar=False):
731         fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
732         return {'fields_view': fields_view}
733
734 class ListView(View):
735     _cp_path = "/base/listview"
736
737     @openerpweb.jsonrequest
738     def load(self, req, model, view_id, toolbar=False):
739         fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
740         return {'fields_view': fields_view}
741
742     def process_colors(self, view, row, context):
743         colors = view['arch']['attrs'].get('colors')
744
745         if not colors:
746             return None
747
748         color = [
749             pair.split(':')[0]
750             for pair in colors.split(';')
751             if eval(pair.split(':')[1], dict(context, **row))
752         ]
753
754         if not color:
755             return None
756         elif len(color) == 1:
757             return color[0]
758         return 'maroon'
759
760 class SearchView(View):
761     _cp_path = "/base/searchview"
762
763     @openerpweb.jsonrequest
764     def load(self, req, model, view_id):
765         fields_view = self.fields_view_get(req, model, view_id, 'search')
766         return {'fields_view': fields_view}
767
768     @openerpweb.jsonrequest
769     def fields_get(self, req, model):
770         Model = req.session.model(model)
771         fields = Model.fields_get(False, req.session.eval_context(req.context))
772         return {'fields': fields}
773
774 class Binary(openerpweb.Controller):
775     _cp_path = "/base/binary"
776
777     @openerpweb.httprequest
778     def image(self, request, session_id, model, id, field, **kw):
779         cherrypy.response.headers['Content-Type'] = 'image/png'
780         Model = request.session.model(model)
781         context = request.session.eval_context(request.context)
782         try:
783             if not id:
784                 res = Model.default_get([field], context).get(field, '')
785             else:
786                 res = Model.read([int(id)], [field], context)[0].get(field, '')
787             return base64.decodestring(res)
788         except: # TODO: what's the exception here?
789             return self.placeholder()
790     def placeholder(self):
791         return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
792
793     @openerpweb.httprequest
794     def saveas(self, request, session_id, model, id, field, fieldname, **kw):
795         Model = request.session.model(model)
796         context = request.session.eval_context(request.context)
797         res = Model.read([int(id)], [field, fieldname], context)[0]
798         filecontent = res.get(field, '')
799         if not filecontent:
800             raise cherrypy.NotFound
801         else:
802             cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
803             filename = '%s_%s' % (model.replace('.', '_'), id)
804             if fieldname:
805                 filename = res.get(fieldname, '') or filename
806             cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' +  filename
807             return base64.decodestring(filecontent)
808
809     @openerpweb.httprequest
810     def upload(self, request, session_id, callback, ufile=None):
811         cherrypy.response.timeout = 500
812         headers = {}
813         for key, val in cherrypy.request.headers.iteritems():
814             headers[key.lower()] = val
815         size = int(headers.get('content-length', 0))
816         # TODO: might be useful to have a configuration flag for max-length file uploads
817         try:
818             out = """<script language="javascript" type="text/javascript">
819                         var win = window.top.window,
820                             callback = win[%s];
821                         if (typeof(callback) === 'function') {
822                             callback.apply(this, %s);
823                         } else {
824                             win.jQuery('#oe_notification', win.document).notify('create', {
825                                 title: "Ajax File Upload",
826                                 text: "Could not find callback"
827                             });
828                         }
829                     </script>"""
830             data = ufile.file.read()
831             args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
832         except Exception, e:
833             args = [False, e.message]
834         return out % (simplejson.dumps(callback), simplejson.dumps(args))
835
836     @openerpweb.httprequest
837     def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
838         cherrypy.response.timeout = 500
839         context = request.session.eval_context(request.context)
840         Model = request.session.model('ir.attachment')
841         try:
842             out = """<script language="javascript" type="text/javascript">
843                         var win = window.top.window,
844                             callback = win[%s];
845                         if (typeof(callback) === 'function') {
846                             callback.call(this, %s);
847                         }
848                     </script>"""
849             attachment_id = Model.create({
850                 'name': ufile.filename,
851                 'datas': base64.encodestring(ufile.file.read()),
852                 'res_model': model,
853                 'res_id': int(id)
854             }, context)
855             args = {
856                 'filename': ufile.filename,
857                 'id':  attachment_id
858             }
859         except Exception, e:
860             args = { 'error': e.message }
861         return out % (simplejson.dumps(callback), simplejson.dumps(args))
862
863 class Action(openerpweb.Controller):
864     _cp_path = "/base/action"
865
866     @openerpweb.jsonrequest
867     def load(self, req, action_id):
868         Actions = req.session.model('ir.actions.actions')
869         value = False
870         context = req.session.eval_context(req.context)
871         action_type = Actions.read([action_id], ['type'], context)
872         if action_type:
873             action = req.session.model(action_type[0]['type']).read([action_id], False,
874                                                                     context)
875             if action:
876                 value = clean_action(action[0], req.session)
877         return {'result': value}
878
879     @openerpweb.jsonrequest
880     def run(self, req, action_id):
881         return clean_action(req.session.model('ir.actions.server').run(
882             [action_id], req.session.eval_context(req.context)), req.session)