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