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