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 cherrypy.response.headers['Content-Type'] = "application/data"
140 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
141 return base64.decodestring(res)
143 elif flag == 'restore':
144 filename = kw.get('filename')
146 password = kw.get('password')
148 data = base64.encodestring(filename.file.read())
149 return req.session.proxy("db").restore(password, db, data)
151 elif flag == 'change_password':
152 old_password = kw.get('old_password')
153 new_password = kw.get('new_password')
154 confirm_password = kw.get('confirm_password')
156 if old_password and new_password and confirm_password:
157 return req.session.proxy("db").change_admin_password(old_password, new_password)
159 @openerpweb.jsonrequest
160 def modules(self, req):
161 return {"modules": [name
162 for name, manifest in openerpweb.addons_manifest.iteritems()
163 if manifest.get('active', True)]}
165 @openerpweb.jsonrequest
166 def csslist(self, req, mods='base'):
167 return {'files': self.manifest_glob(mods.split(','), 'css')}
169 @openerpweb.jsonrequest
170 def jslist(self, req, mods='base'):
171 return {'files': self.manifest_glob(mods.split(','), 'js')}
173 def css(self, req, mods='base,base_hello'):
174 files = self.manifest_glob(mods.split(','), 'css')
175 concat = self.concat_files(files)[0]
176 # TODO request set the Date of last modif and Etag
180 def js(self, req, mods='base,base_hello'):
181 files = self.manifest_glob(mods.split(','), 'js')
182 concat = self.concat_files(files)[0]
183 # TODO request set the Date of last modif and Etag
187 @openerpweb.jsonrequest
188 def eval_domain_and_context(self, req, contexts, domains,
190 """ Evaluates sequences of domains and contexts, composing them into
191 a single context, domain or group_by sequence.
193 :param list contexts: list of contexts to merge together. Contexts are
194 evaluated in sequence, all previous contexts
195 are part of their own evaluation context
196 (starting at the session context).
197 :param list domains: list of domains to merge together. Domains are
198 evaluated in sequence and appended to one another
199 (implicit AND), their evaluation domain is the
200 result of merging all contexts.
201 :param list group_by_seq: list of domains (which may be in a different
202 order than the ``contexts`` parameter),
203 evaluated in sequence, their ``'group_by'``
204 key is extracted if they have one.
209 the global context created by merging all of
213 the concatenation of all domains
216 a list of fields to group by, potentially empty (in which case
217 no group by should be performed)
219 context, domain = eval_context_and_domain(req.session,
220 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
221 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
223 group_by_sequence = []
224 for candidate in (group_by_seq or []):
225 ctx = req.session.eval_context(candidate, context)
226 group_by = ctx.get('group_by')
229 elif isinstance(group_by, basestring):
230 group_by_sequence.append(group_by)
232 group_by_sequence.extend(group_by)
237 'group_by': group_by_sequence
240 @openerpweb.jsonrequest
241 def save_session_action(self, req, the_action):
243 This method store an action object in the session object and returns an integer
244 identifying that action. The method get_session_action() can be used to get
247 :param the_action: The action to save in the session.
248 :type the_action: anything
249 :return: A key identifying the saved action.
252 saved_actions = cherrypy.session.get('saved_actions')
253 if not saved_actions:
254 saved_actions = {"next":0, "actions":{}}
255 cherrypy.session['saved_actions'] = saved_actions
256 # we don't allow more than 10 stored actions
257 if len(saved_actions["actions"]) >= 10:
258 del saved_actions["actions"][min(saved_actions["actions"].keys())]
259 key = saved_actions["next"]
260 saved_actions["actions"][key] = the_action
261 saved_actions["next"] = key + 1
264 @openerpweb.jsonrequest
265 def get_session_action(self, req, key):
267 Gets back a previously saved action. This method can return None if the action
268 was saved since too much time (this case should be handled in a smart way).
270 :param key: The key given by save_session_action()
272 :return: The saved action or None.
275 saved_actions = cherrypy.session.get('saved_actions')
276 if not saved_actions:
278 return saved_actions["actions"].get(key)
280 def eval_context_and_domain(session, context, domain=None):
281 e_context = session.eval_context(context)
282 # should we give the evaluated context as an evaluation context to the domain?
283 e_domain = session.eval_domain(domain or [])
285 return e_context, e_domain
287 def load_actions_from_ir_values(req, key, key2, models, meta, context):
288 Values = req.session.model('ir.values')
289 actions = Values.get(key, key2, models, meta, context)
291 return [(id, name, clean_action(action, req.session))
292 for id, name, action in actions]
294 def clean_action(action, session):
295 if action['type'] != 'ir.actions.act_window':
297 # values come from the server, we can just eval them
298 if isinstance(action.get('context', None), basestring):
299 action['context'] = eval(
301 session.evaluation_context()) or {}
303 if isinstance(action.get('domain', None), basestring):
304 action['domain'] = eval(
306 session.evaluation_context(
307 action['context'])) or []
308 if 'flags' not in action:
309 # Set empty flags dictionary for web client.
310 action['flags'] = dict()
311 return fix_view_modes(action)
313 def generate_views(action):
315 While the server generates a sequence called "views" computing dependencies
316 between a bunch of stuff for views coming directly from the database
317 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
318 to return custom view dictionaries generated on the fly.
320 In that case, there is no ``views`` key available on the action.
322 Since the web client relies on ``action['views']``, generate it here from
323 ``view_mode`` and ``view_id``.
325 Currently handles two different cases:
327 * no view_id, multiple view_mode
328 * single view_id, single view_mode
330 :param dict action: action descriptor dictionary to generate a views key for
332 view_id = action.get('view_id', False)
333 if isinstance(view_id, (list, tuple)):
336 # providing at least one view mode is a requirement, not an option
337 view_modes = action['view_mode'].split(',')
339 if len(view_modes) > 1:
341 raise ValueError('Non-db action dictionaries should provide '
342 'either multiple view modes or a single view '
343 'mode and an optional view id.\n\n Got view '
344 'modes %r and view id %r for action %r' % (
345 view_modes, view_id, action))
346 action['views'] = [(False, mode) for mode in view_modes]
348 action['views'] = [(view_id, view_modes[0])]
351 def fix_view_modes(action):
352 """ For historical reasons, OpenERP has weird dealings in relation to
353 view_mode and the view_type attribute (on window actions):
355 * one of the view modes is ``tree``, which stands for both list views
357 * the choice is made by checking ``view_type``, which is either
358 ``form`` for a list view or ``tree`` for an actual tree view
360 This methods simply folds the view_type into view_mode by adding a
361 new view mode ``list`` which is the result of the ``tree`` view_mode
362 in conjunction with the ``form`` view_type.
364 TODO: this should go into the doc, some kind of "peculiarities" section
366 :param dict action: an action descriptor
367 :returns: nothing, the action is modified in place
369 if 'views' not in action:
370 generate_views(action)
372 if action.pop('view_type') != 'form':
376 [id, mode if mode != 'tree' else 'list']
377 for id, mode in action['views']
382 class Menu(openerpweb.Controller):
383 _cp_path = "/base/menu"
385 @openerpweb.jsonrequest
387 return {'data': self.do_load(req)}
389 def do_load(self, req):
390 """ Loads all menu items (all applications and their sub-menus).
392 :param req: A request object, with an OpenERP session attribute
393 :type req: < session -> OpenERPSession >
394 :return: the menu root
395 :rtype: dict('children': menu_nodes)
397 Menus = req.session.model('ir.ui.menu')
398 # menus are loaded fully unlike a regular tree view, cause there are
399 # less than 512 items
400 context = req.session.eval_context(req.context)
401 menu_ids = Menus.search([], 0, False, False, context)
402 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
403 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
404 menu_items.append(menu_root)
406 # make a tree using parent_id
407 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
408 for menu_item in menu_items:
409 if menu_item['parent_id']:
410 parent = menu_item['parent_id'][0]
413 if parent in menu_items_map:
414 menu_items_map[parent].setdefault(
415 'children', []).append(menu_item)
417 # sort by sequence a tree using parent_id
418 for menu_item in menu_items:
419 menu_item.setdefault('children', []).sort(
420 key=lambda x:x["sequence"])
424 @openerpweb.jsonrequest
425 def action(self, req, menu_id):
426 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
427 [('ir.ui.menu', menu_id)], False,
428 req.session.eval_context(req.context))
429 return {"action": actions}
431 class DataSet(openerpweb.Controller):
432 _cp_path = "/base/dataset"
434 @openerpweb.jsonrequest
435 def fields(self, req, model):
436 return {'fields': req.session.model(model).fields_get(False,
437 req.session.eval_context(req.context))}
439 @openerpweb.jsonrequest
440 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
441 return self.do_search_read(request, model, fields, offset, limit, domain, context, sort)
442 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
443 """ Performs a search() followed by a read() (if needed) using the
444 provided search criteria
446 :param request: a JSON-RPC request object
447 :type request: openerpweb.JsonRequest
448 :param str model: the name of the model to search on
449 :param fields: a list of the fields to return in the result records
451 :param int offset: from which index should the results start being returned
452 :param int limit: the maximum number of records to return
453 :param list domain: the search domain for the query
454 :param list sort: sorting directives
455 :returns: a list of result records
458 Model = request.session.model(model)
459 context, domain = eval_context_and_domain(request.session, request.context, domain)
461 ids = Model.search(domain, offset or 0, limit or False,
462 sort or False, context)
464 if fields and fields == ['id']:
465 # shortcut read if we only want the ids
466 return map(lambda id: {'id': id}, ids)
468 reads = Model.read(ids, fields or False, context)
469 reads.sort(key=lambda obj: ids.index(obj['id']))
472 @openerpweb.jsonrequest
473 def get(self, request, model, ids, fields=False):
474 return self.do_get(request, model, ids, fields)
475 def do_get(self, request, model, ids, fields=False):
476 """ Fetches and returns the records of the model ``model`` whose ids
479 The results are in the same order as the inputs, but elements may be
480 missing (if there is no record left for the id)
482 :param request: the JSON-RPC2 request object
483 :type request: openerpweb.JsonRequest
484 :param model: the model to read from
486 :param ids: a list of identifiers
488 :param fields: a list of fields to fetch, ``False`` or empty to fetch
489 all fields in the model
490 :type fields: list | False
491 :returns: a list of records, in the same order as the list of ids
494 Model = request.session.model(model)
495 records = Model.read(ids, fields, request.session.eval_context(request.context))
497 record_map = dict((record['id'], record) for record in records)
499 return [record_map[id] for id in ids if record_map.get(id)]
501 @openerpweb.jsonrequest
502 def load(self, req, model, id, fields):
503 m = req.session.model(model)
505 r = m.read([id], False, req.session.eval_context(req.context))
508 return {'value': value}
510 @openerpweb.jsonrequest
511 def create(self, req, model, data):
512 m = req.session.model(model)
513 r = m.create(data, req.session.eval_context(req.context))
516 @openerpweb.jsonrequest
517 def save(self, req, model, id, data):
518 m = req.session.model(model)
519 r = m.write([id], data, req.session.eval_context(req.context))
522 @openerpweb.jsonrequest
523 def unlink(self, request, model, ids=()):
524 Model = request.session.model(model)
525 return Model.unlink(ids, request.session.eval_context(request.context))
527 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
528 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
529 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
530 c, d = eval_context_and_domain(req.session, context, domain)
531 if domain_id and len(args) - 1 >= domain_id:
533 if context_id and len(args) - 1 >= context_id:
536 return getattr(req.session.model(model), method)(*args)
538 @openerpweb.jsonrequest
539 def call(self, req, model, method, args, domain_id=None, context_id=None):
540 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
542 @openerpweb.jsonrequest
543 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
544 action = self.call_common(req, model, method, args, domain_id, context_id)
545 if isinstance(action, dict) and action.get('type') != '':
546 return {'result': clean_action(action, req.session)}
547 return {'result': False}
549 @openerpweb.jsonrequest
550 def exec_workflow(self, req, model, id, signal):
551 r = req.session.exec_workflow(model, id, signal)
554 @openerpweb.jsonrequest
555 def default_get(self, req, model, fields):
556 m = req.session.model(model)
557 r = m.default_get(fields, req.session.eval_context(req.context))
560 class DataGroup(openerpweb.Controller):
561 _cp_path = "/base/group"
562 @openerpweb.jsonrequest
563 def read(self, request, model, group_by_fields, domain=None):
564 Model = request.session.model(model)
565 context, domain = eval_context_and_domain(request.session, request.context, domain)
567 return Model.read_group(
568 domain or [], False, group_by_fields, 0, False,
569 dict(context, group_by=group_by_fields))
571 class View(openerpweb.Controller):
572 _cp_path = "/base/view"
574 def fields_view_get(self, request, model, view_id, view_type,
575 transform=True, toolbar=False, submenu=False):
576 Model = request.session.model(model)
577 context = request.session.eval_context(request.context)
578 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
579 # todo fme?: check that we should pass the evaluated context here
580 self.process_view(request.session, fvg, context, transform)
583 def process_view(self, session, fvg, context, transform):
584 # depending on how it feels, xmlrpclib.ServerProxy can translate
585 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
586 # enjoy unicode strings which can not be trivially converted to
587 # strings, and it blows up during parsing.
589 # So ensure we fix this retardation by converting view xml back to
591 if isinstance(fvg['arch'], unicode):
592 arch = fvg['arch'].encode('utf-8')
597 evaluation_context = session.evaluation_context(context or {})
598 xml = self.transform_view(arch, session, evaluation_context)
600 xml = ElementTree.fromstring(arch)
601 fvg['arch'] = Xml2Json.convert_element(xml)
602 for field in fvg['fields'].values():
603 if field.has_key('views') and field['views']:
604 for view in field["views"].values():
605 self.process_view(session, view, None, transform)
607 @openerpweb.jsonrequest
608 def add_custom(self, request, view_id, arch):
609 CustomView = request.session.model('ir.ui.view.custom')
611 'user_id': request.session._uid,
614 }, request.session.eval_context(request.context))
615 return {'result': True}
617 @openerpweb.jsonrequest
618 def undo_custom(self, request, view_id, reset=False):
619 CustomView = request.session.model('ir.ui.view.custom')
620 context = request.session.eval_context(request.context)
621 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
622 0, False, False, context)
625 CustomView.unlink(vcustom, context)
627 CustomView.unlink([vcustom[0]], context)
628 return {'result': True}
629 return {'result': False}
631 def normalize_attrs(self, elem, context):
632 """ Normalize @attrs, @invisible, @required, @readonly and @states, so
633 the client only has to deal with @attrs.
635 See `the discoveries pad <http://pad.openerp.com/discoveries>`_ for
638 :param elem: the current view node (Python object)
639 :type elem: xml.etree.ElementTree.Element
640 :param dict context: evaluation context
642 # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
643 attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
644 if 'states' in elem.attrib:
645 attrs.setdefault('invisible', [])\
646 .append(('state', 'not in', elem.attrib.pop('states').split(',')))
648 elem.set('attrs', simplejson.dumps(attrs))
649 for a in ['invisible', 'readonly', 'required']:
651 # In the XML we trust
652 avalue = bool(eval(elem.get(a, 'False'),
653 {'context': context or {}}))
658 if a == 'invisible' and 'attrs' in elem.attrib:
659 del elem.attrib['attrs']
661 def transform_view(self, view_string, session, context=None):
662 # transform nodes on the fly via iterparse, instead of
663 # doing it statically on the parsing result
664 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
666 for event, elem in parser:
670 self.normalize_attrs(elem, context)
671 self.parse_domains_and_contexts(elem, session)
674 def parse_domain(self, elem, attr_name, session):
675 """ Parses an attribute of the provided name as a domain, transforms it
676 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
678 :param elem: the node being parsed
679 :type param: xml.etree.ElementTree.Element
680 :param str attr_name: the name of the attribute which should be parsed
681 :param session: Current OpenERP session
682 :type session: openerpweb.openerpweb.OpenERPSession
684 domain = elem.get(attr_name, '').strip()
689 openerpweb.ast.literal_eval(
694 openerpweb.nonliterals.Domain(session, domain))
696 def parse_domains_and_contexts(self, elem, session):
697 """ Converts domains and contexts from the view into Python objects,
698 either literals if they can be parsed by literal_eval or a special
699 placeholder object if the domain or context refers to free variables.
701 :param elem: the current node being parsed
702 :type param: xml.etree.ElementTree.Element
703 :param session: OpenERP session object, used to store and retrieve
705 :type session: openerpweb.openerpweb.OpenERPSession
707 self.parse_domain(elem, 'domain', session)
708 self.parse_domain(elem, 'filter_domain', session)
709 for el in ['context', 'default_get']:
710 context_string = elem.get(el, '').strip()
714 openerpweb.ast.literal_eval(context_string))
717 openerpweb.nonliterals.Context(
718 session, context_string))
720 class FormView(View):
721 _cp_path = "/base/formview"
723 @openerpweb.jsonrequest
724 def load(self, req, model, view_id, toolbar=False):
725 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
726 return {'fields_view': fields_view}
728 class ListView(View):
729 _cp_path = "/base/listview"
731 @openerpweb.jsonrequest
732 def load(self, req, model, view_id, toolbar=False):
733 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
734 return {'fields_view': fields_view}
736 def process_colors(self, view, row, context):
737 colors = view['arch']['attrs'].get('colors')
744 for pair in colors.split(';')
745 if eval(pair.split(':')[1], dict(context, **row))
750 elif len(color) == 1:
754 class SearchView(View):
755 _cp_path = "/base/searchview"
757 @openerpweb.jsonrequest
758 def load(self, req, model, view_id):
759 fields_view = self.fields_view_get(req, model, view_id, 'search')
760 return {'fields_view': fields_view}
762 @openerpweb.jsonrequest
763 def fields_get(self, req, model):
764 Model = req.session.model(model)
765 fields = Model.fields_get(False, req.session.eval_context(req.context))
766 return {'fields': fields}
768 class Binary(openerpweb.Controller):
769 _cp_path = "/base/binary"
771 @openerpweb.httprequest
772 def image(self, request, session_id, model, id, field, **kw):
773 cherrypy.response.headers['Content-Type'] = 'image/png'
774 Model = request.session.model(model)
775 context = request.session.eval_context(request.context)
778 res = Model.default_get([field], context).get(field, '')
780 res = Model.read([int(id)], [field], context)[0].get(field, '')
781 return base64.decodestring(res)
782 except: # TODO: what's the exception here?
783 return self.placeholder()
784 def placeholder(self):
785 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
787 @openerpweb.httprequest
788 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
789 Model = request.session.model(model)
790 context = request.session.eval_context(request.context)
791 res = Model.read([int(id)], [field, fieldname], context)[0]
792 filecontent = res.get(field, '')
794 raise cherrypy.NotFound
796 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
797 filename = '%s_%s' % (model.replace('.', '_'), id)
799 filename = res.get(fieldname, '') or filename
800 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
801 return base64.decodestring(filecontent)
803 @openerpweb.httprequest
804 def upload(self, request, session_id, callback, ufile=None):
805 cherrypy.response.timeout = 500
807 for key, val in cherrypy.request.headers.iteritems():
808 headers[key.lower()] = val
809 size = int(headers.get('content-length', 0))
810 # TODO: might be useful to have a configuration flag for max-length file uploads
812 out = """<script language="javascript" type="text/javascript">
813 var win = window.top.window,
815 if (typeof(callback) === 'function') {
816 callback.apply(this, %s);
818 win.jQuery('#oe_notification', win.document).notify('create', {
819 title: "Ajax File Upload",
820 text: "Could not find callback"
824 data = ufile.file.read()
825 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
827 args = [False, e.message]
828 return out % (simplejson.dumps(callback), simplejson.dumps(args))
830 @openerpweb.httprequest
831 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
832 cherrypy.response.timeout = 500
833 context = request.session.eval_context(request.context)
834 Model = request.session.model('ir.attachment')
836 out = """<script language="javascript" type="text/javascript">
837 var win = window.top.window,
839 if (typeof(callback) === 'function') {
840 callback.call(this, %s);
843 attachment_id = Model.create({
844 'name': ufile.filename,
845 'datas': base64.encodestring(ufile.file.read()),
850 'filename': ufile.filename,
854 args = { 'error': e.message }
855 return out % (simplejson.dumps(callback), simplejson.dumps(args))
857 class Action(openerpweb.Controller):
858 _cp_path = "/base/action"
860 @openerpweb.jsonrequest
861 def load(self, req, action_id):
862 Actions = req.session.model('ir.actions.actions')
864 context = req.session.eval_context(req.context)
865 action_type = Actions.read([action_id], ['type'], context)
867 action = req.session.model(action_type[0]['type']).read([action_id], False,
870 value = clean_action(action[0], req.session)
871 return {'result': value}
873 @openerpweb.jsonrequest
874 def run(self, req, action_id):
875 return clean_action(req.session.model('ir.actions.server').run(
876 [action_id], req.session.eval_context(req.context)), req.session)