1 # -*- coding: utf-8 -*-
3 import base64, glob, os, re
4 from xml.etree import ElementTree
5 from cStringIO import StringIO
12 import openerpweb.nonliterals
17 # Should move to openerpweb.Xml2Json
20 # Simple and straightforward XML-to-JSON converter in Python
23 # URL: http://code.google.com/p/xml2json-direct/
25 def convert_to_json(s):
26 return simplejson.dumps(
27 Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
30 def convert_to_structure(s):
31 root = ElementTree.fromstring(s)
32 return Xml2Json.convert_element(root)
35 def convert_element(el, skip_whitespaces=True):
38 ns, name = el.tag.rsplit("}", 1)
40 res["namespace"] = ns[1:]
44 for k, v in el.items():
47 if el.text and (not skip_whitespaces or el.text.strip() != ''):
50 kids.append(Xml2Json.convert_element(kid))
51 if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
53 res["children"] = kids
56 #----------------------------------------------------------
57 # OpenERP Web base Controllers
58 #----------------------------------------------------------
60 def manifest_glob(addons, key):
63 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
65 for pattern in globlist:
66 for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)):
67 files.append(path[len(openerpweb.path_addons):])
70 def concat_files(file_list):
71 """ Concatenate file content
72 return (concat,timestamp)
73 concat: concatenation of file content
74 timestamp: max(os.path.getmtime of file_list)
76 root = openerpweb.path_root
80 fname = os.path.join(root, i)
81 ftime = os.path.getmtime(fname)
82 if ftime > files_timestamp:
83 files_timestamp = ftime
84 files_content = open(fname).read()
85 files_concat = "".join(files_content)
88 class WebClient(openerpweb.Controller):
89 _cp_path = "/base/webclient"
91 @openerpweb.jsonrequest
92 def csslist(self, req, mods='base'):
93 return manifest_glob(mods.split(','), 'css')
95 @openerpweb.jsonrequest
96 def jslist(self, req, mods='base'):
97 return manifest_glob(mods.split(','), 'js')
99 @openerpweb.httprequest
100 def css(self, req, mods='base'):
101 cherrypy.response.headers['Content-Type'] = 'text/css'
102 files = manifest_glob(mods.split(','), 'css')
103 concat = concat_files(files)[0]
104 # TODO request set the Date of last modif and Etag
107 @openerpweb.httprequest
108 def js(self, req, mods='base'):
109 cherrypy.response.headers['Content-Type'] = 'application/javascript'
110 files = manifest_glob(mods.split(','), 'js')
111 concat = concat_files(files)[0]
112 # TODO request set the Date of last modif and Etag
115 @openerpweb.httprequest
117 template ="""<!DOCTYPE html>
118 <html style="height: 100%%">
120 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
121 <title>OpenERP</title>
123 <script type="text/javascript">
125 QWeb = new QWeb2.Engine();
126 openerp.init().base.webclient("oe");
129 <link rel="shortcut icon" href="/base/static/src/img/favicon.ico" type="image/x-icon"/>
132 <link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
135 <body id="oe" class="openerp"></body>
137 """.replace('\n'+' '*8,'\n')
140 jslist = ['/base/webclient/js']
142 jslist = manifest_glob(['base'], 'js')
143 js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
146 csslist = ['/base/webclient/css']
148 csslist = manifest_glob(['base'], 'css')
149 css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
150 r = template % (js, css)
153 class Database(openerpweb.Controller):
154 _cp_path = "/base/database"
156 @openerpweb.jsonrequest
157 def get_databases_list(self, req):
158 proxy = req.session.proxy("db")
160 h = req.httprequest.headers['Host'].split(':')[0]
162 r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
163 dbs = [i for i in dbs if re.match(r, i)]
164 return {"db_list": dbs}
166 @openerpweb.jsonrequest
167 def create_db(self, req, fields):
169 params = dict(map(operator.itemgetter('name', 'value'), fields))
170 create_attrs = operator.itemgetter(
171 'super_admin_pwd', 'db_name', 'demo_data', 'db_lang', 'create_admin_pwd')(
175 return req.session.proxy("db").create(*create_attrs)
176 except xmlrpclib.Fault, e:
177 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
178 return {'error': e.faultCode, 'title': 'Create Database'}
179 return {'error': 'Could not create database !', 'title': 'Create Database'}
181 @openerpweb.jsonrequest
182 def drop_db(self, req, fields):
183 password, db = operator.itemgetter(
184 'drop_pwd', 'drop_db')(
185 dict(map(operator.itemgetter('name', 'value'), fields)))
188 return req.session.proxy("db").drop(password, db)
189 except xmlrpclib.Fault, e:
190 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
191 return {'error': e.faultCode, 'title': 'Drop Database'}
192 return {'error': 'Could not drop database !', 'title': 'Drop Database'}
194 @openerpweb.jsonrequest
195 def backup_db(self, req, fields):
196 password, db = operator.itemgetter(
197 'backup_pwd', 'backup_db')(
198 dict(map(operator.itemgetter('name', 'value'), fields)))
201 res = req.session.proxy("db").dump(password, db)
203 cherrypy.response.headers['Content-Type'] = "application/data"
204 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
205 return base64.decodestring(res)
206 except xmlrpclib.Fault, e:
207 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
208 return {'error': e.faultCode, 'title': 'Backup Database'}
209 return {'error': 'Could not drop database !', 'title': 'Backup Database'}
211 @openerpweb.jsonrequest
212 def restore_db(self, req, fields):
213 password, db, filename = operator.itemgetter(
214 'restore_pwd', 'new_db', 'value')(
215 dict(map(operator.itemgetter('name', 'value'), fields)))
218 data = base64.encodestring(filename.file.read())
219 return req.session.proxy("db").restore(password, db, data)
220 except xmlrpclib.Fault, e:
221 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
222 return {'error': e.faultCode, 'title': 'Restore Database'}
223 return {'error': 'Could not restore database !', 'title': 'Restore Database'}
225 @openerpweb.jsonrequest
226 def change_password_db(self, req, fields):
227 old_password, new_password = operator.itemgetter(
229 dict(map(operator.itemgetter('name', 'value'), fields)))
231 return req.session.proxy("db").change_admin_password(old_password, new_password)
232 except xmlrpclib.Fault, e:
233 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
234 return {'error': e.faultCode, 'title': 'Change Password'}
235 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
237 class Session(openerpweb.Controller):
238 _cp_path = "/base/session"
240 @openerpweb.jsonrequest
241 def login(self, req, db, login, password):
242 req.session.login(db, login, password)
245 "session_id": req.session_id,
246 "uid": req.session._uid,
249 @openerpweb.jsonrequest
250 def sc_list(self, req):
251 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
252 req.session.eval_context(req.context))
254 @openerpweb.jsonrequest
255 def get_lang_list(self, req):
256 lang_list = [('en_US', 'English (US)')]
258 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
260 return {"error": e, "title": "Languages"}
261 return {"lang_list": lang_list, "error": ""}
263 @openerpweb.jsonrequest
264 def modules(self, req):
265 # TODO query server for installed web modules
267 for name, manifest in openerpweb.addons_manifest.items():
268 if name != 'base' and manifest.get('active', True):
272 @openerpweb.jsonrequest
273 def eval_domain_and_context(self, req, contexts, domains,
275 """ Evaluates sequences of domains and contexts, composing them into
276 a single context, domain or group_by sequence.
278 :param list contexts: list of contexts to merge together. Contexts are
279 evaluated in sequence, all previous contexts
280 are part of their own evaluation context
281 (starting at the session context).
282 :param list domains: list of domains to merge together. Domains are
283 evaluated in sequence and appended to one another
284 (implicit AND), their evaluation domain is the
285 result of merging all contexts.
286 :param list group_by_seq: list of domains (which may be in a different
287 order than the ``contexts`` parameter),
288 evaluated in sequence, their ``'group_by'``
289 key is extracted if they have one.
294 the global context created by merging all of
298 the concatenation of all domains
301 a list of fields to group by, potentially empty (in which case
302 no group by should be performed)
304 context, domain = eval_context_and_domain(req.session,
305 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
306 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
308 group_by_sequence = []
309 for candidate in (group_by_seq or []):
310 ctx = req.session.eval_context(candidate, context)
311 group_by = ctx.get('group_by')
314 elif isinstance(group_by, basestring):
315 group_by_sequence.append(group_by)
317 group_by_sequence.extend(group_by)
322 'group_by': group_by_sequence
325 @openerpweb.jsonrequest
326 def save_session_action(self, req, the_action):
328 This method store an action object in the session object and returns an integer
329 identifying that action. The method get_session_action() can be used to get
332 :param the_action: The action to save in the session.
333 :type the_action: anything
334 :return: A key identifying the saved action.
337 saved_actions = cherrypy.session.get('saved_actions')
338 if not saved_actions:
339 saved_actions = {"next":0, "actions":{}}
340 cherrypy.session['saved_actions'] = saved_actions
341 # we don't allow more than 10 stored actions
342 if len(saved_actions["actions"]) >= 10:
343 del saved_actions["actions"][min(saved_actions["actions"].keys())]
344 key = saved_actions["next"]
345 saved_actions["actions"][key] = the_action
346 saved_actions["next"] = key + 1
349 @openerpweb.jsonrequest
350 def get_session_action(self, req, key):
352 Gets back a previously saved action. This method can return None if the action
353 was saved since too much time (this case should be handled in a smart way).
355 :param key: The key given by save_session_action()
357 :return: The saved action or None.
360 saved_actions = cherrypy.session.get('saved_actions')
361 if not saved_actions:
363 return saved_actions["actions"].get(key)
365 def eval_context_and_domain(session, context, domain=None):
366 e_context = session.eval_context(context)
367 # should we give the evaluated context as an evaluation context to the domain?
368 e_domain = session.eval_domain(domain or [])
370 return e_context, e_domain
372 def load_actions_from_ir_values(req, key, key2, models, meta, context):
373 Values = req.session.model('ir.values')
374 actions = Values.get(key, key2, models, meta, context)
376 return [(id, name, clean_action(action, req.session))
377 for id, name, action in actions]
379 def clean_action(action, session):
380 if action['type'] != 'ir.actions.act_window':
382 # values come from the server, we can just eval them
383 if isinstance(action.get('context', None), basestring):
384 action['context'] = eval(
386 session.evaluation_context()) or {}
388 if isinstance(action.get('domain', None), basestring):
389 action['domain'] = eval(
391 session.evaluation_context(
392 action.get('context', {}))) or []
393 if 'flags' not in action:
394 # Set empty flags dictionary for web client.
395 action['flags'] = dict()
396 return fix_view_modes(action)
398 def generate_views(action):
400 While the server generates a sequence called "views" computing dependencies
401 between a bunch of stuff for views coming directly from the database
402 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
403 to return custom view dictionaries generated on the fly.
405 In that case, there is no ``views`` key available on the action.
407 Since the web client relies on ``action['views']``, generate it here from
408 ``view_mode`` and ``view_id``.
410 Currently handles two different cases:
412 * no view_id, multiple view_mode
413 * single view_id, single view_mode
415 :param dict action: action descriptor dictionary to generate a views key for
417 view_id = action.get('view_id', False)
418 if isinstance(view_id, (list, tuple)):
421 # providing at least one view mode is a requirement, not an option
422 view_modes = action['view_mode'].split(',')
424 if len(view_modes) > 1:
426 raise ValueError('Non-db action dictionaries should provide '
427 'either multiple view modes or a single view '
428 'mode and an optional view id.\n\n Got view '
429 'modes %r and view id %r for action %r' % (
430 view_modes, view_id, action))
431 action['views'] = [(False, mode) for mode in view_modes]
433 action['views'] = [(view_id, view_modes[0])]
435 def fix_view_modes(action):
436 """ For historical reasons, OpenERP has weird dealings in relation to
437 view_mode and the view_type attribute (on window actions):
439 * one of the view modes is ``tree``, which stands for both list views
441 * the choice is made by checking ``view_type``, which is either
442 ``form`` for a list view or ``tree`` for an actual tree view
444 This methods simply folds the view_type into view_mode by adding a
445 new view mode ``list`` which is the result of the ``tree`` view_mode
446 in conjunction with the ``form`` view_type.
448 TODO: this should go into the doc, some kind of "peculiarities" section
450 :param dict action: an action descriptor
451 :returns: nothing, the action is modified in place
453 if 'views' not in action:
454 generate_views(action)
456 if action.pop('view_type') != 'form':
460 [id, mode if mode != 'tree' else 'list']
461 for id, mode in action['views']
466 class Menu(openerpweb.Controller):
467 _cp_path = "/base/menu"
469 @openerpweb.jsonrequest
471 return {'data': self.do_load(req)}
473 def do_load(self, req):
474 """ Loads all menu items (all applications and their sub-menus).
476 :param req: A request object, with an OpenERP session attribute
477 :type req: < session -> OpenERPSession >
478 :return: the menu root
479 :rtype: dict('children': menu_nodes)
481 Menus = req.session.model('ir.ui.menu')
482 # menus are loaded fully unlike a regular tree view, cause there are
483 # less than 512 items
484 context = req.session.eval_context(req.context)
485 menu_ids = Menus.search([], 0, False, False, context)
486 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
487 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
488 menu_items.append(menu_root)
490 # make a tree using parent_id
491 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
492 for menu_item in menu_items:
493 if menu_item['parent_id']:
494 parent = menu_item['parent_id'][0]
497 if parent in menu_items_map:
498 menu_items_map[parent].setdefault(
499 'children', []).append(menu_item)
501 # sort by sequence a tree using parent_id
502 for menu_item in menu_items:
503 menu_item.setdefault('children', []).sort(
504 key=lambda x:x["sequence"])
508 @openerpweb.jsonrequest
509 def action(self, req, menu_id):
510 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
511 [('ir.ui.menu', menu_id)], False,
512 req.session.eval_context(req.context))
513 return {"action": actions}
515 class DataSet(openerpweb.Controller):
516 _cp_path = "/base/dataset"
518 @openerpweb.jsonrequest
519 def fields(self, req, model):
520 return {'fields': req.session.model(model).fields_get(False,
521 req.session.eval_context(req.context))}
523 @openerpweb.jsonrequest
524 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
525 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
526 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
528 """ Performs a search() followed by a read() (if needed) using the
529 provided search criteria
531 :param request: a JSON-RPC request object
532 :type request: openerpweb.JsonRequest
533 :param str model: the name of the model to search on
534 :param fields: a list of the fields to return in the result records
536 :param int offset: from which index should the results start being returned
537 :param int limit: the maximum number of records to return
538 :param list domain: the search domain for the query
539 :param list sort: sorting directives
540 :returns: A structure (dict) with two keys: ids (all the ids matching
541 the (domain, context) pair) and records (paginated records
542 matching fields selection set)
545 Model = request.session.model(model)
546 context, domain = eval_context_and_domain(
547 request.session, request.context, domain)
549 ids = Model.search(domain, 0, False, sort or False, context)
550 # need to fill the dataset with all ids for the (domain, context) pair,
551 # so search un-paginated and paginate manually before reading
552 paginated_ids = ids[offset:(offset + limit if limit else None)]
553 if fields and fields == ['id']:
554 # shortcut read if we only want the ids
557 'records': map(lambda id: {'id': id}, paginated_ids)
560 records = Model.read(paginated_ids, fields or False, context)
561 records.sort(key=lambda obj: ids.index(obj['id']))
568 @openerpweb.jsonrequest
569 def get(self, request, model, ids, fields=False):
570 return self.do_get(request, model, ids, fields)
571 def do_get(self, request, model, ids, fields=False):
572 """ Fetches and returns the records of the model ``model`` whose ids
575 The results are in the same order as the inputs, but elements may be
576 missing (if there is no record left for the id)
578 :param request: the JSON-RPC2 request object
579 :type request: openerpweb.JsonRequest
580 :param model: the model to read from
582 :param ids: a list of identifiers
584 :param fields: a list of fields to fetch, ``False`` or empty to fetch
585 all fields in the model
586 :type fields: list | False
587 :returns: a list of records, in the same order as the list of ids
590 Model = request.session.model(model)
591 records = Model.read(ids, fields, request.session.eval_context(request.context))
593 record_map = dict((record['id'], record) for record in records)
595 return [record_map[id] for id in ids if record_map.get(id)]
597 @openerpweb.jsonrequest
598 def load(self, req, model, id, fields):
599 m = req.session.model(model)
601 r = m.read([id], False, req.session.eval_context(req.context))
604 return {'value': value}
606 @openerpweb.jsonrequest
607 def create(self, req, model, data):
608 m = req.session.model(model)
609 r = m.create(data, req.session.eval_context(req.context))
612 @openerpweb.jsonrequest
613 def save(self, req, model, id, data):
614 m = req.session.model(model)
615 r = m.write([id], data, req.session.eval_context(req.context))
618 @openerpweb.jsonrequest
619 def unlink(self, request, model, ids=()):
620 Model = request.session.model(model)
621 return Model.unlink(ids, request.session.eval_context(request.context))
623 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
624 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
625 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
626 c, d = eval_context_and_domain(req.session, context, domain)
627 if domain_id and len(args) - 1 >= domain_id:
629 if context_id and len(args) - 1 >= context_id:
632 return getattr(req.session.model(model), method)(*args)
634 @openerpweb.jsonrequest
635 def call(self, req, model, method, args, domain_id=None, context_id=None):
636 return self.call_common(req, model, method, args, domain_id, context_id)
638 @openerpweb.jsonrequest
639 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
640 action = self.call_common(req, model, method, args, domain_id, context_id)
641 if isinstance(action, dict) and action.get('type') != '':
642 return {'result': clean_action(action, req.session)}
643 return {'result': False}
645 @openerpweb.jsonrequest
646 def exec_workflow(self, req, model, id, signal):
647 r = req.session.exec_workflow(model, id, signal)
650 @openerpweb.jsonrequest
651 def default_get(self, req, model, fields):
652 Model = req.session.model(model)
653 return Model.default_get(fields, req.session.eval_context(req.context))
655 class DataGroup(openerpweb.Controller):
656 _cp_path = "/base/group"
657 @openerpweb.jsonrequest
658 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
659 Model = request.session.model(model)
660 context, domain = eval_context_and_domain(request.session, request.context, domain)
662 return Model.read_group(
663 domain or [], fields, group_by_fields, 0, False,
664 dict(context, group_by=group_by_fields), sort or False)
666 class View(openerpweb.Controller):
667 _cp_path = "/base/view"
669 def fields_view_get(self, request, model, view_id, view_type,
670 transform=True, toolbar=False, submenu=False):
671 Model = request.session.model(model)
672 context = request.session.eval_context(request.context)
673 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
674 # todo fme?: check that we should pass the evaluated context here
675 self.process_view(request.session, fvg, context, transform)
678 def process_view(self, session, fvg, context, transform):
679 # depending on how it feels, xmlrpclib.ServerProxy can translate
680 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
681 # enjoy unicode strings which can not be trivially converted to
682 # strings, and it blows up during parsing.
684 # So ensure we fix this retardation by converting view xml back to
686 if isinstance(fvg['arch'], unicode):
687 arch = fvg['arch'].encode('utf-8')
692 evaluation_context = session.evaluation_context(context or {})
693 xml = self.transform_view(arch, session, evaluation_context)
695 xml = ElementTree.fromstring(arch)
696 fvg['arch'] = Xml2Json.convert_element(xml)
698 for field in fvg['fields'].itervalues():
699 if field.get('views'):
700 for view in field["views"].itervalues():
701 self.process_view(session, view, None, transform)
702 if field.get('domain'):
703 field["domain"] = self.parse_domain(field["domain"], session)
704 if field.get('context'):
705 field["context"] = self.parse_context(field["context"], session)
707 @openerpweb.jsonrequest
708 def add_custom(self, request, view_id, arch):
709 CustomView = request.session.model('ir.ui.view.custom')
711 'user_id': request.session._uid,
714 }, request.session.eval_context(request.context))
715 return {'result': True}
717 @openerpweb.jsonrequest
718 def undo_custom(self, request, view_id, reset=False):
719 CustomView = request.session.model('ir.ui.view.custom')
720 context = request.session.eval_context(request.context)
721 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
722 0, False, False, context)
725 CustomView.unlink(vcustom, context)
727 CustomView.unlink([vcustom[0]], context)
728 return {'result': True}
729 return {'result': False}
731 def transform_view(self, view_string, session, context=None):
732 # transform nodes on the fly via iterparse, instead of
733 # doing it statically on the parsing result
734 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
736 for event, elem in parser:
740 self.parse_domains_and_contexts(elem, session)
743 def parse_domain(self, domain, session):
744 """ Parses an arbitrary string containing a domain, transforms it
745 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
747 :param domain: the domain to parse, if the domain is not a string it is assumed to
748 be a literal domain and is returned as-is
749 :param session: Current OpenERP session
750 :type session: openerpweb.openerpweb.OpenERPSession
752 if not isinstance(domain, (str, unicode)):
755 return openerpweb.ast.literal_eval(domain)
758 return openerpweb.nonliterals.Domain(session, domain)
760 def parse_context(self, context, session):
761 """ Parses an arbitrary string containing a context, transforms it
762 to either a literal context or a :class:`openerpweb.nonliterals.Context`
764 :param context: the context to parse, if the context is not a string it is assumed to
765 be a literal domain and is returned as-is
766 :param session: Current OpenERP session
767 :type session: openerpweb.openerpweb.OpenERPSession
769 if not isinstance(context, (str, unicode)):
772 return openerpweb.ast.literal_eval(context)
774 return openerpweb.nonliterals.Context(session, context)
776 def parse_domains_and_contexts(self, elem, session):
777 """ Converts domains and contexts from the view into Python objects,
778 either literals if they can be parsed by literal_eval or a special
779 placeholder object if the domain or context refers to free variables.
781 :param elem: the current node being parsed
782 :type param: xml.etree.ElementTree.Element
783 :param session: OpenERP session object, used to store and retrieve
785 :type session: openerpweb.openerpweb.OpenERPSession
787 for el in ['domain', 'filter_domain']:
788 domain = elem.get(el, '').strip()
790 elem.set(el, self.parse_domain(domain, session))
791 for el in ['context', 'default_get']:
792 context_string = elem.get(el, '').strip()
794 elem.set(el, self.parse_context(context_string, session))
796 class FormView(View):
797 _cp_path = "/base/formview"
799 @openerpweb.jsonrequest
800 def load(self, req, model, view_id, toolbar=False):
801 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
802 return {'fields_view': fields_view}
804 class ListView(View):
805 _cp_path = "/base/listview"
807 @openerpweb.jsonrequest
808 def load(self, req, model, view_id, toolbar=False):
809 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
810 return {'fields_view': fields_view}
812 def process_colors(self, view, row, context):
813 colors = view['arch']['attrs'].get('colors')
820 for pair in colors.split(';')
821 if eval(pair.split(':')[1], dict(context, **row))
826 elif len(color) == 1:
830 class SearchView(View):
831 _cp_path = "/base/searchview"
833 @openerpweb.jsonrequest
834 def load(self, req, model, view_id):
835 fields_view = self.fields_view_get(req, model, view_id, 'search')
836 return {'fields_view': fields_view}
838 @openerpweb.jsonrequest
839 def fields_get(self, req, model):
840 Model = req.session.model(model)
841 fields = Model.fields_get(False, req.session.eval_context(req.context))
842 for field in fields.values():
843 # shouldn't convert the views too?
844 if field.get('domain'):
845 field["domain"] = self.parse_domain(field["domain"], req.session)
846 if field.get('context'):
847 field["context"] = self.parse_domain(field["context"], req.session)
848 return {'fields': fields}
850 @openerpweb.jsonrequest
851 def get_filters(self, req, model):
852 Model = req.session.model("ir.filters")
853 filters = Model.get_filters(model)
854 for filter in filters:
855 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
856 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
859 @openerpweb.jsonrequest
860 def save_filter(self, req, model, name, context_to_save, domain):
861 Model = req.session.model("ir.filters")
862 ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
863 ctx.session = req.session
865 domain = openerpweb.nonliterals.CompoundDomain(domain)
866 domain.session = req.session
867 domain = domain.evaluate()
868 uid = req.session._uid
869 context = req.session.eval_context(req.context)
870 to_return = Model.create_or_replace({"context": ctx,
878 class Binary(openerpweb.Controller):
879 _cp_path = "/base/binary"
881 @openerpweb.httprequest
882 def image(self, request, session_id, model, id, field, **kw):
883 cherrypy.response.headers['Content-Type'] = 'image/png'
884 Model = request.session.model(model)
885 context = request.session.eval_context(request.context)
888 res = Model.default_get([field], context).get(field, '')
890 res = Model.read([int(id)], [field], context)[0].get(field, '')
891 return base64.decodestring(res)
892 except: # TODO: what's the exception here?
893 return self.placeholder()
894 def placeholder(self):
895 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
897 @openerpweb.httprequest
898 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
899 Model = request.session.model(model)
900 context = request.session.eval_context(request.context)
901 res = Model.read([int(id)], [field, fieldname], context)[0]
902 filecontent = res.get(field, '')
904 raise cherrypy.NotFound
906 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
907 filename = '%s_%s' % (model.replace('.', '_'), id)
909 filename = res.get(fieldname, '') or filename
910 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
911 return base64.decodestring(filecontent)
913 @openerpweb.httprequest
914 def upload(self, request, session_id, callback, ufile=None):
915 cherrypy.response.timeout = 500
917 for key, val in cherrypy.request.headers.iteritems():
918 headers[key.lower()] = val
919 size = int(headers.get('content-length', 0))
920 # TODO: might be useful to have a configuration flag for max-length file uploads
922 out = """<script language="javascript" type="text/javascript">
923 var win = window.top.window,
925 if (typeof(callback) === 'function') {
926 callback.apply(this, %s);
928 win.jQuery('#oe_notification', win.document).notify('create', {
929 title: "Ajax File Upload",
930 text: "Could not find callback"
934 data = ufile.file.read()
935 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
937 args = [False, e.message]
938 return out % (simplejson.dumps(callback), simplejson.dumps(args))
940 @openerpweb.httprequest
941 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
942 cherrypy.response.timeout = 500
943 context = request.session.eval_context(request.context)
944 Model = request.session.model('ir.attachment')
946 out = """<script language="javascript" type="text/javascript">
947 var win = window.top.window,
949 if (typeof(callback) === 'function') {
950 callback.call(this, %s);
953 attachment_id = Model.create({
954 'name': ufile.filename,
955 'datas': base64.encodestring(ufile.file.read()),
960 'filename': ufile.filename,
964 args = { 'error': e.message }
965 return out % (simplejson.dumps(callback), simplejson.dumps(args))
967 class Action(openerpweb.Controller):
968 _cp_path = "/base/action"
970 @openerpweb.jsonrequest
971 def load(self, req, action_id):
972 Actions = req.session.model('ir.actions.actions')
974 context = req.session.eval_context(req.context)
975 action_type = Actions.read([action_id], ['type'], context)
977 action = req.session.model(action_type[0]['type']).read([action_id], False,
980 value = clean_action(action[0], req.session)
981 return {'result': value}
983 @openerpweb.jsonrequest
984 def run(self, req, action_id):
985 return clean_action(req.session.model('ir.actions.server').run(
986 [action_id], req.session.eval_context(req.context)), req.session)