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