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