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