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