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