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