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 = rpc.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 rpc.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 return rpc.session.proxy("db").change_admin_password(old_password, new_password)
156 @openerpweb.jsonrequest
157 def modules(self, req):
158 return {"modules": [name
159 for name, manifest in openerpweb.addons_manifest.iteritems()
160 if manifest.get('active', True)]}
162 @openerpweb.jsonrequest
163 def csslist(self, req, mods='base'):
164 return {'files': self.manifest_glob(mods.split(','), 'css')}
166 @openerpweb.jsonrequest
167 def jslist(self, req, mods='base'):
168 return {'files': self.manifest_glob(mods.split(','), 'js')}
170 def css(self, req, mods='base,base_hello'):
171 files = self.manifest_glob(mods.split(','), 'css')
172 concat = self.concat_files(files)[0]
173 # TODO request set the Date of last modif and Etag
177 def js(self, req, mods='base,base_hello'):
178 files = self.manifest_glob(mods.split(','), 'js')
179 concat = self.concat_files(files)[0]
180 # TODO request set the Date of last modif and Etag
184 @openerpweb.jsonrequest
185 def eval_domain_and_context(self, req, contexts, domains,
187 """ Evaluates sequences of domains and contexts, composing them into
188 a single context, domain or group_by sequence.
190 :param list contexts: list of contexts to merge together. Contexts are
191 evaluated in sequence, all previous contexts
192 are part of their own evaluation context
193 (starting at the session context).
194 :param list domains: list of domains to merge together. Domains are
195 evaluated in sequence and appended to one another
196 (implicit AND), their evaluation domain is the
197 result of merging all contexts.
198 :param list group_by_seq: list of domains (which may be in a different
199 order than the ``contexts`` parameter),
200 evaluated in sequence, their ``'group_by'``
201 key is extracted if they have one.
206 the global context created by merging all of
210 the concatenation of all domains
213 a list of fields to group by, potentially empty (in which case
214 no group by should be performed)
216 context, domain = eval_context_and_domain(req.session,
217 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
218 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
220 group_by_sequence = []
221 for candidate in (group_by_seq or []):
222 ctx = req.session.eval_context(candidate, context)
223 group_by = ctx.get('group_by')
226 elif isinstance(group_by, basestring):
227 group_by_sequence.append(group_by)
229 group_by_sequence.extend(group_by)
234 'group_by': group_by_sequence
237 @openerpweb.jsonrequest
238 def save_session_action(self, req, the_action):
240 This method store an action object in the session object and returns an integer
241 identifying that action. The method get_session_action() can be used to get
244 :param the_action: The action to save in the session.
245 :type the_action: anything
246 :return: A key identifying the saved action.
249 saved_actions = cherrypy.session.get('saved_actions')
250 if not saved_actions:
251 saved_actions = {"next":0, "actions":{}}
252 cherrypy.session['saved_actions'] = saved_actions
253 # we don't allow more than 10 stored actions
254 if len(saved_actions["actions"]) >= 10:
255 del saved_actions["actions"][min(saved_actions["actions"].keys())]
256 key = saved_actions["next"]
257 saved_actions["actions"][key] = the_action
258 saved_actions["next"] = key + 1
261 @openerpweb.jsonrequest
262 def get_session_action(self, req, key):
264 Gets back a previously saved action. This method can return None if the action
265 was saved since too much time (this case should be handled in a smart way).
267 :param key: The key given by save_session_action()
269 :return: The saved action or None.
272 saved_actions = cherrypy.session.get('saved_actions')
273 if not saved_actions:
275 return saved_actions["actions"].get(key)
277 def eval_context_and_domain(session, context, domain=None):
278 e_context = session.eval_context(context)
279 # should we give the evaluated context as an evaluation context to the domain?
280 e_domain = session.eval_domain(domain or [])
282 return e_context, e_domain
284 def load_actions_from_ir_values(req, key, key2, models, meta, context):
285 Values = req.session.model('ir.values')
286 actions = Values.get(key, key2, models, meta, context)
288 return [(id, name, clean_action(action, req.session))
289 for id, name, action in actions]
291 def clean_action(action, session):
292 if action['type'] != 'ir.actions.act_window':
294 # values come from the server, we can just eval them
295 if isinstance(action.get('context', None), basestring):
296 action['context'] = eval(
298 session.evaluation_context()) or {}
300 if isinstance(action.get('domain', None), basestring):
301 action['domain'] = eval(
303 session.evaluation_context(
304 action['context'])) or []
305 if 'flags' not in action:
306 # Set empty flags dictionary for web client.
307 action['flags'] = dict()
308 return fix_view_modes(action)
310 def generate_views(action):
312 While the server generates a sequence called "views" computing dependencies
313 between a bunch of stuff for views coming directly from the database
314 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
315 to return custom view dictionaries generated on the fly.
317 In that case, there is no ``views`` key available on the action.
319 Since the web client relies on ``action['views']``, generate it here from
320 ``view_mode`` and ``view_id``.
322 Currently handles two different cases:
324 * no view_id, multiple view_mode
325 * single view_id, single view_mode
327 :param dict action: action descriptor dictionary to generate a views key for
329 view_id = action.get('view_id', False)
330 if isinstance(view_id, (list, tuple)):
333 # providing at least one view mode is a requirement, not an option
334 view_modes = action['view_mode'].split(',')
336 if len(view_modes) > 1:
338 raise ValueError('Non-db action dictionaries should provide '
339 'either multiple view modes or a single view '
340 'mode and an optional view id.\n\n Got view '
341 'modes %r and view id %r for action %r' % (
342 view_modes, view_id, action))
343 action['views'] = [(False, mode) for mode in view_modes]
345 action['views'] = [(view_id, view_modes[0])]
348 def fix_view_modes(action):
349 """ For historical reasons, OpenERP has weird dealings in relation to
350 view_mode and the view_type attribute (on window actions):
352 * one of the view modes is ``tree``, which stands for both list views
354 * the choice is made by checking ``view_type``, which is either
355 ``form`` for a list view or ``tree`` for an actual tree view
357 This methods simply folds the view_type into view_mode by adding a
358 new view mode ``list`` which is the result of the ``tree`` view_mode
359 in conjunction with the ``form`` view_type.
361 TODO: this should go into the doc, some kind of "peculiarities" section
363 :param dict action: an action descriptor
364 :returns: nothing, the action is modified in place
366 if 'views' not in action:
367 generate_views(action)
369 if action.pop('view_type') != 'form':
373 [id, mode if mode != 'tree' else 'list']
374 for id, mode in action['views']
379 class Menu(openerpweb.Controller):
380 _cp_path = "/base/menu"
382 @openerpweb.jsonrequest
384 return {'data': self.do_load(req)}
386 def do_load(self, req):
387 """ Loads all menu items (all applications and their sub-menus).
389 :param req: A request object, with an OpenERP session attribute
390 :type req: < session -> OpenERPSession >
391 :return: the menu root
392 :rtype: dict('children': menu_nodes)
394 Menus = req.session.model('ir.ui.menu')
395 # menus are loaded fully unlike a regular tree view, cause there are
396 # less than 512 items
397 context = req.session.eval_context(req.context)
398 menu_ids = Menus.search([], 0, False, False, context)
399 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
400 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
401 menu_items.append(menu_root)
403 # make a tree using parent_id
404 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
405 for menu_item in menu_items:
406 if menu_item['parent_id']:
407 parent = menu_item['parent_id'][0]
410 if parent in menu_items_map:
411 menu_items_map[parent].setdefault(
412 'children', []).append(menu_item)
414 # sort by sequence a tree using parent_id
415 for menu_item in menu_items:
416 menu_item.setdefault('children', []).sort(
417 key=lambda x:x["sequence"])
421 @openerpweb.jsonrequest
422 def action(self, req, menu_id):
423 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
424 [('ir.ui.menu', menu_id)], False,
425 req.session.eval_context(req.context))
426 return {"action": actions}
428 class DataSet(openerpweb.Controller):
429 _cp_path = "/base/dataset"
431 @openerpweb.jsonrequest
432 def fields(self, req, model):
433 return {'fields': req.session.model(model).fields_get(False,
434 req.session.eval_context(req.context))}
436 @openerpweb.jsonrequest
437 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
438 return self.do_search_read(request, model, fields, offset, limit, domain, context, sort)
439 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
440 """ Performs a search() followed by a read() (if needed) using the
441 provided search criteria
443 :param request: a JSON-RPC request object
444 :type request: openerpweb.JsonRequest
445 :param str model: the name of the model to search on
446 :param fields: a list of the fields to return in the result records
448 :param int offset: from which index should the results start being returned
449 :param int limit: the maximum number of records to return
450 :param list domain: the search domain for the query
451 :param list sort: sorting directives
452 :returns: a list of result records
455 Model = request.session.model(model)
456 context, domain = eval_context_and_domain(request.session, request.context, domain)
458 ids = Model.search(domain, offset or 0, limit or False,
459 sort or False, context)
461 if fields and fields == ['id']:
462 # shortcut read if we only want the ids
463 return map(lambda id: {'id': id}, ids)
465 reads = Model.read(ids, fields or False, context)
466 reads.sort(key=lambda obj: ids.index(obj['id']))
469 @openerpweb.jsonrequest
470 def get(self, request, model, ids, fields=False):
471 return self.do_get(request, model, ids, fields)
472 def do_get(self, request, model, ids, fields=False):
473 """ Fetches and returns the records of the model ``model`` whose ids
476 The results are in the same order as the inputs, but elements may be
477 missing (if there is no record left for the id)
479 :param request: the JSON-RPC2 request object
480 :type request: openerpweb.JsonRequest
481 :param model: the model to read from
483 :param ids: a list of identifiers
485 :param fields: a list of fields to fetch, ``False`` or empty to fetch
486 all fields in the model
487 :type fields: list | False
488 :returns: a list of records, in the same order as the list of ids
491 Model = request.session.model(model)
492 records = Model.read(ids, fields, request.session.eval_context(request.context))
494 record_map = dict((record['id'], record) for record in records)
496 return [record_map[id] for id in ids if record_map.get(id)]
498 @openerpweb.jsonrequest
499 def load(self, req, model, id, fields):
500 m = req.session.model(model)
502 r = m.read([id], False, req.session.eval_context(req.context))
505 return {'value': value}
507 @openerpweb.jsonrequest
508 def create(self, req, model, data):
509 m = req.session.model(model)
510 r = m.create(data, req.session.eval_context(req.context))
513 @openerpweb.jsonrequest
514 def save(self, req, model, id, data):
515 m = req.session.model(model)
516 r = m.write([id], data, req.session.eval_context(req.context))
519 @openerpweb.jsonrequest
520 def unlink(self, request, model, ids=()):
521 Model = request.session.model(model)
522 return Model.unlink(ids, request.session.eval_context(request.context))
524 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
525 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
526 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
527 c, d = eval_context_and_domain(req.session, context, domain)
528 if domain_id and len(args) - 1 >= domain_id:
530 if context_id and len(args) - 1 >= context_id:
533 return getattr(req.session.model(model), method)(*args)
535 @openerpweb.jsonrequest
536 def call(self, req, model, method, args, domain_id=None, context_id=None):
537 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
539 @openerpweb.jsonrequest
540 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
541 action = self.call_common(req, model, method, args, domain_id, context_id)
542 if isinstance(action, dict) and action.get('type') != '':
543 return {'result': clean_action(action, req.session)}
544 return {'result': False}
546 @openerpweb.jsonrequest
547 def exec_workflow(self, req, model, id, signal):
548 r = req.session.exec_workflow(model, id, signal)
551 @openerpweb.jsonrequest
552 def default_get(self, req, model, fields):
553 m = req.session.model(model)
554 r = m.default_get(fields, req.session.eval_context(req.context))
557 class DataGroup(openerpweb.Controller):
558 _cp_path = "/base/group"
559 @openerpweb.jsonrequest
560 def read(self, request, model, group_by_fields, domain=None):
561 Model = request.session.model(model)
562 context, domain = eval_context_and_domain(request.session, request.context, domain)
564 return Model.read_group(
565 domain or [], False, group_by_fields, 0, False,
566 dict(context, group_by=group_by_fields))
568 class View(openerpweb.Controller):
569 _cp_path = "/base/view"
571 def fields_view_get(self, request, model, view_id, view_type,
572 transform=True, toolbar=False, submenu=False):
573 Model = request.session.model(model)
574 context = request.session.eval_context(request.context)
575 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
576 # todo fme?: check that we should pass the evaluated context here
577 self.process_view(request.session, fvg, context, transform)
580 def process_view(self, session, fvg, context, transform):
581 # depending on how it feels, xmlrpclib.ServerProxy can translate
582 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
583 # enjoy unicode strings which can not be trivially converted to
584 # strings, and it blows up during parsing.
586 # So ensure we fix this retardation by converting view xml back to
588 if isinstance(fvg['arch'], unicode):
589 arch = fvg['arch'].encode('utf-8')
594 evaluation_context = session.evaluation_context(context or {})
595 xml = self.transform_view(arch, session, evaluation_context)
597 xml = ElementTree.fromstring(arch)
598 fvg['arch'] = Xml2Json.convert_element(xml)
599 for field in fvg['fields'].values():
600 if field.has_key('views') and field['views']:
601 for view in field["views"].values():
602 self.process_view(session, view, None, transform)
604 @openerpweb.jsonrequest
605 def add_custom(self, request, view_id, arch):
606 CustomView = request.session.model('ir.ui.view.custom')
608 'user_id': request.session._uid,
611 }, request.session.eval_context(request.context))
612 return {'result': True}
614 @openerpweb.jsonrequest
615 def undo_custom(self, request, view_id, reset=False):
616 CustomView = request.session.model('ir.ui.view.custom')
617 context = request.session.eval_context(request.context)
618 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
619 0, False, False, context)
622 CustomView.unlink(vcustom, context)
624 CustomView.unlink([vcustom[0]], context)
625 return {'result': True}
626 return {'result': False}
628 def normalize_attrs(self, elem, context):
629 """ Normalize @attrs, @invisible, @required, @readonly and @states, so
630 the client only has to deal with @attrs.
632 See `the discoveries pad <http://pad.openerp.com/discoveries>`_ for
635 :param elem: the current view node (Python object)
636 :type elem: xml.etree.ElementTree.Element
637 :param dict context: evaluation context
639 # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
640 attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
641 if 'states' in elem.attrib:
642 attrs.setdefault('invisible', [])\
643 .append(('state', 'not in', elem.attrib.pop('states').split(',')))
645 elem.set('attrs', simplejson.dumps(attrs))
646 for a in ['invisible', 'readonly', 'required']:
648 # In the XML we trust
649 avalue = bool(eval(elem.get(a, 'False'),
650 {'context': context or {}}))
655 if a == 'invisible' and 'attrs' in elem.attrib:
656 del elem.attrib['attrs']
658 def transform_view(self, view_string, session, context=None):
659 # transform nodes on the fly via iterparse, instead of
660 # doing it statically on the parsing result
661 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
663 for event, elem in parser:
667 self.normalize_attrs(elem, context)
668 self.parse_domains_and_contexts(elem, session)
671 def parse_domain(self, elem, attr_name, session):
672 """ Parses an attribute of the provided name as a domain, transforms it
673 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
675 :param elem: the node being parsed
676 :type param: xml.etree.ElementTree.Element
677 :param str attr_name: the name of the attribute which should be parsed
678 :param session: Current OpenERP session
679 :type session: openerpweb.openerpweb.OpenERPSession
681 domain = elem.get(attr_name, '').strip()
686 openerpweb.ast.literal_eval(
691 openerpweb.nonliterals.Domain(session, domain))
693 def parse_domains_and_contexts(self, elem, session):
694 """ Converts domains and contexts from the view into Python objects,
695 either literals if they can be parsed by literal_eval or a special
696 placeholder object if the domain or context refers to free variables.
698 :param elem: the current node being parsed
699 :type param: xml.etree.ElementTree.Element
700 :param session: OpenERP session object, used to store and retrieve
702 :type session: openerpweb.openerpweb.OpenERPSession
704 self.parse_domain(elem, 'domain', session)
705 self.parse_domain(elem, 'filter_domain', session)
706 for el in ['context', 'default_get']:
707 context_string = elem.get(el, '').strip()
711 openerpweb.ast.literal_eval(context_string))
714 openerpweb.nonliterals.Context(
715 session, context_string))
717 class FormView(View):
718 _cp_path = "/base/formview"
720 @openerpweb.jsonrequest
721 def load(self, req, model, view_id, toolbar=False):
722 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
723 return {'fields_view': fields_view}
725 class ListView(View):
726 _cp_path = "/base/listview"
728 @openerpweb.jsonrequest
729 def load(self, req, model, view_id, toolbar=False):
730 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
731 return {'fields_view': fields_view}
733 def process_colors(self, view, row, context):
734 colors = view['arch']['attrs'].get('colors')
741 for pair in colors.split(';')
742 if eval(pair.split(':')[1], dict(context, **row))
747 elif len(color) == 1:
751 class SearchView(View):
752 _cp_path = "/base/searchview"
754 @openerpweb.jsonrequest
755 def load(self, req, model, view_id):
756 fields_view = self.fields_view_get(req, model, view_id, 'search')
757 return {'fields_view': fields_view}
759 @openerpweb.jsonrequest
760 def fields_get(self, req, model):
761 Model = req.session.model(model)
762 fields = Model.fields_get(False, req.session.eval_context(req.context))
763 return {'fields': fields}
765 class Binary(openerpweb.Controller):
766 _cp_path = "/base/binary"
768 @openerpweb.httprequest
769 def image(self, request, session_id, model, id, field, **kw):
770 cherrypy.response.headers['Content-Type'] = 'image/png'
771 Model = request.session.model(model)
772 context = request.session.eval_context(request.context)
775 res = Model.default_get([field], context).get(field, '')
777 res = Model.read([int(id)], [field], context)[0].get(field, '')
778 return base64.decodestring(res)
779 except: # TODO: what's the exception here?
780 return self.placeholder()
781 def placeholder(self):
782 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
784 @openerpweb.httprequest
785 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
786 Model = request.session.model(model)
787 context = request.session.eval_context(request.context)
788 res = Model.read([int(id)], [field, fieldname], context)[0]
789 filecontent = res.get(field, '')
791 raise cherrypy.NotFound
793 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
794 filename = '%s_%s' % (model.replace('.', '_'), id)
796 filename = res.get(fieldname, '') or filename
797 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
798 return base64.decodestring(filecontent)
800 @openerpweb.httprequest
801 def upload(self, request, session_id, callback, ufile=None):
802 cherrypy.response.timeout = 500
804 for key, val in cherrypy.request.headers.iteritems():
805 headers[key.lower()] = val
806 size = int(headers.get('content-length', 0))
807 # TODO: might be useful to have a configuration flag for max-length file uploads
809 out = """<script language="javascript" type="text/javascript">
810 var win = window.top.window,
812 if (typeof(callback) === 'function') {
813 callback.apply(this, %s);
815 win.jQuery('#oe_notification', win.document).notify('create', {
816 title: "Ajax File Upload",
817 text: "Could not find callback"
821 data = ufile.file.read()
822 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
824 args = [False, e.message]
825 return out % (simplejson.dumps(callback), simplejson.dumps(args))
827 @openerpweb.httprequest
828 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
829 cherrypy.response.timeout = 500
830 context = request.session.eval_context(request.context)
831 Model = request.session.model('ir.attachment')
833 out = """<script language="javascript" type="text/javascript">
834 var win = window.top.window,
836 if (typeof(callback) === 'function') {
837 callback.call(this, %s);
840 attachment_id = Model.create({
841 'name': ufile.filename,
842 'datas': base64.encodestring(ufile.file.read()),
847 'filename': ufile.filename,
851 args = { 'error': e.message }
852 return out % (simplejson.dumps(callback), simplejson.dumps(args))
854 class Action(openerpweb.Controller):
855 _cp_path = "/base/action"
857 @openerpweb.jsonrequest
858 def load(self, req, action_id):
859 Actions = req.session.model('ir.actions.actions')
861 context = req.session.eval_context(req.context)
862 action_type = Actions.read([action_id], ['type'], context)
864 action = req.session.model(action_type[0]['type']).read([action_id], False,
867 value = clean_action(action[0], req.session)
868 return {'result': value}
870 @openerpweb.jsonrequest
871 def run(self, req, action_id):
872 return clean_action(req.session.model('ir.actions.server').run(
873 [action_id], req.session.eval_context(req.context)), req.session)