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