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 progress(self, req, password, id):
168 return req.session.proxy('db').get_progress(password, id)
170 @openerpweb.jsonrequest
171 def create_db(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_db(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_db(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_db(self, req, db_file, restore_pwd, new_db):
217 data = base64.encodestring(db_file.file.read())
218 response = simplejson.dumps(
219 req.session.proxy("db").restore(restore_pwd, new_db, data))
220 except xmlrpclib.Fault, e:
221 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
222 response = simplejson.dumps({'error': e.faultCode, 'title': 'Restore Database'})
224 response = simplejson.dumps({'error': 'Could not restore database !', 'title': 'Restore Database'})
226 cherrypy.response.headers['Content-Type'] = 'application/json'
227 cherrypy.response.headers['Content-Length'] = len(response)
230 @openerpweb.jsonrequest
231 def change_password_db(self, req, fields):
232 old_password, new_password = operator.itemgetter(
233 'old_pwd', 'new_pwd')(
234 dict(map(operator.itemgetter('name', 'value'), fields)))
236 return req.session.proxy("db").change_admin_password(old_password, new_password)
237 except xmlrpclib.Fault, e:
238 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
239 return {'error': e.faultCode, 'title': 'Change Password'}
240 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
242 class Session(openerpweb.Controller):
243 _cp_path = "/base/session"
245 @openerpweb.jsonrequest
246 def login(self, req, db, login, password):
247 req.session.login(db, login, password)
250 "session_id": req.session_id,
251 "uid": req.session._uid,
254 @openerpweb.jsonrequest
255 def sc_list(self, req):
256 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
257 req.session.eval_context(req.context))
259 @openerpweb.jsonrequest
260 def get_lang_list(self, req):
263 'lang_list': (req.session.proxy("db").list_lang() or []),
267 return {"error": e, "title": "Languages"}
269 @openerpweb.jsonrequest
270 def modules(self, req):
271 # TODO query server for installed web modules
273 for name, manifest in openerpweb.addons_manifest.items():
274 if name != 'base' and manifest.get('active', True):
278 @openerpweb.jsonrequest
279 def eval_domain_and_context(self, req, contexts, domains,
281 """ Evaluates sequences of domains and contexts, composing them into
282 a single context, domain or group_by sequence.
284 :param list contexts: list of contexts to merge together. Contexts are
285 evaluated in sequence, all previous contexts
286 are part of their own evaluation context
287 (starting at the session context).
288 :param list domains: list of domains to merge together. Domains are
289 evaluated in sequence and appended to one another
290 (implicit AND), their evaluation domain is the
291 result of merging all contexts.
292 :param list group_by_seq: list of domains (which may be in a different
293 order than the ``contexts`` parameter),
294 evaluated in sequence, their ``'group_by'``
295 key is extracted if they have one.
300 the global context created by merging all of
304 the concatenation of all domains
307 a list of fields to group by, potentially empty (in which case
308 no group by should be performed)
310 context, domain = eval_context_and_domain(req.session,
311 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
312 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
314 group_by_sequence = []
315 for candidate in (group_by_seq or []):
316 ctx = req.session.eval_context(candidate, context)
317 group_by = ctx.get('group_by')
320 elif isinstance(group_by, basestring):
321 group_by_sequence.append(group_by)
323 group_by_sequence.extend(group_by)
328 'group_by': group_by_sequence
331 @openerpweb.jsonrequest
332 def save_session_action(self, req, the_action):
334 This method store an action object in the session object and returns an integer
335 identifying that action. The method get_session_action() can be used to get
338 :param the_action: The action to save in the session.
339 :type the_action: anything
340 :return: A key identifying the saved action.
343 saved_actions = cherrypy.session.get('saved_actions')
344 if not saved_actions:
345 saved_actions = {"next":0, "actions":{}}
346 cherrypy.session['saved_actions'] = saved_actions
347 # we don't allow more than 10 stored actions
348 if len(saved_actions["actions"]) >= 10:
349 del saved_actions["actions"][min(saved_actions["actions"].keys())]
350 key = saved_actions["next"]
351 saved_actions["actions"][key] = the_action
352 saved_actions["next"] = key + 1
355 @openerpweb.jsonrequest
356 def get_session_action(self, req, key):
358 Gets back a previously saved action. This method can return None if the action
359 was saved since too much time (this case should be handled in a smart way).
361 :param key: The key given by save_session_action()
363 :return: The saved action or None.
366 saved_actions = cherrypy.session.get('saved_actions')
367 if not saved_actions:
369 return saved_actions["actions"].get(key)
371 def eval_context_and_domain(session, context, domain=None):
372 e_context = session.eval_context(context)
373 # should we give the evaluated context as an evaluation context to the domain?
374 e_domain = session.eval_domain(domain or [])
376 return e_context, e_domain
378 def load_actions_from_ir_values(req, key, key2, models, meta, context):
379 Values = req.session.model('ir.values')
380 actions = Values.get(key, key2, models, meta, context)
382 return [(id, name, clean_action(action, req.session))
383 for id, name, action in actions]
385 def clean_action(action, session):
386 if action['type'] != 'ir.actions.act_window':
388 # values come from the server, we can just eval them
389 if isinstance(action.get('context', None), basestring):
390 action['context'] = eval(
392 session.evaluation_context()) or {}
394 if isinstance(action.get('domain', None), basestring):
395 action['domain'] = eval(
397 session.evaluation_context(
398 action.get('context', {}))) or []
399 if 'flags' not in action:
400 # Set empty flags dictionary for web client.
401 action['flags'] = dict()
402 return fix_view_modes(action)
404 def generate_views(action):
406 While the server generates a sequence called "views" computing dependencies
407 between a bunch of stuff for views coming directly from the database
408 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
409 to return custom view dictionaries generated on the fly.
411 In that case, there is no ``views`` key available on the action.
413 Since the web client relies on ``action['views']``, generate it here from
414 ``view_mode`` and ``view_id``.
416 Currently handles two different cases:
418 * no view_id, multiple view_mode
419 * single view_id, single view_mode
421 :param dict action: action descriptor dictionary to generate a views key for
423 view_id = action.get('view_id', False)
424 if isinstance(view_id, (list, tuple)):
427 # providing at least one view mode is a requirement, not an option
428 view_modes = action['view_mode'].split(',')
430 if len(view_modes) > 1:
432 raise ValueError('Non-db action dictionaries should provide '
433 'either multiple view modes or a single view '
434 'mode and an optional view id.\n\n Got view '
435 'modes %r and view id %r for action %r' % (
436 view_modes, view_id, action))
437 action['views'] = [(False, mode) for mode in view_modes]
439 action['views'] = [(view_id, view_modes[0])]
441 def fix_view_modes(action):
442 """ For historical reasons, OpenERP has weird dealings in relation to
443 view_mode and the view_type attribute (on window actions):
445 * one of the view modes is ``tree``, which stands for both list views
447 * the choice is made by checking ``view_type``, which is either
448 ``form`` for a list view or ``tree`` for an actual tree view
450 This methods simply folds the view_type into view_mode by adding a
451 new view mode ``list`` which is the result of the ``tree`` view_mode
452 in conjunction with the ``form`` view_type.
454 TODO: this should go into the doc, some kind of "peculiarities" section
456 :param dict action: an action descriptor
457 :returns: nothing, the action is modified in place
459 if 'views' not in action:
460 generate_views(action)
462 if action.pop('view_type') != 'form':
466 [id, mode if mode != 'tree' else 'list']
467 for id, mode in action['views']
472 class Menu(openerpweb.Controller):
473 _cp_path = "/base/menu"
475 @openerpweb.jsonrequest
477 return {'data': self.do_load(req)}
479 def do_load(self, req):
480 """ Loads all menu items (all applications and their sub-menus).
482 :param req: A request object, with an OpenERP session attribute
483 :type req: < session -> OpenERPSession >
484 :return: the menu root
485 :rtype: dict('children': menu_nodes)
487 Menus = req.session.model('ir.ui.menu')
488 # menus are loaded fully unlike a regular tree view, cause there are
489 # less than 512 items
490 context = req.session.eval_context(req.context)
491 menu_ids = Menus.search([], 0, False, False, context)
492 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
493 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
494 menu_items.append(menu_root)
496 # make a tree using parent_id
497 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
498 for menu_item in menu_items:
499 if menu_item['parent_id']:
500 parent = menu_item['parent_id'][0]
503 if parent in menu_items_map:
504 menu_items_map[parent].setdefault(
505 'children', []).append(menu_item)
507 # sort by sequence a tree using parent_id
508 for menu_item in menu_items:
509 menu_item.setdefault('children', []).sort(
510 key=lambda x:x["sequence"])
514 @openerpweb.jsonrequest
515 def action(self, req, menu_id):
516 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
517 [('ir.ui.menu', menu_id)], False,
518 req.session.eval_context(req.context))
519 return {"action": actions}
521 class DataSet(openerpweb.Controller):
522 _cp_path = "/base/dataset"
524 @openerpweb.jsonrequest
525 def fields(self, req, model):
526 return {'fields': req.session.model(model).fields_get(False,
527 req.session.eval_context(req.context))}
529 @openerpweb.jsonrequest
530 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
531 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
532 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
534 """ Performs a search() followed by a read() (if needed) using the
535 provided search criteria
537 :param request: a JSON-RPC request object
538 :type request: openerpweb.JsonRequest
539 :param str model: the name of the model to search on
540 :param fields: a list of the fields to return in the result records
542 :param int offset: from which index should the results start being returned
543 :param int limit: the maximum number of records to return
544 :param list domain: the search domain for the query
545 :param list sort: sorting directives
546 :returns: A structure (dict) with two keys: ids (all the ids matching
547 the (domain, context) pair) and records (paginated records
548 matching fields selection set)
551 Model = request.session.model(model)
552 context, domain = eval_context_and_domain(
553 request.session, request.context, domain)
555 ids = Model.search(domain, 0, False, sort or False, context)
556 # need to fill the dataset with all ids for the (domain, context) pair,
557 # so search un-paginated and paginate manually before reading
558 paginated_ids = ids[offset:(offset + limit if limit else None)]
559 if fields and fields == ['id']:
560 # shortcut read if we only want the ids
563 'records': map(lambda id: {'id': id}, paginated_ids)
566 records = Model.read(paginated_ids, fields or False, context)
567 records.sort(key=lambda obj: ids.index(obj['id']))
574 @openerpweb.jsonrequest
575 def get(self, request, model, ids, fields=False):
576 return self.do_get(request, model, ids, fields)
577 def do_get(self, request, model, ids, fields=False):
578 """ Fetches and returns the records of the model ``model`` whose ids
581 The results are in the same order as the inputs, but elements may be
582 missing (if there is no record left for the id)
584 :param request: the JSON-RPC2 request object
585 :type request: openerpweb.JsonRequest
586 :param model: the model to read from
588 :param ids: a list of identifiers
590 :param fields: a list of fields to fetch, ``False`` or empty to fetch
591 all fields in the model
592 :type fields: list | False
593 :returns: a list of records, in the same order as the list of ids
596 Model = request.session.model(model)
597 records = Model.read(ids, fields, request.session.eval_context(request.context))
599 record_map = dict((record['id'], record) for record in records)
601 return [record_map[id] for id in ids if record_map.get(id)]
603 @openerpweb.jsonrequest
604 def load(self, req, model, id, fields):
605 m = req.session.model(model)
607 r = m.read([id], False, req.session.eval_context(req.context))
610 return {'value': value}
612 @openerpweb.jsonrequest
613 def create(self, req, model, data):
614 m = req.session.model(model)
615 r = m.create(data, req.session.eval_context(req.context))
618 @openerpweb.jsonrequest
619 def save(self, req, model, id, data):
620 m = req.session.model(model)
621 r = m.write([id], data, req.session.eval_context(req.context))
624 @openerpweb.jsonrequest
625 def unlink(self, request, model, ids=()):
626 Model = request.session.model(model)
627 return Model.unlink(ids, request.session.eval_context(request.context))
629 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
630 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
631 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
632 c, d = eval_context_and_domain(req.session, context, domain)
633 if domain_id and len(args) - 1 >= domain_id:
635 if context_id and len(args) - 1 >= context_id:
638 return getattr(req.session.model(model), method)(*args)
640 @openerpweb.jsonrequest
641 def call(self, req, model, method, args, domain_id=None, context_id=None):
642 return self.call_common(req, model, method, args, domain_id, context_id)
644 @openerpweb.jsonrequest
645 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
646 action = self.call_common(req, model, method, args, domain_id, context_id)
647 if isinstance(action, dict) and action.get('type') != '':
648 return {'result': clean_action(action, req.session)}
649 return {'result': False}
651 @openerpweb.jsonrequest
652 def exec_workflow(self, req, model, id, signal):
653 r = req.session.exec_workflow(model, id, signal)
656 @openerpweb.jsonrequest
657 def default_get(self, req, model, fields):
658 Model = req.session.model(model)
659 return Model.default_get(fields, req.session.eval_context(req.context))
661 class DataGroup(openerpweb.Controller):
662 _cp_path = "/base/group"
663 @openerpweb.jsonrequest
664 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
665 Model = request.session.model(model)
666 context, domain = eval_context_and_domain(request.session, request.context, domain)
668 return Model.read_group(
669 domain or [], fields, group_by_fields, 0, False,
670 dict(context, group_by=group_by_fields), sort or False)
672 class View(openerpweb.Controller):
673 _cp_path = "/base/view"
675 def fields_view_get(self, request, model, view_id, view_type,
676 transform=True, toolbar=False, submenu=False):
677 Model = request.session.model(model)
678 context = request.session.eval_context(request.context)
679 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
680 # todo fme?: check that we should pass the evaluated context here
681 self.process_view(request.session, fvg, context, transform)
684 def process_view(self, session, fvg, context, transform):
685 # depending on how it feels, xmlrpclib.ServerProxy can translate
686 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
687 # enjoy unicode strings which can not be trivially converted to
688 # strings, and it blows up during parsing.
690 # So ensure we fix this retardation by converting view xml back to
692 if isinstance(fvg['arch'], unicode):
693 arch = fvg['arch'].encode('utf-8')
698 evaluation_context = session.evaluation_context(context or {})
699 xml = self.transform_view(arch, session, evaluation_context)
701 xml = ElementTree.fromstring(arch)
702 fvg['arch'] = Xml2Json.convert_element(xml)
704 for field in fvg['fields'].itervalues():
705 if field.get('views'):
706 for view in field["views"].itervalues():
707 self.process_view(session, view, None, transform)
708 if field.get('domain'):
709 field["domain"] = self.parse_domain(field["domain"], session)
710 if field.get('context'):
711 field["context"] = self.parse_context(field["context"], session)
713 @openerpweb.jsonrequest
714 def add_custom(self, request, view_id, arch):
715 CustomView = request.session.model('ir.ui.view.custom')
717 'user_id': request.session._uid,
720 }, request.session.eval_context(request.context))
721 return {'result': True}
723 @openerpweb.jsonrequest
724 def undo_custom(self, request, view_id, reset=False):
725 CustomView = request.session.model('ir.ui.view.custom')
726 context = request.session.eval_context(request.context)
727 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
728 0, False, False, context)
731 CustomView.unlink(vcustom, context)
733 CustomView.unlink([vcustom[0]], context)
734 return {'result': True}
735 return {'result': False}
737 def transform_view(self, view_string, session, context=None):
738 # transform nodes on the fly via iterparse, instead of
739 # doing it statically on the parsing result
740 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
742 for event, elem in parser:
746 self.parse_domains_and_contexts(elem, session)
749 def parse_domain(self, domain, session):
750 """ Parses an arbitrary string containing a domain, transforms it
751 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
753 :param domain: the domain to parse, if the domain is not a string it is assumed to
754 be a literal domain and is returned as-is
755 :param session: Current OpenERP session
756 :type session: openerpweb.openerpweb.OpenERPSession
758 if not isinstance(domain, (str, unicode)):
761 return openerpweb.ast.literal_eval(domain)
764 return openerpweb.nonliterals.Domain(session, domain)
766 def parse_context(self, context, session):
767 """ Parses an arbitrary string containing a context, transforms it
768 to either a literal context or a :class:`openerpweb.nonliterals.Context`
770 :param context: the context to parse, if the context is not a string it is assumed to
771 be a literal domain and is returned as-is
772 :param session: Current OpenERP session
773 :type session: openerpweb.openerpweb.OpenERPSession
775 if not isinstance(context, (str, unicode)):
778 return openerpweb.ast.literal_eval(context)
780 return openerpweb.nonliterals.Context(session, context)
782 def parse_domains_and_contexts(self, elem, session):
783 """ Converts domains and contexts from the view into Python objects,
784 either literals if they can be parsed by literal_eval or a special
785 placeholder object if the domain or context refers to free variables.
787 :param elem: the current node being parsed
788 :type param: xml.etree.ElementTree.Element
789 :param session: OpenERP session object, used to store and retrieve
791 :type session: openerpweb.openerpweb.OpenERPSession
793 for el in ['domain', 'filter_domain']:
794 domain = elem.get(el, '').strip()
796 elem.set(el, self.parse_domain(domain, session))
797 for el in ['context', 'default_get']:
798 context_string = elem.get(el, '').strip()
800 elem.set(el, self.parse_context(context_string, session))
802 class FormView(View):
803 _cp_path = "/base/formview"
805 @openerpweb.jsonrequest
806 def load(self, req, model, view_id, toolbar=False):
807 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
808 return {'fields_view': fields_view}
810 class ListView(View):
811 _cp_path = "/base/listview"
813 @openerpweb.jsonrequest
814 def load(self, req, model, view_id, toolbar=False):
815 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
816 return {'fields_view': fields_view}
818 def process_colors(self, view, row, context):
819 colors = view['arch']['attrs'].get('colors')
826 for pair in colors.split(';')
827 if eval(pair.split(':')[1], dict(context, **row))
832 elif len(color) == 1:
836 class SearchView(View):
837 _cp_path = "/base/searchview"
839 @openerpweb.jsonrequest
840 def load(self, req, model, view_id):
841 fields_view = self.fields_view_get(req, model, view_id, 'search')
842 return {'fields_view': fields_view}
844 @openerpweb.jsonrequest
845 def fields_get(self, req, model):
846 Model = req.session.model(model)
847 fields = Model.fields_get(False, req.session.eval_context(req.context))
848 for field in fields.values():
849 # shouldn't convert the views too?
850 if field.get('domain'):
851 field["domain"] = self.parse_domain(field["domain"], req.session)
852 if field.get('context'):
853 field["context"] = self.parse_domain(field["context"], req.session)
854 return {'fields': fields}
856 @openerpweb.jsonrequest
857 def get_filters(self, req, model):
858 Model = req.session.model("ir.filters")
859 filters = Model.get_filters(model)
860 for filter in filters:
861 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
862 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
865 @openerpweb.jsonrequest
866 def save_filter(self, req, model, name, context_to_save, domain):
867 Model = req.session.model("ir.filters")
868 ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
869 ctx.session = req.session
871 domain = openerpweb.nonliterals.CompoundDomain(domain)
872 domain.session = req.session
873 domain = domain.evaluate()
874 uid = req.session._uid
875 context = req.session.eval_context(req.context)
876 to_return = Model.create_or_replace({"context": ctx,
884 class Binary(openerpweb.Controller):
885 _cp_path = "/base/binary"
887 @openerpweb.httprequest
888 def image(self, request, session_id, model, id, field, **kw):
889 cherrypy.response.headers['Content-Type'] = 'image/png'
890 Model = request.session.model(model)
891 context = request.session.eval_context(request.context)
894 res = Model.default_get([field], context).get(field, '')
896 res = Model.read([int(id)], [field], context)[0].get(field, '')
897 return base64.decodestring(res)
898 except: # TODO: what's the exception here?
899 return self.placeholder()
900 def placeholder(self):
901 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
903 @openerpweb.httprequest
904 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
905 Model = request.session.model(model)
906 context = request.session.eval_context(request.context)
907 res = Model.read([int(id)], [field, fieldname], context)[0]
908 filecontent = res.get(field, '')
910 raise cherrypy.NotFound
912 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
913 filename = '%s_%s' % (model.replace('.', '_'), id)
915 filename = res.get(fieldname, '') or filename
916 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
917 return base64.decodestring(filecontent)
919 @openerpweb.httprequest
920 def upload(self, request, session_id, callback, ufile=None):
921 cherrypy.response.timeout = 500
923 for key, val in cherrypy.request.headers.iteritems():
924 headers[key.lower()] = val
925 size = int(headers.get('content-length', 0))
926 # TODO: might be useful to have a configuration flag for max-length file uploads
928 out = """<script language="javascript" type="text/javascript">
929 var win = window.top.window,
931 if (typeof(callback) === 'function') {
932 callback.apply(this, %s);
934 win.jQuery('#oe_notification', win.document).notify('create', {
935 title: "Ajax File Upload",
936 text: "Could not find callback"
940 data = ufile.file.read()
941 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
943 args = [False, e.message]
944 return out % (simplejson.dumps(callback), simplejson.dumps(args))
946 @openerpweb.httprequest
947 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
948 cherrypy.response.timeout = 500
949 context = request.session.eval_context(request.context)
950 Model = request.session.model('ir.attachment')
952 out = """<script language="javascript" type="text/javascript">
953 var win = window.top.window,
955 if (typeof(callback) === 'function') {
956 callback.call(this, %s);
959 attachment_id = Model.create({
960 'name': ufile.filename,
961 'datas': base64.encodestring(ufile.file.read()),
966 'filename': ufile.filename,
970 args = { 'error': e.message }
971 return out % (simplejson.dumps(callback), simplejson.dumps(args))
973 class Action(openerpweb.Controller):
974 _cp_path = "/base/action"
976 @openerpweb.jsonrequest
977 def load(self, req, action_id):
978 Actions = req.session.model('ir.actions.actions')
980 context = req.session.eval_context(req.context)
981 action_type = Actions.read([action_id], ['type'], context)
983 action = req.session.model(action_type[0]['type']).read([action_id], False,
986 value = clean_action(action[0], req.session)
987 return {'result': value}
989 @openerpweb.jsonrequest
990 def run(self, req, action_id):
991 return clean_action(req.session.model('ir.actions.server').run(
992 [action_id], req.session.eval_context(req.context)), req.session)