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