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