1 # -*- coding: utf-8 -*-
2 import base64, glob, os, re
3 from xml.etree import ElementTree
4 from cStringIO import StringIO
10 import openerpweb.nonliterals
14 # Should move to openerpweb.Xml2Json
17 # Simple and straightforward XML-to-JSON converter in Python
20 # URL: http://code.google.com/p/xml2json-direct/
22 def convert_to_json(s):
23 return simplejson.dumps(
24 Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
27 def convert_to_structure(s):
28 root = ElementTree.fromstring(s)
29 return Xml2Json.convert_element(root)
32 def convert_element(el, skip_whitespaces=True):
35 ns, name = el.tag.rsplit("}", 1)
37 res["namespace"] = ns[1:]
41 for k, v in el.items():
44 if el.text and (not skip_whitespaces or el.text.strip() != ''):
47 kids.append(Xml2Json.convert_element(kid))
48 if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
50 res["children"] = kids
53 #----------------------------------------------------------
54 # OpenERP Web base Controllers
55 #----------------------------------------------------------
57 def manifest_glob(addons, key):
60 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
62 for pattern in globlist:
63 for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)):
64 files.append(path[len(openerpweb.path_addons):])
67 def concat_files(file_list):
68 """ Concatenate file content
69 return (concat,timestamp)
70 concat: concatenation of file content
71 timestamp: max(os.path.getmtime of file_list)
73 root = openerpweb.path_root
77 fname = os.path.join(root, i)
78 ftime = os.path.getmtime(fname)
79 if ftime > files_timestamp:
80 files_timestamp = ftime
81 files_content = open(fname).read()
82 files_concat = "".join(files_content)
85 class WebClient(openerpweb.Controller):
86 _cp_path = "/base/webclient"
88 @openerpweb.jsonrequest
89 def csslist(self, req, mods='base'):
90 return manifest_glob(mods.split(','), 'css')
92 @openerpweb.jsonrequest
93 def jslist(self, req, mods='base'):
94 return manifest_glob(mods.split(','), 'js')
96 @openerpweb.httprequest
97 def css(self, req, mods='base'):
98 cherrypy.response.headers['Content-Type'] = 'text/css'
99 files = manifest_glob(mods.split(','), 'css')
100 concat = concat_files(files)[0]
101 # TODO request set the Date of last modif and Etag
104 @openerpweb.httprequest
105 def js(self, req, mods='base'):
106 cherrypy.response.headers['Content-Type'] = 'application/javascript'
107 files = manifest_glob(mods.split(','), 'js')
108 concat = concat_files(files)[0]
109 # TODO request set the Date of last modif and Etag
112 @openerpweb.httprequest
114 template ="""<!DOCTYPE html>
115 <html style="height: 100%%">
117 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
118 <title>OpenERP</title>
120 <script type="text/javascript">
122 QWeb = new QWeb2.Engine();
123 openerp.init().base.webclient("oe");
126 <link rel="shortcut icon" href="/base/static/src/img/favicon.ico" type="image/x-icon"/>
129 <link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
132 <body id="oe" class="openerp"></body>
134 """.replace('\n'+' '*8,'\n')
137 jslist = ['/base/webclient/js']
139 jslist = manifest_glob(['base'], 'js')
140 js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
143 csslist = ['/base/webclient/css']
145 csslist = manifest_glob(['base'], 'css')
146 css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
147 r = template % (js, css)
150 class Database(openerpweb.Controller):
151 _cp_path = "/base/database"
153 @openerpweb.jsonrequest
154 def get_databases_list(self, req):
155 proxy = req.session.proxy("db")
157 h = req.httprequest.headers['Host'].split(':')[0]
159 r = cherrypy.config['openerp.dbfilter'].replace('%h',h).replace('%d',d)
161 dbs = [i for i in dbs if re.match(r,i)]
162 return {"db_list": dbs}
164 class Session(openerpweb.Controller):
165 _cp_path = "/base/session"
167 @openerpweb.jsonrequest
168 def login(self, req, db, login, password):
169 req.session.login(db, login, password)
172 "session_id": req.session_id,
173 "uid": req.session._uid,
176 @openerpweb.jsonrequest
177 def sc_list(self, req):
178 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
179 req.session.eval_context(req.context))
181 @openerpweb.jsonrequest
182 def modules(self, req):
183 # TODO query server for installed web modules
185 for name, manifest in openerpweb.addons_manifest.items():
186 if name != 'base' and manifest.get('active', True):
190 @openerpweb.jsonrequest
191 def eval_domain_and_context(self, req, contexts, domains,
193 """ Evaluates sequences of domains and contexts, composing them into
194 a single context, domain or group_by sequence.
196 :param list contexts: list of contexts to merge together. Contexts are
197 evaluated in sequence, all previous contexts
198 are part of their own evaluation context
199 (starting at the session context).
200 :param list domains: list of domains to merge together. Domains are
201 evaluated in sequence and appended to one another
202 (implicit AND), their evaluation domain is the
203 result of merging all contexts.
204 :param list group_by_seq: list of domains (which may be in a different
205 order than the ``contexts`` parameter),
206 evaluated in sequence, their ``'group_by'``
207 key is extracted if they have one.
212 the global context created by merging all of
216 the concatenation of all domains
219 a list of fields to group by, potentially empty (in which case
220 no group by should be performed)
222 context, domain = eval_context_and_domain(req.session,
223 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
224 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
226 group_by_sequence = []
227 for candidate in (group_by_seq or []):
228 ctx = req.session.eval_context(candidate, context)
229 group_by = ctx.get('group_by')
232 elif isinstance(group_by, basestring):
233 group_by_sequence.append(group_by)
235 group_by_sequence.extend(group_by)
240 'group_by': group_by_sequence
243 @openerpweb.jsonrequest
244 def save_session_action(self, req, the_action):
246 This method store an action object in the session object and returns an integer
247 identifying that action. The method get_session_action() can be used to get
250 :param the_action: The action to save in the session.
251 :type the_action: anything
252 :return: A key identifying the saved action.
255 saved_actions = cherrypy.session.get('saved_actions')
256 if not saved_actions:
257 saved_actions = {"next":0, "actions":{}}
258 cherrypy.session['saved_actions'] = saved_actions
259 # we don't allow more than 10 stored actions
260 if len(saved_actions["actions"]) >= 10:
261 del saved_actions["actions"][min(saved_actions["actions"].keys())]
262 key = saved_actions["next"]
263 saved_actions["actions"][key] = the_action
264 saved_actions["next"] = key + 1
267 @openerpweb.jsonrequest
268 def get_session_action(self, req, key):
270 Gets back a previously saved action. This method can return None if the action
271 was saved since too much time (this case should be handled in a smart way).
273 :param key: The key given by save_session_action()
275 :return: The saved action or None.
278 saved_actions = cherrypy.session.get('saved_actions')
279 if not saved_actions:
281 return saved_actions["actions"].get(key)
283 @openerpweb.jsonrequest
284 def check(self, req):
285 req.session.assert_valid()
288 def eval_context_and_domain(session, context, domain=None):
289 e_context = session.eval_context(context)
290 # should we give the evaluated context as an evaluation context to the domain?
291 e_domain = session.eval_domain(domain or [])
293 return e_context, e_domain
295 def load_actions_from_ir_values(req, key, key2, models, meta, context):
296 Values = req.session.model('ir.values')
297 actions = Values.get(key, key2, models, meta, context)
299 return [(id, name, clean_action(action, req.session))
300 for id, name, action in actions]
302 def clean_action(action, session):
303 if action['type'] != 'ir.actions.act_window':
305 # values come from the server, we can just eval them
306 if isinstance(action.get('context', None), basestring):
307 action['context'] = eval(
309 session.evaluation_context()) or {}
311 if isinstance(action.get('domain', None), basestring):
312 action['domain'] = eval(
314 session.evaluation_context(
315 action.get('context', {}))) or []
316 if 'flags' not in action:
317 # Set empty flags dictionary for web client.
318 action['flags'] = dict()
319 return fix_view_modes(action)
321 def generate_views(action):
323 While the server generates a sequence called "views" computing dependencies
324 between a bunch of stuff for views coming directly from the database
325 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
326 to return custom view dictionaries generated on the fly.
328 In that case, there is no ``views`` key available on the action.
330 Since the web client relies on ``action['views']``, generate it here from
331 ``view_mode`` and ``view_id``.
333 Currently handles two different cases:
335 * no view_id, multiple view_mode
336 * single view_id, single view_mode
338 :param dict action: action descriptor dictionary to generate a views key for
340 view_id = action.get('view_id', False)
341 if isinstance(view_id, (list, tuple)):
344 # providing at least one view mode is a requirement, not an option
345 view_modes = action['view_mode'].split(',')
347 if len(view_modes) > 1:
349 raise ValueError('Non-db action dictionaries should provide '
350 'either multiple view modes or a single view '
351 'mode and an optional view id.\n\n Got view '
352 'modes %r and view id %r for action %r' % (
353 view_modes, view_id, action))
354 action['views'] = [(False, mode) for mode in view_modes]
356 action['views'] = [(view_id, view_modes[0])]
358 def fix_view_modes(action):
359 """ For historical reasons, OpenERP has weird dealings in relation to
360 view_mode and the view_type attribute (on window actions):
362 * one of the view modes is ``tree``, which stands for both list views
364 * the choice is made by checking ``view_type``, which is either
365 ``form`` for a list view or ``tree`` for an actual tree view
367 This methods simply folds the view_type into view_mode by adding a
368 new view mode ``list`` which is the result of the ``tree`` view_mode
369 in conjunction with the ``form`` view_type.
371 TODO: this should go into the doc, some kind of "peculiarities" section
373 :param dict action: an action descriptor
374 :returns: nothing, the action is modified in place
376 if 'views' not in action:
377 generate_views(action)
379 if action.pop('view_type') != 'form':
383 [id, mode if mode != 'tree' else 'list']
384 for id, mode in action['views']
389 class Menu(openerpweb.Controller):
390 _cp_path = "/base/menu"
392 @openerpweb.jsonrequest
394 return {'data': self.do_load(req)}
396 def do_load(self, req):
397 """ Loads all menu items (all applications and their sub-menus).
399 :param req: A request object, with an OpenERP session attribute
400 :type req: < session -> OpenERPSession >
401 :return: the menu root
402 :rtype: dict('children': menu_nodes)
404 Menus = req.session.model('ir.ui.menu')
405 # menus are loaded fully unlike a regular tree view, cause there are
406 # less than 512 items
407 context = req.session.eval_context(req.context)
408 menu_ids = Menus.search([], 0, False, False, context)
409 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
410 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
411 menu_items.append(menu_root)
413 # make a tree using parent_id
414 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
415 for menu_item in menu_items:
416 if menu_item['parent_id']:
417 parent = menu_item['parent_id'][0]
420 if parent in menu_items_map:
421 menu_items_map[parent].setdefault(
422 'children', []).append(menu_item)
424 # sort by sequence a tree using parent_id
425 for menu_item in menu_items:
426 menu_item.setdefault('children', []).sort(
427 key=lambda x:x["sequence"])
431 @openerpweb.jsonrequest
432 def action(self, req, menu_id):
433 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
434 [('ir.ui.menu', menu_id)], False,
435 req.session.eval_context(req.context))
436 return {"action": actions}
438 class DataSet(openerpweb.Controller):
439 _cp_path = "/base/dataset"
441 @openerpweb.jsonrequest
442 def fields(self, req, model):
443 return {'fields': req.session.model(model).fields_get(False,
444 req.session.eval_context(req.context))}
446 @openerpweb.jsonrequest
447 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
448 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
449 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
451 """ Performs a search() followed by a read() (if needed) using the
452 provided search criteria
454 :param request: a JSON-RPC request object
455 :type request: openerpweb.JsonRequest
456 :param str model: the name of the model to search on
457 :param fields: a list of the fields to return in the result records
459 :param int offset: from which index should the results start being returned
460 :param int limit: the maximum number of records to return
461 :param list domain: the search domain for the query
462 :param list sort: sorting directives
463 :returns: A structure (dict) with two keys: ids (all the ids matching
464 the (domain, context) pair) and records (paginated records
465 matching fields selection set)
468 Model = request.session.model(model)
469 context, domain = eval_context_and_domain(
470 request.session, request.context, domain)
472 ids = Model.search(domain, 0, False, sort or False, context)
473 # need to fill the dataset with all ids for the (domain, context) pair,
474 # so search un-paginated and paginate manually before reading
475 paginated_ids = ids[offset:(offset + limit if limit else None)]
476 if fields and fields == ['id']:
477 # shortcut read if we only want the ids
480 'records': map(lambda id: {'id': id}, paginated_ids)
483 records = Model.read(paginated_ids, fields or False, context)
484 records.sort(key=lambda obj: ids.index(obj['id']))
491 @openerpweb.jsonrequest
492 def get(self, request, model, ids, fields=False):
493 return self.do_get(request, model, ids, fields)
494 def do_get(self, request, model, ids, fields=False):
495 """ Fetches and returns the records of the model ``model`` whose ids
498 The results are in the same order as the inputs, but elements may be
499 missing (if there is no record left for the id)
501 :param request: the JSON-RPC2 request object
502 :type request: openerpweb.JsonRequest
503 :param model: the model to read from
505 :param ids: a list of identifiers
507 :param fields: a list of fields to fetch, ``False`` or empty to fetch
508 all fields in the model
509 :type fields: list | False
510 :returns: a list of records, in the same order as the list of ids
513 Model = request.session.model(model)
514 records = Model.read(ids, fields, request.session.eval_context(request.context))
516 record_map = dict((record['id'], record) for record in records)
518 return [record_map[id] for id in ids if record_map.get(id)]
520 @openerpweb.jsonrequest
521 def load(self, req, model, id, fields):
522 m = req.session.model(model)
524 r = m.read([id], False, req.session.eval_context(req.context))
527 return {'value': value}
529 @openerpweb.jsonrequest
530 def create(self, req, model, data):
531 m = req.session.model(model)
532 r = m.create(data, req.session.eval_context(req.context))
535 @openerpweb.jsonrequest
536 def save(self, req, model, id, data):
537 m = req.session.model(model)
538 r = m.write([id], data, req.session.eval_context(req.context))
541 @openerpweb.jsonrequest
542 def unlink(self, request, model, ids=()):
543 Model = request.session.model(model)
544 return Model.unlink(ids, request.session.eval_context(request.context))
546 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
547 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
548 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
549 c, d = eval_context_and_domain(req.session, context, domain)
550 if domain_id and len(args) - 1 >= domain_id:
552 if context_id and len(args) - 1 >= context_id:
555 return getattr(req.session.model(model), method)(*args)
557 @openerpweb.jsonrequest
558 def call(self, req, model, method, args, domain_id=None, context_id=None):
559 return self.call_common(req, model, method, args, domain_id, context_id)
561 @openerpweb.jsonrequest
562 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
563 action = self.call_common(req, model, method, args, domain_id, context_id)
564 if isinstance(action, dict) and action.get('type') != '':
565 return {'result': clean_action(action, req.session)}
566 return {'result': False}
568 @openerpweb.jsonrequest
569 def exec_workflow(self, req, model, id, signal):
570 r = req.session.exec_workflow(model, id, signal)
573 @openerpweb.jsonrequest
574 def default_get(self, req, model, fields):
575 Model = req.session.model(model)
576 return Model.default_get(fields, req.session.eval_context(req.context))
578 class DataGroup(openerpweb.Controller):
579 _cp_path = "/base/group"
580 @openerpweb.jsonrequest
581 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
582 Model = request.session.model(model)
583 context, domain = eval_context_and_domain(request.session, request.context, domain)
585 return Model.read_group(
586 domain or [], fields, group_by_fields, 0, False,
587 dict(context, group_by=group_by_fields), sort or False)
589 class View(openerpweb.Controller):
590 _cp_path = "/base/view"
592 def fields_view_get(self, request, model, view_id, view_type,
593 transform=True, toolbar=False, submenu=False):
594 Model = request.session.model(model)
595 context = request.session.eval_context(request.context)
596 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
597 # todo fme?: check that we should pass the evaluated context here
598 self.process_view(request.session, fvg, context, transform)
601 def process_view(self, session, fvg, context, transform):
602 # depending on how it feels, xmlrpclib.ServerProxy can translate
603 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
604 # enjoy unicode strings which can not be trivially converted to
605 # strings, and it blows up during parsing.
607 # So ensure we fix this retardation by converting view xml back to
609 if isinstance(fvg['arch'], unicode):
610 arch = fvg['arch'].encode('utf-8')
615 evaluation_context = session.evaluation_context(context or {})
616 xml = self.transform_view(arch, session, evaluation_context)
618 xml = ElementTree.fromstring(arch)
619 fvg['arch'] = Xml2Json.convert_element(xml)
621 for field in fvg['fields'].itervalues():
622 if field.get('views'):
623 for view in field["views"].itervalues():
624 self.process_view(session, view, None, transform)
625 if field.get('domain'):
626 field["domain"] = self.parse_domain(field["domain"], session)
627 if field.get('context'):
628 field["context"] = self.parse_context(field["context"], session)
630 @openerpweb.jsonrequest
631 def add_custom(self, request, view_id, arch):
632 CustomView = request.session.model('ir.ui.view.custom')
634 'user_id': request.session._uid,
637 }, request.session.eval_context(request.context))
638 return {'result': True}
640 @openerpweb.jsonrequest
641 def undo_custom(self, request, view_id, reset=False):
642 CustomView = request.session.model('ir.ui.view.custom')
643 context = request.session.eval_context(request.context)
644 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
645 0, False, False, context)
648 CustomView.unlink(vcustom, context)
650 CustomView.unlink([vcustom[0]], context)
651 return {'result': True}
652 return {'result': False}
654 def transform_view(self, view_string, session, context=None):
655 # transform nodes on the fly via iterparse, instead of
656 # doing it statically on the parsing result
657 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
659 for event, elem in parser:
663 self.parse_domains_and_contexts(elem, session)
666 def parse_domain(self, domain, session):
667 """ Parses an arbitrary string containing a domain, transforms it
668 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
670 :param domain: the domain to parse, if the domain is not a string it is assumed to
671 be a literal domain and is returned as-is
672 :param session: Current OpenERP session
673 :type session: openerpweb.openerpweb.OpenERPSession
675 if not isinstance(domain, (str, unicode)):
678 return openerpweb.ast.literal_eval(domain)
681 return openerpweb.nonliterals.Domain(session, domain)
683 def parse_context(self, context, session):
684 """ Parses an arbitrary string containing a context, transforms it
685 to either a literal context or a :class:`openerpweb.nonliterals.Context`
687 :param context: the context to parse, if the context is not a string it is assumed to
688 be a literal domain and is returned as-is
689 :param session: Current OpenERP session
690 :type session: openerpweb.openerpweb.OpenERPSession
692 if not isinstance(context, (str, unicode)):
695 return openerpweb.ast.literal_eval(context)
697 return openerpweb.nonliterals.Context(session, context)
699 def parse_domains_and_contexts(self, elem, session):
700 """ Converts domains and contexts from the view into Python objects,
701 either literals if they can be parsed by literal_eval or a special
702 placeholder object if the domain or context refers to free variables.
704 :param elem: the current node being parsed
705 :type param: xml.etree.ElementTree.Element
706 :param session: OpenERP session object, used to store and retrieve
708 :type session: openerpweb.openerpweb.OpenERPSession
710 for el in ['domain', 'filter_domain']:
711 domain = elem.get(el, '').strip()
713 elem.set(el, self.parse_domain(domain, session))
714 for el in ['context', 'default_get']:
715 context_string = elem.get(el, '').strip()
717 elem.set(el, self.parse_context(context_string, session))
719 class FormView(View):
720 _cp_path = "/base/formview"
722 @openerpweb.jsonrequest
723 def load(self, req, model, view_id, toolbar=False):
724 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
725 return {'fields_view': fields_view}
727 class ListView(View):
728 _cp_path = "/base/listview"
730 @openerpweb.jsonrequest
731 def load(self, req, model, view_id, toolbar=False):
732 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
733 return {'fields_view': fields_view}
735 def process_colors(self, view, row, context):
736 colors = view['arch']['attrs'].get('colors')
743 for pair in colors.split(';')
744 if eval(pair.split(':')[1], dict(context, **row))
749 elif len(color) == 1:
753 class SearchView(View):
754 _cp_path = "/base/searchview"
756 @openerpweb.jsonrequest
757 def load(self, req, model, view_id):
758 fields_view = self.fields_view_get(req, model, view_id, 'search')
759 return {'fields_view': fields_view}
761 @openerpweb.jsonrequest
762 def fields_get(self, req, model):
763 Model = req.session.model(model)
764 fields = Model.fields_get(False, req.session.eval_context(req.context))
765 for field in fields.values():
766 # shouldn't convert the views too?
767 if field.get('domain'):
768 field["domain"] = self.parse_domain(field["domain"], req.session)
769 if field.get('context'):
770 field["context"] = self.parse_domain(field["context"], req.session)
771 return {'fields': fields}
773 @openerpweb.jsonrequest
774 def get_filters(self, req, model):
775 Model = req.session.model("ir.filters")
776 filters = Model.get_filters(model)
777 for filter in filters:
778 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
779 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
782 @openerpweb.jsonrequest
783 def save_filter(self, req, model, name, context_to_save, domain):
784 Model = req.session.model("ir.filters")
785 ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
786 ctx.session = req.session
788 domain = openerpweb.nonliterals.CompoundDomain(domain)
789 domain.session = req.session
790 domain = domain.evaluate()
791 uid = req.session._uid
792 context = req.session.eval_context(req.context)
793 to_return = Model.create_or_replace({"context": ctx,
801 class Binary(openerpweb.Controller):
802 _cp_path = "/base/binary"
804 @openerpweb.httprequest
805 def image(self, request, session_id, model, id, field, **kw):
806 cherrypy.response.headers['Content-Type'] = 'image/png'
807 Model = request.session.model(model)
808 context = request.session.eval_context(request.context)
811 res = Model.default_get([field], context).get(field, '')
813 res = Model.read([int(id)], [field], context)[0].get(field, '')
814 return base64.decodestring(res)
815 except: # TODO: what's the exception here?
816 return self.placeholder()
817 def placeholder(self):
818 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
820 @openerpweb.httprequest
821 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
822 Model = request.session.model(model)
823 context = request.session.eval_context(request.context)
824 res = Model.read([int(id)], [field, fieldname], context)[0]
825 filecontent = res.get(field, '')
827 raise cherrypy.NotFound
829 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
830 filename = '%s_%s' % (model.replace('.', '_'), id)
832 filename = res.get(fieldname, '') or filename
833 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
834 return base64.decodestring(filecontent)
836 @openerpweb.httprequest
837 def upload(self, request, session_id, callback, ufile=None):
838 cherrypy.response.timeout = 500
840 for key, val in cherrypy.request.headers.iteritems():
841 headers[key.lower()] = val
842 size = int(headers.get('content-length', 0))
843 # TODO: might be useful to have a configuration flag for max-length file uploads
845 out = """<script language="javascript" type="text/javascript">
846 var win = window.top.window,
848 if (typeof(callback) === 'function') {
849 callback.apply(this, %s);
851 win.jQuery('#oe_notification', win.document).notify('create', {
852 title: "Ajax File Upload",
853 text: "Could not find callback"
857 data = ufile.file.read()
858 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
860 args = [False, e.message]
861 return out % (simplejson.dumps(callback), simplejson.dumps(args))
863 @openerpweb.httprequest
864 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
865 cherrypy.response.timeout = 500
866 context = request.session.eval_context(request.context)
867 Model = request.session.model('ir.attachment')
869 out = """<script language="javascript" type="text/javascript">
870 var win = window.top.window,
872 if (typeof(callback) === 'function') {
873 callback.call(this, %s);
876 attachment_id = Model.create({
877 'name': ufile.filename,
878 'datas': base64.encodestring(ufile.file.read()),
883 'filename': ufile.filename,
887 args = { 'error': e.message }
888 return out % (simplejson.dumps(callback), simplejson.dumps(args))
890 class Action(openerpweb.Controller):
891 _cp_path = "/base/action"
893 @openerpweb.jsonrequest
894 def load(self, req, action_id):
895 Actions = req.session.model('ir.actions.actions')
897 context = req.session.eval_context(req.context)
898 action_type = Actions.read([action_id], ['type'], context)
900 action = req.session.model(action_type[0]['type']).read([action_id], False,
903 value = clean_action(action[0], req.session)
904 return {'result': value}
906 @openerpweb.jsonrequest
907 def run(self, req, action_id):
908 return clean_action(req.session.model('ir.actions.server').run(
909 [action_id], req.session.eval_context(req.context)), req.session)