1 # -*- coding: utf-8 -*-
3 import base64, glob, os, re
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 Database(openerpweb.Controller):
63 _cp_path = "/base/database"
65 @openerpweb.jsonrequest
66 def get_databases_list(self, req):
67 proxy = req.session.proxy("db")
69 h = req.httprequest.headers['Host'].split(':')[0]
71 r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
72 dbs = [i for i in dbs if re.match(r, i)]
73 return {"db_list": dbs}
75 @openerpweb.jsonrequest
76 def db_operation(self, req, flag, **kw):
80 super_admin_pwd = kw.get('super_admin_pwd')
82 demo_data = kw.get('demo_data')
83 db_lang = kw.get('db_lang')
84 admin_pwd = kw.get('admin_pwd')
85 confirm_pwd = kw.get('confirm_pwd')
87 if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', dbname):
88 return {'error': "You must avoid all accents, space or special characters.", 'title': 'Bad database name'}
92 return req.session.proxy("db").create(super_admin_pwd, dbname, demo_data, db_lang, admin_pwd)
95 # progress, users = req.session.proxy('db').get_progress(super_admin_pwd, res)
98 # if x['login'] == 'admin':
99 # req.session.login(dbname, 'admin', x['password'])
105 # raise DatabaseCreationCrash()
106 # except DatabaseCreationCrash:
107 # return {'error': "The server crashed during installation.\nWe suggest you to drop this database.",
108 # 'title': 'Error during database creation'}
110 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
111 return {'error': 'Bad super admin password !', 'title': 'Create Database'}
113 return {'error': 'Could not create database !', 'title': 'Create Database'}
117 password = kw.get('password')
120 return req.session.proxy("db").drop(password, db)
122 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
123 return {'error': 'Bad super admin password !', 'title': 'Drop Database'}
125 return {'error': 'Could not drop database !', 'title': 'Drop Database'}
127 elif flag == 'backup':
129 password = kw.get('password')
131 res = req.session.proxy("db").dump(password, db)
133 cherrypy.response.headers['Content-Type'] = "application/data"
134 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
135 return base64.decodestring(res)
137 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
138 return {'error': 'Bad super admin password !', 'title': 'Backup Database'}
140 return {'error': 'Could not drop database !', 'title': 'Backup Database'}
142 elif flag == 'restore':
143 filename = kw.get('filename')
145 password = kw.get('password')
148 data = base64.encodestring(filename.file.read())
149 return req.session.proxy("db").restore(password, db, data)
151 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
152 return {'error': 'Bad super admin password !', 'title': 'Restore Database'}
154 return {'error': 'Could not restore database !', 'title': 'Restore Database'}
156 elif flag == 'change_password':
157 old_password = kw.get('old_password')
158 new_password = kw.get('new_password')
159 confirm_password = kw.get('confirm_password')
162 return req.session.proxy("db").change_admin_password(old_password, new_password)
164 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
165 return {'error': 'Bad super admin password !', 'title': 'Change Password'}
167 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
169 class Session(openerpweb.Controller):
170 _cp_path = "/base/session"
172 def manifest_glob(self, addons, key):
175 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
178 resource_path[len(openerpweb.path_addons):]
179 for pattern in globlist
180 for resource_path in glob.glob(os.path.join(
181 openerpweb.path_addons, addon, pattern))
185 def concat_files(self, file_list):
186 """ Concatenate file content
187 return (concat,timestamp)
188 concat: concatenation of file content
189 timestamp: max(os.path.getmtime of file_list)
191 root = openerpweb.path_root
195 fname = os.path.join(root, i)
196 ftime = os.path.getmtime(fname)
197 if ftime > files_timestamp:
198 files_timestamp = ftime
199 files_content = open(fname).read()
200 files_concat = "".join(files_content)
203 @openerpweb.jsonrequest
204 def login(self, req, db, login, password):
205 req.session.login(db, login, password)
208 "session_id": req.session_id,
209 "uid": req.session._uid,
212 @openerpweb.jsonrequest
213 def sc_list(self, req):
214 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
215 req.session.eval_context(req.context))
217 @openerpweb.jsonrequest
218 def get_lang_list(self, req):
219 lang_list = [('en_US', 'English (US)')]
221 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
224 return {"lang_list": lang_list}
226 @openerpweb.jsonrequest
227 def modules(self, req):
228 return {"modules": [name
229 for name, manifest in openerpweb.addons_manifest.iteritems()
230 if manifest.get('active', True)]}
232 @openerpweb.jsonrequest
233 def csslist(self, req, mods='base'):
234 return {'files': self.manifest_glob(mods.split(','), 'css')}
236 @openerpweb.jsonrequest
237 def jslist(self, req, mods='base'):
238 return {'files': self.manifest_glob(mods.split(','), 'js')}
240 def css(self, req, mods='base'):
241 files = self.manifest_glob(mods.split(','), 'css')
242 concat = self.concat_files(files)[0]
243 # TODO request set the Date of last modif and Etag
247 def js(self, req, mods='base'):
248 files = self.manifest_glob(mods.split(','), 'js')
249 concat = self.concat_files(files)[0]
250 # TODO request set the Date of last modif and Etag
254 @openerpweb.jsonrequest
255 def eval_domain_and_context(self, req, contexts, domains,
257 """ Evaluates sequences of domains and contexts, composing them into
258 a single context, domain or group_by sequence.
260 :param list contexts: list of contexts to merge together. Contexts are
261 evaluated in sequence, all previous contexts
262 are part of their own evaluation context
263 (starting at the session context).
264 :param list domains: list of domains to merge together. Domains are
265 evaluated in sequence and appended to one another
266 (implicit AND), their evaluation domain is the
267 result of merging all contexts.
268 :param list group_by_seq: list of domains (which may be in a different
269 order than the ``contexts`` parameter),
270 evaluated in sequence, their ``'group_by'``
271 key is extracted if they have one.
276 the global context created by merging all of
280 the concatenation of all domains
283 a list of fields to group by, potentially empty (in which case
284 no group by should be performed)
286 context, domain = eval_context_and_domain(req.session,
287 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
288 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
290 group_by_sequence = []
291 for candidate in (group_by_seq or []):
292 ctx = req.session.eval_context(candidate, context)
293 group_by = ctx.get('group_by')
296 elif isinstance(group_by, basestring):
297 group_by_sequence.append(group_by)
299 group_by_sequence.extend(group_by)
304 'group_by': group_by_sequence
307 @openerpweb.jsonrequest
308 def save_session_action(self, req, the_action):
310 This method store an action object in the session object and returns an integer
311 identifying that action. The method get_session_action() can be used to get
314 :param the_action: The action to save in the session.
315 :type the_action: anything
316 :return: A key identifying the saved action.
319 saved_actions = cherrypy.session.get('saved_actions')
320 if not saved_actions:
321 saved_actions = {"next":0, "actions":{}}
322 cherrypy.session['saved_actions'] = saved_actions
323 # we don't allow more than 10 stored actions
324 if len(saved_actions["actions"]) >= 10:
325 del saved_actions["actions"][min(saved_actions["actions"].keys())]
326 key = saved_actions["next"]
327 saved_actions["actions"][key] = the_action
328 saved_actions["next"] = key + 1
331 @openerpweb.jsonrequest
332 def get_session_action(self, req, key):
334 Gets back a previously saved action. This method can return None if the action
335 was saved since too much time (this case should be handled in a smart way).
337 :param key: The key given by save_session_action()
339 :return: The saved action or None.
342 saved_actions = cherrypy.session.get('saved_actions')
343 if not saved_actions:
345 return saved_actions["actions"].get(key)
347 def eval_context_and_domain(session, context, domain=None):
348 e_context = session.eval_context(context)
349 # should we give the evaluated context as an evaluation context to the domain?
350 e_domain = session.eval_domain(domain or [])
352 return e_context, e_domain
354 def load_actions_from_ir_values(req, key, key2, models, meta, context):
355 Values = req.session.model('ir.values')
356 actions = Values.get(key, key2, models, meta, context)
358 return [(id, name, clean_action(action, req.session))
359 for id, name, action in actions]
361 def clean_action(action, session):
362 if action['type'] != 'ir.actions.act_window':
364 # values come from the server, we can just eval them
365 if isinstance(action.get('context', None), basestring):
366 action['context'] = eval(
368 session.evaluation_context()) or {}
370 if isinstance(action.get('domain', None), basestring):
371 action['domain'] = eval(
373 session.evaluation_context(
374 action['context'])) or []
375 if 'flags' not in action:
376 # Set empty flags dictionary for web client.
377 action['flags'] = dict()
378 return fix_view_modes(action)
380 def generate_views(action):
382 While the server generates a sequence called "views" computing dependencies
383 between a bunch of stuff for views coming directly from the database
384 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
385 to return custom view dictionaries generated on the fly.
387 In that case, there is no ``views`` key available on the action.
389 Since the web client relies on ``action['views']``, generate it here from
390 ``view_mode`` and ``view_id``.
392 Currently handles two different cases:
394 * no view_id, multiple view_mode
395 * single view_id, single view_mode
397 :param dict action: action descriptor dictionary to generate a views key for
399 view_id = action.get('view_id', False)
400 if isinstance(view_id, (list, tuple)):
403 # providing at least one view mode is a requirement, not an option
404 view_modes = action['view_mode'].split(',')
406 if len(view_modes) > 1:
408 raise ValueError('Non-db action dictionaries should provide '
409 'either multiple view modes or a single view '
410 'mode and an optional view id.\n\n Got view '
411 'modes %r and view id %r for action %r' % (
412 view_modes, view_id, action))
413 action['views'] = [(False, mode) for mode in view_modes]
415 action['views'] = [(view_id, view_modes[0])]
417 def fix_view_modes(action):
418 """ For historical reasons, OpenERP has weird dealings in relation to
419 view_mode and the view_type attribute (on window actions):
421 * one of the view modes is ``tree``, which stands for both list views
423 * the choice is made by checking ``view_type``, which is either
424 ``form`` for a list view or ``tree`` for an actual tree view
426 This methods simply folds the view_type into view_mode by adding a
427 new view mode ``list`` which is the result of the ``tree`` view_mode
428 in conjunction with the ``form`` view_type.
430 TODO: this should go into the doc, some kind of "peculiarities" section
432 :param dict action: an action descriptor
433 :returns: nothing, the action is modified in place
435 if 'views' not in action:
436 generate_views(action)
438 if action.pop('view_type') != 'form':
442 [id, mode if mode != 'tree' else 'list']
443 for id, mode in action['views']
448 class Menu(openerpweb.Controller):
449 _cp_path = "/base/menu"
451 @openerpweb.jsonrequest
453 return {'data': self.do_load(req)}
455 def do_load(self, req):
456 """ Loads all menu items (all applications and their sub-menus).
458 :param req: A request object, with an OpenERP session attribute
459 :type req: < session -> OpenERPSession >
460 :return: the menu root
461 :rtype: dict('children': menu_nodes)
463 Menus = req.session.model('ir.ui.menu')
464 # menus are loaded fully unlike a regular tree view, cause there are
465 # less than 512 items
466 context = req.session.eval_context(req.context)
467 menu_ids = Menus.search([], 0, False, False, context)
468 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
469 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
470 menu_items.append(menu_root)
472 # make a tree using parent_id
473 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
474 for menu_item in menu_items:
475 if menu_item['parent_id']:
476 parent = menu_item['parent_id'][0]
479 if parent in menu_items_map:
480 menu_items_map[parent].setdefault(
481 'children', []).append(menu_item)
483 # sort by sequence a tree using parent_id
484 for menu_item in menu_items:
485 menu_item.setdefault('children', []).sort(
486 key=lambda x:x["sequence"])
490 @openerpweb.jsonrequest
491 def action(self, req, menu_id):
492 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
493 [('ir.ui.menu', menu_id)], False,
494 req.session.eval_context(req.context))
495 return {"action": actions}
497 class DataSet(openerpweb.Controller):
498 _cp_path = "/base/dataset"
500 @openerpweb.jsonrequest
501 def fields(self, req, model):
502 return {'fields': req.session.model(model).fields_get(False,
503 req.session.eval_context(req.context))}
505 @openerpweb.jsonrequest
506 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
507 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
508 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
510 """ Performs a search() followed by a read() (if needed) using the
511 provided search criteria
513 :param request: a JSON-RPC request object
514 :type request: openerpweb.JsonRequest
515 :param str model: the name of the model to search on
516 :param fields: a list of the fields to return in the result records
518 :param int offset: from which index should the results start being returned
519 :param int limit: the maximum number of records to return
520 :param list domain: the search domain for the query
521 :param list sort: sorting directives
522 :returns: A structure (dict) with two keys: ids (all the ids matching
523 the (domain, context) pair) and records (paginated records
524 matching fields selection set)
527 Model = request.session.model(model)
528 context, domain = eval_context_and_domain(
529 request.session, request.context, domain)
531 ids = Model.search(domain, 0, False, sort or False, context)
532 # need to fill the dataset with all ids for the (domain, context) pair,
533 # so search un-paginated and paginate manually before reading
534 paginated_ids = ids[offset:(offset + limit if limit else None)]
535 if fields and fields == ['id']:
536 # shortcut read if we only want the ids
539 'records': map(lambda id: {'id': id}, paginated_ids)
542 records = Model.read(paginated_ids, fields or False, context)
543 records.sort(key=lambda obj: ids.index(obj['id']))
550 @openerpweb.jsonrequest
551 def get(self, request, model, ids, fields=False):
552 return self.do_get(request, model, ids, fields)
553 def do_get(self, request, model, ids, fields=False):
554 """ Fetches and returns the records of the model ``model`` whose ids
557 The results are in the same order as the inputs, but elements may be
558 missing (if there is no record left for the id)
560 :param request: the JSON-RPC2 request object
561 :type request: openerpweb.JsonRequest
562 :param model: the model to read from
564 :param ids: a list of identifiers
566 :param fields: a list of fields to fetch, ``False`` or empty to fetch
567 all fields in the model
568 :type fields: list | False
569 :returns: a list of records, in the same order as the list of ids
572 Model = request.session.model(model)
573 records = Model.read(ids, fields, request.session.eval_context(request.context))
575 record_map = dict((record['id'], record) for record in records)
577 return [record_map[id] for id in ids if record_map.get(id)]
579 @openerpweb.jsonrequest
580 def load(self, req, model, id, fields):
581 m = req.session.model(model)
583 r = m.read([id], False, req.session.eval_context(req.context))
586 return {'value': value}
588 @openerpweb.jsonrequest
589 def create(self, req, model, data):
590 m = req.session.model(model)
591 r = m.create(data, req.session.eval_context(req.context))
594 @openerpweb.jsonrequest
595 def save(self, req, model, id, data):
596 m = req.session.model(model)
597 r = m.write([id], data, req.session.eval_context(req.context))
600 @openerpweb.jsonrequest
601 def unlink(self, request, model, ids=()):
602 Model = request.session.model(model)
603 return Model.unlink(ids, request.session.eval_context(request.context))
605 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
606 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
607 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
608 c, d = eval_context_and_domain(req.session, context, domain)
609 if domain_id and len(args) - 1 >= domain_id:
611 if context_id and len(args) - 1 >= context_id:
614 return getattr(req.session.model(model), method)(*args)
616 @openerpweb.jsonrequest
617 def call(self, req, model, method, args, domain_id=None, context_id=None):
618 return {'result': self.call_common(req, model, method, args, domain_id, context_id)}
620 @openerpweb.jsonrequest
621 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
622 action = self.call_common(req, model, method, args, domain_id, context_id)
623 if isinstance(action, dict) and action.get('type') != '':
624 return {'result': clean_action(action, req.session)}
625 return {'result': False}
627 @openerpweb.jsonrequest
628 def exec_workflow(self, req, model, id, signal):
629 r = req.session.exec_workflow(model, id, signal)
632 @openerpweb.jsonrequest
633 def default_get(self, req, model, fields):
634 m = req.session.model(model)
635 r = m.default_get(fields, req.session.eval_context(req.context))
638 class DataGroup(openerpweb.Controller):
639 _cp_path = "/base/group"
640 @openerpweb.jsonrequest
641 def read(self, request, model, fields, group_by_fields, domain=None):
642 Model = request.session.model(model)
643 context, domain = eval_context_and_domain(request.session, request.context, domain)
645 return Model.read_group(
646 domain or [], fields, group_by_fields, 0, False,
647 dict(context, group_by=group_by_fields))
649 class View(openerpweb.Controller):
650 _cp_path = "/base/view"
652 def fields_view_get(self, request, model, view_id, view_type,
653 transform=True, toolbar=False, submenu=False):
654 Model = request.session.model(model)
655 context = request.session.eval_context(request.context)
656 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
657 # todo fme?: check that we should pass the evaluated context here
658 self.process_view(request.session, fvg, context, transform)
661 def process_view(self, session, fvg, context, transform):
662 # depending on how it feels, xmlrpclib.ServerProxy can translate
663 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
664 # enjoy unicode strings which can not be trivially converted to
665 # strings, and it blows up during parsing.
667 # So ensure we fix this retardation by converting view xml back to
669 if isinstance(fvg['arch'], unicode):
670 arch = fvg['arch'].encode('utf-8')
675 evaluation_context = session.evaluation_context(context or {})
676 xml = self.transform_view(arch, session, evaluation_context)
678 xml = ElementTree.fromstring(arch)
679 fvg['arch'] = Xml2Json.convert_element(xml)
680 for field in fvg['fields'].values():
681 if field.has_key('views') and field['views']:
682 for view in field["views"].values():
683 self.process_view(session, view, None, transform)
685 @openerpweb.jsonrequest
686 def add_custom(self, request, view_id, arch):
687 CustomView = request.session.model('ir.ui.view.custom')
689 'user_id': request.session._uid,
692 }, request.session.eval_context(request.context))
693 return {'result': True}
695 @openerpweb.jsonrequest
696 def undo_custom(self, request, view_id, reset=False):
697 CustomView = request.session.model('ir.ui.view.custom')
698 context = request.session.eval_context(request.context)
699 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
700 0, False, False, context)
703 CustomView.unlink(vcustom, context)
705 CustomView.unlink([vcustom[0]], context)
706 return {'result': True}
707 return {'result': False}
709 def transform_view(self, view_string, session, context=None):
710 # transform nodes on the fly via iterparse, instead of
711 # doing it statically on the parsing result
712 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
714 for event, elem in parser:
718 self.parse_domains_and_contexts(elem, session)
721 def parse_domain(self, elem, attr_name, session):
722 """ Parses an attribute of the provided name as a domain, transforms it
723 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
725 :param elem: the node being parsed
726 :type param: xml.etree.ElementTree.Element
727 :param str attr_name: the name of the attribute which should be parsed
728 :param session: Current OpenERP session
729 :type session: openerpweb.openerpweb.OpenERPSession
731 domain = elem.get(attr_name, '').strip()
736 openerpweb.ast.literal_eval(
741 openerpweb.nonliterals.Domain(session, domain))
743 def parse_domains_and_contexts(self, elem, session):
744 """ Converts domains and contexts from the view into Python objects,
745 either literals if they can be parsed by literal_eval or a special
746 placeholder object if the domain or context refers to free variables.
748 :param elem: the current node being parsed
749 :type param: xml.etree.ElementTree.Element
750 :param session: OpenERP session object, used to store and retrieve
752 :type session: openerpweb.openerpweb.OpenERPSession
754 self.parse_domain(elem, 'domain', session)
755 self.parse_domain(elem, 'filter_domain', session)
756 for el in ['context', 'default_get']:
757 context_string = elem.get(el, '').strip()
761 openerpweb.ast.literal_eval(context_string))
764 openerpweb.nonliterals.Context(
765 session, context_string))
767 class FormView(View):
768 _cp_path = "/base/formview"
770 @openerpweb.jsonrequest
771 def load(self, req, model, view_id, toolbar=False):
772 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
773 return {'fields_view': fields_view}
775 class ListView(View):
776 _cp_path = "/base/listview"
778 @openerpweb.jsonrequest
779 def load(self, req, model, view_id, toolbar=False):
780 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
781 return {'fields_view': fields_view}
783 def process_colors(self, view, row, context):
784 colors = view['arch']['attrs'].get('colors')
791 for pair in colors.split(';')
792 if eval(pair.split(':')[1], dict(context, **row))
797 elif len(color) == 1:
801 class SearchView(View):
802 _cp_path = "/base/searchview"
804 @openerpweb.jsonrequest
805 def load(self, req, model, view_id):
806 fields_view = self.fields_view_get(req, model, view_id, 'search')
807 return {'fields_view': fields_view}
809 @openerpweb.jsonrequest
810 def fields_get(self, req, model):
811 Model = req.session.model(model)
812 fields = Model.fields_get(False, req.session.eval_context(req.context))
813 return {'fields': fields}
815 class Binary(openerpweb.Controller):
816 _cp_path = "/base/binary"
818 @openerpweb.httprequest
819 def image(self, request, session_id, model, id, field, **kw):
820 cherrypy.response.headers['Content-Type'] = 'image/png'
821 Model = request.session.model(model)
822 context = request.session.eval_context(request.context)
825 res = Model.default_get([field], context).get(field, '')
827 res = Model.read([int(id)], [field], context)[0].get(field, '')
828 return base64.decodestring(res)
829 except: # TODO: what's the exception here?
830 return self.placeholder()
831 def placeholder(self):
832 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
834 @openerpweb.httprequest
835 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
836 Model = request.session.model(model)
837 context = request.session.eval_context(request.context)
838 res = Model.read([int(id)], [field, fieldname], context)[0]
839 filecontent = res.get(field, '')
841 raise cherrypy.NotFound
843 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
844 filename = '%s_%s' % (model.replace('.', '_'), id)
846 filename = res.get(fieldname, '') or filename
847 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
848 return base64.decodestring(filecontent)
850 @openerpweb.httprequest
851 def upload(self, request, session_id, callback, ufile=None):
852 cherrypy.response.timeout = 500
854 for key, val in cherrypy.request.headers.iteritems():
855 headers[key.lower()] = val
856 size = int(headers.get('content-length', 0))
857 # TODO: might be useful to have a configuration flag for max-length file uploads
859 out = """<script language="javascript" type="text/javascript">
860 var win = window.top.window,
862 if (typeof(callback) === 'function') {
863 callback.apply(this, %s);
865 win.jQuery('#oe_notification', win.document).notify('create', {
866 title: "Ajax File Upload",
867 text: "Could not find callback"
871 data = ufile.file.read()
872 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
874 args = [False, e.message]
875 return out % (simplejson.dumps(callback), simplejson.dumps(args))
877 @openerpweb.httprequest
878 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
879 cherrypy.response.timeout = 500
880 context = request.session.eval_context(request.context)
881 Model = request.session.model('ir.attachment')
883 out = """<script language="javascript" type="text/javascript">
884 var win = window.top.window,
886 if (typeof(callback) === 'function') {
887 callback.call(this, %s);
890 attachment_id = Model.create({
891 'name': ufile.filename,
892 'datas': base64.encodestring(ufile.file.read()),
897 'filename': ufile.filename,
901 args = { 'error': e.message }
902 return out % (simplejson.dumps(callback), simplejson.dumps(args))
904 class Action(openerpweb.Controller):
905 _cp_path = "/base/action"
907 @openerpweb.jsonrequest
908 def load(self, req, action_id):
909 Actions = req.session.model('ir.actions.actions')
911 context = req.session.eval_context(req.context)
912 action_type = Actions.read([action_id], ['type'], context)
914 action = req.session.model(action_type[0]['type']).read([action_id], False,
917 value = clean_action(action[0], req.session)
918 return {'result': value}
920 @openerpweb.jsonrequest
921 def run(self, req, action_id):
922 return clean_action(req.session.model('ir.actions.server').run(
923 [action_id], req.session.eval_context(req.context)), req.session)