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 create_db(self, req, **kw):
78 super_admin_pwd = kw.get('super_admin_pwd')
80 demo_data = kw.get('demo_data')
81 db_lang = kw.get('db_lang')
82 admin_pwd = kw.get('admin_pwd')
83 confirm_pwd = kw.get('confirm_pwd')
85 if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', dbname):
86 return {'error': "You must avoid all accents, space or special characters.", 'title': 'Bad database name'}
90 return req.session.proxy("db").create(super_admin_pwd, dbname, demo_data, db_lang, admin_pwd)
93 # progress, users = req.session.proxy('db').get_progress(super_admin_pwd, res)
96 # if x['login'] == 'admin':
97 # req.session.login(dbname, 'admin', x['password'])
103 # raise DatabaseCreationCrash()
104 # except DatabaseCreationCrash:
105 # return {'error': "The server crashed during installation.\nWe suggest you to drop this database.",
106 # 'title': 'Error during database creation'}
108 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
109 return {'error': 'Bad super admin password !', 'title': 'Create Database'}
111 return {'error': 'Could not create database !', 'title': 'Create Database'}
113 @openerpweb.jsonrequest
114 def drop_db(self, req, **kw):
116 password = kw.get('password')
119 return req.session.proxy("db").drop(password, db)
121 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
122 return {'error': 'Bad super admin password !', 'title': 'Drop Database'}
124 return {'error': 'Could not drop database !', 'title': 'Drop Database'}
126 @openerpweb.jsonrequest
127 def backup_db(self, req, **kw):
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 @openerpweb.jsonrequest
143 def restore_db(self, req, **kw):
144 filename = kw.get('filename')
146 password = kw.get('password')
149 data = base64.encodestring(filename.file.read())
150 return req.session.proxy("db").restore(password, db, data)
152 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
153 return {'error': 'Bad super admin password !', 'title': 'Restore Database'}
155 return {'error': 'Could not restore database !', 'title': 'Restore Database'}
157 @openerpweb.jsonrequest
158 def change_password_db(self, req, **kw):
159 old_password = kw.get('old_password')
160 new_password = kw.get('new_password')
161 confirm_password = kw.get('confirm_password')
164 return req.session.proxy("db").change_admin_password(old_password, new_password)
166 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
167 return {'error': 'Bad super admin password !', 'title': 'Change Password'}
169 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
171 class Session(openerpweb.Controller):
172 _cp_path = "/base/session"
174 def manifest_glob(self, addons, key):
177 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
180 resource_path[len(openerpweb.path_addons):]
181 for pattern in globlist
182 for resource_path in glob.glob(os.path.join(
183 openerpweb.path_addons, addon, pattern))
187 def concat_files(self, file_list):
188 """ Concatenate file content
189 return (concat,timestamp)
190 concat: concatenation of file content
191 timestamp: max(os.path.getmtime of file_list)
193 root = openerpweb.path_root
197 fname = os.path.join(root, i)
198 ftime = os.path.getmtime(fname)
199 if ftime > files_timestamp:
200 files_timestamp = ftime
201 files_content = open(fname).read()
202 files_concat = "".join(files_content)
205 @openerpweb.jsonrequest
206 def login(self, req, db, login, password):
207 req.session.login(db, login, password)
210 "session_id": req.session_id,
211 "uid": req.session._uid,
214 @openerpweb.jsonrequest
215 def sc_list(self, req):
216 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
217 req.session.eval_context(req.context))
219 @openerpweb.jsonrequest
220 def get_lang_list(self, req):
221 lang_list = [('en_US', 'English (US)')]
223 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
226 return {"lang_list": lang_list}
228 @openerpweb.jsonrequest
229 def modules(self, req):
230 return {"modules": [name
231 for name, manifest in openerpweb.addons_manifest.iteritems()
232 if manifest.get('active', True)]}
234 @openerpweb.jsonrequest
235 def csslist(self, req, mods='base'):
236 return {'files': self.manifest_glob(mods.split(','), 'css')}
238 @openerpweb.jsonrequest
239 def jslist(self, req, mods='base'):
240 return {'files': self.manifest_glob(mods.split(','), 'js')}
242 def css(self, req, mods='base'):
243 files = self.manifest_glob(mods.split(','), 'css')
244 concat = self.concat_files(files)[0]
245 # TODO request set the Date of last modif and Etag
249 def js(self, req, mods='base'):
250 files = self.manifest_glob(mods.split(','), 'js')
251 concat = self.concat_files(files)[0]
252 # TODO request set the Date of last modif and Etag
256 @openerpweb.jsonrequest
257 def eval_domain_and_context(self, req, contexts, domains,
259 """ Evaluates sequences of domains and contexts, composing them into
260 a single context, domain or group_by sequence.
262 :param list contexts: list of contexts to merge together. Contexts are
263 evaluated in sequence, all previous contexts
264 are part of their own evaluation context
265 (starting at the session context).
266 :param list domains: list of domains to merge together. Domains are
267 evaluated in sequence and appended to one another
268 (implicit AND), their evaluation domain is the
269 result of merging all contexts.
270 :param list group_by_seq: list of domains (which may be in a different
271 order than the ``contexts`` parameter),
272 evaluated in sequence, their ``'group_by'``
273 key is extracted if they have one.
278 the global context created by merging all of
282 the concatenation of all domains
285 a list of fields to group by, potentially empty (in which case
286 no group by should be performed)
288 context, domain = eval_context_and_domain(req.session,
289 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
290 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
292 group_by_sequence = []
293 for candidate in (group_by_seq or []):
294 ctx = req.session.eval_context(candidate, context)
295 group_by = ctx.get('group_by')
298 elif isinstance(group_by, basestring):
299 group_by_sequence.append(group_by)
301 group_by_sequence.extend(group_by)
306 'group_by': group_by_sequence
309 @openerpweb.jsonrequest
310 def save_session_action(self, req, the_action):
312 This method store an action object in the session object and returns an integer
313 identifying that action. The method get_session_action() can be used to get
316 :param the_action: The action to save in the session.
317 :type the_action: anything
318 :return: A key identifying the saved action.
321 saved_actions = cherrypy.session.get('saved_actions')
322 if not saved_actions:
323 saved_actions = {"next":0, "actions":{}}
324 cherrypy.session['saved_actions'] = saved_actions
325 # we don't allow more than 10 stored actions
326 if len(saved_actions["actions"]) >= 10:
327 del saved_actions["actions"][min(saved_actions["actions"].keys())]
328 key = saved_actions["next"]
329 saved_actions["actions"][key] = the_action
330 saved_actions["next"] = key + 1
333 @openerpweb.jsonrequest
334 def get_session_action(self, req, key):
336 Gets back a previously saved action. This method can return None if the action
337 was saved since too much time (this case should be handled in a smart way).
339 :param key: The key given by save_session_action()
341 :return: The saved action or None.
344 saved_actions = cherrypy.session.get('saved_actions')
345 if not saved_actions:
347 return saved_actions["actions"].get(key)
349 def eval_context_and_domain(session, context, domain=None):
350 e_context = session.eval_context(context)
351 # should we give the evaluated context as an evaluation context to the domain?
352 e_domain = session.eval_domain(domain or [])
354 return e_context, e_domain
356 def load_actions_from_ir_values(req, key, key2, models, meta, context):
357 Values = req.session.model('ir.values')
358 actions = Values.get(key, key2, models, meta, context)
360 return [(id, name, clean_action(action, req.session))
361 for id, name, action in actions]
363 def clean_action(action, session):
364 if action['type'] != 'ir.actions.act_window':
366 # values come from the server, we can just eval them
367 if isinstance(action.get('context', None), basestring):
368 action['context'] = eval(
370 session.evaluation_context()) or {}
372 if isinstance(action.get('domain', None), basestring):
373 action['domain'] = eval(
375 session.evaluation_context(
376 action.get('context', {}))) or []
377 if 'flags' not in action:
378 # Set empty flags dictionary for web client.
379 action['flags'] = dict()
380 return fix_view_modes(action)
382 def generate_views(action):
384 While the server generates a sequence called "views" computing dependencies
385 between a bunch of stuff for views coming directly from the database
386 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
387 to return custom view dictionaries generated on the fly.
389 In that case, there is no ``views`` key available on the action.
391 Since the web client relies on ``action['views']``, generate it here from
392 ``view_mode`` and ``view_id``.
394 Currently handles two different cases:
396 * no view_id, multiple view_mode
397 * single view_id, single view_mode
399 :param dict action: action descriptor dictionary to generate a views key for
401 view_id = action.get('view_id', False)
402 if isinstance(view_id, (list, tuple)):
405 # providing at least one view mode is a requirement, not an option
406 view_modes = action['view_mode'].split(',')
408 if len(view_modes) > 1:
410 raise ValueError('Non-db action dictionaries should provide '
411 'either multiple view modes or a single view '
412 'mode and an optional view id.\n\n Got view '
413 'modes %r and view id %r for action %r' % (
414 view_modes, view_id, action))
415 action['views'] = [(False, mode) for mode in view_modes]
417 action['views'] = [(view_id, view_modes[0])]
419 def fix_view_modes(action):
420 """ For historical reasons, OpenERP has weird dealings in relation to
421 view_mode and the view_type attribute (on window actions):
423 * one of the view modes is ``tree``, which stands for both list views
425 * the choice is made by checking ``view_type``, which is either
426 ``form`` for a list view or ``tree`` for an actual tree view
428 This methods simply folds the view_type into view_mode by adding a
429 new view mode ``list`` which is the result of the ``tree`` view_mode
430 in conjunction with the ``form`` view_type.
432 TODO: this should go into the doc, some kind of "peculiarities" section
434 :param dict action: an action descriptor
435 :returns: nothing, the action is modified in place
437 if 'views' not in action:
438 generate_views(action)
440 if action.pop('view_type') != 'form':
444 [id, mode if mode != 'tree' else 'list']
445 for id, mode in action['views']
450 class Menu(openerpweb.Controller):
451 _cp_path = "/base/menu"
453 @openerpweb.jsonrequest
455 return {'data': self.do_load(req)}
457 def do_load(self, req):
458 """ Loads all menu items (all applications and their sub-menus).
460 :param req: A request object, with an OpenERP session attribute
461 :type req: < session -> OpenERPSession >
462 :return: the menu root
463 :rtype: dict('children': menu_nodes)
465 Menus = req.session.model('ir.ui.menu')
466 # menus are loaded fully unlike a regular tree view, cause there are
467 # less than 512 items
468 context = req.session.eval_context(req.context)
469 menu_ids = Menus.search([], 0, False, False, context)
470 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
471 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
472 menu_items.append(menu_root)
474 # make a tree using parent_id
475 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
476 for menu_item in menu_items:
477 if menu_item['parent_id']:
478 parent = menu_item['parent_id'][0]
481 if parent in menu_items_map:
482 menu_items_map[parent].setdefault(
483 'children', []).append(menu_item)
485 # sort by sequence a tree using parent_id
486 for menu_item in menu_items:
487 menu_item.setdefault('children', []).sort(
488 key=lambda x:x["sequence"])
492 @openerpweb.jsonrequest
493 def action(self, req, menu_id):
494 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
495 [('ir.ui.menu', menu_id)], False,
496 req.session.eval_context(req.context))
497 return {"action": actions}
499 class DataSet(openerpweb.Controller):
500 _cp_path = "/base/dataset"
502 @openerpweb.jsonrequest
503 def fields(self, req, model):
504 return {'fields': req.session.model(model).fields_get(False,
505 req.session.eval_context(req.context))}
507 @openerpweb.jsonrequest
508 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
509 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
510 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
512 """ Performs a search() followed by a read() (if needed) using the
513 provided search criteria
515 :param request: a JSON-RPC request object
516 :type request: openerpweb.JsonRequest
517 :param str model: the name of the model to search on
518 :param fields: a list of the fields to return in the result records
520 :param int offset: from which index should the results start being returned
521 :param int limit: the maximum number of records to return
522 :param list domain: the search domain for the query
523 :param list sort: sorting directives
524 :returns: A structure (dict) with two keys: ids (all the ids matching
525 the (domain, context) pair) and records (paginated records
526 matching fields selection set)
529 Model = request.session.model(model)
530 context, domain = eval_context_and_domain(
531 request.session, request.context, domain)
533 ids = Model.search(domain, 0, False, sort or False, context)
534 # need to fill the dataset with all ids for the (domain, context) pair,
535 # so search un-paginated and paginate manually before reading
536 paginated_ids = ids[offset:(offset + limit if limit else None)]
537 if fields and fields == ['id']:
538 # shortcut read if we only want the ids
541 'records': map(lambda id: {'id': id}, paginated_ids)
544 records = Model.read(paginated_ids, fields or False, context)
545 records.sort(key=lambda obj: ids.index(obj['id']))
552 @openerpweb.jsonrequest
553 def get(self, request, model, ids, fields=False):
554 return self.do_get(request, model, ids, fields)
555 def do_get(self, request, model, ids, fields=False):
556 """ Fetches and returns the records of the model ``model`` whose ids
559 The results are in the same order as the inputs, but elements may be
560 missing (if there is no record left for the id)
562 :param request: the JSON-RPC2 request object
563 :type request: openerpweb.JsonRequest
564 :param model: the model to read from
566 :param ids: a list of identifiers
568 :param fields: a list of fields to fetch, ``False`` or empty to fetch
569 all fields in the model
570 :type fields: list | False
571 :returns: a list of records, in the same order as the list of ids
574 Model = request.session.model(model)
575 records = Model.read(ids, fields, request.session.eval_context(request.context))
577 record_map = dict((record['id'], record) for record in records)
579 return [record_map[id] for id in ids if record_map.get(id)]
581 @openerpweb.jsonrequest
582 def load(self, req, model, id, fields):
583 m = req.session.model(model)
585 r = m.read([id], False, req.session.eval_context(req.context))
588 return {'value': value}
590 @openerpweb.jsonrequest
591 def create(self, req, model, data):
592 m = req.session.model(model)
593 r = m.create(data, req.session.eval_context(req.context))
596 @openerpweb.jsonrequest
597 def save(self, req, model, id, data):
598 m = req.session.model(model)
599 r = m.write([id], data, req.session.eval_context(req.context))
602 @openerpweb.jsonrequest
603 def unlink(self, request, model, ids=()):
604 Model = request.session.model(model)
605 return Model.unlink(ids, request.session.eval_context(request.context))
607 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
608 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
609 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
610 c, d = eval_context_and_domain(req.session, context, domain)
611 if domain_id and len(args) - 1 >= domain_id:
613 if context_id and len(args) - 1 >= context_id:
616 return getattr(req.session.model(model), method)(*args)
618 @openerpweb.jsonrequest
619 def call(self, req, model, method, args, domain_id=None, context_id=None):
620 return self.call_common(req, model, method, args, domain_id, context_id)
622 @openerpweb.jsonrequest
623 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
624 action = self.call_common(req, model, method, args, domain_id, context_id)
625 if isinstance(action, dict) and action.get('type') != '':
626 return {'result': clean_action(action, req.session)}
627 return {'result': False}
629 @openerpweb.jsonrequest
630 def exec_workflow(self, req, model, id, signal):
631 r = req.session.exec_workflow(model, id, signal)
634 @openerpweb.jsonrequest
635 def default_get(self, req, model, fields):
636 Model = req.session.model(model)
637 return Model.default_get(fields, req.session.eval_context(req.context))
639 class DataGroup(openerpweb.Controller):
640 _cp_path = "/base/group"
641 @openerpweb.jsonrequest
642 def read(self, request, model, fields, group_by_fields, domain=None, sort=None):
643 Model = request.session.model(model)
644 context, domain = eval_context_and_domain(request.session, request.context, domain)
646 return Model.read_group(
647 domain or [], fields, group_by_fields, 0, False,
648 dict(context, group_by=group_by_fields), sort or False)
650 class View(openerpweb.Controller):
651 _cp_path = "/base/view"
653 def fields_view_get(self, request, model, view_id, view_type,
654 transform=True, toolbar=False, submenu=False):
655 Model = request.session.model(model)
656 context = request.session.eval_context(request.context)
657 fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
658 # todo fme?: check that we should pass the evaluated context here
659 self.process_view(request.session, fvg, context, transform)
662 def process_view(self, session, fvg, context, transform):
663 # depending on how it feels, xmlrpclib.ServerProxy can translate
664 # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
665 # enjoy unicode strings which can not be trivially converted to
666 # strings, and it blows up during parsing.
668 # So ensure we fix this retardation by converting view xml back to
670 if isinstance(fvg['arch'], unicode):
671 arch = fvg['arch'].encode('utf-8')
676 evaluation_context = session.evaluation_context(context or {})
677 xml = self.transform_view(arch, session, evaluation_context)
679 xml = ElementTree.fromstring(arch)
680 fvg['arch'] = Xml2Json.convert_element(xml)
682 for field in fvg['fields'].itervalues():
683 if field.get('views'):
684 for view in field["views"].itervalues():
685 self.process_view(session, view, None, transform)
686 if field.get('domain'):
687 field["domain"] = self.parse_domain(field["domain"], session)
688 if field.get('context'):
689 field["context"] = self.parse_context(field["context"], session)
691 @openerpweb.jsonrequest
692 def add_custom(self, request, view_id, arch):
693 CustomView = request.session.model('ir.ui.view.custom')
695 'user_id': request.session._uid,
698 }, request.session.eval_context(request.context))
699 return {'result': True}
701 @openerpweb.jsonrequest
702 def undo_custom(self, request, view_id, reset=False):
703 CustomView = request.session.model('ir.ui.view.custom')
704 context = request.session.eval_context(request.context)
705 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
706 0, False, False, context)
709 CustomView.unlink(vcustom, context)
711 CustomView.unlink([vcustom[0]], context)
712 return {'result': True}
713 return {'result': False}
715 def transform_view(self, view_string, session, context=None):
716 # transform nodes on the fly via iterparse, instead of
717 # doing it statically on the parsing result
718 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
720 for event, elem in parser:
724 self.parse_domains_and_contexts(elem, session)
727 def parse_domain(self, domain, session):
728 """ Parses an arbitrary string containing a domain, transforms it
729 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
731 :param domain: the domain to parse, if the domain is not a string it is assumed to
732 be a literal domain and is returned as-is
733 :param session: Current OpenERP session
734 :type session: openerpweb.openerpweb.OpenERPSession
736 if not isinstance(domain, (str, unicode)):
739 return openerpweb.ast.literal_eval(domain)
742 return openerpweb.nonliterals.Domain(session, domain)
744 def parse_context(self, context, session):
745 """ Parses an arbitrary string containing a context, transforms it
746 to either a literal context or a :class:`openerpweb.nonliterals.Context`
748 :param context: the context to parse, if the context is not a string it is assumed to
749 be a literal domain and is returned as-is
750 :param session: Current OpenERP session
751 :type session: openerpweb.openerpweb.OpenERPSession
753 if not isinstance(context, (str, unicode)):
756 return openerpweb.ast.literal_eval(context)
758 return openerpweb.nonliterals.Context(session, context)
760 def parse_domains_and_contexts(self, elem, session):
761 """ Converts domains and contexts from the view into Python objects,
762 either literals if they can be parsed by literal_eval or a special
763 placeholder object if the domain or context refers to free variables.
765 :param elem: the current node being parsed
766 :type param: xml.etree.ElementTree.Element
767 :param session: OpenERP session object, used to store and retrieve
769 :type session: openerpweb.openerpweb.OpenERPSession
771 for el in ['domain', 'filter_domain']:
772 domain = elem.get(el, '').strip()
774 elem.set(el, self.parse_domain(domain, session))
775 for el in ['context', 'default_get']:
776 context_string = elem.get(el, '').strip()
778 elem.set(el, self.parse_context(context_string, session))
780 class FormView(View):
781 _cp_path = "/base/formview"
783 @openerpweb.jsonrequest
784 def load(self, req, model, view_id, toolbar=False):
785 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
786 return {'fields_view': fields_view}
788 class ListView(View):
789 _cp_path = "/base/listview"
791 @openerpweb.jsonrequest
792 def load(self, req, model, view_id, toolbar=False):
793 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
794 return {'fields_view': fields_view}
796 def process_colors(self, view, row, context):
797 colors = view['arch']['attrs'].get('colors')
804 for pair in colors.split(';')
805 if eval(pair.split(':')[1], dict(context, **row))
810 elif len(color) == 1:
814 class SearchView(View):
815 _cp_path = "/base/searchview"
817 @openerpweb.jsonrequest
818 def load(self, req, model, view_id):
819 fields_view = self.fields_view_get(req, model, view_id, 'search')
820 return {'fields_view': fields_view}
822 @openerpweb.jsonrequest
823 def fields_get(self, req, model):
824 Model = req.session.model(model)
825 fields = Model.fields_get(False, req.session.eval_context(req.context))
826 for field in fields.values():
827 # shouldn't convert the views too?
828 if field.get('domain'):
829 field["domain"] = self.parse_domain(field["domain"], req.session)
830 if field.get('context'):
831 field["context"] = self.parse_domain(field["context"], req.session)
832 return {'fields': fields}
834 class Binary(openerpweb.Controller):
835 _cp_path = "/base/binary"
837 @openerpweb.httprequest
838 def image(self, request, session_id, model, id, field, **kw):
839 cherrypy.response.headers['Content-Type'] = 'image/png'
840 Model = request.session.model(model)
841 context = request.session.eval_context(request.context)
844 res = Model.default_get([field], context).get(field, '')
846 res = Model.read([int(id)], [field], context)[0].get(field, '')
847 return base64.decodestring(res)
848 except: # TODO: what's the exception here?
849 return self.placeholder()
850 def placeholder(self):
851 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
853 @openerpweb.httprequest
854 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
855 Model = request.session.model(model)
856 context = request.session.eval_context(request.context)
857 res = Model.read([int(id)], [field, fieldname], context)[0]
858 filecontent = res.get(field, '')
860 raise cherrypy.NotFound
862 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
863 filename = '%s_%s' % (model.replace('.', '_'), id)
865 filename = res.get(fieldname, '') or filename
866 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
867 return base64.decodestring(filecontent)
869 @openerpweb.httprequest
870 def upload(self, request, session_id, callback, ufile=None):
871 cherrypy.response.timeout = 500
873 for key, val in cherrypy.request.headers.iteritems():
874 headers[key.lower()] = val
875 size = int(headers.get('content-length', 0))
876 # TODO: might be useful to have a configuration flag for max-length file uploads
878 out = """<script language="javascript" type="text/javascript">
879 var win = window.top.window,
881 if (typeof(callback) === 'function') {
882 callback.apply(this, %s);
884 win.jQuery('#oe_notification', win.document).notify('create', {
885 title: "Ajax File Upload",
886 text: "Could not find callback"
890 data = ufile.file.read()
891 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
893 args = [False, e.message]
894 return out % (simplejson.dumps(callback), simplejson.dumps(args))
896 @openerpweb.httprequest
897 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
898 cherrypy.response.timeout = 500
899 context = request.session.eval_context(request.context)
900 Model = request.session.model('ir.attachment')
902 out = """<script language="javascript" type="text/javascript">
903 var win = window.top.window,
905 if (typeof(callback) === 'function') {
906 callback.call(this, %s);
909 attachment_id = Model.create({
910 'name': ufile.filename,
911 'datas': base64.encodestring(ufile.file.read()),
916 'filename': ufile.filename,
920 args = { 'error': e.message }
921 return out % (simplejson.dumps(callback), simplejson.dumps(args))
923 class Action(openerpweb.Controller):
924 _cp_path = "/base/action"
926 @openerpweb.jsonrequest
927 def load(self, req, action_id):
928 Actions = req.session.model('ir.actions.actions')
930 context = req.session.eval_context(req.context)
931 action_type = Actions.read([action_id], ['type'], context)
933 action = req.session.model(action_type[0]['type']).read([action_id], False,
936 value = clean_action(action[0], req.session)
937 return {'result': value}
939 @openerpweb.jsonrequest
940 def run(self, req, action_id):
941 return clean_action(req.session.model('ir.actions.server').run(
942 [action_id], req.session.eval_context(req.context)), req.session)