1 # -*- coding: utf-8 -*-
5 from xml.etree import ElementTree
6 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 class Session(openerpweb.Controller):
61 _cp_path = "/base/session"
63 def manifest_glob(self, addons, key):
66 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
69 resource_path[len(openerpweb.path_addons):]
70 for pattern in globlist
71 for resource_path in glob.glob(os.path.join(
72 openerpweb.path_addons, addon, pattern))
76 def concat_files(self, file_list):
77 """ Concatenate file content
78 return (concat,timestamp)
79 concat: concatenation of file content
80 timestamp: max(os.path.getmtime of file_list)
82 root = openerpweb.path_root
86 fname = os.path.join(root, i)
87 ftime = os.path.getmtime(fname)
88 if ftime > files_timestamp:
89 files_timestamp = ftime
90 files_content = open(fname).read()
91 files_concat = "".join(files_content)
94 @openerpweb.jsonrequest
95 def login(self, req, db, login, password):
96 req.session.login(db, login, password)
99 "session_id": req.session_id,
100 "uid": req.session._uid,
103 @openerpweb.jsonrequest
104 def sc_list(self, req):
105 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
106 req.session.eval_context(req.context))
108 @openerpweb.jsonrequest
109 def get_databases_list(self, req):
110 proxy = req.session.proxy("db")
113 return {"db_list": dbs}
115 @openerpweb.jsonrequest
116 def get_lang_list(self, req):
117 lang_list = [('en_US', 'English (US)')]
119 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
122 return {"lang_list": lang_list}
124 @openerpweb.jsonrequest
125 def db_operation(self, req, flag, **kw):
129 password = kw.get('password')
131 return req.session.proxy("db").drop(password, db)
133 elif flag == 'backup':
135 password = kw.get('password')
137 res = req.session.proxy("db").dump(password, db)
139 return base64.decodestring(res)
141 elif flag == 'restore':
142 filename = kw.get('filename')
144 password = kw.get('password')
146 data = base64.encodestring(filename.file.read())
147 return req.session.proxy("db").restore(password, db, data)
149 elif flag == 'change_password':
150 old_password = kw.get('old_password')
151 new_password = kw.get('new_password')
152 confirm_password = kw.get('confirm_password')
154 if old_password and new_password and confirm_password:
155 return req.session.proxy("db").change_admin_password(old_password, new_password)
157 @openerpweb.jsonrequest
158 def modules(self, req):
159 return {"modules": [name
160 for name, manifest in openerpweb.addons_manifest.iteritems()
161 if manifest.get('active', True)]}
163 @openerpweb.jsonrequest
164 def csslist(self, req, mods='base'):
165 return {'files': self.manifest_glob(mods.split(','), 'css')}
167 @openerpweb.jsonrequest
168 def jslist(self, req, mods='base'):
169 return {'files': self.manifest_glob(mods.split(','), 'js')}
171 def css(self, req, mods='base,base_hello'):
172 files = self.manifest_glob(mods.split(','), 'css')
173 concat = self.concat_files(files)[0]
174 # TODO request set the Date of last modif and Etag
178 def js(self, req, mods='base,base_hello'):
179 files = self.manifest_glob(mods.split(','), 'js')
180 concat = self.concat_files(files)[0]
181 # TODO request set the Date of last modif and Etag
185 @openerpweb.jsonrequest
186 def eval_domain_and_context(self, req, contexts, domains,
188 """ Evaluates sequences of domains and contexts, composing them into
189 a single context, domain or group_by sequence.
191 :param list contexts: list of contexts to merge together. Contexts are
192 evaluated in sequence, all previous contexts
193 are part of their own evaluation context
194 (starting at the session context).
195 :param list domains: list of domains to merge together. Domains are
196 evaluated in sequence and appended to one another
197 (implicit AND), their evaluation domain is the
198 result of merging all contexts.
199 :param list group_by_seq: list of domains (which may be in a different
200 order than the ``contexts`` parameter),
201 evaluated in sequence, their ``'group_by'``
202 key is extracted if they have one.
207 the global context created by merging all of
211 the concatenation of all domains
214 a list of fields to group by, potentially empty (in which case
215 no group by should be performed)
217 context, domain = eval_context_and_domain(req.session,
218 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
219 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
221 group_by_sequence = []
222 for candidate in (group_by_seq or []):
223 ctx = req.session.eval_context(candidate, context)
224 group_by = ctx.get('group_by')
227 elif isinstance(group_by, basestring):
228 group_by_sequence.append(group_by)
230 group_by_sequence.extend(group_by)
235 'group_by': group_by_sequence
238 @openerpweb.jsonrequest
239 def save_session_action(self, req, the_action):
241 This method store an action object in the session object and returns an integer
242 identifying that action. The method get_session_action() can be used to get
245 :param the_action: The action to save in the session.
246 :type the_action: anything
247 :return: A key identifying the saved action.
250 saved_actions = cherrypy.session.get('saved_actions')
251 if not saved_actions:
252 saved_actions = {"next":0, "actions":{}}
253 cherrypy.session['saved_actions'] = saved_actions
254 # we don't allow more than 10 stored actions
255 if len(saved_actions["actions"]) >= 10:
256 del saved_actions["actions"][min(saved_actions["actions"].keys())]
257 key = saved_actions["next"]
258 saved_actions["actions"][key] = the_action
259 saved_actions["next"] = key + 1
262 @openerpweb.jsonrequest
263 def get_session_action(self, req, key):
265 Gets back a previously saved action. This method can return None if the action
266 was saved since too much time (this case should be handled in a smart way).
268 :param key: The key given by save_session_action()
270 :return: The saved action or None.
273 saved_actions = cherrypy.session.get('saved_actions')
274 if not saved_actions:
276 return saved_actions["actions"].get(key)
278 def eval_context_and_domain(session, context, domain=None):
279 e_context = session.eval_context(context)
280 # should we give the evaluated context as an evaluation context to the domain?
281 e_domain = session.eval_domain(domain or [])
283 return e_context, e_domain
285 def load_actions_from_ir_values(req, key, key2, models, meta, context):
286 Values = req.session.model('ir.values')
287 actions = Values.get(key, key2, models, meta, context)
289 return [(id, name, clean_action(action, req.session))
290 for id, name, action in actions]
292 def clean_action(action, session):
293 if action['type'] != 'ir.actions.act_window':
295 # values come from the server, we can just eval them
296 if isinstance(action.get('context', None), basestring):
297 action['context'] = eval(
299 session.evaluation_context()) or {}
301 if isinstance(action.get('domain', None), basestring):
302 action['domain'] = eval(
304 session.evaluation_context(
305 action['context'])) or []
306 if 'flags' not in action:
307 # Set empty flags dictionary for web client.
308 action['flags'] = dict()
309 return fix_view_modes(action)
311 def generate_views(action):
313 While the server generates a sequence called "views" computing dependencies
314 between a bunch of stuff for views coming directly from the database
315 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
316 to return custom view dictionaries generated on the fly.
318 In that case, there is no ``views`` key available on the action.
320 Since the web client relies on ``action['views']``, generate it here from
321 ``view_mode`` and ``view_id``.
323 Currently handles two different cases:
325 * no view_id, multiple view_mode
326 * single view_id, single view_mode
328 :param dict action: action descriptor dictionary to generate a views key for
330 view_id = action.get('view_id', False)
331 if isinstance(view_id, (list, tuple)):
334 # providing at least one view mode is a requirement, not an option
335 view_modes = action['view_mode'].split(',')
337 if len(view_modes) > 1:
339 raise ValueError('Non-db action dictionaries should provide '
340 'either multiple view modes or a single view '
341 'mode and an optional view id.\n\n Got view '
342 'modes %r and view id %r for action %r' % (
343 view_modes, view_id, action))
344 action['views'] = [(False, mode) for mode in view_modes]
346 action['views'] = [(view_id, view_modes[0])]
349 def fix_view_modes(action):
350 """ For historical reasons, OpenERP has weird dealings in relation to
351 view_mode and the view_type attribute (on window actions):
353 * one of the view modes is ``tree``, which stands for both list views
355 * the choice is made by checking ``view_type``, which is either
356 ``form`` for a list view or ``tree`` for an actual tree view
358 This methods simply folds the view_type into view_mode by adding a
359 new view mode ``list`` which is the result of the ``tree`` view_mode
360 in conjunction with the ``form`` view_type.
362 TODO: this should go into the doc, some kind of "peculiarities" section
364 :param dict action: an action descriptor
365 :returns: nothing, the action is modified in place
367 if 'views' not in action:
368 generate_views(action)
370 if action.pop('view_type') != 'form':
374 [id, mode if mode != 'tree' else 'list']
375 for id, mode in action['views']
380 class Menu(openerpweb.Controller):
381 _cp_path = "/base/menu"
383 @openerpweb.jsonrequest
385 return {'data': self.do_load(req)}
387 def do_load(self, req):
388 """ Loads all menu items (all applications and their sub-menus).
390 :param req: A request object, with an OpenERP session attribute
391 :type req: < session -> OpenERPSession >
392 :return: the menu root
393 :rtype: dict('children': menu_nodes)
395 Menus = req.session.model('ir.ui.menu')
396 # menus are loaded fully unlike a regular tree view, cause there are
397 # less than 512 items
398 context = req.session.eval_context(req.context)
399 menu_ids = Menus.search([], 0, False, False, context)
400 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
401 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
402 menu_items.append(menu_root)
404 # make a tree using parent_id
405 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
406 for menu_item in menu_items:
407 if menu_item['parent_id']:
408 parent = menu_item['parent_id'][0]
411 if parent in menu_items_map:
412 menu_items_map[parent].setdefault(
413 'children', []).append(menu_item)
415 # sort by sequence a tree using parent_id
416 for menu_item in menu_items:
417 menu_item.setdefault('children', []).sort(
418 key=lambda x:x["sequence"])
422 @openerpweb.jsonrequest
423 def action(self, req, menu_id):
424 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
425 [('ir.ui.menu', menu_id)], False,
426 req.session.eval_context(req.context))
427 return {"action": actions}
429 class DataSet(openerpweb.Controller):
430 _cp_path = "/base/dataset"
432 @openerpweb.jsonrequest
433 def fields(self, req, model):
434 return {'fields': req.session.model(model).fields_get(False,
435 req.session.eval_context(req.context))}
437 @openerpweb.jsonrequest
438 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
439 return self.do_search_read(request, model, fields, offset, limit, domain, context, sort)
440 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
441 """ Performs a search() followed by a read() (if needed) using the
442 provided search criteria
444 :param request: a JSON-RPC request object
445 :type request: openerpweb.JsonRequest
446 :param str model: the name of the model to search on
447 :param fields: a list of the fields to return in the result records
449 :param int offset: from which index should the results start being returned
450 :param int limit: the maximum number of records to return
451 :param list domain: the search domain for the query
452 :param list sort: sorting directives
453 :returns: a list of result records
456 Model = request.session.model(model)
457 context, domain = eval_context_and_domain(request.session, request.context, domain)
459 ids = Model.search(domain, offset or 0, limit or False,
460 sort or False, context)
462 if fields and fields == ['id']:
463 # shortcut read if we only want the ids
464 return map(lambda id: {'id': id}, ids)
466 reads = Model.read(ids, fields or False, context)
467 reads.sort(key=lambda obj: ids.index(obj['id']))
470 @openerpweb.jsonrequest
471 def get(self, request, model, ids, fields=False):
472 return self.do_get(request, model, ids, fields)
473 def do_get(self, request, model, ids, fields=False):
474 """ Fetches and returns the records of the model ``model`` whose ids
477 The results are in the same order as the inputs, but elements may be
478 missing (if there is no record left for the id)
480 :param request: the JSON-RPC2 request object
481 :type request: openerpweb.JsonRequest
482 :param model: the model to read from
484 :param ids: a list of identifiers
486 :param fields: a list of fields to fetch, ``False`` or empty to fetch
487 all fields in the model
488 :type fields: list | False
489 :returns: a list of records, in the same order as the list of ids
492 Model = request.session.model(model)
493 records = Model.read(ids, fields, request.session.eval_context(request.context))
495 record_map = dict((record['id'], record) for record in records)
497 return [record_map[id] for id in ids if record_map.get(id)]
499 @openerpweb.jsonrequest
500 def load(self, req, model, id, fields):
501 m = req.session.model(model)
503 r = m.read([id], False, req.session.eval_context(req.context))
506 return {'value': value}
508 @openerpweb.jsonrequest
509 def create(self, req, model, data):
510 m = req.session.model(model)
511 r = m.create(data, req.session.eval_context(req.context))
514 @openerpweb.jsonrequest
515 def save(self, req, model, id, data):
516 m = req.session.model(model)
517 r = m.write([id], data, req.session.eval_context(req.context))
520 @openerpweb.jsonrequest
521 def unlink(self, request, model, ids=()):
522 Model = request.session.model(model)
523 return Model.unlink(ids, request.session.eval_context(request.context))
525 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
526 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
527 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
528 c, d = eval_context_and_domain(req.session, context, domain)
529 if domain_id and len(args) - 1 >= domain_id:
531 if context_id and len(args) - 1 >= context_id:
534 return getattr(req.session.model(model), method)(*args)
536 @openerpweb.jsonrequest
537 def call(self, req, model, method, args, domain_id=None, context_id=None):
538 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
540 @openerpweb.jsonrequest
541 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
542 action = self.call_common(req, model, method, args, domain_id, context_id)
543 if isinstance(action, dict) and action.get('type') != '':
544 return {'result': clean_action(action, req.session)}
545 return {'result': False}
547 @openerpweb.jsonrequest
548 def exec_workflow(self, req, model, id, signal):
549 r = req.session.exec_workflow(model, id, signal)
552 @openerpweb.jsonrequest
553 def default_get(self, req, model, fields):
554 m = req.session.model(model)
555 r = m.default_get(fields, req.session.eval_context(req.context))
558 class DataGroup(openerpweb.Controller):
559 _cp_path = "/base/group"
560 @openerpweb.jsonrequest
561 def read(self, request, model, group_by_fields, domain=None):
562 Model = request.session.model(model)
563 context, domain = eval_context_and_domain(request.session, request.context, domain)
565 return Model.read_group(
566 domain or [], False, group_by_fields, 0, False,
567 dict(context, group_by=group_by_fields))
569 class View(openerpweb.Controller):
570 _cp_path = "/base/view"
572 def fields_view_get(self, request, model, view_id, view_type,
573 transform=True, toolbar=False, submenu=False):
574 Model = request.session.model(model)
575 context = request.session.eval_context(request.context)
576 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
577 # todo fme?: check that we should pass the evaluated context here
578 self.process_view(request.session, fvg, context, transform)
581 def process_view(self, session, fvg, context, transform):
582 # depending on how it feels, xmlrpclib.ServerProxy can translate
583 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
584 # enjoy unicode strings which can not be trivially converted to
585 # strings, and it blows up during parsing.
587 # So ensure we fix this retardation by converting view xml back to
589 if isinstance(fvg['arch'], unicode):
590 arch = fvg['arch'].encode('utf-8')
595 evaluation_context = session.evaluation_context(context or {})
596 xml = self.transform_view(arch, session, evaluation_context)
598 xml = ElementTree.fromstring(arch)
599 fvg['arch'] = Xml2Json.convert_element(xml)
600 for field in fvg['fields'].values():
601 if field.has_key('views') and field['views']:
602 for view in field["views"].values():
603 self.process_view(session, view, None, transform)
605 @openerpweb.jsonrequest
606 def add_custom(self, request, view_id, arch):
607 CustomView = request.session.model('ir.ui.view.custom')
609 'user_id': request.session._uid,
612 }, request.session.eval_context(request.context))
613 return {'result': True}
615 @openerpweb.jsonrequest
616 def undo_custom(self, request, view_id, reset=False):
617 CustomView = request.session.model('ir.ui.view.custom')
618 context = request.session.eval_context(request.context)
619 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
620 0, False, False, context)
623 CustomView.unlink(vcustom, context)
625 CustomView.unlink([vcustom[0]], context)
626 return {'result': True}
627 return {'result': False}
629 def normalize_attrs(self, elem, context):
630 """ Normalize @attrs, @invisible, @required, @readonly and @states, so
631 the client only has to deal with @attrs.
633 See `the discoveries pad <http://pad.openerp.com/discoveries>`_ for
636 :param elem: the current view node (Python object)
637 :type elem: xml.etree.ElementTree.Element
638 :param dict context: evaluation context
640 # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
641 attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
642 if 'states' in elem.attrib:
643 attrs.setdefault('invisible', [])\
644 .append(('state', 'not in', elem.attrib.pop('states').split(',')))
646 elem.set('attrs', simplejson.dumps(attrs))
647 for a in ['invisible', 'readonly', 'required']:
649 # In the XML we trust
650 avalue = bool(eval(elem.get(a, 'False'),
651 {'context': context or {}}))
656 if a == 'invisible' and 'attrs' in elem.attrib:
657 del elem.attrib['attrs']
659 def transform_view(self, view_string, session, context=None):
660 # transform nodes on the fly via iterparse, instead of
661 # doing it statically on the parsing result
662 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
664 for event, elem in parser:
668 self.normalize_attrs(elem, context)
669 self.parse_domains_and_contexts(elem, session)
672 def parse_domain(self, elem, attr_name, session):
673 """ Parses an attribute of the provided name as a domain, transforms it
674 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
676 :param elem: the node being parsed
677 :type param: xml.etree.ElementTree.Element
678 :param str attr_name: the name of the attribute which should be parsed
679 :param session: Current OpenERP session
680 :type session: openerpweb.openerpweb.OpenERPSession
682 domain = elem.get(attr_name, '').strip()
687 openerpweb.ast.literal_eval(
692 openerpweb.nonliterals.Domain(session, domain))
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 self.parse_domain(elem, 'domain', session)
706 self.parse_domain(elem, 'filter_domain', session)
707 for el in ['context', 'default_get']:
708 context_string = elem.get(el, '').strip()
712 openerpweb.ast.literal_eval(context_string))
715 openerpweb.nonliterals.Context(
716 session, context_string))
718 class FormView(View):
719 _cp_path = "/base/formview"
721 @openerpweb.jsonrequest
722 def load(self, req, model, view_id, toolbar=False):
723 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
724 return {'fields_view': fields_view}
726 class ListView(View):
727 _cp_path = "/base/listview"
729 @openerpweb.jsonrequest
730 def load(self, req, model, view_id, toolbar=False):
731 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
732 return {'fields_view': fields_view}
734 def process_colors(self, view, row, context):
735 colors = view['arch']['attrs'].get('colors')
742 for pair in colors.split(';')
743 if eval(pair.split(':')[1], dict(context, **row))
748 elif len(color) == 1:
752 class SearchView(View):
753 _cp_path = "/base/searchview"
755 @openerpweb.jsonrequest
756 def load(self, req, model, view_id):
757 fields_view = self.fields_view_get(req, model, view_id, 'search')
758 return {'fields_view': fields_view}
760 @openerpweb.jsonrequest
761 def fields_get(self, req, model):
762 Model = req.session.model(model)
763 fields = Model.fields_get(False, req.session.eval_context(req.context))
764 return {'fields': fields}
766 class Binary(openerpweb.Controller):
767 _cp_path = "/base/binary"
769 @openerpweb.httprequest
770 def image(self, request, session_id, model, id, field, **kw):
771 cherrypy.response.headers['Content-Type'] = 'image/png'
772 Model = request.session.model(model)
773 context = request.session.eval_context(request.context)
776 res = Model.default_get([field], context).get(field, '')
778 res = Model.read([int(id)], [field], context)[0].get(field, '')
779 return base64.decodestring(res)
780 except: # TODO: what's the exception here?
781 return self.placeholder()
782 def placeholder(self):
783 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
785 @openerpweb.httprequest
786 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
787 Model = request.session.model(model)
788 context = request.session.eval_context(request.context)
789 res = Model.read([int(id)], [field, fieldname], context)[0]
790 filecontent = res.get(field, '')
792 raise cherrypy.NotFound
794 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
795 filename = '%s_%s' % (model.replace('.', '_'), id)
797 filename = res.get(fieldname, '') or filename
798 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
799 return base64.decodestring(filecontent)
801 @openerpweb.httprequest
802 def upload(self, request, session_id, callback, ufile=None):
803 cherrypy.response.timeout = 500
805 for key, val in cherrypy.request.headers.iteritems():
806 headers[key.lower()] = val
807 size = int(headers.get('content-length', 0))
808 # TODO: might be useful to have a configuration flag for max-length file uploads
810 out = """<script language="javascript" type="text/javascript">
811 var win = window.top.window,
813 if (typeof(callback) === 'function') {
814 callback.apply(this, %s);
816 win.jQuery('#oe_notification', win.document).notify('create', {
817 title: "Ajax File Upload",
818 text: "Could not find callback"
822 data = ufile.file.read()
823 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
825 args = [False, e.message]
826 return out % (simplejson.dumps(callback), simplejson.dumps(args))
828 @openerpweb.httprequest
829 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
830 cherrypy.response.timeout = 500
831 context = request.session.eval_context(request.context)
832 Model = request.session.model('ir.attachment')
834 out = """<script language="javascript" type="text/javascript">
835 var win = window.top.window,
837 if (typeof(callback) === 'function') {
838 callback.call(this, %s);
841 attachment_id = Model.create({
842 'name': ufile.filename,
843 'datas': base64.encodestring(ufile.file.read()),
848 'filename': ufile.filename,
852 args = { 'error': e.message }
853 return out % (simplejson.dumps(callback), simplejson.dumps(args))
855 class Action(openerpweb.Controller):
856 _cp_path = "/base/action"
858 @openerpweb.jsonrequest
859 def load(self, req, action_id):
860 Actions = req.session.model('ir.actions.actions')
862 context = req.session.eval_context(req.context)
863 action_type = Actions.read([action_id], ['type'], context)
865 action = req.session.model(action_type[0]['type']).read([action_id], False,
868 value = clean_action(action[0], req.session)
869 return {'result': value}
871 @openerpweb.jsonrequest
872 def run(self, req, action_id):
873 return clean_action(req.session.model('ir.actions.server').run(
874 [action_id], req.session.eval_context(req.context)), req.session)