1 # -*- coding: utf-8 -*-
4 from xml.etree import ElementTree
5 from cStringIO import StringIO
11 import openerpweb.nonliterals
16 # Should move to openerpweb.Xml2Json
19 # Simple and straightforward XML-to-JSON converter in Python
22 # URL: http://code.google.com/p/xml2json-direct/
24 def convert_to_json(s):
25 return simplejson.dumps(
26 Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
29 def convert_to_structure(s):
30 root = ElementTree.fromstring(s)
31 return Xml2Json.convert_element(root)
34 def convert_element(el, skip_whitespaces=True):
37 ns, name = el.tag.rsplit("}", 1)
39 res["namespace"] = ns[1:]
43 for k, v in el.items():
46 if el.text and (not skip_whitespaces or el.text.strip() != ''):
49 kids.append(Xml2Json.convert_element(kid))
50 if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
52 res["children"] = kids
55 #----------------------------------------------------------
56 # OpenERP Web base Controllers
57 #----------------------------------------------------------
59 class DatabaseCreationError(Exception): pass
60 class DatabaseCreationCrash(DatabaseCreationError): pass
62 class Session(openerpweb.Controller):
63 _cp_path = "/base/session"
65 def manifest_glob(self, addons, key):
68 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
71 resource_path[len(openerpweb.path_addons):]
72 for pattern in globlist
73 for resource_path in glob.glob(os.path.join(
74 openerpweb.path_addons, addon, pattern))
78 def concat_files(self, file_list):
79 """ Concatenate file content
80 return (concat,timestamp)
81 concat: concatenation of file content
82 timestamp: max(os.path.getmtime of file_list)
84 root = openerpweb.path_root
88 fname = os.path.join(root, i)
89 ftime = os.path.getmtime(fname)
90 if ftime > files_timestamp:
91 files_timestamp = ftime
92 files_content = open(fname).read()
93 files_concat = "".join(files_content)
96 @openerpweb.jsonrequest
97 def login(self, req, db, login, password):
98 req.session.login(db, login, password)
101 "session_id": req.session_id,
102 "uid": req.session._uid,
105 @openerpweb.jsonrequest
106 def sc_list(self, req):
107 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
108 req.session.eval_context(req.context))
110 @openerpweb.jsonrequest
111 def get_databases_list(self, req):
112 proxy = req.session.proxy("db")
115 return {"db_list": dbs}
117 @openerpweb.jsonrequest
118 def get_lang_list(self, req):
119 lang_list = [('en_US', 'English (US)')]
121 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
124 return {"lang_list": lang_list}
126 @openerpweb.jsonrequest
127 def db_operation(self, req, flag, **kw):
131 super_admin_pwd = kw.get('super_admin_pwd')
132 dbname = kw.get('db')
133 demo_data = kw.get('demo_data')
134 db_lang = kw.get('db_lang')
135 admin_pwd = kw.get('admin_pwd')
136 confirm_pwd = kw.get('confirm_pwd')
138 if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', dbname):
139 return {'error': "You must avoid all accents, space or special characters.", 'title': 'Bad database name'}
143 return req.session.proxy("db").create(super_admin_pwd, dbname, demo_data, db_lang, admin_pwd)
146 # progress, users = req.session.proxy('db').get_progress(super_admin_pwd, res)
147 # if progress == 1.0:
149 # if x['login'] == 'admin':
150 # req.session.login(dbname, 'admin', x['password'])
156 # raise DatabaseCreationCrash()
157 # except DatabaseCreationCrash:
158 # return {'error': "The server crashed during installation.\nWe suggest you to drop this database.",
159 # 'title': 'Error during database creation'}
161 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
162 return {'error': 'Bad super admin password !', 'title': 'Create Database'}
164 return {'error': 'Could not create database !', 'title': 'Create Database'}
168 password = kw.get('password')
171 return req.session.proxy("db").drop(password, db)
173 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
174 return {'error': 'Bad super admin password !', 'title': 'Drop Database'}
176 return {'error': 'Could not drop database !', 'title': 'Drop Database'}
178 elif flag == 'backup':
180 password = kw.get('password')
182 res = req.session.proxy("db").dump(password, db)
184 cherrypy.response.headers['Content-Type'] = "application/data"
185 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
186 return base64.decodestring(res)
188 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
189 return {'error': 'Bad super admin password !', 'title': 'Backup Database'}
191 return {'error': 'Could not drop database !', 'title': 'Backup Database'}
193 elif flag == 'restore':
194 filename = kw.get('filename')
196 password = kw.get('password')
199 data = base64.encodestring(filename.file.read())
200 return req.session.proxy("db").restore(password, db, data)
202 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
203 return {'error': 'Bad super admin password !', 'title': 'Restore Database'}
205 return {'error': 'Could not restore database !', 'title': 'Restore Database'}
207 elif flag == 'change_password':
208 old_password = kw.get('old_password')
209 new_password = kw.get('new_password')
210 confirm_password = kw.get('confirm_password')
213 return req.session.proxy("db").change_admin_password(old_password, new_password)
215 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
216 return {'error': 'Bad super admin password !', 'title': 'Change Password'}
218 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
220 @openerpweb.jsonrequest
221 def modules(self, req):
222 return {"modules": [name
223 for name, manifest in openerpweb.addons_manifest.iteritems()
224 if manifest.get('active', True)]}
226 @openerpweb.jsonrequest
227 def csslist(self, req, mods='base'):
228 return {'files': self.manifest_glob(mods.split(','), 'css')}
230 @openerpweb.jsonrequest
231 def jslist(self, req, mods='base'):
232 return {'files': self.manifest_glob(mods.split(','), 'js')}
234 def css(self, req, mods='base'):
235 files = self.manifest_glob(mods.split(','), 'css')
236 concat = self.concat_files(files)[0]
237 # TODO request set the Date of last modif and Etag
241 def js(self, req, mods='base'):
242 files = self.manifest_glob(mods.split(','), 'js')
243 concat = self.concat_files(files)[0]
244 # TODO request set the Date of last modif and Etag
248 @openerpweb.jsonrequest
249 def eval_domain_and_context(self, req, contexts, domains,
251 """ Evaluates sequences of domains and contexts, composing them into
252 a single context, domain or group_by sequence.
254 :param list contexts: list of contexts to merge together. Contexts are
255 evaluated in sequence, all previous contexts
256 are part of their own evaluation context
257 (starting at the session context).
258 :param list domains: list of domains to merge together. Domains are
259 evaluated in sequence and appended to one another
260 (implicit AND), their evaluation domain is the
261 result of merging all contexts.
262 :param list group_by_seq: list of domains (which may be in a different
263 order than the ``contexts`` parameter),
264 evaluated in sequence, their ``'group_by'``
265 key is extracted if they have one.
270 the global context created by merging all of
274 the concatenation of all domains
277 a list of fields to group by, potentially empty (in which case
278 no group by should be performed)
280 context, domain = eval_context_and_domain(req.session,
281 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
282 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
284 group_by_sequence = []
285 for candidate in (group_by_seq or []):
286 ctx = req.session.eval_context(candidate, context)
287 group_by = ctx.get('group_by')
290 elif isinstance(group_by, basestring):
291 group_by_sequence.append(group_by)
293 group_by_sequence.extend(group_by)
298 'group_by': group_by_sequence
301 @openerpweb.jsonrequest
302 def save_session_action(self, req, the_action):
304 This method store an action object in the session object and returns an integer
305 identifying that action. The method get_session_action() can be used to get
308 :param the_action: The action to save in the session.
309 :type the_action: anything
310 :return: A key identifying the saved action.
313 saved_actions = cherrypy.session.get('saved_actions')
314 if not saved_actions:
315 saved_actions = {"next":0, "actions":{}}
316 cherrypy.session['saved_actions'] = saved_actions
317 # we don't allow more than 10 stored actions
318 if len(saved_actions["actions"]) >= 10:
319 del saved_actions["actions"][min(saved_actions["actions"].keys())]
320 key = saved_actions["next"]
321 saved_actions["actions"][key] = the_action
322 saved_actions["next"] = key + 1
325 @openerpweb.jsonrequest
326 def get_session_action(self, req, key):
328 Gets back a previously saved action. This method can return None if the action
329 was saved since too much time (this case should be handled in a smart way).
331 :param key: The key given by save_session_action()
333 :return: The saved action or None.
336 saved_actions = cherrypy.session.get('saved_actions')
337 if not saved_actions:
339 return saved_actions["actions"].get(key)
341 def eval_context_and_domain(session, context, domain=None):
342 e_context = session.eval_context(context)
343 # should we give the evaluated context as an evaluation context to the domain?
344 e_domain = session.eval_domain(domain or [])
346 return e_context, e_domain
348 def load_actions_from_ir_values(req, key, key2, models, meta, context):
349 Values = req.session.model('ir.values')
350 actions = Values.get(key, key2, models, meta, context)
352 return [(id, name, clean_action(action, req.session))
353 for id, name, action in actions]
355 def clean_action(action, session):
356 if action['type'] != 'ir.actions.act_window':
358 # values come from the server, we can just eval them
359 if isinstance(action.get('context', None), basestring):
360 action['context'] = eval(
362 session.evaluation_context()) or {}
364 if isinstance(action.get('domain', None), basestring):
365 action['domain'] = eval(
367 session.evaluation_context(
368 action['context'])) or []
369 if 'flags' not in action:
370 # Set empty flags dictionary for web client.
371 action['flags'] = dict()
372 return fix_view_modes(action)
374 def generate_views(action):
376 While the server generates a sequence called "views" computing dependencies
377 between a bunch of stuff for views coming directly from the database
378 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
379 to return custom view dictionaries generated on the fly.
381 In that case, there is no ``views`` key available on the action.
383 Since the web client relies on ``action['views']``, generate it here from
384 ``view_mode`` and ``view_id``.
386 Currently handles two different cases:
388 * no view_id, multiple view_mode
389 * single view_id, single view_mode
391 :param dict action: action descriptor dictionary to generate a views key for
393 view_id = action.get('view_id', False)
394 if isinstance(view_id, (list, tuple)):
397 # providing at least one view mode is a requirement, not an option
398 view_modes = action['view_mode'].split(',')
400 if len(view_modes) > 1:
402 raise ValueError('Non-db action dictionaries should provide '
403 'either multiple view modes or a single view '
404 'mode and an optional view id.\n\n Got view '
405 'modes %r and view id %r for action %r' % (
406 view_modes, view_id, action))
407 action['views'] = [(False, mode) for mode in view_modes]
409 action['views'] = [(view_id, view_modes[0])]
412 def fix_view_modes(action):
413 """ For historical reasons, OpenERP has weird dealings in relation to
414 view_mode and the view_type attribute (on window actions):
416 * one of the view modes is ``tree``, which stands for both list views
418 * the choice is made by checking ``view_type``, which is either
419 ``form`` for a list view or ``tree`` for an actual tree view
421 This methods simply folds the view_type into view_mode by adding a
422 new view mode ``list`` which is the result of the ``tree`` view_mode
423 in conjunction with the ``form`` view_type.
425 TODO: this should go into the doc, some kind of "peculiarities" section
427 :param dict action: an action descriptor
428 :returns: nothing, the action is modified in place
430 if 'views' not in action:
431 generate_views(action)
433 if action.pop('view_type') != 'form':
437 [id, mode if mode != 'tree' else 'list']
438 for id, mode in action['views']
443 class Menu(openerpweb.Controller):
444 _cp_path = "/base/menu"
446 @openerpweb.jsonrequest
448 return {'data': self.do_load(req)}
450 def do_load(self, req):
451 """ Loads all menu items (all applications and their sub-menus).
453 :param req: A request object, with an OpenERP session attribute
454 :type req: < session -> OpenERPSession >
455 :return: the menu root
456 :rtype: dict('children': menu_nodes)
458 Menus = req.session.model('ir.ui.menu')
459 # menus are loaded fully unlike a regular tree view, cause there are
460 # less than 512 items
461 context = req.session.eval_context(req.context)
462 menu_ids = Menus.search([], 0, False, False, context)
463 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
464 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
465 menu_items.append(menu_root)
467 # make a tree using parent_id
468 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
469 for menu_item in menu_items:
470 if menu_item['parent_id']:
471 parent = menu_item['parent_id'][0]
474 if parent in menu_items_map:
475 menu_items_map[parent].setdefault(
476 'children', []).append(menu_item)
478 # sort by sequence a tree using parent_id
479 for menu_item in menu_items:
480 menu_item.setdefault('children', []).sort(
481 key=lambda x:x["sequence"])
485 @openerpweb.jsonrequest
486 def action(self, req, menu_id):
487 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
488 [('ir.ui.menu', menu_id)], False,
489 req.session.eval_context(req.context))
490 return {"action": actions}
492 class DataSet(openerpweb.Controller):
493 _cp_path = "/base/dataset"
495 @openerpweb.jsonrequest
496 def fields(self, req, model):
497 return {'fields': req.session.model(model).fields_get(False,
498 req.session.eval_context(req.context))}
500 @openerpweb.jsonrequest
501 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
502 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
503 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
505 """ Performs a search() followed by a read() (if needed) using the
506 provided search criteria
508 :param request: a JSON-RPC request object
509 :type request: openerpweb.JsonRequest
510 :param str model: the name of the model to search on
511 :param fields: a list of the fields to return in the result records
513 :param int offset: from which index should the results start being returned
514 :param int limit: the maximum number of records to return
515 :param list domain: the search domain for the query
516 :param list sort: sorting directives
517 :returns: A structure (dict) with two keys: ids (all the ids matching
518 the (domain, context) pair) and records (paginated records
519 matching fields selection set)
522 Model = request.session.model(model)
523 context, domain = eval_context_and_domain(
524 request.session, request.context, domain)
526 ids = Model.search(domain, 0, False, sort or False, context)
527 # need to fill the dataset with all ids for the (domain, context) pair,
528 # so search un-paginated and paginate manually before reading
529 paginated_ids = ids[offset:(offset + limit if limit else None)]
530 if fields and fields == ['id']:
531 # shortcut read if we only want the ids
534 'records': map(lambda id: {'id': id}, paginated_ids)
537 records = Model.read(paginated_ids, fields or False, context)
538 records.sort(key=lambda obj: ids.index(obj['id']))
545 @openerpweb.jsonrequest
546 def get(self, request, model, ids, fields=False):
547 return self.do_get(request, model, ids, fields)
548 def do_get(self, request, model, ids, fields=False):
549 """ Fetches and returns the records of the model ``model`` whose ids
552 The results are in the same order as the inputs, but elements may be
553 missing (if there is no record left for the id)
555 :param request: the JSON-RPC2 request object
556 :type request: openerpweb.JsonRequest
557 :param model: the model to read from
559 :param ids: a list of identifiers
561 :param fields: a list of fields to fetch, ``False`` or empty to fetch
562 all fields in the model
563 :type fields: list | False
564 :returns: a list of records, in the same order as the list of ids
567 Model = request.session.model(model)
568 records = Model.read(ids, fields, request.session.eval_context(request.context))
570 record_map = dict((record['id'], record) for record in records)
572 return [record_map[id] for id in ids if record_map.get(id)]
574 @openerpweb.jsonrequest
575 def load(self, req, model, id, fields):
576 m = req.session.model(model)
578 r = m.read([id], False, req.session.eval_context(req.context))
581 return {'value': value}
583 @openerpweb.jsonrequest
584 def create(self, req, model, data):
585 m = req.session.model(model)
586 r = m.create(data, req.session.eval_context(req.context))
589 @openerpweb.jsonrequest
590 def save(self, req, model, id, data):
591 m = req.session.model(model)
592 r = m.write([id], data, req.session.eval_context(req.context))
595 @openerpweb.jsonrequest
596 def unlink(self, request, model, ids=()):
597 Model = request.session.model(model)
598 return Model.unlink(ids, request.session.eval_context(request.context))
600 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
601 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
602 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
603 c, d = eval_context_and_domain(req.session, context, domain)
604 if domain_id and len(args) - 1 >= domain_id:
606 if context_id and len(args) - 1 >= context_id:
609 return getattr(req.session.model(model), method)(*args)
611 @openerpweb.jsonrequest
612 def call(self, req, model, method, args, domain_id=None, context_id=None):
613 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
615 @openerpweb.jsonrequest
616 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
617 action = self.call_common(req, model, method, args, domain_id, context_id)
618 if isinstance(action, dict) and action.get('type') != '':
619 return {'result': clean_action(action, req.session)}
620 return {'result': False}
622 @openerpweb.jsonrequest
623 def exec_workflow(self, req, model, id, signal):
624 r = req.session.exec_workflow(model, id, signal)
627 @openerpweb.jsonrequest
628 def default_get(self, req, model, fields):
629 m = req.session.model(model)
630 r = m.default_get(fields, req.session.eval_context(req.context))
633 class DataGroup(openerpweb.Controller):
634 _cp_path = "/base/group"
635 @openerpweb.jsonrequest
636 def read(self, request, model, fields, group_by_fields, domain=None):
637 Model = request.session.model(model)
638 context, domain = eval_context_and_domain(request.session, request.context, domain)
640 return Model.read_group(
641 domain or [], fields, group_by_fields, 0, False,
642 dict(context, group_by=group_by_fields))
644 class View(openerpweb.Controller):
645 _cp_path = "/base/view"
647 def fields_view_get(self, request, model, view_id, view_type,
648 transform=True, toolbar=False, submenu=False):
649 Model = request.session.model(model)
650 context = request.session.eval_context(request.context)
651 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
652 # todo fme?: check that we should pass the evaluated context here
653 self.process_view(request.session, fvg, context, transform)
656 def process_view(self, session, fvg, context, transform):
657 # depending on how it feels, xmlrpclib.ServerProxy can translate
658 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
659 # enjoy unicode strings which can not be trivially converted to
660 # strings, and it blows up during parsing.
662 # So ensure we fix this retardation by converting view xml back to
664 if isinstance(fvg['arch'], unicode):
665 arch = fvg['arch'].encode('utf-8')
670 evaluation_context = session.evaluation_context(context or {})
671 xml = self.transform_view(arch, session, evaluation_context)
673 xml = ElementTree.fromstring(arch)
674 fvg['arch'] = Xml2Json.convert_element(xml)
675 for field in fvg['fields'].values():
676 if field.has_key('views') and field['views']:
677 for view in field["views"].values():
678 self.process_view(session, view, None, transform)
680 @openerpweb.jsonrequest
681 def add_custom(self, request, view_id, arch):
682 CustomView = request.session.model('ir.ui.view.custom')
684 'user_id': request.session._uid,
687 }, request.session.eval_context(request.context))
688 return {'result': True}
690 @openerpweb.jsonrequest
691 def undo_custom(self, request, view_id, reset=False):
692 CustomView = request.session.model('ir.ui.view.custom')
693 context = request.session.eval_context(request.context)
694 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
695 0, False, False, context)
698 CustomView.unlink(vcustom, context)
700 CustomView.unlink([vcustom[0]], context)
701 return {'result': True}
702 return {'result': False}
704 def transform_view(self, view_string, session, context=None):
705 # transform nodes on the fly via iterparse, instead of
706 # doing it statically on the parsing result
707 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
709 for event, elem in parser:
713 self.parse_domains_and_contexts(elem, session)
716 def parse_domain(self, elem, attr_name, session):
717 """ Parses an attribute of the provided name as a domain, transforms it
718 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
720 :param elem: the node being parsed
721 :type param: xml.etree.ElementTree.Element
722 :param str attr_name: the name of the attribute which should be parsed
723 :param session: Current OpenERP session
724 :type session: openerpweb.openerpweb.OpenERPSession
726 domain = elem.get(attr_name, '').strip()
731 openerpweb.ast.literal_eval(
736 openerpweb.nonliterals.Domain(session, domain))
738 def parse_domains_and_contexts(self, elem, session):
739 """ Converts domains and contexts from the view into Python objects,
740 either literals if they can be parsed by literal_eval or a special
741 placeholder object if the domain or context refers to free variables.
743 :param elem: the current node being parsed
744 :type param: xml.etree.ElementTree.Element
745 :param session: OpenERP session object, used to store and retrieve
747 :type session: openerpweb.openerpweb.OpenERPSession
749 self.parse_domain(elem, 'domain', session)
750 self.parse_domain(elem, 'filter_domain', session)
751 for el in ['context', 'default_get']:
752 context_string = elem.get(el, '').strip()
756 openerpweb.ast.literal_eval(context_string))
759 openerpweb.nonliterals.Context(
760 session, context_string))
762 class FormView(View):
763 _cp_path = "/base/formview"
765 @openerpweb.jsonrequest
766 def load(self, req, model, view_id, toolbar=False):
767 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
768 return {'fields_view': fields_view}
770 class ListView(View):
771 _cp_path = "/base/listview"
773 @openerpweb.jsonrequest
774 def load(self, req, model, view_id, toolbar=False):
775 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
776 return {'fields_view': fields_view}
778 def process_colors(self, view, row, context):
779 colors = view['arch']['attrs'].get('colors')
786 for pair in colors.split(';')
787 if eval(pair.split(':')[1], dict(context, **row))
792 elif len(color) == 1:
796 class SearchView(View):
797 _cp_path = "/base/searchview"
799 @openerpweb.jsonrequest
800 def load(self, req, model, view_id):
801 fields_view = self.fields_view_get(req, model, view_id, 'search')
802 return {'fields_view': fields_view}
804 @openerpweb.jsonrequest
805 def fields_get(self, req, model):
806 Model = req.session.model(model)
807 fields = Model.fields_get(False, req.session.eval_context(req.context))
808 return {'fields': fields}
810 class Binary(openerpweb.Controller):
811 _cp_path = "/base/binary"
813 @openerpweb.httprequest
814 def image(self, request, session_id, model, id, field, **kw):
815 cherrypy.response.headers['Content-Type'] = 'image/png'
816 Model = request.session.model(model)
817 context = request.session.eval_context(request.context)
820 res = Model.default_get([field], context).get(field, '')
822 res = Model.read([int(id)], [field], context)[0].get(field, '')
823 return base64.decodestring(res)
824 except: # TODO: what's the exception here?
825 return self.placeholder()
826 def placeholder(self):
827 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
829 @openerpweb.httprequest
830 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
831 Model = request.session.model(model)
832 context = request.session.eval_context(request.context)
833 res = Model.read([int(id)], [field, fieldname], context)[0]
834 filecontent = res.get(field, '')
836 raise cherrypy.NotFound
838 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
839 filename = '%s_%s' % (model.replace('.', '_'), id)
841 filename = res.get(fieldname, '') or filename
842 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
843 return base64.decodestring(filecontent)
845 @openerpweb.httprequest
846 def upload(self, request, session_id, callback, ufile=None):
847 cherrypy.response.timeout = 500
849 for key, val in cherrypy.request.headers.iteritems():
850 headers[key.lower()] = val
851 size = int(headers.get('content-length', 0))
852 # TODO: might be useful to have a configuration flag for max-length file uploads
854 out = """<script language="javascript" type="text/javascript">
855 var win = window.top.window,
857 if (typeof(callback) === 'function') {
858 callback.apply(this, %s);
860 win.jQuery('#oe_notification', win.document).notify('create', {
861 title: "Ajax File Upload",
862 text: "Could not find callback"
866 data = ufile.file.read()
867 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
869 args = [False, e.message]
870 return out % (simplejson.dumps(callback), simplejson.dumps(args))
872 @openerpweb.httprequest
873 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
874 cherrypy.response.timeout = 500
875 context = request.session.eval_context(request.context)
876 Model = request.session.model('ir.attachment')
878 out = """<script language="javascript" type="text/javascript">
879 var win = window.top.window,
881 if (typeof(callback) === 'function') {
882 callback.call(this, %s);
885 attachment_id = Model.create({
886 'name': ufile.filename,
887 'datas': base64.encodestring(ufile.file.read()),
892 'filename': ufile.filename,
896 args = { 'error': e.message }
897 return out % (simplejson.dumps(callback), simplejson.dumps(args))
899 class Action(openerpweb.Controller):
900 _cp_path = "/base/action"
902 @openerpweb.jsonrequest
903 def load(self, req, action_id):
904 Actions = req.session.model('ir.actions.actions')
906 context = req.session.eval_context(req.context)
907 action_type = Actions.read([action_id], ['type'], context)
909 action = req.session.model(action_type[0]['type']).read([action_id], False,
912 value = clean_action(action[0], req.session)
913 return {'result': value}
915 @openerpweb.jsonrequest
916 def run(self, req, action_id):
917 return clean_action(req.session.model('ir.actions.server').run(
918 [action_id], req.session.eval_context(req.context)), req.session)