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 def eval_context_and_domain(session, context, domain=None):
284 e_context = session.eval_context(context)
285 # should we give the evaluated context as an evaluation context to the domain?
286 e_domain = session.eval_domain(domain or [])
288 return e_context, e_domain
290 def load_actions_from_ir_values(req, key, key2, models, meta, context):
291 Values = req.session.model('ir.values')
292 actions = Values.get(key, key2, models, meta, context)
294 return [(id, name, clean_action(action, req.session))
295 for id, name, action in actions]
297 def clean_action(action, session):
298 if action['type'] != 'ir.actions.act_window':
300 # values come from the server, we can just eval them
301 if isinstance(action.get('context', None), basestring):
302 action['context'] = eval(
304 session.evaluation_context()) or {}
306 if isinstance(action.get('domain', None), basestring):
307 action['domain'] = eval(
309 session.evaluation_context(
310 action.get('context', {}))) or []
311 if 'flags' not in action:
312 # Set empty flags dictionary for web client.
313 action['flags'] = dict()
314 return fix_view_modes(action)
316 def generate_views(action):
318 While the server generates a sequence called "views" computing dependencies
319 between a bunch of stuff for views coming directly from the database
320 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
321 to return custom view dictionaries generated on the fly.
323 In that case, there is no ``views`` key available on the action.
325 Since the web client relies on ``action['views']``, generate it here from
326 ``view_mode`` and ``view_id``.
328 Currently handles two different cases:
330 * no view_id, multiple view_mode
331 * single view_id, single view_mode
333 :param dict action: action descriptor dictionary to generate a views key for
335 view_id = action.get('view_id', False)
336 if isinstance(view_id, (list, tuple)):
339 # providing at least one view mode is a requirement, not an option
340 view_modes = action['view_mode'].split(',')
342 if len(view_modes) > 1:
344 raise ValueError('Non-db action dictionaries should provide '
345 'either multiple view modes or a single view '
346 'mode and an optional view id.\n\n Got view '
347 'modes %r and view id %r for action %r' % (
348 view_modes, view_id, action))
349 action['views'] = [(False, mode) for mode in view_modes]
351 action['views'] = [(view_id, view_modes[0])]
353 def fix_view_modes(action):
354 """ For historical reasons, OpenERP has weird dealings in relation to
355 view_mode and the view_type attribute (on window actions):
357 * one of the view modes is ``tree``, which stands for both list views
359 * the choice is made by checking ``view_type``, which is either
360 ``form`` for a list view or ``tree`` for an actual tree view
362 This methods simply folds the view_type into view_mode by adding a
363 new view mode ``list`` which is the result of the ``tree`` view_mode
364 in conjunction with the ``form`` view_type.
366 TODO: this should go into the doc, some kind of "peculiarities" section
368 :param dict action: an action descriptor
369 :returns: nothing, the action is modified in place
371 if 'views' not in action:
372 generate_views(action)
374 if action.pop('view_type') != 'form':
378 [id, mode if mode != 'tree' else 'list']
379 for id, mode in action['views']
384 class Menu(openerpweb.Controller):
385 _cp_path = "/base/menu"
387 @openerpweb.jsonrequest
389 return {'data': self.do_load(req)}
391 def do_load(self, req):
392 """ Loads all menu items (all applications and their sub-menus).
394 :param req: A request object, with an OpenERP session attribute
395 :type req: < session -> OpenERPSession >
396 :return: the menu root
397 :rtype: dict('children': menu_nodes)
399 Menus = req.session.model('ir.ui.menu')
400 # menus are loaded fully unlike a regular tree view, cause there are
401 # less than 512 items
402 context = req.session.eval_context(req.context)
403 menu_ids = Menus.search([], 0, False, False, context)
404 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
405 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
406 menu_items.append(menu_root)
408 # make a tree using parent_id
409 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
410 for menu_item in menu_items:
411 if menu_item['parent_id']:
412 parent = menu_item['parent_id'][0]
415 if parent in menu_items_map:
416 menu_items_map[parent].setdefault(
417 'children', []).append(menu_item)
419 # sort by sequence a tree using parent_id
420 for menu_item in menu_items:
421 menu_item.setdefault('children', []).sort(
422 key=lambda x:x["sequence"])
426 @openerpweb.jsonrequest
427 def action(self, req, menu_id):
428 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
429 [('ir.ui.menu', menu_id)], False,
430 req.session.eval_context(req.context))
431 return {"action": actions}
433 class DataSet(openerpweb.Controller):
434 _cp_path = "/base/dataset"
436 @openerpweb.jsonrequest
437 def fields(self, req, model):
438 return {'fields': req.session.model(model).fields_get(False,
439 req.session.eval_context(req.context))}
441 @openerpweb.jsonrequest
442 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
443 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
444 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
446 """ Performs a search() followed by a read() (if needed) using the
447 provided search criteria
449 :param request: a JSON-RPC request object
450 :type request: openerpweb.JsonRequest
451 :param str model: the name of the model to search on
452 :param fields: a list of the fields to return in the result records
454 :param int offset: from which index should the results start being returned
455 :param int limit: the maximum number of records to return
456 :param list domain: the search domain for the query
457 :param list sort: sorting directives
458 :returns: A structure (dict) with two keys: ids (all the ids matching
459 the (domain, context) pair) and records (paginated records
460 matching fields selection set)
463 Model = request.session.model(model)
464 context, domain = eval_context_and_domain(
465 request.session, request.context, domain)
467 ids = Model.search(domain, 0, False, sort or False, context)
468 # need to fill the dataset with all ids for the (domain, context) pair,
469 # so search un-paginated and paginate manually before reading
470 paginated_ids = ids[offset:(offset + limit if limit else None)]
471 if fields and fields == ['id']:
472 # shortcut read if we only want the ids
475 'records': map(lambda id: {'id': id}, paginated_ids)
478 records = Model.read(paginated_ids, fields or False, context)
479 records.sort(key=lambda obj: ids.index(obj['id']))
486 @openerpweb.jsonrequest
487 def get(self, request, model, ids, fields=False):
488 return self.do_get(request, model, ids, fields)
489 def do_get(self, request, model, ids, fields=False):
490 """ Fetches and returns the records of the model ``model`` whose ids
493 The results are in the same order as the inputs, but elements may be
494 missing (if there is no record left for the id)
496 :param request: the JSON-RPC2 request object
497 :type request: openerpweb.JsonRequest
498 :param model: the model to read from
500 :param ids: a list of identifiers
502 :param fields: a list of fields to fetch, ``False`` or empty to fetch
503 all fields in the model
504 :type fields: list | False
505 :returns: a list of records, in the same order as the list of ids
508 Model = request.session.model(model)
509 records = Model.read(ids, fields, request.session.eval_context(request.context))
511 record_map = dict((record['id'], record) for record in records)
513 return [record_map[id] for id in ids if record_map.get(id)]
515 @openerpweb.jsonrequest
516 def load(self, req, model, id, fields):
517 m = req.session.model(model)
519 r = m.read([id], False, req.session.eval_context(req.context))
522 return {'value': value}
524 @openerpweb.jsonrequest
525 def create(self, req, model, data):
526 m = req.session.model(model)
527 r = m.create(data, req.session.eval_context(req.context))
530 @openerpweb.jsonrequest
531 def save(self, req, model, id, data):
532 m = req.session.model(model)
533 r = m.write([id], data, req.session.eval_context(req.context))
536 @openerpweb.jsonrequest
537 def unlink(self, request, model, ids=()):
538 Model = request.session.model(model)
539 return Model.unlink(ids, request.session.eval_context(request.context))
541 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
542 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
543 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
544 c, d = eval_context_and_domain(req.session, context, domain)
545 if domain_id and len(args) - 1 >= domain_id:
547 if context_id and len(args) - 1 >= context_id:
550 return getattr(req.session.model(model), method)(*args)
552 @openerpweb.jsonrequest
553 def call(self, req, model, method, args, domain_id=None, context_id=None):
554 return self.call_common(req, model, method, args, domain_id, context_id)
556 @openerpweb.jsonrequest
557 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
558 action = self.call_common(req, model, method, args, domain_id, context_id)
559 if isinstance(action, dict) and action.get('type') != '':
560 return {'result': clean_action(action, req.session)}
561 return {'result': False}
563 @openerpweb.jsonrequest
564 def exec_workflow(self, req, model, id, signal):
565 r = req.session.exec_workflow(model, id, signal)
568 @openerpweb.jsonrequest
569 def default_get(self, req, model, fields):
570 Model = req.session.model(model)
571 return Model.default_get(fields, req.session.eval_context(req.context))
573 class DataGroup(openerpweb.Controller):
574 _cp_path = "/base/group"
575 @openerpweb.jsonrequest
576 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
577 Model = request.session.model(model)
578 context, domain = eval_context_and_domain(request.session, request.context, domain)
580 return Model.read_group(
581 domain or [], fields, group_by_fields, 0, False,
582 dict(context, group_by=group_by_fields), sort or False)
584 class View(openerpweb.Controller):
585 _cp_path = "/base/view"
587 def fields_view_get(self, request, model, view_id, view_type,
588 transform=True, toolbar=False, submenu=False):
589 Model = request.session.model(model)
590 context = request.session.eval_context(request.context)
591 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
592 # todo fme?: check that we should pass the evaluated context here
593 self.process_view(request.session, fvg, context, transform)
596 def process_view(self, session, fvg, context, transform):
597 # depending on how it feels, xmlrpclib.ServerProxy can translate
598 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
599 # enjoy unicode strings which can not be trivially converted to
600 # strings, and it blows up during parsing.
602 # So ensure we fix this retardation by converting view xml back to
604 if isinstance(fvg['arch'], unicode):
605 arch = fvg['arch'].encode('utf-8')
610 evaluation_context = session.evaluation_context(context or {})
611 xml = self.transform_view(arch, session, evaluation_context)
613 xml = ElementTree.fromstring(arch)
614 fvg['arch'] = Xml2Json.convert_element(xml)
616 for field in fvg['fields'].itervalues():
617 if field.get('views'):
618 for view in field["views"].itervalues():
619 self.process_view(session, view, None, transform)
620 if field.get('domain'):
621 field["domain"] = self.parse_domain(field["domain"], session)
622 if field.get('context'):
623 field["context"] = self.parse_context(field["context"], session)
625 @openerpweb.jsonrequest
626 def add_custom(self, request, view_id, arch):
627 CustomView = request.session.model('ir.ui.view.custom')
629 'user_id': request.session._uid,
632 }, request.session.eval_context(request.context))
633 return {'result': True}
635 @openerpweb.jsonrequest
636 def undo_custom(self, request, view_id, reset=False):
637 CustomView = request.session.model('ir.ui.view.custom')
638 context = request.session.eval_context(request.context)
639 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
640 0, False, False, context)
643 CustomView.unlink(vcustom, context)
645 CustomView.unlink([vcustom[0]], context)
646 return {'result': True}
647 return {'result': False}
649 def transform_view(self, view_string, session, context=None):
650 # transform nodes on the fly via iterparse, instead of
651 # doing it statically on the parsing result
652 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
654 for event, elem in parser:
658 self.parse_domains_and_contexts(elem, session)
661 def parse_domain(self, domain, session):
662 """ Parses an arbitrary string containing a domain, transforms it
663 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
665 :param domain: the domain to parse, if the domain is not a string it is assumed to
666 be a literal domain and is returned as-is
667 :param session: Current OpenERP session
668 :type session: openerpweb.openerpweb.OpenERPSession
670 if not isinstance(domain, (str, unicode)):
673 return openerpweb.ast.literal_eval(domain)
676 return openerpweb.nonliterals.Domain(session, domain)
678 def parse_context(self, context, session):
679 """ Parses an arbitrary string containing a context, transforms it
680 to either a literal context or a :class:`openerpweb.nonliterals.Context`
682 :param context: the context to parse, if the context is not a string it is assumed to
683 be a literal domain and is returned as-is
684 :param session: Current OpenERP session
685 :type session: openerpweb.openerpweb.OpenERPSession
687 if not isinstance(context, (str, unicode)):
690 return openerpweb.ast.literal_eval(context)
692 return openerpweb.nonliterals.Context(session, context)
694 def parse_domains_and_contexts(self, elem, session):
695 """ Converts domains and contexts from the view into Python objects,
696 either literals if they can be parsed by literal_eval or a special
697 placeholder object if the domain or context refers to free variables.
699 :param elem: the current node being parsed
700 :type param: xml.etree.ElementTree.Element
701 :param session: OpenERP session object, used to store and retrieve
703 :type session: openerpweb.openerpweb.OpenERPSession
705 for el in ['domain', 'filter_domain']:
706 domain = elem.get(el, '').strip()
708 elem.set(el, self.parse_domain(domain, session))
709 for el in ['context', 'default_get']:
710 context_string = elem.get(el, '').strip()
712 elem.set(el, self.parse_context(context_string, session))
714 class FormView(View):
715 _cp_path = "/base/formview"
717 @openerpweb.jsonrequest
718 def load(self, req, model, view_id, toolbar=False):
719 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
720 return {'fields_view': fields_view}
722 class ListView(View):
723 _cp_path = "/base/listview"
725 @openerpweb.jsonrequest
726 def load(self, req, model, view_id, toolbar=False):
727 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
728 return {'fields_view': fields_view}
730 def process_colors(self, view, row, context):
731 colors = view['arch']['attrs'].get('colors')
738 for pair in colors.split(';')
739 if eval(pair.split(':')[1], dict(context, **row))
744 elif len(color) == 1:
748 class SearchView(View):
749 _cp_path = "/base/searchview"
751 @openerpweb.jsonrequest
752 def load(self, req, model, view_id):
753 fields_view = self.fields_view_get(req, model, view_id, 'search')
754 return {'fields_view': fields_view}
756 @openerpweb.jsonrequest
757 def fields_get(self, req, model):
758 Model = req.session.model(model)
759 fields = Model.fields_get(False, req.session.eval_context(req.context))
760 for field in fields.values():
761 # shouldn't convert the views too?
762 if field.get('domain'):
763 field["domain"] = self.parse_domain(field["domain"], req.session)
764 if field.get('context'):
765 field["context"] = self.parse_domain(field["context"], req.session)
766 return {'fields': fields}
768 @openerpweb.jsonrequest
769 def get_filters(self, req, model):
770 Model = req.session.model("ir.filters")
771 filters = Model.get_filters(model)
772 for filter in filters:
773 filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
774 filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
777 class Binary(openerpweb.Controller):
778 _cp_path = "/base/binary"
780 @openerpweb.httprequest
781 def image(self, request, session_id, model, id, field, **kw):
782 cherrypy.response.headers['Content-Type'] = 'image/png'
783 Model = request.session.model(model)
784 context = request.session.eval_context(request.context)
787 res = Model.default_get([field], context).get(field, '')
789 res = Model.read([int(id)], [field], context)[0].get(field, '')
790 return base64.decodestring(res)
791 except: # TODO: what's the exception here?
792 return self.placeholder()
793 def placeholder(self):
794 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
796 @openerpweb.httprequest
797 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
798 Model = request.session.model(model)
799 context = request.session.eval_context(request.context)
800 res = Model.read([int(id)], [field, fieldname], context)[0]
801 filecontent = res.get(field, '')
803 raise cherrypy.NotFound
805 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
806 filename = '%s_%s' % (model.replace('.', '_'), id)
808 filename = res.get(fieldname, '') or filename
809 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
810 return base64.decodestring(filecontent)
812 @openerpweb.httprequest
813 def upload(self, request, session_id, callback, ufile=None):
814 cherrypy.response.timeout = 500
816 for key, val in cherrypy.request.headers.iteritems():
817 headers[key.lower()] = val
818 size = int(headers.get('content-length', 0))
819 # TODO: might be useful to have a configuration flag for max-length file uploads
821 out = """<script language="javascript" type="text/javascript">
822 var win = window.top.window,
824 if (typeof(callback) === 'function') {
825 callback.apply(this, %s);
827 win.jQuery('#oe_notification', win.document).notify('create', {
828 title: "Ajax File Upload",
829 text: "Could not find callback"
833 data = ufile.file.read()
834 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
836 args = [False, e.message]
837 return out % (simplejson.dumps(callback), simplejson.dumps(args))
839 @openerpweb.httprequest
840 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
841 cherrypy.response.timeout = 500
842 context = request.session.eval_context(request.context)
843 Model = request.session.model('ir.attachment')
845 out = """<script language="javascript" type="text/javascript">
846 var win = window.top.window,
848 if (typeof(callback) === 'function') {
849 callback.call(this, %s);
852 attachment_id = Model.create({
853 'name': ufile.filename,
854 'datas': base64.encodestring(ufile.file.read()),
859 'filename': ufile.filename,
863 args = { 'error': e.message }
864 return out % (simplejson.dumps(callback), simplejson.dumps(args))
866 class Action(openerpweb.Controller):
867 _cp_path = "/base/action"
869 @openerpweb.jsonrequest
870 def load(self, req, action_id):
871 Actions = req.session.model('ir.actions.actions')
873 context = req.session.eval_context(req.context)
874 action_type = Actions.read([action_id], ['type'], context)
876 action = req.session.model(action_type[0]['type']).read([action_id], False,
879 value = clean_action(action[0], req.session)
880 return {'result': value}
882 @openerpweb.jsonrequest
883 def run(self, req, action_id):
884 return clean_action(req.session.model('ir.actions.server').run(
885 [action_id], req.session.eval_context(req.context)), req.session)