1 # -*- coding: utf-8 -*-
3 import base64, glob, os, re
4 from xml.etree import ElementTree
5 from cStringIO import StringIO
11 import openerpweb.nonliterals
16 # Should move to openerpweb.Xml2Json
19 # Simple and straightforward XML-to-JSON converter in Python
22 # URL: http://code.google.com/p/xml2json-direct/
24 def convert_to_json(s):
25 return simplejson.dumps(
26 Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
29 def convert_to_structure(s):
30 root = ElementTree.fromstring(s)
31 return Xml2Json.convert_element(root)
34 def convert_element(el, skip_whitespaces=True):
37 ns, name = el.tag.rsplit("}", 1)
39 res["namespace"] = ns[1:]
43 for k, v in el.items():
46 if el.text and (not skip_whitespaces or el.text.strip() != ''):
49 kids.append(Xml2Json.convert_element(kid))
50 if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
52 res["children"] = kids
55 #----------------------------------------------------------
56 # OpenERP Web base Controllers
57 #----------------------------------------------------------
59 def manifest_glob(addons, key):
62 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
64 for pattern in globlist:
65 for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)):
66 files.append(path[len(openerpweb.path_addons):])
69 def concat_files(file_list):
70 """ Concatenate file content
71 return (concat,timestamp)
72 concat: concatenation of file content
73 timestamp: max(os.path.getmtime of file_list)
75 root = openerpweb.path_root
79 fname = os.path.join(root, i)
80 ftime = os.path.getmtime(fname)
81 if ftime > files_timestamp:
82 files_timestamp = ftime
83 files_content = open(fname).read()
84 files_concat = "".join(files_content)
87 class WebClient(openerpweb.Controller):
88 _cp_path = "/base/webclient"
90 @openerpweb.jsonrequest
91 def csslist(self, req, mods='base'):
92 return manifest_glob(mods.split(','), 'css')
94 @openerpweb.jsonrequest
95 def jslist(self, req, mods='base'):
96 return manifest_glob(mods.split(','), 'js')
98 @openerpweb.httprequest
99 def css(self, req, mods='base'):
100 cherrypy.response.headers['Content-Type'] = 'text/css'
101 files = manifest_glob(mods.split(','), 'css')
102 concat = concat_files(files)[0]
103 # TODO request set the Date of last modif and Etag
106 @openerpweb.httprequest
107 def js(self, req, mods='base'):
108 cherrypy.response.headers['Content-Type'] = 'application/javascript'
109 files = manifest_glob(mods.split(','), 'js')
110 concat = concat_files(files)[0]
111 # TODO request set the Date of last modif and Etag
114 @openerpweb.httprequest
116 template ="""<!DOCTYPE html>
117 <html style="height: 100%%">
119 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
120 <title>OpenERP</title>
122 <script type="text/javascript">
124 QWeb = new QWeb2.Engine();
125 openerp.init().base.webclient("oe");
128 <link rel="shortcut icon" href="/base/static/src/img/favicon.ico" type="image/x-icon"/>
131 <link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
134 <body id="oe" class="openerp"></body>
136 """.replace('\n'+' '*8,'\n')
139 jslist = ['/base/webclient/js']
141 jslist = manifest_glob(['base'], 'js')
142 js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
145 csslist = ['/base/webclient/css']
147 csslist = manifest_glob(['base'], 'css')
148 css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
149 r = template % (js, css)
152 class Database(openerpweb.Controller):
153 _cp_path = "/base/database"
155 @openerpweb.jsonrequest
156 def get_databases_list(self, req):
157 proxy = req.session.proxy("db")
159 h = req.httprequest.headers['Host'].split(':')[0]
161 r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
162 dbs = [i for i in dbs if re.match(r, i)]
163 return {"db_list": dbs}
165 @openerpweb.jsonrequest
166 def create_db(self, req, fields):
169 if field['name'] == 'super_admin_pwd':
170 super_admin_pwd = field['value']
171 elif field['name'] == 'db_name':
172 dbname = field['value']
173 elif field['name'] == 'demo_data':
174 demo_data = field['value']
175 elif field['name'] == 'db_lang':
176 db_lang = field['value']
177 elif field['name'] == 'create_admin_pwd':
178 admin_pwd = field['value']
180 if dbname and not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', dbname):
181 return {'error': "You must avoid all accents, space or special characters.", 'title': 'Bad database name'}
185 return req.session.proxy("db").create(super_admin_pwd, dbname, demo_data, db_lang, admin_pwd)
187 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
188 return {'error': e.faultCode, 'title': 'Create Database'}
190 return {'error': 'Could not create database !', 'title': 'Create Database'}
192 @openerpweb.jsonrequest
193 def drop_db(self, req, fields):
195 if field['name'] == 'drop_db':
197 elif field['name'] == 'drop_pwd':
198 password = field['value']
201 return req.session.proxy("db").drop(password, db)
203 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
204 return {'error': e.faultCode, 'title': 'Drop Database'}
206 return {'error': 'Could not drop database !', 'title': 'Drop Database'}
208 @openerpweb.jsonrequest
209 def backup_db(self, req, fields):
211 if field['name'] == 'backup_db':
213 elif field['name'] == 'backup_pwd':
214 password = field['value']
217 res = req.session.proxy("db").dump(password, db)
219 cherrypy.response.headers['Content-Type'] = "application/data"
220 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
221 return base64.decodestring(res)
223 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
224 return {'error': e.faultCode, 'title': 'Backup Database'}
226 return {'error': 'Could not drop database !', 'title': 'Backup Database'}
228 @openerpweb.jsonrequest
229 def restore_db(self, req, fields):
231 if field['name'] == 'restore_db':
232 filename = field['value']
233 elif field['name'] == 'new_db':
235 elif field['name'] == 'restore_pwd':
236 password = field['value']
239 data = base64.encodestring(filename.file.read())
240 return req.session.proxy("db").restore(password, db, data)
242 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
243 return {'error': e.faultCode, 'title': 'Restore Database'}
245 return {'error': 'Could not restore database !', 'title': 'Restore Database'}
247 @openerpweb.jsonrequest
248 def change_password_db(self, req, fields):
250 if field['name'] == 'old_pwd':
251 old_password = field['value']
252 elif field['name'] == 'new_pwd':
253 new_password = field['value']
256 return req.session.proxy("db").change_admin_password(old_password, new_password)
258 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
259 return {'error': e.faultCode, 'title': 'Change Password'}
261 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
263 class Session(openerpweb.Controller):
264 _cp_path = "/base/session"
266 @openerpweb.jsonrequest
267 def login(self, req, db, login, password):
268 req.session.login(db, login, password)
271 "session_id": req.session_id,
272 "uid": req.session._uid,
275 @openerpweb.jsonrequest
276 def sc_list(self, req):
277 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
278 req.session.eval_context(req.context))
280 @openerpweb.jsonrequest
281 def get_lang_list(self, req):
282 lang_list = [('en_US', 'English (US)')]
284 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
286 return {"error": e, "title": "Languages"}
287 return {"lang_list": lang_list, "error": ""}
289 @openerpweb.jsonrequest
290 def modules(self, req):
291 # TODO query server for installed web modules
293 for name, manifest in openerpweb.addons_manifest.items():
294 if name != 'base' and manifest.get('active', True):
298 @openerpweb.jsonrequest
299 def eval_domain_and_context(self, req, contexts, domains,
301 """ Evaluates sequences of domains and contexts, composing them into
302 a single context, domain or group_by sequence.
304 :param list contexts: list of contexts to merge together. Contexts are
305 evaluated in sequence, all previous contexts
306 are part of their own evaluation context
307 (starting at the session context).
308 :param list domains: list of domains to merge together. Domains are
309 evaluated in sequence and appended to one another
310 (implicit AND), their evaluation domain is the
311 result of merging all contexts.
312 :param list group_by_seq: list of domains (which may be in a different
313 order than the ``contexts`` parameter),
314 evaluated in sequence, their ``'group_by'``
315 key is extracted if they have one.
320 the global context created by merging all of
324 the concatenation of all domains
327 a list of fields to group by, potentially empty (in which case
328 no group by should be performed)
330 context, domain = eval_context_and_domain(req.session,
331 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
332 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
334 group_by_sequence = []
335 for candidate in (group_by_seq or []):
336 ctx = req.session.eval_context(candidate, context)
337 group_by = ctx.get('group_by')
340 elif isinstance(group_by, basestring):
341 group_by_sequence.append(group_by)
343 group_by_sequence.extend(group_by)
348 'group_by': group_by_sequence
351 @openerpweb.jsonrequest
352 def save_session_action(self, req, the_action):
354 This method store an action object in the session object and returns an integer
355 identifying that action. The method get_session_action() can be used to get
358 :param the_action: The action to save in the session.
359 :type the_action: anything
360 :return: A key identifying the saved action.
363 saved_actions = cherrypy.session.get('saved_actions')
364 if not saved_actions:
365 saved_actions = {"next":0, "actions":{}}
366 cherrypy.session['saved_actions'] = saved_actions
367 # we don't allow more than 10 stored actions
368 if len(saved_actions["actions"]) >= 10:
369 del saved_actions["actions"][min(saved_actions["actions"].keys())]
370 key = saved_actions["next"]
371 saved_actions["actions"][key] = the_action
372 saved_actions["next"] = key + 1
375 @openerpweb.jsonrequest
376 def get_session_action(self, req, key):
378 Gets back a previously saved action. This method can return None if the action
379 was saved since too much time (this case should be handled in a smart way).
381 :param key: The key given by save_session_action()
383 :return: The saved action or None.
386 saved_actions = cherrypy.session.get('saved_actions')
387 if not saved_actions:
389 return saved_actions["actions"].get(key)
391 def eval_context_and_domain(session, context, domain=None):
392 e_context = session.eval_context(context)
393 # should we give the evaluated context as an evaluation context to the domain?
394 e_domain = session.eval_domain(domain or [])
396 return e_context, e_domain
398 def load_actions_from_ir_values(req, key, key2, models, meta, context):
399 Values = req.session.model('ir.values')
400 actions = Values.get(key, key2, models, meta, context)
402 return [(id, name, clean_action(action, req.session))
403 for id, name, action in actions]
405 def clean_action(action, session):
406 if action['type'] != 'ir.actions.act_window':
408 # values come from the server, we can just eval them
409 if isinstance(action.get('context', None), basestring):
410 action['context'] = eval(
412 session.evaluation_context()) or {}
414 if isinstance(action.get('domain', None), basestring):
415 action['domain'] = eval(
417 session.evaluation_context(
418 action.get('context', {}))) or []
419 if 'flags' not in action:
420 # Set empty flags dictionary for web client.
421 action['flags'] = dict()
422 return fix_view_modes(action)
424 def generate_views(action):
426 While the server generates a sequence called "views" computing dependencies
427 between a bunch of stuff for views coming directly from the database
428 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
429 to return custom view dictionaries generated on the fly.
431 In that case, there is no ``views`` key available on the action.
433 Since the web client relies on ``action['views']``, generate it here from
434 ``view_mode`` and ``view_id``.
436 Currently handles two different cases:
438 * no view_id, multiple view_mode
439 * single view_id, single view_mode
441 :param dict action: action descriptor dictionary to generate a views key for
443 view_id = action.get('view_id', False)
444 if isinstance(view_id, (list, tuple)):
447 # providing at least one view mode is a requirement, not an option
448 view_modes = action['view_mode'].split(',')
450 if len(view_modes) > 1:
452 raise ValueError('Non-db action dictionaries should provide '
453 'either multiple view modes or a single view '
454 'mode and an optional view id.\n\n Got view '
455 'modes %r and view id %r for action %r' % (
456 view_modes, view_id, action))
457 action['views'] = [(False, mode) for mode in view_modes]
459 action['views'] = [(view_id, view_modes[0])]
461 def fix_view_modes(action):
462 """ For historical reasons, OpenERP has weird dealings in relation to
463 view_mode and the view_type attribute (on window actions):
465 * one of the view modes is ``tree``, which stands for both list views
467 * the choice is made by checking ``view_type``, which is either
468 ``form`` for a list view or ``tree`` for an actual tree view
470 This methods simply folds the view_type into view_mode by adding a
471 new view mode ``list`` which is the result of the ``tree`` view_mode
472 in conjunction with the ``form`` view_type.
474 TODO: this should go into the doc, some kind of "peculiarities" section
476 :param dict action: an action descriptor
477 :returns: nothing, the action is modified in place
479 if 'views' not in action:
480 generate_views(action)
482 if action.pop('view_type') != 'form':
486 [id, mode if mode != 'tree' else 'list']
487 for id, mode in action['views']
492 class Menu(openerpweb.Controller):
493 _cp_path = "/base/menu"
495 @openerpweb.jsonrequest
497 return {'data': self.do_load(req)}
499 def do_load(self, req):
500 """ Loads all menu items (all applications and their sub-menus).
502 :param req: A request object, with an OpenERP session attribute
503 :type req: < session -> OpenERPSession >
504 :return: the menu root
505 :rtype: dict('children': menu_nodes)
507 Menus = req.session.model('ir.ui.menu')
508 # menus are loaded fully unlike a regular tree view, cause there are
509 # less than 512 items
510 context = req.session.eval_context(req.context)
511 menu_ids = Menus.search([], 0, False, False, context)
512 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
513 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
514 menu_items.append(menu_root)
516 # make a tree using parent_id
517 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
518 for menu_item in menu_items:
519 if menu_item['parent_id']:
520 parent = menu_item['parent_id'][0]
523 if parent in menu_items_map:
524 menu_items_map[parent].setdefault(
525 'children', []).append(menu_item)
527 # sort by sequence a tree using parent_id
528 for menu_item in menu_items:
529 menu_item.setdefault('children', []).sort(
530 key=lambda x:x["sequence"])
534 @openerpweb.jsonrequest
535 def action(self, req, menu_id):
536 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
537 [('ir.ui.menu', menu_id)], False,
538 req.session.eval_context(req.context))
539 return {"action": actions}
541 class DataSet(openerpweb.Controller):
542 _cp_path = "/base/dataset"
544 @openerpweb.jsonrequest
545 def fields(self, req, model):
546 return {'fields': req.session.model(model).fields_get(False,
547 req.session.eval_context(req.context))}
549 @openerpweb.jsonrequest
550 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
551 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
552 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
554 """ Performs a search() followed by a read() (if needed) using the
555 provided search criteria
557 :param request: a JSON-RPC request object
558 :type request: openerpweb.JsonRequest
559 :param str model: the name of the model to search on
560 :param fields: a list of the fields to return in the result records
562 :param int offset: from which index should the results start being returned
563 :param int limit: the maximum number of records to return
564 :param list domain: the search domain for the query
565 :param list sort: sorting directives
566 :returns: A structure (dict) with two keys: ids (all the ids matching
567 the (domain, context) pair) and records (paginated records
568 matching fields selection set)
571 Model = request.session.model(model)
572 context, domain = eval_context_and_domain(
573 request.session, request.context, domain)
575 ids = Model.search(domain, 0, False, sort or False, context)
576 # need to fill the dataset with all ids for the (domain, context) pair,
577 # so search un-paginated and paginate manually before reading
578 paginated_ids = ids[offset:(offset + limit if limit else None)]
579 if fields and fields == ['id']:
580 # shortcut read if we only want the ids
583 'records': map(lambda id: {'id': id}, paginated_ids)
586 records = Model.read(paginated_ids, fields or False, context)
587 records.sort(key=lambda obj: ids.index(obj['id']))
594 @openerpweb.jsonrequest
595 def get(self, request, model, ids, fields=False):
596 return self.do_get(request, model, ids, fields)
597 def do_get(self, request, model, ids, fields=False):
598 """ Fetches and returns the records of the model ``model`` whose ids
601 The results are in the same order as the inputs, but elements may be
602 missing (if there is no record left for the id)
604 :param request: the JSON-RPC2 request object
605 :type request: openerpweb.JsonRequest
606 :param model: the model to read from
608 :param ids: a list of identifiers
610 :param fields: a list of fields to fetch, ``False`` or empty to fetch
611 all fields in the model
612 :type fields: list | False
613 :returns: a list of records, in the same order as the list of ids
616 Model = request.session.model(model)
617 records = Model.read(ids, fields, request.session.eval_context(request.context))
619 record_map = dict((record['id'], record) for record in records)
621 return [record_map[id] for id in ids if record_map.get(id)]
623 @openerpweb.jsonrequest
624 def load(self, req, model, id, fields):
625 m = req.session.model(model)
627 r = m.read([id], False, req.session.eval_context(req.context))
630 return {'value': value}
632 @openerpweb.jsonrequest
633 def create(self, req, model, data):
634 m = req.session.model(model)
635 r = m.create(data, req.session.eval_context(req.context))
638 @openerpweb.jsonrequest
639 def save(self, req, model, id, data):
640 m = req.session.model(model)
641 r = m.write([id], data, req.session.eval_context(req.context))
644 @openerpweb.jsonrequest
645 def unlink(self, request, model, ids=()):
646 Model = request.session.model(model)
647 return Model.unlink(ids, request.session.eval_context(request.context))
649 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
650 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
651 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
652 c, d = eval_context_and_domain(req.session, context, domain)
653 if domain_id and len(args) - 1 >= domain_id:
655 if context_id and len(args) - 1 >= context_id:
658 return getattr(req.session.model(model), method)(*args)
660 @openerpweb.jsonrequest
661 def call(self, req, model, method, args, domain_id=None, context_id=None):
662 return self.call_common(req, model, method, args, domain_id, context_id)
664 @openerpweb.jsonrequest
665 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
666 action = self.call_common(req, model, method, args, domain_id, context_id)
667 if isinstance(action, dict) and action.get('type') != '':
668 return {'result': clean_action(action, req.session)}
669 return {'result': False}
671 @openerpweb.jsonrequest
672 def exec_workflow(self, req, model, id, signal):
673 r = req.session.exec_workflow(model, id, signal)
676 @openerpweb.jsonrequest
677 def default_get(self, req, model, fields):
678 Model = req.session.model(model)
679 return Model.default_get(fields, req.session.eval_context(req.context))
681 class DataGroup(openerpweb.Controller):
682 _cp_path = "/base/group"
683 @openerpweb.jsonrequest
684 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
685 Model = request.session.model(model)
686 context, domain = eval_context_and_domain(request.session, request.context, domain)
688 return Model.read_group(
689 domain or [], fields, group_by_fields, 0, False,
690 dict(context, group_by=group_by_fields), sort or False)
692 class View(openerpweb.Controller):
693 _cp_path = "/base/view"
695 def fields_view_get(self, request, model, view_id, view_type,
696 transform=True, toolbar=False, submenu=False):
697 Model = request.session.model(model)
698 context = request.session.eval_context(request.context)
699 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
700 # todo fme?: check that we should pass the evaluated context here
701 self.process_view(request.session, fvg, context, transform)
704 def process_view(self, session, fvg, context, transform):
705 # depending on how it feels, xmlrpclib.ServerProxy can translate
706 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
707 # enjoy unicode strings which can not be trivially converted to
708 # strings, and it blows up during parsing.
710 # So ensure we fix this retardation by converting view xml back to
712 if isinstance(fvg['arch'], unicode):
713 arch = fvg['arch'].encode('utf-8')
718 evaluation_context = session.evaluation_context(context or {})
719 xml = self.transform_view(arch, session, evaluation_context)
721 xml = ElementTree.fromstring(arch)
722 fvg['arch'] = Xml2Json.convert_element(xml)
724 for field in fvg['fields'].itervalues():
725 if field.get('views'):
726 for view in field["views"].itervalues():
727 self.process_view(session, view, None, transform)
728 if field.get('domain'):
729 field["domain"] = self.parse_domain(field["domain"], session)
730 if field.get('context'):
731 field["context"] = self.parse_context(field["context"], session)
733 @openerpweb.jsonrequest
734 def add_custom(self, request, view_id, arch):
735 CustomView = request.session.model('ir.ui.view.custom')
737 'user_id': request.session._uid,
740 }, request.session.eval_context(request.context))
741 return {'result': True}
743 @openerpweb.jsonrequest
744 def undo_custom(self, request, view_id, reset=False):
745 CustomView = request.session.model('ir.ui.view.custom')
746 context = request.session.eval_context(request.context)
747 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
748 0, False, False, context)
751 CustomView.unlink(vcustom, context)
753 CustomView.unlink([vcustom[0]], context)
754 return {'result': True}
755 return {'result': False}
757 def transform_view(self, view_string, session, context=None):
758 # transform nodes on the fly via iterparse, instead of
759 # doing it statically on the parsing result
760 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
762 for event, elem in parser:
766 self.parse_domains_and_contexts(elem, session)
769 def parse_domain(self, domain, session):
770 """ Parses an arbitrary string containing a domain, transforms it
771 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
773 :param domain: the domain to parse, if the domain is not a string it is assumed to
774 be a literal domain and is returned as-is
775 :param session: Current OpenERP session
776 :type session: openerpweb.openerpweb.OpenERPSession
778 if not isinstance(domain, (str, unicode)):
781 return openerpweb.ast.literal_eval(domain)
784 return openerpweb.nonliterals.Domain(session, domain)
786 def parse_context(self, context, session):
787 """ Parses an arbitrary string containing a context, transforms it
788 to either a literal context or a :class:`openerpweb.nonliterals.Context`
790 :param context: the context to parse, if the context is not a string it is assumed to
791 be a literal domain and is returned as-is
792 :param session: Current OpenERP session
793 :type session: openerpweb.openerpweb.OpenERPSession
795 if not isinstance(context, (str, unicode)):
798 return openerpweb.ast.literal_eval(context)
800 return openerpweb.nonliterals.Context(session, context)
802 def parse_domains_and_contexts(self, elem, session):
803 """ Converts domains and contexts from the view into Python objects,
804 either literals if they can be parsed by literal_eval or a special
805 placeholder object if the domain or context refers to free variables.
807 :param elem: the current node being parsed
808 :type param: xml.etree.ElementTree.Element
809 :param session: OpenERP session object, used to store and retrieve
811 :type session: openerpweb.openerpweb.OpenERPSession
813 for el in ['domain', 'filter_domain']:
814 domain = elem.get(el, '').strip()
816 elem.set(el, self.parse_domain(domain, session))
817 for el in ['context', 'default_get']:
818 context_string = elem.get(el, '').strip()
820 elem.set(el, self.parse_context(context_string, session))
822 class FormView(View):
823 _cp_path = "/base/formview"
825 @openerpweb.jsonrequest
826 def load(self, req, model, view_id, toolbar=False):
827 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
828 return {'fields_view': fields_view}
830 class ListView(View):
831 _cp_path = "/base/listview"
833 @openerpweb.jsonrequest
834 def load(self, req, model, view_id, toolbar=False):
835 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
836 return {'fields_view': fields_view}
838 def process_colors(self, view, row, context):
839 colors = view['arch']['attrs'].get('colors')
846 for pair in colors.split(';')
847 if eval(pair.split(':')[1], dict(context, **row))
852 elif len(color) == 1:
856 class SearchView(View):
857 _cp_path = "/base/searchview"
859 @openerpweb.jsonrequest
860 def load(self, req, model, view_id):
861 fields_view = self.fields_view_get(req, model, view_id, 'search')
862 return {'fields_view': fields_view}
864 @openerpweb.jsonrequest
865 def fields_get(self, req, model):
866 Model = req.session.model(model)
867 fields = Model.fields_get(False, req.session.eval_context(req.context))
868 for field in fields.values():
869 # shouldn't convert the views too?
870 if field.get('domain'):
871 field["domain"] = self.parse_domain(field["domain"], req.session)
872 if field.get('context'):
873 field["context"] = self.parse_domain(field["context"], req.session)
874 return {'fields': fields}
876 @openerpweb.jsonrequest
877 def get_filters(self, req, model):
878 Model = req.session.model("ir.filters")
879 filters = Model.get_filters(model)
880 for filter in filters:
881 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
882 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
885 @openerpweb.jsonrequest
886 def save_filter(self, req, model, name, context_to_save, domain):
887 Model = req.session.model("ir.filters")
888 ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
889 ctx.session = req.session
891 domain = openerpweb.nonliterals.CompoundDomain(domain)
892 domain.session = req.session
893 domain = domain.evaluate()
894 uid = req.session._uid
895 context = req.session.eval_context(req.context)
896 to_return = Model.create_or_replace({"context": ctx,
904 class Binary(openerpweb.Controller):
905 _cp_path = "/base/binary"
907 @openerpweb.httprequest
908 def image(self, request, session_id, model, id, field, **kw):
909 cherrypy.response.headers['Content-Type'] = 'image/png'
910 Model = request.session.model(model)
911 context = request.session.eval_context(request.context)
914 res = Model.default_get([field], context).get(field, '')
916 res = Model.read([int(id)], [field], context)[0].get(field, '')
917 return base64.decodestring(res)
918 except: # TODO: what's the exception here?
919 return self.placeholder()
920 def placeholder(self):
921 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
923 @openerpweb.httprequest
924 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
925 Model = request.session.model(model)
926 context = request.session.eval_context(request.context)
927 res = Model.read([int(id)], [field, fieldname], context)[0]
928 filecontent = res.get(field, '')
930 raise cherrypy.NotFound
932 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
933 filename = '%s_%s' % (model.replace('.', '_'), id)
935 filename = res.get(fieldname, '') or filename
936 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
937 return base64.decodestring(filecontent)
939 @openerpweb.httprequest
940 def upload(self, request, session_id, callback, ufile=None):
941 cherrypy.response.timeout = 500
943 for key, val in cherrypy.request.headers.iteritems():
944 headers[key.lower()] = val
945 size = int(headers.get('content-length', 0))
946 # TODO: might be useful to have a configuration flag for max-length file uploads
948 out = """<script language="javascript" type="text/javascript">
949 var win = window.top.window,
951 if (typeof(callback) === 'function') {
952 callback.apply(this, %s);
954 win.jQuery('#oe_notification', win.document).notify('create', {
955 title: "Ajax File Upload",
956 text: "Could not find callback"
960 data = ufile.file.read()
961 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
963 args = [False, e.message]
964 return out % (simplejson.dumps(callback), simplejson.dumps(args))
966 @openerpweb.httprequest
967 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
968 cherrypy.response.timeout = 500
969 context = request.session.eval_context(request.context)
970 Model = request.session.model('ir.attachment')
972 out = """<script language="javascript" type="text/javascript">
973 var win = window.top.window,
975 if (typeof(callback) === 'function') {
976 callback.call(this, %s);
979 attachment_id = Model.create({
980 'name': ufile.filename,
981 'datas': base64.encodestring(ufile.file.read()),
986 'filename': ufile.filename,
990 args = { 'error': e.message }
991 return out % (simplejson.dumps(callback), simplejson.dumps(args))
993 class Action(openerpweb.Controller):
994 _cp_path = "/base/action"
996 @openerpweb.jsonrequest
997 def load(self, req, action_id):
998 Actions = req.session.model('ir.actions.actions')
1000 context = req.session.eval_context(req.context)
1001 action_type = Actions.read([action_id], ['type'], context)
1003 action = req.session.model(action_type[0]['type']).read([action_id], False,
1006 value = clean_action(action[0], req.session)
1007 return {'result': value}
1009 @openerpweb.jsonrequest
1010 def run(self, req, action_id):
1011 return clean_action(req.session.model('ir.actions.server').run(
1012 [action_id], req.session.eval_context(req.context)), req.session)