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.httprequest
210 def restore_db(self, req, db_file, restore_pwd, new_db):
213 data = base64.encodestring(db_file.file.read())
214 response = simplejson.dumps(
215 req.session.proxy("db").restore(restore_pwd, new_db, data))
216 except xmlrpclib.Fault, e:
217 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
218 response = simplejson.dumps({'error': e.faultCode, 'title': 'Restore Database'})
220 response = simplejson.dumps({'error': 'Could not restore database !', 'title': 'Restore Database'})
222 cherrypy.response.headers['Content-Type'] = 'application/json'
223 cherrypy.response.headers['Content-Length'] = len(response)
226 @openerpweb.jsonrequest
227 def change_password_db(self, req, fields):
228 old_password, new_password = operator.itemgetter(
229 'old_pwd', 'new_pwd')(
230 dict(map(operator.itemgetter('name', 'value'), fields)))
232 return req.session.proxy("db").change_admin_password(old_password, new_password)
233 except xmlrpclib.Fault, e:
234 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
235 return {'error': e.faultCode, 'title': 'Change Password'}
236 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
238 class Session(openerpweb.Controller):
239 _cp_path = "/base/session"
241 @openerpweb.jsonrequest
242 def login(self, req, db, login, password):
243 req.session.login(db, login, password)
246 "session_id": req.session_id,
247 "uid": req.session._uid,
250 @openerpweb.jsonrequest
251 def sc_list(self, req):
252 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
253 req.session.eval_context(req.context))
255 @openerpweb.jsonrequest
256 def get_lang_list(self, req):
259 'lang_list': (req.session.proxy("db").list_lang() or []),
263 return {"error": e, "title": "Languages"}
265 @openerpweb.jsonrequest
266 def modules(self, req):
267 # TODO query server for installed web modules
269 for name, manifest in openerpweb.addons_manifest.items():
270 if name != 'base' and manifest.get('active', True):
274 @openerpweb.jsonrequest
275 def eval_domain_and_context(self, req, contexts, domains,
277 """ Evaluates sequences of domains and contexts, composing them into
278 a single context, domain or group_by sequence.
280 :param list contexts: list of contexts to merge together. Contexts are
281 evaluated in sequence, all previous contexts
282 are part of their own evaluation context
283 (starting at the session context).
284 :param list domains: list of domains to merge together. Domains are
285 evaluated in sequence and appended to one another
286 (implicit AND), their evaluation domain is the
287 result of merging all contexts.
288 :param list group_by_seq: list of domains (which may be in a different
289 order than the ``contexts`` parameter),
290 evaluated in sequence, their ``'group_by'``
291 key is extracted if they have one.
296 the global context created by merging all of
300 the concatenation of all domains
303 a list of fields to group by, potentially empty (in which case
304 no group by should be performed)
306 context, domain = eval_context_and_domain(req.session,
307 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
308 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
310 group_by_sequence = []
311 for candidate in (group_by_seq or []):
312 ctx = req.session.eval_context(candidate, context)
313 group_by = ctx.get('group_by')
316 elif isinstance(group_by, basestring):
317 group_by_sequence.append(group_by)
319 group_by_sequence.extend(group_by)
324 'group_by': group_by_sequence
327 @openerpweb.jsonrequest
328 def save_session_action(self, req, the_action):
330 This method store an action object in the session object and returns an integer
331 identifying that action. The method get_session_action() can be used to get
334 :param the_action: The action to save in the session.
335 :type the_action: anything
336 :return: A key identifying the saved action.
339 saved_actions = cherrypy.session.get('saved_actions')
340 if not saved_actions:
341 saved_actions = {"next":0, "actions":{}}
342 cherrypy.session['saved_actions'] = saved_actions
343 # we don't allow more than 10 stored actions
344 if len(saved_actions["actions"]) >= 10:
345 del saved_actions["actions"][min(saved_actions["actions"].keys())]
346 key = saved_actions["next"]
347 saved_actions["actions"][key] = the_action
348 saved_actions["next"] = key + 1
351 @openerpweb.jsonrequest
352 def get_session_action(self, req, key):
354 Gets back a previously saved action. This method can return None if the action
355 was saved since too much time (this case should be handled in a smart way).
357 :param key: The key given by save_session_action()
359 :return: The saved action or None.
362 saved_actions = cherrypy.session.get('saved_actions')
363 if not saved_actions:
365 return saved_actions["actions"].get(key)
367 def eval_context_and_domain(session, context, domain=None):
368 e_context = session.eval_context(context)
369 # should we give the evaluated context as an evaluation context to the domain?
370 e_domain = session.eval_domain(domain or [])
372 return e_context, e_domain
374 def load_actions_from_ir_values(req, key, key2, models, meta, context):
375 Values = req.session.model('ir.values')
376 actions = Values.get(key, key2, models, meta, context)
378 return [(id, name, clean_action(action, req.session))
379 for id, name, action in actions]
381 def clean_action(action, session):
382 if action['type'] != 'ir.actions.act_window':
384 # values come from the server, we can just eval them
385 if isinstance(action.get('context', None), basestring):
386 action['context'] = eval(
388 session.evaluation_context()) or {}
390 if isinstance(action.get('domain', None), basestring):
391 action['domain'] = eval(
393 session.evaluation_context(
394 action.get('context', {}))) or []
395 if 'flags' not in action:
396 # Set empty flags dictionary for web client.
397 action['flags'] = dict()
398 return fix_view_modes(action)
400 def generate_views(action):
402 While the server generates a sequence called "views" computing dependencies
403 between a bunch of stuff for views coming directly from the database
404 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
405 to return custom view dictionaries generated on the fly.
407 In that case, there is no ``views`` key available on the action.
409 Since the web client relies on ``action['views']``, generate it here from
410 ``view_mode`` and ``view_id``.
412 Currently handles two different cases:
414 * no view_id, multiple view_mode
415 * single view_id, single view_mode
417 :param dict action: action descriptor dictionary to generate a views key for
419 view_id = action.get('view_id', False)
420 if isinstance(view_id, (list, tuple)):
423 # providing at least one view mode is a requirement, not an option
424 view_modes = action['view_mode'].split(',')
426 if len(view_modes) > 1:
428 raise ValueError('Non-db action dictionaries should provide '
429 'either multiple view modes or a single view '
430 'mode and an optional view id.\n\n Got view '
431 'modes %r and view id %r for action %r' % (
432 view_modes, view_id, action))
433 action['views'] = [(False, mode) for mode in view_modes]
435 action['views'] = [(view_id, view_modes[0])]
437 def fix_view_modes(action):
438 """ For historical reasons, OpenERP has weird dealings in relation to
439 view_mode and the view_type attribute (on window actions):
441 * one of the view modes is ``tree``, which stands for both list views
443 * the choice is made by checking ``view_type``, which is either
444 ``form`` for a list view or ``tree`` for an actual tree view
446 This methods simply folds the view_type into view_mode by adding a
447 new view mode ``list`` which is the result of the ``tree`` view_mode
448 in conjunction with the ``form`` view_type.
450 TODO: this should go into the doc, some kind of "peculiarities" section
452 :param dict action: an action descriptor
453 :returns: nothing, the action is modified in place
455 if 'views' not in action:
456 generate_views(action)
458 if action.pop('view_type') != 'form':
462 [id, mode if mode != 'tree' else 'list']
463 for id, mode in action['views']
468 class Menu(openerpweb.Controller):
469 _cp_path = "/base/menu"
471 @openerpweb.jsonrequest
473 return {'data': self.do_load(req)}
475 def do_load(self, req):
476 """ Loads all menu items (all applications and their sub-menus).
478 :param req: A request object, with an OpenERP session attribute
479 :type req: < session -> OpenERPSession >
480 :return: the menu root
481 :rtype: dict('children': menu_nodes)
483 Menus = req.session.model('ir.ui.menu')
484 # menus are loaded fully unlike a regular tree view, cause there are
485 # less than 512 items
486 context = req.session.eval_context(req.context)
487 menu_ids = Menus.search([], 0, False, False, context)
488 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
489 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
490 menu_items.append(menu_root)
492 # make a tree using parent_id
493 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
494 for menu_item in menu_items:
495 if menu_item['parent_id']:
496 parent = menu_item['parent_id'][0]
499 if parent in menu_items_map:
500 menu_items_map[parent].setdefault(
501 'children', []).append(menu_item)
503 # sort by sequence a tree using parent_id
504 for menu_item in menu_items:
505 menu_item.setdefault('children', []).sort(
506 key=lambda x:x["sequence"])
510 @openerpweb.jsonrequest
511 def action(self, req, menu_id):
512 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
513 [('ir.ui.menu', menu_id)], False,
514 req.session.eval_context(req.context))
515 return {"action": actions}
517 class DataSet(openerpweb.Controller):
518 _cp_path = "/base/dataset"
520 @openerpweb.jsonrequest
521 def fields(self, req, model):
522 return {'fields': req.session.model(model).fields_get(False,
523 req.session.eval_context(req.context))}
525 @openerpweb.jsonrequest
526 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
527 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
528 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
530 """ Performs a search() followed by a read() (if needed) using the
531 provided search criteria
533 :param request: a JSON-RPC request object
534 :type request: openerpweb.JsonRequest
535 :param str model: the name of the model to search on
536 :param fields: a list of the fields to return in the result records
538 :param int offset: from which index should the results start being returned
539 :param int limit: the maximum number of records to return
540 :param list domain: the search domain for the query
541 :param list sort: sorting directives
542 :returns: A structure (dict) with two keys: ids (all the ids matching
543 the (domain, context) pair) and records (paginated records
544 matching fields selection set)
547 Model = request.session.model(model)
548 context, domain = eval_context_and_domain(
549 request.session, request.context, domain)
551 ids = Model.search(domain, 0, False, sort or False, context)
552 # need to fill the dataset with all ids for the (domain, context) pair,
553 # so search un-paginated and paginate manually before reading
554 paginated_ids = ids[offset:(offset + limit if limit else None)]
555 if fields and fields == ['id']:
556 # shortcut read if we only want the ids
559 'records': map(lambda id: {'id': id}, paginated_ids)
562 records = Model.read(paginated_ids, fields or False, context)
563 records.sort(key=lambda obj: ids.index(obj['id']))
570 @openerpweb.jsonrequest
571 def get(self, request, model, ids, fields=False):
572 return self.do_get(request, model, ids, fields)
573 def do_get(self, request, model, ids, fields=False):
574 """ Fetches and returns the records of the model ``model`` whose ids
577 The results are in the same order as the inputs, but elements may be
578 missing (if there is no record left for the id)
580 :param request: the JSON-RPC2 request object
581 :type request: openerpweb.JsonRequest
582 :param model: the model to read from
584 :param ids: a list of identifiers
586 :param fields: a list of fields to fetch, ``False`` or empty to fetch
587 all fields in the model
588 :type fields: list | False
589 :returns: a list of records, in the same order as the list of ids
592 Model = request.session.model(model)
593 records = Model.read(ids, fields, request.session.eval_context(request.context))
595 record_map = dict((record['id'], record) for record in records)
597 return [record_map[id] for id in ids if record_map.get(id)]
599 @openerpweb.jsonrequest
600 def load(self, req, model, id, fields):
601 m = req.session.model(model)
603 r = m.read([id], False, req.session.eval_context(req.context))
606 return {'value': value}
608 @openerpweb.jsonrequest
609 def create(self, req, model, data):
610 m = req.session.model(model)
611 r = m.create(data, req.session.eval_context(req.context))
614 @openerpweb.jsonrequest
615 def save(self, req, model, id, data):
616 m = req.session.model(model)
617 r = m.write([id], data, req.session.eval_context(req.context))
620 @openerpweb.jsonrequest
621 def unlink(self, request, model, ids=()):
622 Model = request.session.model(model)
623 return Model.unlink(ids, request.session.eval_context(request.context))
625 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
626 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
627 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
628 c, d = eval_context_and_domain(req.session, context, domain)
629 if domain_id and len(args) - 1 >= domain_id:
631 if context_id and len(args) - 1 >= context_id:
634 return getattr(req.session.model(model), method)(*args)
636 @openerpweb.jsonrequest
637 def call(self, req, model, method, args, domain_id=None, context_id=None):
638 return self.call_common(req, model, method, args, domain_id, context_id)
640 @openerpweb.jsonrequest
641 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
642 action = self.call_common(req, model, method, args, domain_id, context_id)
643 if isinstance(action, dict) and action.get('type') != '':
644 return {'result': clean_action(action, req.session)}
645 return {'result': False}
647 @openerpweb.jsonrequest
648 def exec_workflow(self, req, model, id, signal):
649 r = req.session.exec_workflow(model, id, signal)
652 @openerpweb.jsonrequest
653 def default_get(self, req, model, fields):
654 Model = req.session.model(model)
655 return Model.default_get(fields, req.session.eval_context(req.context))
657 class DataGroup(openerpweb.Controller):
658 _cp_path = "/base/group"
659 @openerpweb.jsonrequest
660 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
661 Model = request.session.model(model)
662 context, domain = eval_context_and_domain(request.session, request.context, domain)
664 return Model.read_group(
665 domain or [], fields, group_by_fields, 0, False,
666 dict(context, group_by=group_by_fields), sort or False)
668 class View(openerpweb.Controller):
669 _cp_path = "/base/view"
671 def fields_view_get(self, request, model, view_id, view_type,
672 transform=True, toolbar=False, submenu=False):
673 Model = request.session.model(model)
674 context = request.session.eval_context(request.context)
675 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
676 # todo fme?: check that we should pass the evaluated context here
677 self.process_view(request.session, fvg, context, transform)
680 def process_view(self, session, fvg, context, transform):
681 # depending on how it feels, xmlrpclib.ServerProxy can translate
682 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
683 # enjoy unicode strings which can not be trivially converted to
684 # strings, and it blows up during parsing.
686 # So ensure we fix this retardation by converting view xml back to
688 if isinstance(fvg['arch'], unicode):
689 arch = fvg['arch'].encode('utf-8')
694 evaluation_context = session.evaluation_context(context or {})
695 xml = self.transform_view(arch, session, evaluation_context)
697 xml = ElementTree.fromstring(arch)
698 fvg['arch'] = Xml2Json.convert_element(xml)
700 for field in fvg['fields'].itervalues():
701 if field.get('views'):
702 for view in field["views"].itervalues():
703 self.process_view(session, view, None, transform)
704 if field.get('domain'):
705 field["domain"] = self.parse_domain(field["domain"], session)
706 if field.get('context'):
707 field["context"] = self.parse_context(field["context"], session)
709 @openerpweb.jsonrequest
710 def add_custom(self, request, view_id, arch):
711 CustomView = request.session.model('ir.ui.view.custom')
713 'user_id': request.session._uid,
716 }, request.session.eval_context(request.context))
717 return {'result': True}
719 @openerpweb.jsonrequest
720 def undo_custom(self, request, view_id, reset=False):
721 CustomView = request.session.model('ir.ui.view.custom')
722 context = request.session.eval_context(request.context)
723 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
724 0, False, False, context)
727 CustomView.unlink(vcustom, context)
729 CustomView.unlink([vcustom[0]], context)
730 return {'result': True}
731 return {'result': False}
733 def transform_view(self, view_string, session, context=None):
734 # transform nodes on the fly via iterparse, instead of
735 # doing it statically on the parsing result
736 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
738 for event, elem in parser:
742 self.parse_domains_and_contexts(elem, session)
745 def parse_domain(self, domain, session):
746 """ Parses an arbitrary string containing a domain, transforms it
747 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
749 :param domain: the domain to parse, if the domain is not a string it is assumed to
750 be a literal domain and is returned as-is
751 :param session: Current OpenERP session
752 :type session: openerpweb.openerpweb.OpenERPSession
754 if not isinstance(domain, (str, unicode)):
757 return openerpweb.ast.literal_eval(domain)
760 return openerpweb.nonliterals.Domain(session, domain)
762 def parse_context(self, context, session):
763 """ Parses an arbitrary string containing a context, transforms it
764 to either a literal context or a :class:`openerpweb.nonliterals.Context`
766 :param context: the context to parse, if the context is not a string it is assumed to
767 be a literal domain and is returned as-is
768 :param session: Current OpenERP session
769 :type session: openerpweb.openerpweb.OpenERPSession
771 if not isinstance(context, (str, unicode)):
774 return openerpweb.ast.literal_eval(context)
776 return openerpweb.nonliterals.Context(session, context)
778 def parse_domains_and_contexts(self, elem, session):
779 """ Converts domains and contexts from the view into Python objects,
780 either literals if they can be parsed by literal_eval or a special
781 placeholder object if the domain or context refers to free variables.
783 :param elem: the current node being parsed
784 :type param: xml.etree.ElementTree.Element
785 :param session: OpenERP session object, used to store and retrieve
787 :type session: openerpweb.openerpweb.OpenERPSession
789 for el in ['domain', 'filter_domain']:
790 domain = elem.get(el, '').strip()
792 elem.set(el, self.parse_domain(domain, session))
793 for el in ['context', 'default_get']:
794 context_string = elem.get(el, '').strip()
796 elem.set(el, self.parse_context(context_string, session))
798 class FormView(View):
799 _cp_path = "/base/formview"
801 @openerpweb.jsonrequest
802 def load(self, req, model, view_id, toolbar=False):
803 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
804 return {'fields_view': fields_view}
806 class ListView(View):
807 _cp_path = "/base/listview"
809 @openerpweb.jsonrequest
810 def load(self, req, model, view_id, toolbar=False):
811 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
812 return {'fields_view': fields_view}
814 def process_colors(self, view, row, context):
815 colors = view['arch']['attrs'].get('colors')
822 for pair in colors.split(';')
823 if eval(pair.split(':')[1], dict(context, **row))
828 elif len(color) == 1:
832 class SearchView(View):
833 _cp_path = "/base/searchview"
835 @openerpweb.jsonrequest
836 def load(self, req, model, view_id):
837 fields_view = self.fields_view_get(req, model, view_id, 'search')
838 return {'fields_view': fields_view}
840 @openerpweb.jsonrequest
841 def fields_get(self, req, model):
842 Model = req.session.model(model)
843 fields = Model.fields_get(False, req.session.eval_context(req.context))
844 for field in fields.values():
845 # shouldn't convert the views too?
846 if field.get('domain'):
847 field["domain"] = self.parse_domain(field["domain"], req.session)
848 if field.get('context'):
849 field["context"] = self.parse_domain(field["context"], req.session)
850 return {'fields': fields}
852 @openerpweb.jsonrequest
853 def get_filters(self, req, model):
854 Model = req.session.model("ir.filters")
855 filters = Model.get_filters(model)
856 for filter in filters:
857 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
858 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
861 @openerpweb.jsonrequest
862 def save_filter(self, req, model, name, context_to_save, domain):
863 Model = req.session.model("ir.filters")
864 ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
865 ctx.session = req.session
867 domain = openerpweb.nonliterals.CompoundDomain(domain)
868 domain.session = req.session
869 domain = domain.evaluate()
870 uid = req.session._uid
871 context = req.session.eval_context(req.context)
872 to_return = Model.create_or_replace({"context": ctx,
880 class Binary(openerpweb.Controller):
881 _cp_path = "/base/binary"
883 @openerpweb.httprequest
884 def image(self, request, session_id, model, id, field, **kw):
885 cherrypy.response.headers['Content-Type'] = 'image/png'
886 Model = request.session.model(model)
887 context = request.session.eval_context(request.context)
890 res = Model.default_get([field], context).get(field, '')
892 res = Model.read([int(id)], [field], context)[0].get(field, '')
893 return base64.decodestring(res)
894 except: # TODO: what's the exception here?
895 return self.placeholder()
896 def placeholder(self):
897 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
899 @openerpweb.httprequest
900 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
901 Model = request.session.model(model)
902 context = request.session.eval_context(request.context)
903 res = Model.read([int(id)], [field, fieldname], context)[0]
904 filecontent = res.get(field, '')
906 raise cherrypy.NotFound
908 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
909 filename = '%s_%s' % (model.replace('.', '_'), id)
911 filename = res.get(fieldname, '') or filename
912 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
913 return base64.decodestring(filecontent)
915 @openerpweb.httprequest
916 def upload(self, request, session_id, callback, ufile=None):
917 cherrypy.response.timeout = 500
919 for key, val in cherrypy.request.headers.iteritems():
920 headers[key.lower()] = val
921 size = int(headers.get('content-length', 0))
922 # TODO: might be useful to have a configuration flag for max-length file uploads
924 out = """<script language="javascript" type="text/javascript">
925 var win = window.top.window,
927 if (typeof(callback) === 'function') {
928 callback.apply(this, %s);
930 win.jQuery('#oe_notification', win.document).notify('create', {
931 title: "Ajax File Upload",
932 text: "Could not find callback"
936 data = ufile.file.read()
937 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
939 args = [False, e.message]
940 return out % (simplejson.dumps(callback), simplejson.dumps(args))
942 @openerpweb.httprequest
943 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
944 cherrypy.response.timeout = 500
945 context = request.session.eval_context(request.context)
946 Model = request.session.model('ir.attachment')
948 out = """<script language="javascript" type="text/javascript">
949 var win = window.top.window,
951 if (typeof(callback) === 'function') {
952 callback.call(this, %s);
955 attachment_id = Model.create({
956 'name': ufile.filename,
957 'datas': base64.encodestring(ufile.file.read()),
962 'filename': ufile.filename,
966 args = { 'error': e.message }
967 return out % (simplejson.dumps(callback), simplejson.dumps(args))
969 class Action(openerpweb.Controller):
970 _cp_path = "/base/action"
972 @openerpweb.jsonrequest
973 def load(self, req, action_id):
974 Actions = req.session.model('ir.actions.actions')
976 context = req.session.eval_context(req.context)
977 action_type = Actions.read([action_id], ['type'], context)
979 action = req.session.model(action_type[0]['type']).read([action_id], False,
982 value = clean_action(action[0], req.session)
983 return {'result': value}
985 @openerpweb.jsonrequest
986 def run(self, req, action_id):
987 return clean_action(req.session.model('ir.actions.server').run(
988 [action_id], req.session.eval_context(req.context)), req.session)