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.httprequest
195 def backup_db(self, req, backup_db, backup_pwd, token):
197 db_dump = base64.decodestring(
198 req.session.proxy("db").dump(backup_pwd, backup_db))
199 cherrypy.response.headers['Content-Type'] = "application/octet-stream; charset=binary"
200 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"'
201 cherrypy.response.cookie['fileToken'] = token
202 cherrypy.response.cookie['fileToken']['path'] = '/'
204 except xmlrpclib.Fault, e:
205 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
206 return {'error': e.faultCode, 'title': 'Backup Database'}
207 return {'error': 'Could not drop database !', 'title': 'Backup Database'}
209 @openerpweb.jsonrequest
210 def restore_db(self, req, fields):
211 password, db, filename = operator.itemgetter(
212 'restore_pwd', 'new_db', 'value')(
213 dict(map(operator.itemgetter('name', 'value'), fields)))
216 data = base64.encodestring(filename.file.read())
217 return req.session.proxy("db").restore(password, db, data)
218 except xmlrpclib.Fault, e:
219 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
220 return {'error': e.faultCode, 'title': 'Restore Database'}
221 return {'error': 'Could not restore database !', 'title': 'Restore Database'}
223 @openerpweb.jsonrequest
224 def change_password_db(self, req, fields):
225 old_password, new_password = operator.itemgetter(
227 dict(map(operator.itemgetter('name', 'value'), fields)))
229 return req.session.proxy("db").change_admin_password(old_password, new_password)
230 except xmlrpclib.Fault, e:
231 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
232 return {'error': e.faultCode, 'title': 'Change Password'}
233 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
235 class Session(openerpweb.Controller):
236 _cp_path = "/base/session"
238 @openerpweb.jsonrequest
239 def login(self, req, db, login, password):
240 req.session.login(db, login, password)
243 "session_id": req.session_id,
244 "uid": req.session._uid,
247 @openerpweb.jsonrequest
248 def sc_list(self, req):
249 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
250 req.session.eval_context(req.context))
252 @openerpweb.jsonrequest
253 def get_lang_list(self, req):
256 'lang_list': (req.session.proxy("db").list_lang() or []),
260 return {"error": e, "title": "Languages"}
262 @openerpweb.jsonrequest
263 def modules(self, req):
264 # TODO query server for installed web modules
266 for name, manifest in openerpweb.addons_manifest.items():
267 if name != 'base' and manifest.get('active', True):
271 @openerpweb.jsonrequest
272 def eval_domain_and_context(self, req, contexts, domains,
274 """ Evaluates sequences of domains and contexts, composing them into
275 a single context, domain or group_by sequence.
277 :param list contexts: list of contexts to merge together. Contexts are
278 evaluated in sequence, all previous contexts
279 are part of their own evaluation context
280 (starting at the session context).
281 :param list domains: list of domains to merge together. Domains are
282 evaluated in sequence and appended to one another
283 (implicit AND), their evaluation domain is the
284 result of merging all contexts.
285 :param list group_by_seq: list of domains (which may be in a different
286 order than the ``contexts`` parameter),
287 evaluated in sequence, their ``'group_by'``
288 key is extracted if they have one.
293 the global context created by merging all of
297 the concatenation of all domains
300 a list of fields to group by, potentially empty (in which case
301 no group by should be performed)
303 context, domain = eval_context_and_domain(req.session,
304 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
305 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
307 group_by_sequence = []
308 for candidate in (group_by_seq or []):
309 ctx = req.session.eval_context(candidate, context)
310 group_by = ctx.get('group_by')
313 elif isinstance(group_by, basestring):
314 group_by_sequence.append(group_by)
316 group_by_sequence.extend(group_by)
321 'group_by': group_by_sequence
324 @openerpweb.jsonrequest
325 def save_session_action(self, req, the_action):
327 This method store an action object in the session object and returns an integer
328 identifying that action. The method get_session_action() can be used to get
331 :param the_action: The action to save in the session.
332 :type the_action: anything
333 :return: A key identifying the saved action.
336 saved_actions = cherrypy.session.get('saved_actions')
337 if not saved_actions:
338 saved_actions = {"next":0, "actions":{}}
339 cherrypy.session['saved_actions'] = saved_actions
340 # we don't allow more than 10 stored actions
341 if len(saved_actions["actions"]) >= 10:
342 del saved_actions["actions"][min(saved_actions["actions"].keys())]
343 key = saved_actions["next"]
344 saved_actions["actions"][key] = the_action
345 saved_actions["next"] = key + 1
348 @openerpweb.jsonrequest
349 def get_session_action(self, req, key):
351 Gets back a previously saved action. This method can return None if the action
352 was saved since too much time (this case should be handled in a smart way).
354 :param key: The key given by save_session_action()
356 :return: The saved action or None.
359 saved_actions = cherrypy.session.get('saved_actions')
360 if not saved_actions:
362 return saved_actions["actions"].get(key)
364 def eval_context_and_domain(session, context, domain=None):
365 e_context = session.eval_context(context)
366 # should we give the evaluated context as an evaluation context to the domain?
367 e_domain = session.eval_domain(domain or [])
369 return e_context, e_domain
371 def load_actions_from_ir_values(req, key, key2, models, meta, context):
372 Values = req.session.model('ir.values')
373 actions = Values.get(key, key2, models, meta, context)
375 return [(id, name, clean_action(action, req.session))
376 for id, name, action in actions]
378 def clean_action(action, session):
379 if action['type'] != 'ir.actions.act_window':
381 # values come from the server, we can just eval them
382 if isinstance(action.get('context', None), basestring):
383 action['context'] = eval(
385 session.evaluation_context()) or {}
387 if isinstance(action.get('domain', None), basestring):
388 action['domain'] = eval(
390 session.evaluation_context(
391 action.get('context', {}))) or []
392 if 'flags' not in action:
393 # Set empty flags dictionary for web client.
394 action['flags'] = dict()
395 return fix_view_modes(action)
397 def generate_views(action):
399 While the server generates a sequence called "views" computing dependencies
400 between a bunch of stuff for views coming directly from the database
401 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
402 to return custom view dictionaries generated on the fly.
404 In that case, there is no ``views`` key available on the action.
406 Since the web client relies on ``action['views']``, generate it here from
407 ``view_mode`` and ``view_id``.
409 Currently handles two different cases:
411 * no view_id, multiple view_mode
412 * single view_id, single view_mode
414 :param dict action: action descriptor dictionary to generate a views key for
416 view_id = action.get('view_id', False)
417 if isinstance(view_id, (list, tuple)):
420 # providing at least one view mode is a requirement, not an option
421 view_modes = action['view_mode'].split(',')
423 if len(view_modes) > 1:
425 raise ValueError('Non-db action dictionaries should provide '
426 'either multiple view modes or a single view '
427 'mode and an optional view id.\n\n Got view '
428 'modes %r and view id %r for action %r' % (
429 view_modes, view_id, action))
430 action['views'] = [(False, mode) for mode in view_modes]
432 action['views'] = [(view_id, view_modes[0])]
434 def fix_view_modes(action):
435 """ For historical reasons, OpenERP has weird dealings in relation to
436 view_mode and the view_type attribute (on window actions):
438 * one of the view modes is ``tree``, which stands for both list views
440 * the choice is made by checking ``view_type``, which is either
441 ``form`` for a list view or ``tree`` for an actual tree view
443 This methods simply folds the view_type into view_mode by adding a
444 new view mode ``list`` which is the result of the ``tree`` view_mode
445 in conjunction with the ``form`` view_type.
447 TODO: this should go into the doc, some kind of "peculiarities" section
449 :param dict action: an action descriptor
450 :returns: nothing, the action is modified in place
452 if 'views' not in action:
453 generate_views(action)
455 if action.pop('view_type') != 'form':
459 [id, mode if mode != 'tree' else 'list']
460 for id, mode in action['views']
465 class Menu(openerpweb.Controller):
466 _cp_path = "/base/menu"
468 @openerpweb.jsonrequest
470 return {'data': self.do_load(req)}
472 def do_load(self, req):
473 """ Loads all menu items (all applications and their sub-menus).
475 :param req: A request object, with an OpenERP session attribute
476 :type req: < session -> OpenERPSession >
477 :return: the menu root
478 :rtype: dict('children': menu_nodes)
480 Menus = req.session.model('ir.ui.menu')
481 # menus are loaded fully unlike a regular tree view, cause there are
482 # less than 512 items
483 context = req.session.eval_context(req.context)
484 menu_ids = Menus.search([], 0, False, False, context)
485 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
486 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
487 menu_items.append(menu_root)
489 # make a tree using parent_id
490 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
491 for menu_item in menu_items:
492 if menu_item['parent_id']:
493 parent = menu_item['parent_id'][0]
496 if parent in menu_items_map:
497 menu_items_map[parent].setdefault(
498 'children', []).append(menu_item)
500 # sort by sequence a tree using parent_id
501 for menu_item in menu_items:
502 menu_item.setdefault('children', []).sort(
503 key=lambda x:x["sequence"])
507 @openerpweb.jsonrequest
508 def action(self, req, menu_id):
509 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
510 [('ir.ui.menu', menu_id)], False,
511 req.session.eval_context(req.context))
512 return {"action": actions}
514 class DataSet(openerpweb.Controller):
515 _cp_path = "/base/dataset"
517 @openerpweb.jsonrequest
518 def fields(self, req, model):
519 return {'fields': req.session.model(model).fields_get(False,
520 req.session.eval_context(req.context))}
522 @openerpweb.jsonrequest
523 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
524 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
525 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
527 """ Performs a search() followed by a read() (if needed) using the
528 provided search criteria
530 :param request: a JSON-RPC request object
531 :type request: openerpweb.JsonRequest
532 :param str model: the name of the model to search on
533 :param fields: a list of the fields to return in the result records
535 :param int offset: from which index should the results start being returned
536 :param int limit: the maximum number of records to return
537 :param list domain: the search domain for the query
538 :param list sort: sorting directives
539 :returns: A structure (dict) with two keys: ids (all the ids matching
540 the (domain, context) pair) and records (paginated records
541 matching fields selection set)
544 Model = request.session.model(model)
545 context, domain = eval_context_and_domain(
546 request.session, request.context, domain)
548 ids = Model.search(domain, 0, False, sort or False, context)
549 # need to fill the dataset with all ids for the (domain, context) pair,
550 # so search un-paginated and paginate manually before reading
551 paginated_ids = ids[offset:(offset + limit if limit else None)]
552 if fields and fields == ['id']:
553 # shortcut read if we only want the ids
556 'records': map(lambda id: {'id': id}, paginated_ids)
559 records = Model.read(paginated_ids, fields or False, context)
560 records.sort(key=lambda obj: ids.index(obj['id']))
567 @openerpweb.jsonrequest
568 def get(self, request, model, ids, fields=False):
569 return self.do_get(request, model, ids, fields)
570 def do_get(self, request, model, ids, fields=False):
571 """ Fetches and returns the records of the model ``model`` whose ids
574 The results are in the same order as the inputs, but elements may be
575 missing (if there is no record left for the id)
577 :param request: the JSON-RPC2 request object
578 :type request: openerpweb.JsonRequest
579 :param model: the model to read from
581 :param ids: a list of identifiers
583 :param fields: a list of fields to fetch, ``False`` or empty to fetch
584 all fields in the model
585 :type fields: list | False
586 :returns: a list of records, in the same order as the list of ids
589 Model = request.session.model(model)
590 records = Model.read(ids, fields, request.session.eval_context(request.context))
592 record_map = dict((record['id'], record) for record in records)
594 return [record_map[id] for id in ids if record_map.get(id)]
596 @openerpweb.jsonrequest
597 def load(self, req, model, id, fields):
598 m = req.session.model(model)
600 r = m.read([id], False, req.session.eval_context(req.context))
603 return {'value': value}
605 @openerpweb.jsonrequest
606 def create(self, req, model, data):
607 m = req.session.model(model)
608 r = m.create(data, req.session.eval_context(req.context))
611 @openerpweb.jsonrequest
612 def save(self, req, model, id, data):
613 m = req.session.model(model)
614 r = m.write([id], data, req.session.eval_context(req.context))
617 @openerpweb.jsonrequest
618 def unlink(self, request, model, ids=()):
619 Model = request.session.model(model)
620 return Model.unlink(ids, request.session.eval_context(request.context))
622 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
623 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
624 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
625 c, d = eval_context_and_domain(req.session, context, domain)
626 if domain_id and len(args) - 1 >= domain_id:
628 if context_id and len(args) - 1 >= context_id:
631 return getattr(req.session.model(model), method)(*args)
633 @openerpweb.jsonrequest
634 def call(self, req, model, method, args, domain_id=None, context_id=None):
635 return self.call_common(req, model, method, args, domain_id, context_id)
637 @openerpweb.jsonrequest
638 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
639 action = self.call_common(req, model, method, args, domain_id, context_id)
640 if isinstance(action, dict) and action.get('type') != '':
641 return {'result': clean_action(action, req.session)}
642 return {'result': False}
644 @openerpweb.jsonrequest
645 def exec_workflow(self, req, model, id, signal):
646 r = req.session.exec_workflow(model, id, signal)
649 @openerpweb.jsonrequest
650 def default_get(self, req, model, fields):
651 Model = req.session.model(model)
652 return Model.default_get(fields, req.session.eval_context(req.context))
654 class DataGroup(openerpweb.Controller):
655 _cp_path = "/base/group"
656 @openerpweb.jsonrequest
657 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
658 Model = request.session.model(model)
659 context, domain = eval_context_and_domain(request.session, request.context, domain)
661 return Model.read_group(
662 domain or [], fields, group_by_fields, 0, False,
663 dict(context, group_by=group_by_fields), sort or False)
665 class View(openerpweb.Controller):
666 _cp_path = "/base/view"
668 def fields_view_get(self, request, model, view_id, view_type,
669 transform=True, toolbar=False, submenu=False):
670 Model = request.session.model(model)
671 context = request.session.eval_context(request.context)
672 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
673 # todo fme?: check that we should pass the evaluated context here
674 self.process_view(request.session, fvg, context, transform)
677 def process_view(self, session, fvg, context, transform):
678 # depending on how it feels, xmlrpclib.ServerProxy can translate
679 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
680 # enjoy unicode strings which can not be trivially converted to
681 # strings, and it blows up during parsing.
683 # So ensure we fix this retardation by converting view xml back to
685 if isinstance(fvg['arch'], unicode):
686 arch = fvg['arch'].encode('utf-8')
691 evaluation_context = session.evaluation_context(context or {})
692 xml = self.transform_view(arch, session, evaluation_context)
694 xml = ElementTree.fromstring(arch)
695 fvg['arch'] = Xml2Json.convert_element(xml)
697 for field in fvg['fields'].itervalues():
698 if field.get('views'):
699 for view in field["views"].itervalues():
700 self.process_view(session, view, None, transform)
701 if field.get('domain'):
702 field["domain"] = self.parse_domain(field["domain"], session)
703 if field.get('context'):
704 field["context"] = self.parse_context(field["context"], session)
706 @openerpweb.jsonrequest
707 def add_custom(self, request, view_id, arch):
708 CustomView = request.session.model('ir.ui.view.custom')
710 'user_id': request.session._uid,
713 }, request.session.eval_context(request.context))
714 return {'result': True}
716 @openerpweb.jsonrequest
717 def undo_custom(self, request, view_id, reset=False):
718 CustomView = request.session.model('ir.ui.view.custom')
719 context = request.session.eval_context(request.context)
720 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
721 0, False, False, context)
724 CustomView.unlink(vcustom, context)
726 CustomView.unlink([vcustom[0]], context)
727 return {'result': True}
728 return {'result': False}
730 def transform_view(self, view_string, session, context=None):
731 # transform nodes on the fly via iterparse, instead of
732 # doing it statically on the parsing result
733 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
735 for event, elem in parser:
739 self.parse_domains_and_contexts(elem, session)
742 def parse_domain(self, domain, session):
743 """ Parses an arbitrary string containing a domain, transforms it
744 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
746 :param domain: the domain to parse, if the domain is not a string it is assumed to
747 be a literal domain and is returned as-is
748 :param session: Current OpenERP session
749 :type session: openerpweb.openerpweb.OpenERPSession
751 if not isinstance(domain, (str, unicode)):
754 return openerpweb.ast.literal_eval(domain)
757 return openerpweb.nonliterals.Domain(session, domain)
759 def parse_context(self, context, session):
760 """ Parses an arbitrary string containing a context, transforms it
761 to either a literal context or a :class:`openerpweb.nonliterals.Context`
763 :param context: the context to parse, if the context is not a string it is assumed to
764 be a literal domain and is returned as-is
765 :param session: Current OpenERP session
766 :type session: openerpweb.openerpweb.OpenERPSession
768 if not isinstance(context, (str, unicode)):
771 return openerpweb.ast.literal_eval(context)
773 return openerpweb.nonliterals.Context(session, context)
775 def parse_domains_and_contexts(self, elem, session):
776 """ Converts domains and contexts from the view into Python objects,
777 either literals if they can be parsed by literal_eval or a special
778 placeholder object if the domain or context refers to free variables.
780 :param elem: the current node being parsed
781 :type param: xml.etree.ElementTree.Element
782 :param session: OpenERP session object, used to store and retrieve
784 :type session: openerpweb.openerpweb.OpenERPSession
786 for el in ['domain', 'filter_domain']:
787 domain = elem.get(el, '').strip()
789 elem.set(el, self.parse_domain(domain, session))
790 for el in ['context', 'default_get']:
791 context_string = elem.get(el, '').strip()
793 elem.set(el, self.parse_context(context_string, session))
795 class FormView(View):
796 _cp_path = "/base/formview"
798 @openerpweb.jsonrequest
799 def load(self, req, model, view_id, toolbar=False):
800 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
801 return {'fields_view': fields_view}
803 class ListView(View):
804 _cp_path = "/base/listview"
806 @openerpweb.jsonrequest
807 def load(self, req, model, view_id, toolbar=False):
808 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
809 return {'fields_view': fields_view}
811 def process_colors(self, view, row, context):
812 colors = view['arch']['attrs'].get('colors')
819 for pair in colors.split(';')
820 if eval(pair.split(':')[1], dict(context, **row))
825 elif len(color) == 1:
829 class SearchView(View):
830 _cp_path = "/base/searchview"
832 @openerpweb.jsonrequest
833 def load(self, req, model, view_id):
834 fields_view = self.fields_view_get(req, model, view_id, 'search')
835 return {'fields_view': fields_view}
837 @openerpweb.jsonrequest
838 def fields_get(self, req, model):
839 Model = req.session.model(model)
840 fields = Model.fields_get(False, req.session.eval_context(req.context))
841 for field in fields.values():
842 # shouldn't convert the views too?
843 if field.get('domain'):
844 field["domain"] = self.parse_domain(field["domain"], req.session)
845 if field.get('context'):
846 field["context"] = self.parse_domain(field["context"], req.session)
847 return {'fields': fields}
849 @openerpweb.jsonrequest
850 def get_filters(self, req, model):
851 Model = req.session.model("ir.filters")
852 filters = Model.get_filters(model)
853 for filter in filters:
854 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
855 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
858 @openerpweb.jsonrequest
859 def save_filter(self, req, model, name, context_to_save, domain):
860 Model = req.session.model("ir.filters")
861 ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
862 ctx.session = req.session
864 domain = openerpweb.nonliterals.CompoundDomain(domain)
865 domain.session = req.session
866 domain = domain.evaluate()
867 uid = req.session._uid
868 context = req.session.eval_context(req.context)
869 to_return = Model.create_or_replace({"context": ctx,
877 class Binary(openerpweb.Controller):
878 _cp_path = "/base/binary"
880 @openerpweb.httprequest
881 def image(self, request, session_id, model, id, field, **kw):
882 cherrypy.response.headers['Content-Type'] = 'image/png'
883 Model = request.session.model(model)
884 context = request.session.eval_context(request.context)
887 res = Model.default_get([field], context).get(field, '')
889 res = Model.read([int(id)], [field], context)[0].get(field, '')
890 return base64.decodestring(res)
891 except: # TODO: what's the exception here?
892 return self.placeholder()
893 def placeholder(self):
894 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
896 @openerpweb.httprequest
897 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
898 Model = request.session.model(model)
899 context = request.session.eval_context(request.context)
900 res = Model.read([int(id)], [field, fieldname], context)[0]
901 filecontent = res.get(field, '')
903 raise cherrypy.NotFound
905 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
906 filename = '%s_%s' % (model.replace('.', '_'), id)
908 filename = res.get(fieldname, '') or filename
909 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
910 return base64.decodestring(filecontent)
912 @openerpweb.httprequest
913 def upload(self, request, session_id, callback, ufile=None):
914 cherrypy.response.timeout = 500
916 for key, val in cherrypy.request.headers.iteritems():
917 headers[key.lower()] = val
918 size = int(headers.get('content-length', 0))
919 # TODO: might be useful to have a configuration flag for max-length file uploads
921 out = """<script language="javascript" type="text/javascript">
922 var win = window.top.window,
924 if (typeof(callback) === 'function') {
925 callback.apply(this, %s);
927 win.jQuery('#oe_notification', win.document).notify('create', {
928 title: "Ajax File Upload",
929 text: "Could not find callback"
933 data = ufile.file.read()
934 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
936 args = [False, e.message]
937 return out % (simplejson.dumps(callback), simplejson.dumps(args))
939 @openerpweb.httprequest
940 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
941 cherrypy.response.timeout = 500
942 context = request.session.eval_context(request.context)
943 Model = request.session.model('ir.attachment')
945 out = """<script language="javascript" type="text/javascript">
946 var win = window.top.window,
948 if (typeof(callback) === 'function') {
949 callback.call(this, %s);
952 attachment_id = Model.create({
953 'name': ufile.filename,
954 'datas': base64.encodestring(ufile.file.read()),
959 'filename': ufile.filename,
963 args = { 'error': e.message }
964 return out % (simplejson.dumps(callback), simplejson.dumps(args))
966 class Action(openerpweb.Controller):
967 _cp_path = "/base/action"
969 @openerpweb.jsonrequest
970 def load(self, req, action_id):
971 Actions = req.session.model('ir.actions.actions')
973 context = req.session.eval_context(req.context)
974 action_type = Actions.read([action_id], ['type'], context)
976 action = req.session.model(action_type[0]['type']).read([action_id], False,
979 value = clean_action(action[0], req.session)
980 return {'result': value}
982 @openerpweb.jsonrequest
983 def run(self, req, action_id):
984 return clean_action(req.session.model('ir.actions.server').run(
985 [action_id], req.session.eval_context(req.context)), req.session)