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