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