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):
132 password = kw.get('password')
134 return req.session.proxy("db").drop(password, db)
136 elif flag == 'backup':
138 password = kw.get('password')
140 res = req.session.proxy("db").dump(password, db)
142 cherrypy.response.headers['Content-Type'] = "application/data"
143 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
144 return base64.decodestring(res)
146 return {'error': 'Could not create backup !'}
148 elif flag == 'restore':
149 filename = kw.get('filename')
151 password = kw.get('password')
155 data = base64.encodestring(filename.file.read())
156 return req.session.proxy("db").restore(password, db, data)
158 return {'error': 'Could not restore database !'}
160 elif flag == 'change_password':
161 old_password = kw.get('old_password')
162 new_password = kw.get('new_password')
163 confirm_password = kw.get('confirm_password')
165 if old_password and new_password and confirm_password:
166 return req.session.proxy("db").change_admin_password(old_password, new_password)
168 @openerpweb.jsonrequest
169 def modules(self, req):
170 return {"modules": [name
171 for name, manifest in openerpweb.addons_manifest.iteritems()
172 if manifest.get('active', True)]}
174 @openerpweb.jsonrequest
175 def csslist(self, req, mods='base'):
176 return {'files': self.manifest_glob(mods.split(','), 'css')}
178 @openerpweb.jsonrequest
179 def jslist(self, req, mods='base'):
180 return {'files': self.manifest_glob(mods.split(','), 'js')}
182 def css(self, req, mods='base,base_hello'):
183 files = self.manifest_glob(mods.split(','), 'css')
184 concat = self.concat_files(files)[0]
185 # TODO request set the Date of last modif and Etag
189 def js(self, req, mods='base,base_hello'):
190 files = self.manifest_glob(mods.split(','), 'js')
191 concat = self.concat_files(files)[0]
192 # TODO request set the Date of last modif and Etag
196 @openerpweb.jsonrequest
197 def eval_domain_and_context(self, req, contexts, domains,
199 """ Evaluates sequences of domains and contexts, composing them into
200 a single context, domain or group_by sequence.
202 :param list contexts: list of contexts to merge together. Contexts are
203 evaluated in sequence, all previous contexts
204 are part of their own evaluation context
205 (starting at the session context).
206 :param list domains: list of domains to merge together. Domains are
207 evaluated in sequence and appended to one another
208 (implicit AND), their evaluation domain is the
209 result of merging all contexts.
210 :param list group_by_seq: list of domains (which may be in a different
211 order than the ``contexts`` parameter),
212 evaluated in sequence, their ``'group_by'``
213 key is extracted if they have one.
218 the global context created by merging all of
222 the concatenation of all domains
225 a list of fields to group by, potentially empty (in which case
226 no group by should be performed)
228 context, domain = eval_context_and_domain(req.session,
229 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
230 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
232 group_by_sequence = []
233 for candidate in (group_by_seq or []):
234 ctx = req.session.eval_context(candidate, context)
235 group_by = ctx.get('group_by')
238 elif isinstance(group_by, basestring):
239 group_by_sequence.append(group_by)
241 group_by_sequence.extend(group_by)
246 'group_by': group_by_sequence
249 @openerpweb.jsonrequest
250 def save_session_action(self, req, the_action):
252 This method store an action object in the session object and returns an integer
253 identifying that action. The method get_session_action() can be used to get
256 :param the_action: The action to save in the session.
257 :type the_action: anything
258 :return: A key identifying the saved action.
261 saved_actions = cherrypy.session.get('saved_actions')
262 if not saved_actions:
263 saved_actions = {"next":0, "actions":{}}
264 cherrypy.session['saved_actions'] = saved_actions
265 # we don't allow more than 10 stored actions
266 if len(saved_actions["actions"]) >= 10:
267 del saved_actions["actions"][min(saved_actions["actions"].keys())]
268 key = saved_actions["next"]
269 saved_actions["actions"][key] = the_action
270 saved_actions["next"] = key + 1
273 @openerpweb.jsonrequest
274 def get_session_action(self, req, key):
276 Gets back a previously saved action. This method can return None if the action
277 was saved since too much time (this case should be handled in a smart way).
279 :param key: The key given by save_session_action()
281 :return: The saved action or None.
284 saved_actions = cherrypy.session.get('saved_actions')
285 if not saved_actions:
287 return saved_actions["actions"].get(key)
289 def eval_context_and_domain(session, context, domain=None):
290 e_context = session.eval_context(context)
291 # should we give the evaluated context as an evaluation context to the domain?
292 e_domain = session.eval_domain(domain or [])
294 return e_context, e_domain
296 def load_actions_from_ir_values(req, key, key2, models, meta, context):
297 Values = req.session.model('ir.values')
298 actions = Values.get(key, key2, models, meta, context)
300 return [(id, name, clean_action(action, req.session))
301 for id, name, action in actions]
303 def clean_action(action, session):
304 if action['type'] != 'ir.actions.act_window':
306 # values come from the server, we can just eval them
307 if isinstance(action.get('context', None), basestring):
308 action['context'] = eval(
310 session.evaluation_context()) or {}
312 if isinstance(action.get('domain', None), basestring):
313 action['domain'] = eval(
315 session.evaluation_context(
316 action['context'])) or []
317 if 'flags' not in action:
318 # Set empty flags dictionary for web client.
319 action['flags'] = dict()
320 return fix_view_modes(action)
322 def generate_views(action):
324 While the server generates a sequence called "views" computing dependencies
325 between a bunch of stuff for views coming directly from the database
326 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
327 to return custom view dictionaries generated on the fly.
329 In that case, there is no ``views`` key available on the action.
331 Since the web client relies on ``action['views']``, generate it here from
332 ``view_mode`` and ``view_id``.
334 Currently handles two different cases:
336 * no view_id, multiple view_mode
337 * single view_id, single view_mode
339 :param dict action: action descriptor dictionary to generate a views key for
341 view_id = action.get('view_id', False)
342 if isinstance(view_id, (list, tuple)):
345 # providing at least one view mode is a requirement, not an option
346 view_modes = action['view_mode'].split(',')
348 if len(view_modes) > 1:
350 raise ValueError('Non-db action dictionaries should provide '
351 'either multiple view modes or a single view '
352 'mode and an optional view id.\n\n Got view '
353 'modes %r and view id %r for action %r' % (
354 view_modes, view_id, action))
355 action['views'] = [(False, mode) for mode in view_modes]
357 action['views'] = [(view_id, view_modes[0])]
360 def fix_view_modes(action):
361 """ For historical reasons, OpenERP has weird dealings in relation to
362 view_mode and the view_type attribute (on window actions):
364 * one of the view modes is ``tree``, which stands for both list views
366 * the choice is made by checking ``view_type``, which is either
367 ``form`` for a list view or ``tree`` for an actual tree view
369 This methods simply folds the view_type into view_mode by adding a
370 new view mode ``list`` which is the result of the ``tree`` view_mode
371 in conjunction with the ``form`` view_type.
373 TODO: this should go into the doc, some kind of "peculiarities" section
375 :param dict action: an action descriptor
376 :returns: nothing, the action is modified in place
378 if 'views' not in action:
379 generate_views(action)
381 if action.pop('view_type') != 'form':
385 [id, mode if mode != 'tree' else 'list']
386 for id, mode in action['views']
391 class Menu(openerpweb.Controller):
392 _cp_path = "/base/menu"
394 @openerpweb.jsonrequest
396 return {'data': self.do_load(req)}
398 def do_load(self, req):
399 """ Loads all menu items (all applications and their sub-menus).
401 :param req: A request object, with an OpenERP session attribute
402 :type req: < session -> OpenERPSession >
403 :return: the menu root
404 :rtype: dict('children': menu_nodes)
406 Menus = req.session.model('ir.ui.menu')
407 # menus are loaded fully unlike a regular tree view, cause there are
408 # less than 512 items
409 context = req.session.eval_context(req.context)
410 menu_ids = Menus.search([], 0, False, False, context)
411 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
412 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
413 menu_items.append(menu_root)
415 # make a tree using parent_id
416 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
417 for menu_item in menu_items:
418 if menu_item['parent_id']:
419 parent = menu_item['parent_id'][0]
422 if parent in menu_items_map:
423 menu_items_map[parent].setdefault(
424 'children', []).append(menu_item)
426 # sort by sequence a tree using parent_id
427 for menu_item in menu_items:
428 menu_item.setdefault('children', []).sort(
429 key=lambda x:x["sequence"])
433 @openerpweb.jsonrequest
434 def action(self, req, menu_id):
435 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
436 [('ir.ui.menu', menu_id)], False,
437 req.session.eval_context(req.context))
438 return {"action": actions}
440 class DataSet(openerpweb.Controller):
441 _cp_path = "/base/dataset"
443 @openerpweb.jsonrequest
444 def fields(self, req, model):
445 return {'fields': req.session.model(model).fields_get(False,
446 req.session.eval_context(req.context))}
448 @openerpweb.jsonrequest
449 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
450 return self.do_search_read(request, model, fields, offset, limit, domain, context, sort)
451 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
452 """ Performs a search() followed by a read() (if needed) using the
453 provided search criteria
455 :param request: a JSON-RPC request object
456 :type request: openerpweb.JsonRequest
457 :param str model: the name of the model to search on
458 :param fields: a list of the fields to return in the result records
460 :param int offset: from which index should the results start being returned
461 :param int limit: the maximum number of records to return
462 :param list domain: the search domain for the query
463 :param list sort: sorting directives
464 :returns: a list of result records
467 Model = request.session.model(model)
468 context, domain = eval_context_and_domain(request.session, request.context, domain)
470 ids = Model.search(domain, offset or 0, limit or False,
471 sort or False, context)
473 if fields and fields == ['id']:
474 # shortcut read if we only want the ids
475 return map(lambda id: {'id': id}, ids)
477 reads = Model.read(ids, fields or False, context)
478 reads.sort(key=lambda obj: ids.index(obj['id']))
481 @openerpweb.jsonrequest
482 def get(self, request, model, ids, fields=False):
483 return self.do_get(request, model, ids, fields)
484 def do_get(self, request, model, ids, fields=False):
485 """ Fetches and returns the records of the model ``model`` whose ids
488 The results are in the same order as the inputs, but elements may be
489 missing (if there is no record left for the id)
491 :param request: the JSON-RPC2 request object
492 :type request: openerpweb.JsonRequest
493 :param model: the model to read from
495 :param ids: a list of identifiers
497 :param fields: a list of fields to fetch, ``False`` or empty to fetch
498 all fields in the model
499 :type fields: list | False
500 :returns: a list of records, in the same order as the list of ids
503 Model = request.session.model(model)
504 records = Model.read(ids, fields, request.session.eval_context(request.context))
506 record_map = dict((record['id'], record) for record in records)
508 return [record_map[id] for id in ids if record_map.get(id)]
510 @openerpweb.jsonrequest
511 def load(self, req, model, id, fields):
512 m = req.session.model(model)
514 r = m.read([id], False, req.session.eval_context(req.context))
517 return {'value': value}
519 @openerpweb.jsonrequest
520 def create(self, req, model, data):
521 m = req.session.model(model)
522 r = m.create(data, req.session.eval_context(req.context))
525 @openerpweb.jsonrequest
526 def save(self, req, model, id, data):
527 m = req.session.model(model)
528 r = m.write([id], data, req.session.eval_context(req.context))
531 @openerpweb.jsonrequest
532 def unlink(self, request, model, ids=()):
533 Model = request.session.model(model)
534 return Model.unlink(ids, request.session.eval_context(request.context))
536 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
537 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
538 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
539 c, d = eval_context_and_domain(req.session, context, domain)
540 if domain_id and len(args) - 1 >= domain_id:
542 if context_id and len(args) - 1 >= context_id:
545 return getattr(req.session.model(model), method)(*args)
547 @openerpweb.jsonrequest
548 def call(self, req, model, method, args, domain_id=None, context_id=None):
549 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
551 @openerpweb.jsonrequest
552 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
553 action = self.call_common(req, model, method, args, domain_id, context_id)
554 if isinstance(action, dict) and action.get('type') != '':
555 return {'result': clean_action(action, req.session)}
556 return {'result': False}
558 @openerpweb.jsonrequest
559 def exec_workflow(self, req, model, id, signal):
560 r = req.session.exec_workflow(model, id, signal)
563 @openerpweb.jsonrequest
564 def default_get(self, req, model, fields):
565 m = req.session.model(model)
566 r = m.default_get(fields, req.session.eval_context(req.context))
569 class DataGroup(openerpweb.Controller):
570 _cp_path = "/base/group"
571 @openerpweb.jsonrequest
572 def read(self, request, model, group_by_fields, domain=None):
573 Model = request.session.model(model)
574 context, domain = eval_context_and_domain(request.session, request.context, domain)
576 return Model.read_group(
577 domain or [], False, group_by_fields, 0, False,
578 dict(context, group_by=group_by_fields))
580 class View(openerpweb.Controller):
581 _cp_path = "/base/view"
583 def fields_view_get(self, request, model, view_id, view_type,
584 transform=True, toolbar=False, submenu=False):
585 Model = request.session.model(model)
586 context = request.session.eval_context(request.context)
587 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
588 # todo fme?: check that we should pass the evaluated context here
589 self.process_view(request.session, fvg, context, transform)
592 def process_view(self, session, fvg, context, transform):
593 # depending on how it feels, xmlrpclib.ServerProxy can translate
594 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
595 # enjoy unicode strings which can not be trivially converted to
596 # strings, and it blows up during parsing.
598 # So ensure we fix this retardation by converting view xml back to
600 if isinstance(fvg['arch'], unicode):
601 arch = fvg['arch'].encode('utf-8')
606 evaluation_context = session.evaluation_context(context or {})
607 xml = self.transform_view(arch, session, evaluation_context)
609 xml = ElementTree.fromstring(arch)
610 fvg['arch'] = Xml2Json.convert_element(xml)
611 for field in fvg['fields'].values():
612 if field.has_key('views') and field['views']:
613 for view in field["views"].values():
614 self.process_view(session, view, None, transform)
616 @openerpweb.jsonrequest
617 def add_custom(self, request, view_id, arch):
618 CustomView = request.session.model('ir.ui.view.custom')
620 'user_id': request.session._uid,
623 }, request.session.eval_context(request.context))
624 return {'result': True}
626 @openerpweb.jsonrequest
627 def undo_custom(self, request, view_id, reset=False):
628 CustomView = request.session.model('ir.ui.view.custom')
629 context = request.session.eval_context(request.context)
630 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
631 0, False, False, context)
634 CustomView.unlink(vcustom, context)
636 CustomView.unlink([vcustom[0]], context)
637 return {'result': True}
638 return {'result': False}
640 def normalize_attrs(self, elem, context):
641 """ Normalize @attrs, @invisible, @required, @readonly and @states, so
642 the client only has to deal with @attrs.
644 See `the discoveries pad <http://pad.openerp.com/discoveries>`_ for
647 :param elem: the current view node (Python object)
648 :type elem: xml.etree.ElementTree.Element
649 :param dict context: evaluation context
651 # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
652 attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
653 if 'states' in elem.attrib:
654 attrs.setdefault('invisible', [])\
655 .append(('state', 'not in', elem.attrib.pop('states').split(',')))
657 elem.set('attrs', simplejson.dumps(attrs))
658 for a in ['invisible', 'readonly', 'required']:
660 # In the XML we trust
661 avalue = bool(eval(elem.get(a, 'False'),
662 {'context': context or {}}))
667 if a == 'invisible' and 'attrs' in elem.attrib:
668 del elem.attrib['attrs']
670 def transform_view(self, view_string, session, context=None):
671 # transform nodes on the fly via iterparse, instead of
672 # doing it statically on the parsing result
673 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
675 for event, elem in parser:
679 self.normalize_attrs(elem, context)
680 self.parse_domains_and_contexts(elem, session)
683 def parse_domain(self, elem, attr_name, session):
684 """ Parses an attribute of the provided name as a domain, transforms it
685 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
687 :param elem: the node being parsed
688 :type param: xml.etree.ElementTree.Element
689 :param str attr_name: the name of the attribute which should be parsed
690 :param session: Current OpenERP session
691 :type session: openerpweb.openerpweb.OpenERPSession
693 domain = elem.get(attr_name, '').strip()
698 openerpweb.ast.literal_eval(
703 openerpweb.nonliterals.Domain(session, domain))
705 def parse_domains_and_contexts(self, elem, session):
706 """ Converts domains and contexts from the view into Python objects,
707 either literals if they can be parsed by literal_eval or a special
708 placeholder object if the domain or context refers to free variables.
710 :param elem: the current node being parsed
711 :type param: xml.etree.ElementTree.Element
712 :param session: OpenERP session object, used to store and retrieve
714 :type session: openerpweb.openerpweb.OpenERPSession
716 self.parse_domain(elem, 'domain', session)
717 self.parse_domain(elem, 'filter_domain', session)
718 for el in ['context', 'default_get']:
719 context_string = elem.get(el, '').strip()
723 openerpweb.ast.literal_eval(context_string))
726 openerpweb.nonliterals.Context(
727 session, context_string))
729 class FormView(View):
730 _cp_path = "/base/formview"
732 @openerpweb.jsonrequest
733 def load(self, req, model, view_id, toolbar=False):
734 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
735 return {'fields_view': fields_view}
737 class ListView(View):
738 _cp_path = "/base/listview"
740 @openerpweb.jsonrequest
741 def load(self, req, model, view_id, toolbar=False):
742 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
743 return {'fields_view': fields_view}
745 def process_colors(self, view, row, context):
746 colors = view['arch']['attrs'].get('colors')
753 for pair in colors.split(';')
754 if eval(pair.split(':')[1], dict(context, **row))
759 elif len(color) == 1:
763 class SearchView(View):
764 _cp_path = "/base/searchview"
766 @openerpweb.jsonrequest
767 def load(self, req, model, view_id):
768 fields_view = self.fields_view_get(req, model, view_id, 'search')
769 return {'fields_view': fields_view}
771 @openerpweb.jsonrequest
772 def fields_get(self, req, model):
773 Model = req.session.model(model)
774 fields = Model.fields_get(False, req.session.eval_context(req.context))
775 return {'fields': fields}
777 class Binary(openerpweb.Controller):
778 _cp_path = "/base/binary"
780 @openerpweb.httprequest
781 def image(self, request, session_id, model, id, field, **kw):
782 cherrypy.response.headers['Content-Type'] = 'image/png'
783 Model = request.session.model(model)
784 context = request.session.eval_context(request.context)
787 res = Model.default_get([field], context).get(field, '')
789 res = Model.read([int(id)], [field], context)[0].get(field, '')
790 return base64.decodestring(res)
791 except: # TODO: what's the exception here?
792 return self.placeholder()
793 def placeholder(self):
794 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
796 @openerpweb.httprequest
797 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
798 Model = request.session.model(model)
799 context = request.session.eval_context(request.context)
800 res = Model.read([int(id)], [field, fieldname], context)[0]
801 filecontent = res.get(field, '')
803 raise cherrypy.NotFound
805 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
806 filename = '%s_%s' % (model.replace('.', '_'), id)
808 filename = res.get(fieldname, '') or filename
809 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
810 return base64.decodestring(filecontent)
812 @openerpweb.httprequest
813 def upload(self, request, session_id, callback, ufile=None):
814 cherrypy.response.timeout = 500
816 for key, val in cherrypy.request.headers.iteritems():
817 headers[key.lower()] = val
818 size = int(headers.get('content-length', 0))
819 # TODO: might be useful to have a configuration flag for max-length file uploads
821 out = """<script language="javascript" type="text/javascript">
822 var win = window.top.window,
824 if (typeof(callback) === 'function') {
825 callback.apply(this, %s);
827 win.jQuery('#oe_notification', win.document).notify('create', {
828 title: "Ajax File Upload",
829 text: "Could not find callback"
833 data = ufile.file.read()
834 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
836 args = [False, e.message]
837 return out % (simplejson.dumps(callback), simplejson.dumps(args))
839 @openerpweb.httprequest
840 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
841 cherrypy.response.timeout = 500
842 context = request.session.eval_context(request.context)
843 Model = request.session.model('ir.attachment')
845 out = """<script language="javascript" type="text/javascript">
846 var win = window.top.window,
848 if (typeof(callback) === 'function') {
849 callback.call(this, %s);
852 attachment_id = Model.create({
853 'name': ufile.filename,
854 'datas': base64.encodestring(ufile.file.read()),
859 'filename': ufile.filename,
863 args = { 'error': e.message }
864 return out % (simplejson.dumps(callback), simplejson.dumps(args))
866 class Action(openerpweb.Controller):
867 _cp_path = "/base/action"
869 @openerpweb.jsonrequest
870 def load(self, req, action_id):
871 Actions = req.session.model('ir.actions.actions')
873 context = req.session.eval_context(req.context)
874 action_type = Actions.read([action_id], ['type'], context)
876 action = req.session.model(action_type[0]['type']).read([action_id], False,
879 value = clean_action(action[0], req.session)
880 return {'result': value}
882 @openerpweb.jsonrequest
883 def run(self, req, action_id):
884 return clean_action(req.session.model('ir.actions.server').run(
885 [action_id], req.session.eval_context(req.context)), req.session)