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