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_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 progress(self, req, password, id):
168 return req.session.proxy('db').get_progress(password, id)
170 @openerpweb.jsonrequest
171 def create(self, req, fields):
173 params = dict(map(operator.itemgetter('name', 'value'), fields))
174 create_attrs = operator.itemgetter(
175 'super_admin_pwd', 'db_name', 'demo_data', 'db_lang', 'create_admin_pwd')(
179 return req.session.proxy("db").create(*create_attrs)
180 except xmlrpclib.Fault, e:
181 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
182 return {'error': e.faultCode, 'title': 'Create Database'}
183 return {'error': 'Could not create database !', 'title': 'Create Database'}
185 @openerpweb.jsonrequest
186 def drop(self, req, fields):
187 password, db = operator.itemgetter(
188 'drop_pwd', 'drop_db')(
189 dict(map(operator.itemgetter('name', 'value'), fields)))
192 return req.session.proxy("db").drop(password, db)
193 except xmlrpclib.Fault, e:
194 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
195 return {'error': e.faultCode, 'title': 'Drop Database'}
196 return {'error': 'Could not drop database !', 'title': 'Drop Database'}
198 @openerpweb.httprequest
199 def backup(self, req, backup_db, backup_pwd, token):
201 db_dump = base64.decodestring(
202 req.session.proxy("db").dump(backup_pwd, backup_db))
203 cherrypy.response.headers['Content-Type'] = "application/octet-stream; charset=binary"
204 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"'
205 cherrypy.response.cookie['fileToken'] = token
206 cherrypy.response.cookie['fileToken']['path'] = '/'
208 except xmlrpclib.Fault, e:
209 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
210 return {'error': e.faultCode, 'title': 'Backup Database'}
211 return {'error': 'Could not drop database !', 'title': 'Backup Database'}
213 @openerpweb.httprequest
214 def restore(self, req, db_file, restore_pwd, new_db):
216 data = base64.encodestring(db_file.file.read())
217 req.session.proxy("db").restore(restore_pwd, new_db, data)
219 except xmlrpclib.Fault, e:
220 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
221 raise cherrypy.HTTPError(403)
223 raise cherrypy.HTTPError()
225 @openerpweb.jsonrequest
226 def change_password(self, req, fields):
227 old_password, new_password = operator.itemgetter(
228 'old_pwd', 'new_pwd')(
229 dict(map(operator.itemgetter('name', 'value'), fields)))
231 return req.session.proxy("db").change_admin_password(old_password, new_password)
232 except xmlrpclib.Fault, e:
233 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
234 return {'error': e.faultCode, 'title': 'Change Password'}
235 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
237 class Session(openerpweb.Controller):
238 _cp_path = "/base/session"
240 @openerpweb.jsonrequest
241 def login(self, req, db, login, password):
242 req.session.login(db, login, password)
245 "session_id": req.session_id,
246 "uid": req.session._uid,
249 @openerpweb.jsonrequest
250 def sc_list(self, req):
251 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
252 req.session.eval_context(req.context))
254 @openerpweb.jsonrequest
255 def get_lang_list(self, req):
258 'lang_list': (req.session.proxy("db").list_lang() or []),
262 return {"error": e, "title": "Languages"}
264 @openerpweb.jsonrequest
265 def modules(self, req):
266 # TODO query server for installed web modules
268 for name, manifest in openerpweb.addons_manifest.items():
269 if name != 'base' and manifest.get('active', True):
273 @openerpweb.jsonrequest
274 def eval_domain_and_context(self, req, contexts, domains,
276 """ Evaluates sequences of domains and contexts, composing them into
277 a single context, domain or group_by sequence.
279 :param list contexts: list of contexts to merge together. Contexts are
280 evaluated in sequence, all previous contexts
281 are part of their own evaluation context
282 (starting at the session context).
283 :param list domains: list of domains to merge together. Domains are
284 evaluated in sequence and appended to one another
285 (implicit AND), their evaluation domain is the
286 result of merging all contexts.
287 :param list group_by_seq: list of domains (which may be in a different
288 order than the ``contexts`` parameter),
289 evaluated in sequence, their ``'group_by'``
290 key is extracted if they have one.
295 the global context created by merging all of
299 the concatenation of all domains
302 a list of fields to group by, potentially empty (in which case
303 no group by should be performed)
305 context, domain = eval_context_and_domain(req.session,
306 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
307 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
309 group_by_sequence = []
310 for candidate in (group_by_seq or []):
311 ctx = req.session.eval_context(candidate, context)
312 group_by = ctx.get('group_by')
315 elif isinstance(group_by, basestring):
316 group_by_sequence.append(group_by)
318 group_by_sequence.extend(group_by)
323 'group_by': group_by_sequence
326 @openerpweb.jsonrequest
327 def save_session_action(self, req, the_action):
329 This method store an action object in the session object and returns an integer
330 identifying that action. The method get_session_action() can be used to get
333 :param the_action: The action to save in the session.
334 :type the_action: anything
335 :return: A key identifying the saved action.
338 saved_actions = cherrypy.session.get('saved_actions')
339 if not saved_actions:
340 saved_actions = {"next":0, "actions":{}}
341 cherrypy.session['saved_actions'] = saved_actions
342 # we don't allow more than 10 stored actions
343 if len(saved_actions["actions"]) >= 10:
344 del saved_actions["actions"][min(saved_actions["actions"].keys())]
345 key = saved_actions["next"]
346 saved_actions["actions"][key] = the_action
347 saved_actions["next"] = key + 1
350 @openerpweb.jsonrequest
351 def get_session_action(self, req, key):
353 Gets back a previously saved action. This method can return None if the action
354 was saved since too much time (this case should be handled in a smart way).
356 :param key: The key given by save_session_action()
358 :return: The saved action or None.
361 saved_actions = cherrypy.session.get('saved_actions')
362 if not saved_actions:
364 return saved_actions["actions"].get(key)
366 def eval_context_and_domain(session, context, domain=None):
367 e_context = session.eval_context(context)
368 # should we give the evaluated context as an evaluation context to the domain?
369 e_domain = session.eval_domain(domain or [])
371 return e_context, e_domain
373 def load_actions_from_ir_values(req, key, key2, models, meta, context):
374 Values = req.session.model('ir.values')
375 actions = Values.get(key, key2, models, meta, context)
377 return [(id, name, clean_action(action, req.session))
378 for id, name, action in actions]
380 def clean_action(action, session):
381 if action['type'] != 'ir.actions.act_window':
383 # values come from the server, we can just eval them
384 if isinstance(action.get('context', None), basestring):
385 action['context'] = eval(
387 session.evaluation_context()) or {}
389 if isinstance(action.get('domain', None), basestring):
390 action['domain'] = eval(
392 session.evaluation_context(
393 action.get('context', {}))) or []
394 if 'flags' not in action:
395 # Set empty flags dictionary for web client.
396 action['flags'] = dict()
397 return fix_view_modes(action)
399 def generate_views(action):
401 While the server generates a sequence called "views" computing dependencies
402 between a bunch of stuff for views coming directly from the database
403 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
404 to return custom view dictionaries generated on the fly.
406 In that case, there is no ``views`` key available on the action.
408 Since the web client relies on ``action['views']``, generate it here from
409 ``view_mode`` and ``view_id``.
411 Currently handles two different cases:
413 * no view_id, multiple view_mode
414 * single view_id, single view_mode
416 :param dict action: action descriptor dictionary to generate a views key for
418 view_id = action.get('view_id', False)
419 if isinstance(view_id, (list, tuple)):
422 # providing at least one view mode is a requirement, not an option
423 view_modes = action['view_mode'].split(',')
425 if len(view_modes) > 1:
427 raise ValueError('Non-db action dictionaries should provide '
428 'either multiple view modes or a single view '
429 'mode and an optional view id.\n\n Got view '
430 'modes %r and view id %r for action %r' % (
431 view_modes, view_id, action))
432 action['views'] = [(False, mode) for mode in view_modes]
434 action['views'] = [(view_id, view_modes[0])]
436 def fix_view_modes(action):
437 """ For historical reasons, OpenERP has weird dealings in relation to
438 view_mode and the view_type attribute (on window actions):
440 * one of the view modes is ``tree``, which stands for both list views
442 * the choice is made by checking ``view_type``, which is either
443 ``form`` for a list view or ``tree`` for an actual tree view
445 This methods simply folds the view_type into view_mode by adding a
446 new view mode ``list`` which is the result of the ``tree`` view_mode
447 in conjunction with the ``form`` view_type.
449 TODO: this should go into the doc, some kind of "peculiarities" section
451 :param dict action: an action descriptor
452 :returns: nothing, the action is modified in place
454 if 'views' not in action:
455 generate_views(action)
457 if action.pop('view_type') != 'form':
461 [id, mode if mode != 'tree' else 'list']
462 for id, mode in action['views']
467 class Menu(openerpweb.Controller):
468 _cp_path = "/base/menu"
470 @openerpweb.jsonrequest
472 return {'data': self.do_load(req)}
474 def do_load(self, req):
475 """ Loads all menu items (all applications and their sub-menus).
477 :param req: A request object, with an OpenERP session attribute
478 :type req: < session -> OpenERPSession >
479 :return: the menu root
480 :rtype: dict('children': menu_nodes)
482 Menus = req.session.model('ir.ui.menu')
483 # menus are loaded fully unlike a regular tree view, cause there are
484 # less than 512 items
485 context = req.session.eval_context(req.context)
486 menu_ids = Menus.search([], 0, False, False, context)
487 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
488 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
489 menu_items.append(menu_root)
491 # make a tree using parent_id
492 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
493 for menu_item in menu_items:
494 if menu_item['parent_id']:
495 parent = menu_item['parent_id'][0]
498 if parent in menu_items_map:
499 menu_items_map[parent].setdefault(
500 'children', []).append(menu_item)
502 # sort by sequence a tree using parent_id
503 for menu_item in menu_items:
504 menu_item.setdefault('children', []).sort(
505 key=lambda x:x["sequence"])
509 @openerpweb.jsonrequest
510 def action(self, req, menu_id):
511 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
512 [('ir.ui.menu', menu_id)], False,
513 req.session.eval_context(req.context))
514 return {"action": actions}
516 class DataSet(openerpweb.Controller):
517 _cp_path = "/base/dataset"
519 @openerpweb.jsonrequest
520 def fields(self, req, model):
521 return {'fields': req.session.model(model).fields_get(False,
522 req.session.eval_context(req.context))}
524 @openerpweb.jsonrequest
525 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
526 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
527 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
529 """ Performs a search() followed by a read() (if needed) using the
530 provided search criteria
532 :param request: a JSON-RPC request object
533 :type request: openerpweb.JsonRequest
534 :param str model: the name of the model to search on
535 :param fields: a list of the fields to return in the result records
537 :param int offset: from which index should the results start being returned
538 :param int limit: the maximum number of records to return
539 :param list domain: the search domain for the query
540 :param list sort: sorting directives
541 :returns: A structure (dict) with two keys: ids (all the ids matching
542 the (domain, context) pair) and records (paginated records
543 matching fields selection set)
546 Model = request.session.model(model)
547 context, domain = eval_context_and_domain(
548 request.session, request.context, domain)
550 ids = Model.search(domain, 0, False, sort or False, context)
551 # need to fill the dataset with all ids for the (domain, context) pair,
552 # so search un-paginated and paginate manually before reading
553 paginated_ids = ids[offset:(offset + limit if limit else None)]
554 if fields and fields == ['id']:
555 # shortcut read if we only want the ids
558 'records': map(lambda id: {'id': id}, paginated_ids)
561 records = Model.read(paginated_ids, fields or False, context)
562 records.sort(key=lambda obj: ids.index(obj['id']))
569 @openerpweb.jsonrequest
570 def get(self, request, model, ids, fields=False):
571 return self.do_get(request, model, ids, fields)
572 def do_get(self, request, model, ids, fields=False):
573 """ Fetches and returns the records of the model ``model`` whose ids
576 The results are in the same order as the inputs, but elements may be
577 missing (if there is no record left for the id)
579 :param request: the JSON-RPC2 request object
580 :type request: openerpweb.JsonRequest
581 :param model: the model to read from
583 :param ids: a list of identifiers
585 :param fields: a list of fields to fetch, ``False`` or empty to fetch
586 all fields in the model
587 :type fields: list | False
588 :returns: a list of records, in the same order as the list of ids
591 Model = request.session.model(model)
592 records = Model.read(ids, fields, request.session.eval_context(request.context))
594 record_map = dict((record['id'], record) for record in records)
596 return [record_map[id] for id in ids if record_map.get(id)]
598 @openerpweb.jsonrequest
599 def load(self, req, model, id, fields):
600 m = req.session.model(model)
602 r = m.read([id], False, req.session.eval_context(req.context))
605 return {'value': value}
607 @openerpweb.jsonrequest
608 def create(self, req, model, data):
609 m = req.session.model(model)
610 r = m.create(data, req.session.eval_context(req.context))
613 @openerpweb.jsonrequest
614 def save(self, req, model, id, data):
615 m = req.session.model(model)
616 r = m.write([id], data, req.session.eval_context(req.context))
619 @openerpweb.jsonrequest
620 def unlink(self, request, model, ids=()):
621 Model = request.session.model(model)
622 return Model.unlink(ids, request.session.eval_context(request.context))
624 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
625 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
626 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
627 c, d = eval_context_and_domain(req.session, context, domain)
628 if domain_id and len(args) - 1 >= domain_id:
630 if context_id and len(args) - 1 >= context_id:
633 return getattr(req.session.model(model), method)(*args)
635 @openerpweb.jsonrequest
636 def call(self, req, model, method, args, domain_id=None, context_id=None):
637 return self.call_common(req, model, method, args, domain_id, context_id)
639 @openerpweb.jsonrequest
640 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
641 action = self.call_common(req, model, method, args, domain_id, context_id)
642 if isinstance(action, dict) and action.get('type') != '':
643 return {'result': clean_action(action, req.session)}
644 return {'result': False}
646 @openerpweb.jsonrequest
647 def exec_workflow(self, req, model, id, signal):
648 r = req.session.exec_workflow(model, id, signal)
651 @openerpweb.jsonrequest
652 def default_get(self, req, model, fields):
653 Model = req.session.model(model)
654 return Model.default_get(fields, req.session.eval_context(req.context))
656 class DataGroup(openerpweb.Controller):
657 _cp_path = "/base/group"
658 @openerpweb.jsonrequest
659 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
660 Model = request.session.model(model)
661 context, domain = eval_context_and_domain(request.session, request.context, domain)
663 return Model.read_group(
664 domain or [], fields, group_by_fields, 0, False,
665 dict(context, group_by=group_by_fields), sort or False)
667 class View(openerpweb.Controller):
668 _cp_path = "/base/view"
670 def fields_view_get(self, request, model, view_id, view_type,
671 transform=True, toolbar=False, submenu=False):
672 Model = request.session.model(model)
673 context = request.session.eval_context(request.context)
674 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
675 # todo fme?: check that we should pass the evaluated context here
676 self.process_view(request.session, fvg, context, transform)
679 def process_view(self, session, fvg, context, transform):
680 # depending on how it feels, xmlrpclib.ServerProxy can translate
681 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
682 # enjoy unicode strings which can not be trivially converted to
683 # strings, and it blows up during parsing.
685 # So ensure we fix this retardation by converting view xml back to
687 if isinstance(fvg['arch'], unicode):
688 arch = fvg['arch'].encode('utf-8')
693 evaluation_context = session.evaluation_context(context or {})
694 xml = self.transform_view(arch, session, evaluation_context)
696 xml = ElementTree.fromstring(arch)
697 fvg['arch'] = Xml2Json.convert_element(xml)
699 for field in fvg['fields'].itervalues():
700 if field.get('views'):
701 for view in field["views"].itervalues():
702 self.process_view(session, view, None, transform)
703 if field.get('domain'):
704 field["domain"] = self.parse_domain(field["domain"], session)
705 if field.get('context'):
706 field["context"] = self.parse_context(field["context"], session)
708 @openerpweb.jsonrequest
709 def add_custom(self, request, view_id, arch):
710 CustomView = request.session.model('ir.ui.view.custom')
712 'user_id': request.session._uid,
715 }, request.session.eval_context(request.context))
716 return {'result': True}
718 @openerpweb.jsonrequest
719 def undo_custom(self, request, view_id, reset=False):
720 CustomView = request.session.model('ir.ui.view.custom')
721 context = request.session.eval_context(request.context)
722 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
723 0, False, False, context)
726 CustomView.unlink(vcustom, context)
728 CustomView.unlink([vcustom[0]], context)
729 return {'result': True}
730 return {'result': False}
732 def transform_view(self, view_string, session, context=None):
733 # transform nodes on the fly via iterparse, instead of
734 # doing it statically on the parsing result
735 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
737 for event, elem in parser:
741 self.parse_domains_and_contexts(elem, session)
744 def parse_domain(self, domain, session):
745 """ Parses an arbitrary string containing a domain, transforms it
746 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
748 :param domain: the domain to parse, if the domain is not a string it is assumed to
749 be a literal domain and is returned as-is
750 :param session: Current OpenERP session
751 :type session: openerpweb.openerpweb.OpenERPSession
753 if not isinstance(domain, (str, unicode)):
756 return openerpweb.ast.literal_eval(domain)
759 return openerpweb.nonliterals.Domain(session, domain)
761 def parse_context(self, context, session):
762 """ Parses an arbitrary string containing a context, transforms it
763 to either a literal context or a :class:`openerpweb.nonliterals.Context`
765 :param context: the context to parse, if the context is not a string it is assumed to
766 be a literal domain and is returned as-is
767 :param session: Current OpenERP session
768 :type session: openerpweb.openerpweb.OpenERPSession
770 if not isinstance(context, (str, unicode)):
773 return openerpweb.ast.literal_eval(context)
775 return openerpweb.nonliterals.Context(session, context)
777 def parse_domains_and_contexts(self, elem, session):
778 """ Converts domains and contexts from the view into Python objects,
779 either literals if they can be parsed by literal_eval or a special
780 placeholder object if the domain or context refers to free variables.
782 :param elem: the current node being parsed
783 :type param: xml.etree.ElementTree.Element
784 :param session: OpenERP session object, used to store and retrieve
786 :type session: openerpweb.openerpweb.OpenERPSession
788 for el in ['domain', 'filter_domain']:
789 domain = elem.get(el, '').strip()
791 elem.set(el, self.parse_domain(domain, session))
792 for el in ['context', 'default_get']:
793 context_string = elem.get(el, '').strip()
795 elem.set(el, self.parse_context(context_string, session))
797 class FormView(View):
798 _cp_path = "/base/formview"
800 @openerpweb.jsonrequest
801 def load(self, req, model, view_id, toolbar=False):
802 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
803 return {'fields_view': fields_view}
805 class ListView(View):
806 _cp_path = "/base/listview"
808 @openerpweb.jsonrequest
809 def load(self, req, model, view_id, toolbar=False):
810 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
811 return {'fields_view': fields_view}
813 def process_colors(self, view, row, context):
814 colors = view['arch']['attrs'].get('colors')
821 for pair in colors.split(';')
822 if eval(pair.split(':')[1], dict(context, **row))
827 elif len(color) == 1:
831 class SearchView(View):
832 _cp_path = "/base/searchview"
834 @openerpweb.jsonrequest
835 def load(self, req, model, view_id):
836 fields_view = self.fields_view_get(req, model, view_id, 'search')
837 return {'fields_view': fields_view}
839 @openerpweb.jsonrequest
840 def fields_get(self, req, model):
841 Model = req.session.model(model)
842 fields = Model.fields_get(False, req.session.eval_context(req.context))
843 for field in fields.values():
844 # shouldn't convert the views too?
845 if field.get('domain'):
846 field["domain"] = self.parse_domain(field["domain"], req.session)
847 if field.get('context'):
848 field["context"] = self.parse_domain(field["context"], req.session)
849 return {'fields': fields}
851 @openerpweb.jsonrequest
852 def get_filters(self, req, model):
853 Model = req.session.model("ir.filters")
854 filters = Model.get_filters(model)
855 for filter in filters:
856 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
857 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
860 @openerpweb.jsonrequest
861 def save_filter(self, req, model, name, context_to_save, domain):
862 Model = req.session.model("ir.filters")
863 ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
864 ctx.session = req.session
866 domain = openerpweb.nonliterals.CompoundDomain(domain)
867 domain.session = req.session
868 domain = domain.evaluate()
869 uid = req.session._uid
870 context = req.session.eval_context(req.context)
871 to_return = Model.create_or_replace({"context": ctx,
879 class Binary(openerpweb.Controller):
880 _cp_path = "/base/binary"
882 @openerpweb.httprequest
883 def image(self, request, session_id, model, id, field, **kw):
884 cherrypy.response.headers['Content-Type'] = 'image/png'
885 Model = request.session.model(model)
886 context = request.session.eval_context(request.context)
889 res = Model.default_get([field], context).get(field, '')
891 res = Model.read([int(id)], [field], context)[0].get(field, '')
892 return base64.decodestring(res)
893 except: # TODO: what's the exception here?
894 return self.placeholder()
895 def placeholder(self):
896 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
898 @openerpweb.httprequest
899 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
900 Model = request.session.model(model)
901 context = request.session.eval_context(request.context)
902 res = Model.read([int(id)], [field, fieldname], context)[0]
903 filecontent = res.get(field, '')
905 raise cherrypy.NotFound
907 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
908 filename = '%s_%s' % (model.replace('.', '_'), id)
910 filename = res.get(fieldname, '') or filename
911 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
912 return base64.decodestring(filecontent)
914 @openerpweb.httprequest
915 def upload(self, request, session_id, callback, ufile=None):
916 cherrypy.response.timeout = 500
918 for key, val in cherrypy.request.headers.iteritems():
919 headers[key.lower()] = val
920 size = int(headers.get('content-length', 0))
921 # TODO: might be useful to have a configuration flag for max-length file uploads
923 out = """<script language="javascript" type="text/javascript">
924 var win = window.top.window,
926 if (typeof(callback) === 'function') {
927 callback.apply(this, %s);
929 win.jQuery('#oe_notification', win.document).notify('create', {
930 title: "Ajax File Upload",
931 text: "Could not find callback"
935 data = ufile.file.read()
936 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
938 args = [False, e.message]
939 return out % (simplejson.dumps(callback), simplejson.dumps(args))
941 @openerpweb.httprequest
942 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
943 cherrypy.response.timeout = 500
944 context = request.session.eval_context(request.context)
945 Model = request.session.model('ir.attachment')
947 out = """<script language="javascript" type="text/javascript">
948 var win = window.top.window,
950 if (typeof(callback) === 'function') {
951 callback.call(this, %s);
954 attachment_id = Model.create({
955 'name': ufile.filename,
956 'datas': base64.encodestring(ufile.file.read()),
961 'filename': ufile.filename,
965 args = { 'error': e.message }
966 return out % (simplejson.dumps(callback), simplejson.dumps(args))
968 class Action(openerpweb.Controller):
969 _cp_path = "/base/action"
971 @openerpweb.jsonrequest
972 def load(self, req, action_id):
973 Actions = req.session.model('ir.actions.actions')
975 context = req.session.eval_context(req.context)
976 action_type = Actions.read([action_id], ['type'], context)
978 action = req.session.model(action_type[0]['type']).read([action_id], False,
981 value = clean_action(action[0], req.session)
982 return {'result': value}
984 @openerpweb.jsonrequest
985 def run(self, req, action_id):
986 return clean_action(req.session.model('ir.actions.server').run(
987 [action_id], req.session.eval_context(req.context)), req.session)