1 # -*- coding: utf-8 -*-
4 from xml.etree import ElementTree
5 from cStringIO import StringIO
11 import openerpweb.nonliterals
16 # Should move to openerpweb.Xml2Json
19 # Simple and straightforward XML-to-JSON converter in Python
22 # URL: http://code.google.com/p/xml2json-direct/
24 def convert_to_json(s):
25 return simplejson.dumps(
26 Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
29 def convert_to_structure(s):
30 root = ElementTree.fromstring(s)
31 return Xml2Json.convert_element(root)
34 def convert_element(el, skip_whitespaces=True):
37 ns, name = el.tag.rsplit("}", 1)
39 res["namespace"] = ns[1:]
43 for k, v in el.items():
46 if el.text and (not skip_whitespaces or el.text.strip() != ''):
49 kids.append(Xml2Json.convert_element(kid))
50 if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
52 res["children"] = kids
55 #----------------------------------------------------------
56 # OpenERP Web base Controllers
57 #----------------------------------------------------------
59 class Session(openerpweb.Controller):
60 _cp_path = "/base/session"
62 def manifest_glob(self, addons, key):
65 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
68 resource_path[len(openerpweb.path_addons):]
69 for pattern in globlist
70 for resource_path in glob.glob(os.path.join(
71 openerpweb.path_addons, addon, pattern))
75 def concat_files(self, file_list):
76 """ Concatenate file content
77 return (concat,timestamp)
78 concat: concatenation of file content
79 timestamp: max(os.path.getmtime of file_list)
81 root = openerpweb.path_root
85 fname = os.path.join(root, i)
86 ftime = os.path.getmtime(fname)
87 if ftime > files_timestamp:
88 files_timestamp = ftime
89 files_content = open(fname).read()
90 files_concat = "".join(files_content)
93 @openerpweb.jsonrequest
94 def login(self, req, db, login, password):
95 req.session.login(db, login, password)
98 "session_id": req.session_id,
99 "uid": req.session._uid,
102 @openerpweb.jsonrequest
103 def sc_list(self, req):
104 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
105 req.session.eval_context(req.context))
107 @openerpweb.jsonrequest
108 def get_databases_list(self, req):
109 proxy = req.session.proxy("db")
112 return {"db_list": dbs}
114 @openerpweb.jsonrequest
115 def get_lang_list(self, req):
116 lang_list = [('en_US', 'English (US)')]
118 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
121 return {"lang_list": lang_list}
123 @openerpweb.jsonrequest
124 def db_operation(self, req, flag, **kw):
131 password = kw.get('password')
134 return req.session.proxy("db").drop(password, db)
136 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
137 return {'error': 'Bad super admin password !'}
139 return {'error': 'Could not drop database !'}
141 elif flag == 'backup':
143 password = kw.get('password')
145 res = req.session.proxy("db").dump(password, db)
147 cherrypy.response.headers['Content-Type'] = "application/data"
148 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
149 return base64.decodestring(res)
151 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
152 return {'error': 'Bad super admin password !'}
154 return {'error': 'Could not drop database !'}
156 elif flag == 'restore':
157 filename = kw.get('filename')
159 password = kw.get('password')
162 data = base64.encodestring(filename.file.read())
163 return req.session.proxy("db").restore(password, db, data)
165 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
166 return {'error': 'Bad super admin password !'}
168 return {'error': 'Could not restore database !'}
170 elif flag == 'change_password':
171 old_password = kw.get('old_password')
172 new_password = kw.get('new_password')
173 confirm_password = kw.get('confirm_password')
176 return req.session.proxy("db").change_admin_password(old_password, new_password)
178 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
179 return {'error': 'Bad super admin password !'}
181 return {'error': 'Error, password not changed !'}
183 @openerpweb.jsonrequest
184 def modules(self, req):
185 return {"modules": [name
186 for name, manifest in openerpweb.addons_manifest.iteritems()
187 if manifest.get('active', True)]}
189 @openerpweb.jsonrequest
190 def csslist(self, req, mods='base'):
191 return {'files': self.manifest_glob(mods.split(','), 'css')}
193 @openerpweb.jsonrequest
194 def jslist(self, req, mods='base'):
195 return {'files': self.manifest_glob(mods.split(','), 'js')}
197 def css(self, req, mods='base'):
198 files = self.manifest_glob(mods.split(','), 'css')
199 concat = self.concat_files(files)[0]
200 # TODO request set the Date of last modif and Etag
204 def js(self, req, mods='base'):
205 files = self.manifest_glob(mods.split(','), 'js')
206 concat = self.concat_files(files)[0]
207 # TODO request set the Date of last modif and Etag
211 @openerpweb.jsonrequest
212 def eval_domain_and_context(self, req, contexts, domains,
214 """ Evaluates sequences of domains and contexts, composing them into
215 a single context, domain or group_by sequence.
217 :param list contexts: list of contexts to merge together. Contexts are
218 evaluated in sequence, all previous contexts
219 are part of their own evaluation context
220 (starting at the session context).
221 :param list domains: list of domains to merge together. Domains are
222 evaluated in sequence and appended to one another
223 (implicit AND), their evaluation domain is the
224 result of merging all contexts.
225 :param list group_by_seq: list of domains (which may be in a different
226 order than the ``contexts`` parameter),
227 evaluated in sequence, their ``'group_by'``
228 key is extracted if they have one.
233 the global context created by merging all of
237 the concatenation of all domains
240 a list of fields to group by, potentially empty (in which case
241 no group by should be performed)
243 context, domain = eval_context_and_domain(req.session,
244 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
245 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
247 group_by_sequence = []
248 for candidate in (group_by_seq or []):
249 ctx = req.session.eval_context(candidate, context)
250 group_by = ctx.get('group_by')
253 elif isinstance(group_by, basestring):
254 group_by_sequence.append(group_by)
256 group_by_sequence.extend(group_by)
261 'group_by': group_by_sequence
264 @openerpweb.jsonrequest
265 def save_session_action(self, req, the_action):
267 This method store an action object in the session object and returns an integer
268 identifying that action. The method get_session_action() can be used to get
271 :param the_action: The action to save in the session.
272 :type the_action: anything
273 :return: A key identifying the saved action.
276 saved_actions = cherrypy.session.get('saved_actions')
277 if not saved_actions:
278 saved_actions = {"next":0, "actions":{}}
279 cherrypy.session['saved_actions'] = saved_actions
280 # we don't allow more than 10 stored actions
281 if len(saved_actions["actions"]) >= 10:
282 del saved_actions["actions"][min(saved_actions["actions"].keys())]
283 key = saved_actions["next"]
284 saved_actions["actions"][key] = the_action
285 saved_actions["next"] = key + 1
288 @openerpweb.jsonrequest
289 def get_session_action(self, req, key):
291 Gets back a previously saved action. This method can return None if the action
292 was saved since too much time (this case should be handled in a smart way).
294 :param key: The key given by save_session_action()
296 :return: The saved action or None.
299 saved_actions = cherrypy.session.get('saved_actions')
300 if not saved_actions:
302 return saved_actions["actions"].get(key)
304 def eval_context_and_domain(session, context, domain=None):
305 e_context = session.eval_context(context)
306 # should we give the evaluated context as an evaluation context to the domain?
307 e_domain = session.eval_domain(domain or [])
309 return e_context, e_domain
311 def load_actions_from_ir_values(req, key, key2, models, meta, context):
312 Values = req.session.model('ir.values')
313 actions = Values.get(key, key2, models, meta, context)
315 return [(id, name, clean_action(action, req.session))
316 for id, name, action in actions]
318 def clean_action(action, session):
319 if action['type'] != 'ir.actions.act_window':
321 # values come from the server, we can just eval them
322 if isinstance(action.get('context', None), basestring):
323 action['context'] = eval(
325 session.evaluation_context()) or {}
327 if isinstance(action.get('domain', None), basestring):
328 action['domain'] = eval(
330 session.evaluation_context(
331 action['context'])) or []
332 if 'flags' not in action:
333 # Set empty flags dictionary for web client.
334 action['flags'] = dict()
335 return fix_view_modes(action)
337 def generate_views(action):
339 While the server generates a sequence called "views" computing dependencies
340 between a bunch of stuff for views coming directly from the database
341 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
342 to return custom view dictionaries generated on the fly.
344 In that case, there is no ``views`` key available on the action.
346 Since the web client relies on ``action['views']``, generate it here from
347 ``view_mode`` and ``view_id``.
349 Currently handles two different cases:
351 * no view_id, multiple view_mode
352 * single view_id, single view_mode
354 :param dict action: action descriptor dictionary to generate a views key for
356 view_id = action.get('view_id', False)
357 if isinstance(view_id, (list, tuple)):
360 # providing at least one view mode is a requirement, not an option
361 view_modes = action['view_mode'].split(',')
363 if len(view_modes) > 1:
365 raise ValueError('Non-db action dictionaries should provide '
366 'either multiple view modes or a single view '
367 'mode and an optional view id.\n\n Got view '
368 'modes %r and view id %r for action %r' % (
369 view_modes, view_id, action))
370 action['views'] = [(False, mode) for mode in view_modes]
372 action['views'] = [(view_id, view_modes[0])]
375 def fix_view_modes(action):
376 """ For historical reasons, OpenERP has weird dealings in relation to
377 view_mode and the view_type attribute (on window actions):
379 * one of the view modes is ``tree``, which stands for both list views
381 * the choice is made by checking ``view_type``, which is either
382 ``form`` for a list view or ``tree`` for an actual tree view
384 This methods simply folds the view_type into view_mode by adding a
385 new view mode ``list`` which is the result of the ``tree`` view_mode
386 in conjunction with the ``form`` view_type.
388 TODO: this should go into the doc, some kind of "peculiarities" section
390 :param dict action: an action descriptor
391 :returns: nothing, the action is modified in place
393 if 'views' not in action:
394 generate_views(action)
396 if action.pop('view_type') != 'form':
400 [id, mode if mode != 'tree' else 'list']
401 for id, mode in action['views']
406 class Menu(openerpweb.Controller):
407 _cp_path = "/base/menu"
409 @openerpweb.jsonrequest
411 return {'data': self.do_load(req)}
413 def do_load(self, req):
414 """ Loads all menu items (all applications and their sub-menus).
416 :param req: A request object, with an OpenERP session attribute
417 :type req: < session -> OpenERPSession >
418 :return: the menu root
419 :rtype: dict('children': menu_nodes)
421 Menus = req.session.model('ir.ui.menu')
422 # menus are loaded fully unlike a regular tree view, cause there are
423 # less than 512 items
424 context = req.session.eval_context(req.context)
425 menu_ids = Menus.search([], 0, False, False, context)
426 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
427 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
428 menu_items.append(menu_root)
430 # make a tree using parent_id
431 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
432 for menu_item in menu_items:
433 if menu_item['parent_id']:
434 parent = menu_item['parent_id'][0]
437 if parent in menu_items_map:
438 menu_items_map[parent].setdefault(
439 'children', []).append(menu_item)
441 # sort by sequence a tree using parent_id
442 for menu_item in menu_items:
443 menu_item.setdefault('children', []).sort(
444 key=lambda x:x["sequence"])
448 @openerpweb.jsonrequest
449 def action(self, req, menu_id):
450 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
451 [('ir.ui.menu', menu_id)], False,
452 req.session.eval_context(req.context))
453 return {"action": actions}
455 class DataSet(openerpweb.Controller):
456 _cp_path = "/base/dataset"
458 @openerpweb.jsonrequest
459 def fields(self, req, model):
460 return {'fields': req.session.model(model).fields_get(False,
461 req.session.eval_context(req.context))}
463 @openerpweb.jsonrequest
464 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
465 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
466 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
468 """ Performs a search() followed by a read() (if needed) using the
469 provided search criteria
471 :param request: a JSON-RPC request object
472 :type request: openerpweb.JsonRequest
473 :param str model: the name of the model to search on
474 :param fields: a list of the fields to return in the result records
476 :param int offset: from which index should the results start being returned
477 :param int limit: the maximum number of records to return
478 :param list domain: the search domain for the query
479 :param list sort: sorting directives
480 :returns: A structure (dict) with two keys: ids (all the ids matching
481 the (domain, context) pair) and records (paginated records
482 matching fields selection set)
485 Model = request.session.model(model)
486 context, domain = eval_context_and_domain(
487 request.session, request.context, domain)
489 ids = Model.search(domain, 0, False, sort or False, context)
490 # need to fill the dataset with all ids for the (domain, context) pair,
491 # so search un-paginated and paginate manually before reading
492 paginated_ids = ids[offset:(offset + limit if limit else None)]
493 if fields and fields == ['id']:
494 # shortcut read if we only want the ids
497 'records': map(lambda id: {'id': id}, paginated_ids)
500 records = Model.read(paginated_ids, fields or False, context)
501 records.sort(key=lambda obj: ids.index(obj['id']))
508 @openerpweb.jsonrequest
509 def get(self, request, model, ids, fields=False):
510 return self.do_get(request, model, ids, fields)
511 def do_get(self, request, model, ids, fields=False):
512 """ Fetches and returns the records of the model ``model`` whose ids
515 The results are in the same order as the inputs, but elements may be
516 missing (if there is no record left for the id)
518 :param request: the JSON-RPC2 request object
519 :type request: openerpweb.JsonRequest
520 :param model: the model to read from
522 :param ids: a list of identifiers
524 :param fields: a list of fields to fetch, ``False`` or empty to fetch
525 all fields in the model
526 :type fields: list | False
527 :returns: a list of records, in the same order as the list of ids
530 Model = request.session.model(model)
531 records = Model.read(ids, fields, request.session.eval_context(request.context))
533 record_map = dict((record['id'], record) for record in records)
535 return [record_map[id] for id in ids if record_map.get(id)]
537 @openerpweb.jsonrequest
538 def load(self, req, model, id, fields):
539 m = req.session.model(model)
541 r = m.read([id], False, req.session.eval_context(req.context))
544 return {'value': value}
546 @openerpweb.jsonrequest
547 def create(self, req, model, data):
548 m = req.session.model(model)
549 r = m.create(data, req.session.eval_context(req.context))
552 @openerpweb.jsonrequest
553 def save(self, req, model, id, data):
554 m = req.session.model(model)
555 r = m.write([id], data, req.session.eval_context(req.context))
558 @openerpweb.jsonrequest
559 def unlink(self, request, model, ids=()):
560 Model = request.session.model(model)
561 return Model.unlink(ids, request.session.eval_context(request.context))
563 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
564 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
565 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
566 c, d = eval_context_and_domain(req.session, context, domain)
567 if domain_id and len(args) - 1 >= domain_id:
569 if context_id and len(args) - 1 >= context_id:
572 return getattr(req.session.model(model), method)(*args)
574 @openerpweb.jsonrequest
575 def call(self, req, model, method, args, domain_id=None, context_id=None):
576 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
578 @openerpweb.jsonrequest
579 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
580 action = self.call_common(req, model, method, args, domain_id, context_id)
581 if isinstance(action, dict) and action.get('type') != '':
582 return {'result': clean_action(action, req.session)}
583 return {'result': False}
585 @openerpweb.jsonrequest
586 def exec_workflow(self, req, model, id, signal):
587 r = req.session.exec_workflow(model, id, signal)
590 @openerpweb.jsonrequest
591 def default_get(self, req, model, fields):
592 m = req.session.model(model)
593 r = m.default_get(fields, req.session.eval_context(req.context))
596 class DataGroup(openerpweb.Controller):
597 _cp_path = "/base/group"
598 @openerpweb.jsonrequest
599 def read(self, request, model, group_by_fields, domain=None):
600 Model = request.session.model(model)
601 context, domain = eval_context_and_domain(request.session, request.context, domain)
603 return Model.read_group(
604 domain or [], False, group_by_fields, 0, False,
605 dict(context, group_by=group_by_fields))
607 class View(openerpweb.Controller):
608 _cp_path = "/base/view"
610 def fields_view_get(self, request, model, view_id, view_type,
611 transform=True, toolbar=False, submenu=False):
612 Model = request.session.model(model)
613 context = request.session.eval_context(request.context)
614 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
615 # todo fme?: check that we should pass the evaluated context here
616 self.process_view(request.session, fvg, context, transform)
619 def process_view(self, session, fvg, context, transform):
620 # depending on how it feels, xmlrpclib.ServerProxy can translate
621 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
622 # enjoy unicode strings which can not be trivially converted to
623 # strings, and it blows up during parsing.
625 # So ensure we fix this retardation by converting view xml back to
627 if isinstance(fvg['arch'], unicode):
628 arch = fvg['arch'].encode('utf-8')
633 evaluation_context = session.evaluation_context(context or {})
634 xml = self.transform_view(arch, session, evaluation_context)
636 xml = ElementTree.fromstring(arch)
637 fvg['arch'] = Xml2Json.convert_element(xml)
638 for field in fvg['fields'].values():
639 if field.has_key('views') and field['views']:
640 for view in field["views"].values():
641 self.process_view(session, view, None, transform)
643 @openerpweb.jsonrequest
644 def add_custom(self, request, view_id, arch):
645 CustomView = request.session.model('ir.ui.view.custom')
647 'user_id': request.session._uid,
650 }, request.session.eval_context(request.context))
651 return {'result': True}
653 @openerpweb.jsonrequest
654 def undo_custom(self, request, view_id, reset=False):
655 CustomView = request.session.model('ir.ui.view.custom')
656 context = request.session.eval_context(request.context)
657 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
658 0, False, False, context)
661 CustomView.unlink(vcustom, context)
663 CustomView.unlink([vcustom[0]], context)
664 return {'result': True}
665 return {'result': False}
667 def transform_view(self, view_string, session, context=None):
668 # transform nodes on the fly via iterparse, instead of
669 # doing it statically on the parsing result
670 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
672 for event, elem in parser:
676 self.parse_domains_and_contexts(elem, session)
679 def parse_domain(self, elem, attr_name, session):
680 """ Parses an attribute of the provided name as a domain, transforms it
681 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
683 :param elem: the node being parsed
684 :type param: xml.etree.ElementTree.Element
685 :param str attr_name: the name of the attribute which should be parsed
686 :param session: Current OpenERP session
687 :type session: openerpweb.openerpweb.OpenERPSession
689 domain = elem.get(attr_name, '').strip()
694 openerpweb.ast.literal_eval(
699 openerpweb.nonliterals.Domain(session, domain))
701 def parse_domains_and_contexts(self, elem, session):
702 """ Converts domains and contexts from the view into Python objects,
703 either literals if they can be parsed by literal_eval or a special
704 placeholder object if the domain or context refers to free variables.
706 :param elem: the current node being parsed
707 :type param: xml.etree.ElementTree.Element
708 :param session: OpenERP session object, used to store and retrieve
710 :type session: openerpweb.openerpweb.OpenERPSession
712 self.parse_domain(elem, 'domain', session)
713 self.parse_domain(elem, 'filter_domain', session)
714 for el in ['context', 'default_get']:
715 context_string = elem.get(el, '').strip()
719 openerpweb.ast.literal_eval(context_string))
722 openerpweb.nonliterals.Context(
723 session, context_string))
725 class FormView(View):
726 _cp_path = "/base/formview"
728 @openerpweb.jsonrequest
729 def load(self, req, model, view_id, toolbar=False):
730 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
731 return {'fields_view': fields_view}
733 class ListView(View):
734 _cp_path = "/base/listview"
736 @openerpweb.jsonrequest
737 def load(self, req, model, view_id, toolbar=False):
738 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
739 return {'fields_view': fields_view}
741 def process_colors(self, view, row, context):
742 colors = view['arch']['attrs'].get('colors')
749 for pair in colors.split(';')
750 if eval(pair.split(':')[1], dict(context, **row))
755 elif len(color) == 1:
759 class SearchView(View):
760 _cp_path = "/base/searchview"
762 @openerpweb.jsonrequest
763 def load(self, req, model, view_id):
764 fields_view = self.fields_view_get(req, model, view_id, 'search')
765 return {'fields_view': fields_view}
767 @openerpweb.jsonrequest
768 def fields_get(self, req, model):
769 Model = req.session.model(model)
770 fields = Model.fields_get(False, req.session.eval_context(req.context))
771 return {'fields': fields}
773 class Binary(openerpweb.Controller):
774 _cp_path = "/base/binary"
776 @openerpweb.httprequest
777 def image(self, request, session_id, model, id, field, **kw):
778 cherrypy.response.headers['Content-Type'] = 'image/png'
779 Model = request.session.model(model)
780 context = request.session.eval_context(request.context)
783 res = Model.default_get([field], context).get(field, '')
785 res = Model.read([int(id)], [field], context)[0].get(field, '')
786 return base64.decodestring(res)
787 except: # TODO: what's the exception here?
788 return self.placeholder()
789 def placeholder(self):
790 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
792 @openerpweb.httprequest
793 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
794 Model = request.session.model(model)
795 context = request.session.eval_context(request.context)
796 res = Model.read([int(id)], [field, fieldname], context)[0]
797 filecontent = res.get(field, '')
799 raise cherrypy.NotFound
801 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
802 filename = '%s_%s' % (model.replace('.', '_'), id)
804 filename = res.get(fieldname, '') or filename
805 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
806 return base64.decodestring(filecontent)
808 @openerpweb.httprequest
809 def upload(self, request, session_id, callback, ufile=None):
810 cherrypy.response.timeout = 500
812 for key, val in cherrypy.request.headers.iteritems():
813 headers[key.lower()] = val
814 size = int(headers.get('content-length', 0))
815 # TODO: might be useful to have a configuration flag for max-length file uploads
817 out = """<script language="javascript" type="text/javascript">
818 var win = window.top.window,
820 if (typeof(callback) === 'function') {
821 callback.apply(this, %s);
823 win.jQuery('#oe_notification', win.document).notify('create', {
824 title: "Ajax File Upload",
825 text: "Could not find callback"
829 data = ufile.file.read()
830 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
832 args = [False, e.message]
833 return out % (simplejson.dumps(callback), simplejson.dumps(args))
835 @openerpweb.httprequest
836 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
837 cherrypy.response.timeout = 500
838 context = request.session.eval_context(request.context)
839 Model = request.session.model('ir.attachment')
841 out = """<script language="javascript" type="text/javascript">
842 var win = window.top.window,
844 if (typeof(callback) === 'function') {
845 callback.call(this, %s);
848 attachment_id = Model.create({
849 'name': ufile.filename,
850 'datas': base64.encodestring(ufile.file.read()),
855 'filename': ufile.filename,
859 args = { 'error': e.message }
860 return out % (simplejson.dumps(callback), simplejson.dumps(args))
862 class Action(openerpweb.Controller):
863 _cp_path = "/base/action"
865 @openerpweb.jsonrequest
866 def load(self, req, action_id):
867 Actions = req.session.model('ir.actions.actions')
869 context = req.session.eval_context(req.context)
870 action_type = Actions.read([action_id], ['type'], context)
872 action = req.session.model(action_type[0]['type']).read([action_id], False,
875 value = clean_action(action[0], req.session)
876 return {'result': value}
878 @openerpweb.jsonrequest
879 def run(self, req, action_id):
880 return clean_action(req.session.model('ir.actions.server').run(
881 [action_id], req.session.eval_context(req.context)), req.session)