[fix] big fix related to domains and contexts defined in a model
[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         for field in fvg['fields'].values():
573             if field.has_key('views') and field['views']:
574                 for view in field["views"].values():
575                     self.process_view(session, view, None, transform)
576             if field.get('domain'):
577                 field["domain"] = self.parse_domain(field["domain"], session)
578             if field.get('context'):
579                 field["context"] = self.parse_domain(field["context"], session)
580
581     @openerpweb.jsonrequest
582     def add_custom(self, request, view_id, arch):
583         CustomView = request.session.model('ir.ui.view.custom')
584         CustomView.create({
585             'user_id': request.session._uid,
586             'ref_id': view_id,
587             'arch': arch
588         }, request.session.eval_context(request.context))
589         return {'result': True}
590
591     @openerpweb.jsonrequest
592     def undo_custom(self, request, view_id, reset=False):
593         CustomView = request.session.model('ir.ui.view.custom')
594         context = request.session.eval_context(request.context)
595         vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
596                                     0, False, False, context)
597         if vcustom:
598             if reset:
599                 CustomView.unlink(vcustom, context)
600             else:
601                 CustomView.unlink([vcustom[0]], context)
602             return {'result': True}
603         return {'result': False}
604
605     def transform_view(self, view_string, session, context=None):
606         # transform nodes on the fly via iterparse, instead of
607         # doing it statically on the parsing result
608         parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
609         root = None
610         for event, elem in parser:
611             if event == "start":
612                 if root is None:
613                     root = elem
614                 self.parse_domains_and_contexts(elem, session)
615         return root
616
617     def parse_domain(self, domain, session):
618         """ Parses an arbitrary string containing a domain, transforms it
619         to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
620
621         :param domain: the domain to parse, if the domain is not a string it is assumed to
622         be a literal domain and is returned as-is
623         :param session: Current OpenERP session
624         :type session: openerpweb.openerpweb.OpenERPSession
625         """
626         if not isinstance(domain, (str, unicode)):
627             return domain
628         try:
629             return openerpweb.ast.literal_eval(domain)
630         except ValueError:
631             # not a literal
632             return openerpweb.nonliterals.Domain(session, domain)
633         
634     def parse_context(self, context, session):
635         """ Parses an arbitrary string containing a context, transforms it
636         to either a literal context or a :class:`openerpweb.nonliterals.Context`
637
638         :param context: the context to parse, if the context is not a string it is assumed to
639         be a literal domain and is returned as-is
640         :param session: Current OpenERP session
641         :type session: openerpweb.openerpweb.OpenERPSession
642         """
643         if not isinstance(context, (str, unicode)):
644             return context
645         try:
646             return openerpweb.ast.literal_eval(context)
647         except ValueError:
648             return openerpweb.nonliterals.Context(session, context)
649
650     def parse_domains_and_contexts(self, elem, session):
651         """ Converts domains and contexts from the view into Python objects,
652         either literals if they can be parsed by literal_eval or a special
653         placeholder object if the domain or context refers to free variables.
654
655         :param elem: the current node being parsed
656         :type param: xml.etree.ElementTree.Element
657         :param session: OpenERP session object, used to store and retrieve
658                         non-literal objects
659         :type session: openerpweb.openerpweb.OpenERPSession
660         """
661         for el in ['domain', 'filter_domain']:
662             domain = elem.get(el, '').strip()
663             if domain:
664                 elem.set(el, self.parse_domain(domain, session))
665         for el in ['context', 'default_get']:
666             context_string = elem.get(el, '').strip()
667             if context_string:
668                 elem.set(el, self.parse_context(context_string, session))
669
670 class FormView(View):
671     _cp_path = "/base/formview"
672
673     @openerpweb.jsonrequest
674     def load(self, req, model, view_id, toolbar=False):
675         fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
676         return {'fields_view': fields_view}
677
678 class ListView(View):
679     _cp_path = "/base/listview"
680
681     @openerpweb.jsonrequest
682     def load(self, req, model, view_id, toolbar=False):
683         fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
684         return {'fields_view': fields_view}
685
686     def process_colors(self, view, row, context):
687         colors = view['arch']['attrs'].get('colors')
688
689         if not colors:
690             return None
691
692         color = [
693             pair.split(':')[0]
694             for pair in colors.split(';')
695             if eval(pair.split(':')[1], dict(context, **row))
696         ]
697
698         if not color:
699             return None
700         elif len(color) == 1:
701             return color[0]
702         return 'maroon'
703
704 class SearchView(View):
705     _cp_path = "/base/searchview"
706
707     @openerpweb.jsonrequest
708     def load(self, req, model, view_id):
709         fields_view = self.fields_view_get(req, model, view_id, 'search')
710         return {'fields_view': fields_view}
711
712     @openerpweb.jsonrequest
713     def fields_get(self, req, model):
714         Model = req.session.model(model)
715         fields = Model.fields_get(False, req.session.eval_context(req.context))
716         return {'fields': fields}
717
718 class Binary(openerpweb.Controller):
719     _cp_path = "/base/binary"
720
721     @openerpweb.httprequest
722     def image(self, request, session_id, model, id, field, **kw):
723         cherrypy.response.headers['Content-Type'] = 'image/png'
724         Model = request.session.model(model)
725         context = request.session.eval_context(request.context)
726         try:
727             if not id:
728                 res = Model.default_get([field], context).get(field, '')
729             else:
730                 res = Model.read([int(id)], [field], context)[0].get(field, '')
731             return base64.decodestring(res)
732         except: # TODO: what's the exception here?
733             return self.placeholder()
734     def placeholder(self):
735         return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
736
737     @openerpweb.httprequest
738     def saveas(self, request, session_id, model, id, field, fieldname, **kw):
739         Model = request.session.model(model)
740         context = request.session.eval_context(request.context)
741         res = Model.read([int(id)], [field, fieldname], context)[0]
742         filecontent = res.get(field, '')
743         if not filecontent:
744             raise cherrypy.NotFound
745         else:
746             cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
747             filename = '%s_%s' % (model.replace('.', '_'), id)
748             if fieldname:
749                 filename = res.get(fieldname, '') or filename
750             cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' +  filename
751             return base64.decodestring(filecontent)
752
753     @openerpweb.httprequest
754     def upload(self, request, session_id, callback, ufile=None):
755         cherrypy.response.timeout = 500
756         headers = {}
757         for key, val in cherrypy.request.headers.iteritems():
758             headers[key.lower()] = val
759         size = int(headers.get('content-length', 0))
760         # TODO: might be useful to have a configuration flag for max-length file uploads
761         try:
762             out = """<script language="javascript" type="text/javascript">
763                         var win = window.top.window,
764                             callback = win[%s];
765                         if (typeof(callback) === 'function') {
766                             callback.apply(this, %s);
767                         } else {
768                             win.jQuery('#oe_notification', win.document).notify('create', {
769                                 title: "Ajax File Upload",
770                                 text: "Could not find callback"
771                             });
772                         }
773                     </script>"""
774             data = ufile.file.read()
775             args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
776         except Exception, e:
777             args = [False, e.message]
778         return out % (simplejson.dumps(callback), simplejson.dumps(args))
779
780     @openerpweb.httprequest
781     def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
782         cherrypy.response.timeout = 500
783         context = request.session.eval_context(request.context)
784         Model = request.session.model('ir.attachment')
785         try:
786             out = """<script language="javascript" type="text/javascript">
787                         var win = window.top.window,
788                             callback = win[%s];
789                         if (typeof(callback) === 'function') {
790                             callback.call(this, %s);
791                         }
792                     </script>"""
793             attachment_id = Model.create({
794                 'name': ufile.filename,
795                 'datas': base64.encodestring(ufile.file.read()),
796                 'res_model': model,
797                 'res_id': int(id)
798             }, context)
799             args = {
800                 'filename': ufile.filename,
801                 'id':  attachment_id
802             }
803         except Exception, e:
804             args = { 'error': e.message }
805         return out % (simplejson.dumps(callback), simplejson.dumps(args))
806
807 class Action(openerpweb.Controller):
808     _cp_path = "/base/action"
809
810     @openerpweb.jsonrequest
811     def load(self, req, action_id):
812         Actions = req.session.model('ir.actions.actions')
813         value = False
814         context = req.session.eval_context(req.context)
815         action_type = Actions.read([action_id], ['type'], context)
816         if action_type:
817             action = req.session.model(action_type[0]['type']).read([action_id], False,
818                                                                     context)
819             if action:
820                 value = clean_action(action[0], req.session)
821         return {'result': value}
822
823     @openerpweb.jsonrequest
824     def run(self, req, action_id):
825         return clean_action(req.session.model('ir.actions.server').run(
826             [action_id], req.session.eval_context(req.context)), req.session)
827
828 #