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