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