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