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 return {'error': 'Could not create backup !'}
145 elif flag == 'restore':
146 filename = kw.get('filename')
148 password = kw.get('password')
152 data = base64.encodestring(filename.file.read())
153 return req.session.proxy("db").restore(password, db, data)
155 return {'error': 'Could not restore database !'}
157 elif flag == 'change_password':
158 old_password = kw.get('old_password')
159 new_password = kw.get('new_password')
160 confirm_password = kw.get('confirm_password')
162 if old_password and new_password and confirm_password:
163 return req.session.proxy("db").change_admin_password(old_password, new_password)
165 @openerpweb.jsonrequest
166 def modules(self, req):
167 return {"modules": [name
168 for name, manifest in openerpweb.addons_manifest.iteritems()
169 if manifest.get('active', True)]}
171 @openerpweb.jsonrequest
172 def csslist(self, req, mods='base'):
173 return {'files': self.manifest_glob(mods.split(','), 'css')}
175 @openerpweb.jsonrequest
176 def jslist(self, req, mods='base'):
177 return {'files': self.manifest_glob(mods.split(','), 'js')}
179 def css(self, req, mods='base,base_hello'):
180 files = self.manifest_glob(mods.split(','), 'css')
181 concat = self.concat_files(files)[0]
182 # TODO request set the Date of last modif and Etag
186 def js(self, req, mods='base,base_hello'):
187 files = self.manifest_glob(mods.split(','), 'js')
188 concat = self.concat_files(files)[0]
189 # TODO request set the Date of last modif and Etag
193 @openerpweb.jsonrequest
194 def eval_domain_and_context(self, req, contexts, domains,
196 """ Evaluates sequences of domains and contexts, composing them into
197 a single context, domain or group_by sequence.
199 :param list contexts: list of contexts to merge together. Contexts are
200 evaluated in sequence, all previous contexts
201 are part of their own evaluation context
202 (starting at the session context).
203 :param list domains: list of domains to merge together. Domains are
204 evaluated in sequence and appended to one another
205 (implicit AND), their evaluation domain is the
206 result of merging all contexts.
207 :param list group_by_seq: list of domains (which may be in a different
208 order than the ``contexts`` parameter),
209 evaluated in sequence, their ``'group_by'``
210 key is extracted if they have one.
215 the global context created by merging all of
219 the concatenation of all domains
222 a list of fields to group by, potentially empty (in which case
223 no group by should be performed)
225 context, domain = eval_context_and_domain(req.session,
226 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
227 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
229 group_by_sequence = []
230 for candidate in (group_by_seq or []):
231 ctx = req.session.eval_context(candidate, context)
232 group_by = ctx.get('group_by')
235 elif isinstance(group_by, basestring):
236 group_by_sequence.append(group_by)
238 group_by_sequence.extend(group_by)
243 'group_by': group_by_sequence
246 @openerpweb.jsonrequest
247 def save_session_action(self, req, the_action):
249 This method store an action object in the session object and returns an integer
250 identifying that action. The method get_session_action() can be used to get
253 :param the_action: The action to save in the session.
254 :type the_action: anything
255 :return: A key identifying the saved action.
258 saved_actions = cherrypy.session.get('saved_actions')
259 if not saved_actions:
260 saved_actions = {"next":0, "actions":{}}
261 cherrypy.session['saved_actions'] = saved_actions
262 # we don't allow more than 10 stored actions
263 if len(saved_actions["actions"]) >= 10:
264 del saved_actions["actions"][min(saved_actions["actions"].keys())]
265 key = saved_actions["next"]
266 saved_actions["actions"][key] = the_action
267 saved_actions["next"] = key + 1
270 @openerpweb.jsonrequest
271 def get_session_action(self, req, key):
273 Gets back a previously saved action. This method can return None if the action
274 was saved since too much time (this case should be handled in a smart way).
276 :param key: The key given by save_session_action()
278 :return: The saved action or None.
281 saved_actions = cherrypy.session.get('saved_actions')
282 if not saved_actions:
284 return saved_actions["actions"].get(key)
286 def eval_context_and_domain(session, context, domain=None):
287 e_context = session.eval_context(context)
288 # should we give the evaluated context as an evaluation context to the domain?
289 e_domain = session.eval_domain(domain or [])
291 return e_context, e_domain
293 def load_actions_from_ir_values(req, key, key2, models, meta, context):
294 Values = req.session.model('ir.values')
295 actions = Values.get(key, key2, models, meta, context)
297 return [(id, name, clean_action(action, req.session))
298 for id, name, action in actions]
300 def clean_action(action, session):
301 if action['type'] != 'ir.actions.act_window':
303 # values come from the server, we can just eval them
304 if isinstance(action.get('context', None), basestring):
305 action['context'] = eval(
307 session.evaluation_context()) or {}
309 if isinstance(action.get('domain', None), basestring):
310 action['domain'] = eval(
312 session.evaluation_context(
313 action['context'])) or []
314 if 'flags' not in action:
315 # Set empty flags dictionary for web client.
316 action['flags'] = dict()
317 return fix_view_modes(action)
319 def generate_views(action):
321 While the server generates a sequence called "views" computing dependencies
322 between a bunch of stuff for views coming directly from the database
323 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
324 to return custom view dictionaries generated on the fly.
326 In that case, there is no ``views`` key available on the action.
328 Since the web client relies on ``action['views']``, generate it here from
329 ``view_mode`` and ``view_id``.
331 Currently handles two different cases:
333 * no view_id, multiple view_mode
334 * single view_id, single view_mode
336 :param dict action: action descriptor dictionary to generate a views key for
338 view_id = action.get('view_id', False)
339 if isinstance(view_id, (list, tuple)):
342 # providing at least one view mode is a requirement, not an option
343 view_modes = action['view_mode'].split(',')
345 if len(view_modes) > 1:
347 raise ValueError('Non-db action dictionaries should provide '
348 'either multiple view modes or a single view '
349 'mode and an optional view id.\n\n Got view '
350 'modes %r and view id %r for action %r' % (
351 view_modes, view_id, action))
352 action['views'] = [(False, mode) for mode in view_modes]
354 action['views'] = [(view_id, view_modes[0])]
357 def fix_view_modes(action):
358 """ For historical reasons, OpenERP has weird dealings in relation to
359 view_mode and the view_type attribute (on window actions):
361 * one of the view modes is ``tree``, which stands for both list views
363 * the choice is made by checking ``view_type``, which is either
364 ``form`` for a list view or ``tree`` for an actual tree view
366 This methods simply folds the view_type into view_mode by adding a
367 new view mode ``list`` which is the result of the ``tree`` view_mode
368 in conjunction with the ``form`` view_type.
370 TODO: this should go into the doc, some kind of "peculiarities" section
372 :param dict action: an action descriptor
373 :returns: nothing, the action is modified in place
375 if 'views' not in action:
376 generate_views(action)
378 if action.pop('view_type') != 'form':
382 [id, mode if mode != 'tree' else 'list']
383 for id, mode in action['views']
388 class Menu(openerpweb.Controller):
389 _cp_path = "/base/menu"
391 @openerpweb.jsonrequest
393 return {'data': self.do_load(req)}
395 def do_load(self, req):
396 """ Loads all menu items (all applications and their sub-menus).
398 :param req: A request object, with an OpenERP session attribute
399 :type req: < session -> OpenERPSession >
400 :return: the menu root
401 :rtype: dict('children': menu_nodes)
403 Menus = req.session.model('ir.ui.menu')
404 # menus are loaded fully unlike a regular tree view, cause there are
405 # less than 512 items
406 context = req.session.eval_context(req.context)
407 menu_ids = Menus.search([], 0, False, False, context)
408 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
409 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
410 menu_items.append(menu_root)
412 # make a tree using parent_id
413 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
414 for menu_item in menu_items:
415 if menu_item['parent_id']:
416 parent = menu_item['parent_id'][0]
419 if parent in menu_items_map:
420 menu_items_map[parent].setdefault(
421 'children', []).append(menu_item)
423 # sort by sequence a tree using parent_id
424 for menu_item in menu_items:
425 menu_item.setdefault('children', []).sort(
426 key=lambda x:x["sequence"])
430 @openerpweb.jsonrequest
431 def action(self, req, menu_id):
432 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
433 [('ir.ui.menu', menu_id)], False,
434 req.session.eval_context(req.context))
435 return {"action": actions}
437 class DataSet(openerpweb.Controller):
438 _cp_path = "/base/dataset"
440 @openerpweb.jsonrequest
441 def fields(self, req, model):
442 return {'fields': req.session.model(model).fields_get(False,
443 req.session.eval_context(req.context))}
445 @openerpweb.jsonrequest
446 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
447 return self.do_search_read(request, model, fields, offset, limit, domain, context, sort)
448 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
449 """ Performs a search() followed by a read() (if needed) using the
450 provided search criteria
452 :param request: a JSON-RPC request object
453 :type request: openerpweb.JsonRequest
454 :param str model: the name of the model to search on
455 :param fields: a list of the fields to return in the result records
457 :param int offset: from which index should the results start being returned
458 :param int limit: the maximum number of records to return
459 :param list domain: the search domain for the query
460 :param list sort: sorting directives
461 :returns: a list of result records
464 Model = request.session.model(model)
465 context, domain = eval_context_and_domain(request.session, request.context, domain)
467 ids = Model.search(domain, offset or 0, limit or False,
468 sort or False, context)
470 if fields and fields == ['id']:
471 # shortcut read if we only want the ids
472 return map(lambda id: {'id': id}, ids)
474 reads = Model.read(ids, fields or False, context)
475 reads.sort(key=lambda obj: ids.index(obj['id']))
478 @openerpweb.jsonrequest
479 def get(self, request, model, ids, fields=False):
480 return self.do_get(request, model, ids, fields)
481 def do_get(self, request, model, ids, fields=False):
482 """ Fetches and returns the records of the model ``model`` whose ids
485 The results are in the same order as the inputs, but elements may be
486 missing (if there is no record left for the id)
488 :param request: the JSON-RPC2 request object
489 :type request: openerpweb.JsonRequest
490 :param model: the model to read from
492 :param ids: a list of identifiers
494 :param fields: a list of fields to fetch, ``False`` or empty to fetch
495 all fields in the model
496 :type fields: list | False
497 :returns: a list of records, in the same order as the list of ids
500 Model = request.session.model(model)
501 records = Model.read(ids, fields, request.session.eval_context(request.context))
503 record_map = dict((record['id'], record) for record in records)
505 return [record_map[id] for id in ids if record_map.get(id)]
507 @openerpweb.jsonrequest
508 def load(self, req, model, id, fields):
509 m = req.session.model(model)
511 r = m.read([id], False, req.session.eval_context(req.context))
514 return {'value': value}
516 @openerpweb.jsonrequest
517 def create(self, req, model, data):
518 m = req.session.model(model)
519 r = m.create(data, req.session.eval_context(req.context))
522 @openerpweb.jsonrequest
523 def save(self, req, model, id, data):
524 m = req.session.model(model)
525 r = m.write([id], data, req.session.eval_context(req.context))
528 @openerpweb.jsonrequest
529 def unlink(self, request, model, ids=()):
530 Model = request.session.model(model)
531 return Model.unlink(ids, request.session.eval_context(request.context))
533 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
534 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
535 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
536 c, d = eval_context_and_domain(req.session, context, domain)
537 if domain_id and len(args) - 1 >= domain_id:
539 if context_id and len(args) - 1 >= context_id:
542 return getattr(req.session.model(model), method)(*args)
544 @openerpweb.jsonrequest
545 def call(self, req, model, method, args, domain_id=None, context_id=None):
546 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
548 @openerpweb.jsonrequest
549 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
550 action = self.call_common(req, model, method, args, domain_id, context_id)
551 if isinstance(action, dict) and action.get('type') != '':
552 return {'result': clean_action(action, req.session)}
553 return {'result': False}
555 @openerpweb.jsonrequest
556 def exec_workflow(self, req, model, id, signal):
557 r = req.session.exec_workflow(model, id, signal)
560 @openerpweb.jsonrequest
561 def default_get(self, req, model, fields):
562 m = req.session.model(model)
563 r = m.default_get(fields, req.session.eval_context(req.context))
566 class DataGroup(openerpweb.Controller):
567 _cp_path = "/base/group"
568 @openerpweb.jsonrequest
569 def read(self, request, model, group_by_fields, domain=None):
570 Model = request.session.model(model)
571 context, domain = eval_context_and_domain(request.session, request.context, domain)
573 return Model.read_group(
574 domain or [], False, group_by_fields, 0, False,
575 dict(context, group_by=group_by_fields))
577 class View(openerpweb.Controller):
578 _cp_path = "/base/view"
580 def fields_view_get(self, request, model, view_id, view_type,
581 transform=True, toolbar=False, submenu=False):
582 Model = request.session.model(model)
583 context = request.session.eval_context(request.context)
584 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
585 # todo fme?: check that we should pass the evaluated context here
586 self.process_view(request.session, fvg, context, transform)
589 def process_view(self, session, fvg, context, transform):
590 # depending on how it feels, xmlrpclib.ServerProxy can translate
591 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
592 # enjoy unicode strings which can not be trivially converted to
593 # strings, and it blows up during parsing.
595 # So ensure we fix this retardation by converting view xml back to
597 if isinstance(fvg['arch'], unicode):
598 arch = fvg['arch'].encode('utf-8')
603 evaluation_context = session.evaluation_context(context or {})
604 xml = self.transform_view(arch, session, evaluation_context)
606 xml = ElementTree.fromstring(arch)
607 fvg['arch'] = Xml2Json.convert_element(xml)
608 for field in fvg['fields'].values():
609 if field.has_key('views') and field['views']:
610 for view in field["views"].values():
611 self.process_view(session, view, None, transform)
613 @openerpweb.jsonrequest
614 def add_custom(self, request, view_id, arch):
615 CustomView = request.session.model('ir.ui.view.custom')
617 'user_id': request.session._uid,
620 }, request.session.eval_context(request.context))
621 return {'result': True}
623 @openerpweb.jsonrequest
624 def undo_custom(self, request, view_id, reset=False):
625 CustomView = request.session.model('ir.ui.view.custom')
626 context = request.session.eval_context(request.context)
627 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
628 0, False, False, context)
631 CustomView.unlink(vcustom, context)
633 CustomView.unlink([vcustom[0]], context)
634 return {'result': True}
635 return {'result': False}
637 def normalize_attrs(self, elem, context):
638 """ Normalize @attrs, @invisible, @required, @readonly and @states, so
639 the client only has to deal with @attrs.
641 See `the discoveries pad <http://pad.openerp.com/discoveries>`_ for
644 :param elem: the current view node (Python object)
645 :type elem: xml.etree.ElementTree.Element
646 :param dict context: evaluation context
648 # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
649 attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
650 if 'states' in elem.attrib:
651 attrs.setdefault('invisible', [])\
652 .append(('state', 'not in', elem.attrib.pop('states').split(',')))
654 elem.set('attrs', simplejson.dumps(attrs))
655 for a in ['invisible', 'readonly', 'required']:
657 # In the XML we trust
658 avalue = bool(eval(elem.get(a, 'False'),
659 {'context': context or {}}))
664 if a == 'invisible' and 'attrs' in elem.attrib:
665 del elem.attrib['attrs']
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.normalize_attrs(elem, context)
677 self.parse_domains_and_contexts(elem, session)
680 def parse_domain(self, elem, attr_name, session):
681 """ Parses an attribute of the provided name as a domain, transforms it
682 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
684 :param elem: the node being parsed
685 :type param: xml.etree.ElementTree.Element
686 :param str attr_name: the name of the attribute which should be parsed
687 :param session: Current OpenERP session
688 :type session: openerpweb.openerpweb.OpenERPSession
690 domain = elem.get(attr_name, '').strip()
695 openerpweb.ast.literal_eval(
700 openerpweb.nonliterals.Domain(session, domain))
702 def parse_domains_and_contexts(self, elem, session):
703 """ Converts domains and contexts from the view into Python objects,
704 either literals if they can be parsed by literal_eval or a special
705 placeholder object if the domain or context refers to free variables.
707 :param elem: the current node being parsed
708 :type param: xml.etree.ElementTree.Element
709 :param session: OpenERP session object, used to store and retrieve
711 :type session: openerpweb.openerpweb.OpenERPSession
713 self.parse_domain(elem, 'domain', session)
714 self.parse_domain(elem, 'filter_domain', session)
715 for el in ['context', 'default_get']:
716 context_string = elem.get(el, '').strip()
720 openerpweb.ast.literal_eval(context_string))
723 openerpweb.nonliterals.Context(
724 session, context_string))
726 class FormView(View):
727 _cp_path = "/base/formview"
729 @openerpweb.jsonrequest
730 def load(self, req, model, view_id, toolbar=False):
731 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
732 return {'fields_view': fields_view}
734 class ListView(View):
735 _cp_path = "/base/listview"
737 @openerpweb.jsonrequest
738 def load(self, req, model, view_id, toolbar=False):
739 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
740 return {'fields_view': fields_view}
742 def process_colors(self, view, row, context):
743 colors = view['arch']['attrs'].get('colors')
750 for pair in colors.split(';')
751 if eval(pair.split(':')[1], dict(context, **row))
756 elif len(color) == 1:
760 class SearchView(View):
761 _cp_path = "/base/searchview"
763 @openerpweb.jsonrequest
764 def load(self, req, model, view_id):
765 fields_view = self.fields_view_get(req, model, view_id, 'search')
766 return {'fields_view': fields_view}
768 @openerpweb.jsonrequest
769 def fields_get(self, req, model):
770 Model = req.session.model(model)
771 fields = Model.fields_get(False, req.session.eval_context(req.context))
772 return {'fields': fields}
774 class Binary(openerpweb.Controller):
775 _cp_path = "/base/binary"
777 @openerpweb.httprequest
778 def image(self, request, session_id, model, id, field, **kw):
779 cherrypy.response.headers['Content-Type'] = 'image/png'
780 Model = request.session.model(model)
781 context = request.session.eval_context(request.context)
784 res = Model.default_get([field], context).get(field, '')
786 res = Model.read([int(id)], [field], context)[0].get(field, '')
787 return base64.decodestring(res)
788 except: # TODO: what's the exception here?
789 return self.placeholder()
790 def placeholder(self):
791 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
793 @openerpweb.httprequest
794 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
795 Model = request.session.model(model)
796 context = request.session.eval_context(request.context)
797 res = Model.read([int(id)], [field, fieldname], context)[0]
798 filecontent = res.get(field, '')
800 raise cherrypy.NotFound
802 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
803 filename = '%s_%s' % (model.replace('.', '_'), id)
805 filename = res.get(fieldname, '') or filename
806 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
807 return base64.decodestring(filecontent)
809 @openerpweb.httprequest
810 def upload(self, request, session_id, callback, ufile=None):
811 cherrypy.response.timeout = 500
813 for key, val in cherrypy.request.headers.iteritems():
814 headers[key.lower()] = val
815 size = int(headers.get('content-length', 0))
816 # TODO: might be useful to have a configuration flag for max-length file uploads
818 out = """<script language="javascript" type="text/javascript">
819 var win = window.top.window,
821 if (typeof(callback) === 'function') {
822 callback.apply(this, %s);
824 win.jQuery('#oe_notification', win.document).notify('create', {
825 title: "Ajax File Upload",
826 text: "Could not find callback"
830 data = ufile.file.read()
831 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
833 args = [False, e.message]
834 return out % (simplejson.dumps(callback), simplejson.dumps(args))
836 @openerpweb.httprequest
837 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
838 cherrypy.response.timeout = 500
839 context = request.session.eval_context(request.context)
840 Model = request.session.model('ir.attachment')
842 out = """<script language="javascript" type="text/javascript">
843 var win = window.top.window,
845 if (typeof(callback) === 'function') {
846 callback.call(this, %s);
849 attachment_id = Model.create({
850 'name': ufile.filename,
851 'datas': base64.encodestring(ufile.file.read()),
856 'filename': ufile.filename,
860 args = { 'error': e.message }
861 return out % (simplejson.dumps(callback), simplejson.dumps(args))
863 class Action(openerpweb.Controller):
864 _cp_path = "/base/action"
866 @openerpweb.jsonrequest
867 def load(self, req, action_id):
868 Actions = req.session.model('ir.actions.actions')
870 context = req.session.eval_context(req.context)
871 action_type = Actions.read([action_id], ['type'], context)
873 action = req.session.model(action_type[0]['type']).read([action_id], False,
876 value = clean_action(action[0], req.session)
877 return {'result': value}
879 @openerpweb.jsonrequest
880 def run(self, req, action_id):
881 return clean_action(req.session.model('ir.actions.server').run(
882 [action_id], req.session.eval_context(req.context)), req.session)