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