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 Database(openerpweb.Controller):
60 _cp_path = "/base/database"
62 @openerpweb.jsonrequest
63 def get_databases_list(self, req):
64 proxy = req.session.proxy("db")
66 h = req.httprequest.headers['Host'].split(':')[0]
68 r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
69 dbs = [i for i in dbs if re.match(r, i)]
70 return {"db_list": dbs}
72 @openerpweb.jsonrequest
73 def create_db(self, req, fields):
76 if field['name'] == 'super_admin_pwd':
77 super_admin_pwd = field['value']
78 elif field['name'] == 'db_name':
79 dbname = field['value']
80 elif field['name'] == 'demo_data':
81 demo_data = field['value']
82 elif field['name'] == 'db_lang':
83 db_lang = field['value']
84 elif field['name'] == 'create_admin_pwd':
85 admin_pwd = field['value']
87 if dbname and 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)
94 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
95 return {'error': e.faultCode, 'title': 'Create Database'}
97 return {'error': 'Could not create database !', 'title': 'Create Database'}
99 @openerpweb.jsonrequest
100 def drop_db(self, req, fields):
102 if field['name'] == 'drop_db':
104 elif field['name'] == 'drop_pwd':
105 password = field['value']
108 return req.session.proxy("db").drop(password, db)
110 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
111 return {'error': e.faultCode, 'title': 'Drop Database'}
113 return {'error': 'Could not drop database !', 'title': 'Drop Database'}
115 @openerpweb.jsonrequest
116 def backup_db(self, req, fields):
118 if field['name'] == 'backup_db':
120 elif field['name'] == 'backup_pwd':
121 password = field['value']
124 res = req.session.proxy("db").dump(password, db)
126 cherrypy.response.headers['Content-Type'] = "application/data"
127 cherrypy.response.headers['Content-Disposition'] = 'filename="' + db + '.dump"'
128 return base64.decodestring(res)
130 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
131 return {'error': e.faultCode, 'title': 'Backup Database'}
133 return {'error': 'Could not drop database !', 'title': 'Backup Database'}
135 @openerpweb.jsonrequest
136 def restore_db(self, req, fields):
138 if field['name'] == 'restore_db':
139 filename = field['value']
140 elif field['name'] == 'new_db':
142 elif field['name'] == 'restore_pwd':
143 password = field['value']
146 data = base64.encodestring(filename.file.read())
147 return req.session.proxy("db").restore(password, db, data)
149 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
150 return {'error': e.faultCode, 'title': 'Restore Database'}
152 return {'error': 'Could not restore database !', 'title': 'Restore Database'}
154 @openerpweb.jsonrequest
155 def change_password_db(self, req, fields):
157 if field['name'] == 'old_pwd':
158 old_password = field['value']
159 elif field['name'] == 'new_pwd':
160 new_password = field['value']
163 return req.session.proxy("db").change_admin_password(old_password, new_password)
165 if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
166 return {'error': e.faultCode, 'title': 'Change Password'}
168 return {'error': 'Error, password not changed !', 'title': 'Change Password'}
170 class Session(openerpweb.Controller):
171 _cp_path = "/base/session"
173 def manifest_glob(self, addons, key):
176 globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
179 resource_path[len(openerpweb.path_addons):]
180 for pattern in globlist
181 for resource_path in glob.glob(os.path.join(
182 openerpweb.path_addons, addon, pattern))
186 def concat_files(self, file_list):
187 """ Concatenate file content
188 return (concat,timestamp)
189 concat: concatenation of file content
190 timestamp: max(os.path.getmtime of file_list)
192 root = openerpweb.path_root
196 fname = os.path.join(root, i)
197 ftime = os.path.getmtime(fname)
198 if ftime > files_timestamp:
199 files_timestamp = ftime
200 files_content = open(fname).read()
201 files_concat = "".join(files_content)
204 @openerpweb.jsonrequest
205 def login(self, req, db, login, password):
206 req.session.login(db, login, password)
209 "session_id": req.session_id,
210 "uid": req.session._uid,
213 @openerpweb.jsonrequest
214 def sc_list(self, req):
215 return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
216 req.session.eval_context(req.context))
218 @openerpweb.jsonrequest
219 def get_lang_list(self, req):
220 lang_list = [('en_US', 'English (US)')]
222 lang_list = lang_list + (req.session.proxy("db").list_lang() or [])
224 return {"error": e, "title": "Languages"}
225 return {"lang_list": lang_list, "error": ""}
227 @openerpweb.jsonrequest
228 def modules(self, req):
229 return {"modules": [name
230 for name, manifest in openerpweb.addons_manifest.iteritems()
231 if manifest.get('active', True)]}
233 @openerpweb.jsonrequest
234 def csslist(self, req, mods='base'):
235 return {'files': self.manifest_glob(mods.split(','), 'css')}
237 @openerpweb.jsonrequest
238 def jslist(self, req, mods='base'):
239 return {'files': self.manifest_glob(mods.split(','), 'js')}
241 def css(self, req, mods='base'):
242 files = self.manifest_glob(mods.split(','), 'css')
243 concat = self.concat_files(files)[0]
244 # TODO request set the Date of last modif and Etag
248 def js(self, req, mods='base'):
249 files = self.manifest_glob(mods.split(','), 'js')
250 concat = self.concat_files(files)[0]
251 # TODO request set the Date of last modif and Etag
255 @openerpweb.jsonrequest
256 def eval_domain_and_context(self, req, contexts, domains,
258 """ Evaluates sequences of domains and contexts, composing them into
259 a single context, domain or group_by sequence.
261 :param list contexts: list of contexts to merge together. Contexts are
262 evaluated in sequence, all previous contexts
263 are part of their own evaluation context
264 (starting at the session context).
265 :param list domains: list of domains to merge together. Domains are
266 evaluated in sequence and appended to one another
267 (implicit AND), their evaluation domain is the
268 result of merging all contexts.
269 :param list group_by_seq: list of domains (which may be in a different
270 order than the ``contexts`` parameter),
271 evaluated in sequence, their ``'group_by'``
272 key is extracted if they have one.
277 the global context created by merging all of
281 the concatenation of all domains
284 a list of fields to group by, potentially empty (in which case
285 no group by should be performed)
287 context, domain = eval_context_and_domain(req.session,
288 openerpweb.nonliterals.CompoundContext(*(contexts or [])),
289 openerpweb.nonliterals.CompoundDomain(*(domains or [])))
291 group_by_sequence = []
292 for candidate in (group_by_seq or []):
293 ctx = req.session.eval_context(candidate, context)
294 group_by = ctx.get('group_by')
297 elif isinstance(group_by, basestring):
298 group_by_sequence.append(group_by)
300 group_by_sequence.extend(group_by)
305 'group_by': group_by_sequence
308 @openerpweb.jsonrequest
309 def save_session_action(self, req, the_action):
311 This method store an action object in the session object and returns an integer
312 identifying that action. The method get_session_action() can be used to get
315 :param the_action: The action to save in the session.
316 :type the_action: anything
317 :return: A key identifying the saved action.
320 saved_actions = cherrypy.session.get('saved_actions')
321 if not saved_actions:
322 saved_actions = {"next":0, "actions":{}}
323 cherrypy.session['saved_actions'] = saved_actions
324 # we don't allow more than 10 stored actions
325 if len(saved_actions["actions"]) >= 10:
326 del saved_actions["actions"][min(saved_actions["actions"].keys())]
327 key = saved_actions["next"]
328 saved_actions["actions"][key] = the_action
329 saved_actions["next"] = key + 1
332 @openerpweb.jsonrequest
333 def get_session_action(self, req, key):
335 Gets back a previously saved action. This method can return None if the action
336 was saved since too much time (this case should be handled in a smart way).
338 :param key: The key given by save_session_action()
340 :return: The saved action or None.
343 saved_actions = cherrypy.session.get('saved_actions')
344 if not saved_actions:
346 return saved_actions["actions"].get(key)
348 def eval_context_and_domain(session, context, domain=None):
349 e_context = session.eval_context(context)
350 # should we give the evaluated context as an evaluation context to the domain?
351 e_domain = session.eval_domain(domain or [])
353 return e_context, e_domain
355 def load_actions_from_ir_values(req, key, key2, models, meta, context):
356 Values = req.session.model('ir.values')
357 actions = Values.get(key, key2, models, meta, context)
359 return [(id, name, clean_action(action, req.session))
360 for id, name, action in actions]
362 def clean_action(action, session):
363 if action['type'] != 'ir.actions.act_window':
365 # values come from the server, we can just eval them
366 if isinstance(action.get('context', None), basestring):
367 action['context'] = eval(
369 session.evaluation_context()) or {}
371 if isinstance(action.get('domain', None), basestring):
372 action['domain'] = eval(
374 session.evaluation_context(
375 action.get('context', {}))) or []
376 if 'flags' not in action:
377 # Set empty flags dictionary for web client.
378 action['flags'] = dict()
379 return fix_view_modes(action)
381 def generate_views(action):
383 While the server generates a sequence called "views" computing dependencies
384 between a bunch of stuff for views coming directly from the database
385 (the ``ir.actions.act_window model``), it's also possible for e.g. buttons
386 to return custom view dictionaries generated on the fly.
388 In that case, there is no ``views`` key available on the action.
390 Since the web client relies on ``action['views']``, generate it here from
391 ``view_mode`` and ``view_id``.
393 Currently handles two different cases:
395 * no view_id, multiple view_mode
396 * single view_id, single view_mode
398 :param dict action: action descriptor dictionary to generate a views key for
400 view_id = action.get('view_id', False)
401 if isinstance(view_id, (list, tuple)):
404 # providing at least one view mode is a requirement, not an option
405 view_modes = action['view_mode'].split(',')
407 if len(view_modes) > 1:
409 raise ValueError('Non-db action dictionaries should provide '
410 'either multiple view modes or a single view '
411 'mode and an optional view id.\n\n Got view '
412 'modes %r and view id %r for action %r' % (
413 view_modes, view_id, action))
414 action['views'] = [(False, mode) for mode in view_modes]
416 action['views'] = [(view_id, view_modes[0])]
418 def fix_view_modes(action):
419 """ For historical reasons, OpenERP has weird dealings in relation to
420 view_mode and the view_type attribute (on window actions):
422 * one of the view modes is ``tree``, which stands for both list views
424 * the choice is made by checking ``view_type``, which is either
425 ``form`` for a list view or ``tree`` for an actual tree view
427 This methods simply folds the view_type into view_mode by adding a
428 new view mode ``list`` which is the result of the ``tree`` view_mode
429 in conjunction with the ``form`` view_type.
431 TODO: this should go into the doc, some kind of "peculiarities" section
433 :param dict action: an action descriptor
434 :returns: nothing, the action is modified in place
436 if 'views' not in action:
437 generate_views(action)
439 if action.pop('view_type') != 'form':
443 [id, mode if mode != 'tree' else 'list']
444 for id, mode in action['views']
449 class Menu(openerpweb.Controller):
450 _cp_path = "/base/menu"
452 @openerpweb.jsonrequest
454 return {'data': self.do_load(req)}
456 def do_load(self, req):
457 """ Loads all menu items (all applications and their sub-menus).
459 :param req: A request object, with an OpenERP session attribute
460 :type req: < session -> OpenERPSession >
461 :return: the menu root
462 :rtype: dict('children': menu_nodes)
464 Menus = req.session.model('ir.ui.menu')
465 # menus are loaded fully unlike a regular tree view, cause there are
466 # less than 512 items
467 context = req.session.eval_context(req.context)
468 menu_ids = Menus.search([], 0, False, False, context)
469 menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
470 menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
471 menu_items.append(menu_root)
473 # make a tree using parent_id
474 menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
475 for menu_item in menu_items:
476 if menu_item['parent_id']:
477 parent = menu_item['parent_id'][0]
480 if parent in menu_items_map:
481 menu_items_map[parent].setdefault(
482 'children', []).append(menu_item)
484 # sort by sequence a tree using parent_id
485 for menu_item in menu_items:
486 menu_item.setdefault('children', []).sort(
487 key=lambda x:x["sequence"])
491 @openerpweb.jsonrequest
492 def action(self, req, menu_id):
493 actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
494 [('ir.ui.menu', menu_id)], False,
495 req.session.eval_context(req.context))
496 return {"action": actions}
498 class DataSet(openerpweb.Controller):
499 _cp_path = "/base/dataset"
501 @openerpweb.jsonrequest
502 def fields(self, req, model):
503 return {'fields': req.session.model(model).fields_get(False,
504 req.session.eval_context(req.context))}
506 @openerpweb.jsonrequest
507 def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
508 return self.do_search_read(request, model, fields, offset, limit, domain, sort)
509 def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
511 """ Performs a search() followed by a read() (if needed) using the
512 provided search criteria
514 :param request: a JSON-RPC request object
515 :type request: openerpweb.JsonRequest
516 :param str model: the name of the model to search on
517 :param fields: a list of the fields to return in the result records
519 :param int offset: from which index should the results start being returned
520 :param int limit: the maximum number of records to return
521 :param list domain: the search domain for the query
522 :param list sort: sorting directives
523 :returns: A structure (dict) with two keys: ids (all the ids matching
524 the (domain, context) pair) and records (paginated records
525 matching fields selection set)
528 Model = request.session.model(model)
529 context, domain = eval_context_and_domain(
530 request.session, request.context, domain)
532 ids = Model.search(domain, 0, False, sort or False, context)
533 # need to fill the dataset with all ids for the (domain, context) pair,
534 # so search un-paginated and paginate manually before reading
535 paginated_ids = ids[offset:(offset + limit if limit else None)]
536 if fields and fields == ['id']:
537 # shortcut read if we only want the ids
540 'records': map(lambda id: {'id': id}, paginated_ids)
543 records = Model.read(paginated_ids, fields or False, context)
544 records.sort(key=lambda obj: ids.index(obj['id']))
551 @openerpweb.jsonrequest
552 def get(self, request, model, ids, fields=False):
553 return self.do_get(request, model, ids, fields)
554 def do_get(self, request, model, ids, fields=False):
555 """ Fetches and returns the records of the model ``model`` whose ids
558 The results are in the same order as the inputs, but elements may be
559 missing (if there is no record left for the id)
561 :param request: the JSON-RPC2 request object
562 :type request: openerpweb.JsonRequest
563 :param model: the model to read from
565 :param ids: a list of identifiers
567 :param fields: a list of fields to fetch, ``False`` or empty to fetch
568 all fields in the model
569 :type fields: list | False
570 :returns: a list of records, in the same order as the list of ids
573 Model = request.session.model(model)
574 records = Model.read(ids, fields, request.session.eval_context(request.context))
576 record_map = dict((record['id'], record) for record in records)
578 return [record_map[id] for id in ids if record_map.get(id)]
580 @openerpweb.jsonrequest
581 def load(self, req, model, id, fields):
582 m = req.session.model(model)
584 r = m.read([id], False, req.session.eval_context(req.context))
587 return {'value': value}
589 @openerpweb.jsonrequest
590 def create(self, req, model, data):
591 m = req.session.model(model)
592 r = m.create(data, req.session.eval_context(req.context))
595 @openerpweb.jsonrequest
596 def save(self, req, model, id, data):
597 m = req.session.model(model)
598 r = m.write([id], data, req.session.eval_context(req.context))
601 @openerpweb.jsonrequest
602 def unlink(self, request, model, ids=()):
603 Model = request.session.model(model)
604 return Model.unlink(ids, request.session.eval_context(request.context))
606 def call_common(self, req, model, method, args, domain_id=None, context_id=None):
607 domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
608 context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
609 c, d = eval_context_and_domain(req.session, context, domain)
610 if domain_id and len(args) - 1 >= domain_id:
612 if context_id and len(args) - 1 >= context_id:
615 return getattr(req.session.model(model), method)(*args)
617 @openerpweb.jsonrequest
618 def call(self, req, model, method, args, domain_id=None, context_id=None):
619 return self.call_common(req, model, method, args, domain_id, context_id)
621 @openerpweb.jsonrequest
622 def call_button(self, req, model, method, args, domain_id=None, context_id=None):
623 action = self.call_common(req, model, method, args, domain_id, context_id)
624 if isinstance(action, dict) and action.get('type') != '':
625 return {'result': clean_action(action, req.session)}
626 return {'result': False}
628 @openerpweb.jsonrequest
629 def exec_workflow(self, req, model, id, signal):
630 r = req.session.exec_workflow(model, id, signal)
633 @openerpweb.jsonrequest
634 def default_get(self, req, model, fields):
635 Model = req.session.model(model)
636 return Model.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, sort=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), sort or False)
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)
681 for field in fvg['fields'].itervalues():
682 if field.get('views'):
683 for view in field["views"].itervalues():
684 self.process_view(session, view, None, transform)
685 if field.get('domain'):
686 field["domain"] = self.parse_domain(field["domain"], session)
687 if field.get('context'):
688 field["context"] = self.parse_context(field["context"], session)
690 @openerpweb.jsonrequest
691 def add_custom(self, request, view_id, arch):
692 CustomView = request.session.model('ir.ui.view.custom')
694 'user_id': request.session._uid,
697 }, request.session.eval_context(request.context))
698 return {'result': True}
700 @openerpweb.jsonrequest
701 def undo_custom(self, request, view_id, reset=False):
702 CustomView = request.session.model('ir.ui.view.custom')
703 context = request.session.eval_context(request.context)
704 vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)],
705 0, False, False, context)
708 CustomView.unlink(vcustom, context)
710 CustomView.unlink([vcustom[0]], context)
711 return {'result': True}
712 return {'result': False}
714 def transform_view(self, view_string, session, context=None):
715 # transform nodes on the fly via iterparse, instead of
716 # doing it statically on the parsing result
717 parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
719 for event, elem in parser:
723 self.parse_domains_and_contexts(elem, session)
726 def parse_domain(self, domain, session):
727 """ Parses an arbitrary string containing a domain, transforms it
728 to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
730 :param domain: the domain to parse, if the domain is not a string it is assumed to
731 be a literal domain and is returned as-is
732 :param session: Current OpenERP session
733 :type session: openerpweb.openerpweb.OpenERPSession
735 if not isinstance(domain, (str, unicode)):
738 return openerpweb.ast.literal_eval(domain)
741 return openerpweb.nonliterals.Domain(session, domain)
743 def parse_context(self, context, session):
744 """ Parses an arbitrary string containing a context, transforms it
745 to either a literal context or a :class:`openerpweb.nonliterals.Context`
747 :param context: the context to parse, if the context is not a string it is assumed to
748 be a literal domain and is returned as-is
749 :param session: Current OpenERP session
750 :type session: openerpweb.openerpweb.OpenERPSession
752 if not isinstance(context, (str, unicode)):
755 return openerpweb.ast.literal_eval(context)
757 return openerpweb.nonliterals.Context(session, context)
759 def parse_domains_and_contexts(self, elem, session):
760 """ Converts domains and contexts from the view into Python objects,
761 either literals if they can be parsed by literal_eval or a special
762 placeholder object if the domain or context refers to free variables.
764 :param elem: the current node being parsed
765 :type param: xml.etree.ElementTree.Element
766 :param session: OpenERP session object, used to store and retrieve
768 :type session: openerpweb.openerpweb.OpenERPSession
770 for el in ['domain', 'filter_domain']:
771 domain = elem.get(el, '').strip()
773 elem.set(el, self.parse_domain(domain, session))
774 for el in ['context', 'default_get']:
775 context_string = elem.get(el, '').strip()
777 elem.set(el, self.parse_context(context_string, session))
779 class FormView(View):
780 _cp_path = "/base/formview"
782 @openerpweb.jsonrequest
783 def load(self, req, model, view_id, toolbar=False):
784 fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
785 return {'fields_view': fields_view}
787 class ListView(View):
788 _cp_path = "/base/listview"
790 @openerpweb.jsonrequest
791 def load(self, req, model, view_id, toolbar=False):
792 fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
793 return {'fields_view': fields_view}
795 def process_colors(self, view, row, context):
796 colors = view['arch']['attrs'].get('colors')
803 for pair in colors.split(';')
804 if eval(pair.split(':')[1], dict(context, **row))
809 elif len(color) == 1:
813 class SearchView(View):
814 _cp_path = "/base/searchview"
816 @openerpweb.jsonrequest
817 def load(self, req, model, view_id):
818 fields_view = self.fields_view_get(req, model, view_id, 'search')
819 return {'fields_view': fields_view}
821 @openerpweb.jsonrequest
822 def fields_get(self, req, model):
823 Model = req.session.model(model)
824 fields = Model.fields_get(False, req.session.eval_context(req.context))
825 for field in fields.values():
826 # shouldn't convert the views too?
827 if field.get('domain'):
828 field["domain"] = self.parse_domain(field["domain"], req.session)
829 if field.get('context'):
830 field["context"] = self.parse_domain(field["context"], req.session)
831 return {'fields': fields}
833 class Binary(openerpweb.Controller):
834 _cp_path = "/base/binary"
836 @openerpweb.httprequest
837 def image(self, request, session_id, model, id, field, **kw):
838 cherrypy.response.headers['Content-Type'] = 'image/png'
839 Model = request.session.model(model)
840 context = request.session.eval_context(request.context)
843 res = Model.default_get([field], context).get(field, '')
845 res = Model.read([int(id)], [field], context)[0].get(field, '')
846 return base64.decodestring(res)
847 except: # TODO: what's the exception here?
848 return self.placeholder()
849 def placeholder(self):
850 return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
852 @openerpweb.httprequest
853 def saveas(self, request, session_id, model, id, field, fieldname, **kw):
854 Model = request.session.model(model)
855 context = request.session.eval_context(request.context)
856 res = Model.read([int(id)], [field, fieldname], context)[0]
857 filecontent = res.get(field, '')
859 raise cherrypy.NotFound
861 cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
862 filename = '%s_%s' % (model.replace('.', '_'), id)
864 filename = res.get(fieldname, '') or filename
865 cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename
866 return base64.decodestring(filecontent)
868 @openerpweb.httprequest
869 def upload(self, request, session_id, callback, ufile=None):
870 cherrypy.response.timeout = 500
872 for key, val in cherrypy.request.headers.iteritems():
873 headers[key.lower()] = val
874 size = int(headers.get('content-length', 0))
875 # TODO: might be useful to have a configuration flag for max-length file uploads
877 out = """<script language="javascript" type="text/javascript">
878 var win = window.top.window,
880 if (typeof(callback) === 'function') {
881 callback.apply(this, %s);
883 win.jQuery('#oe_notification', win.document).notify('create', {
884 title: "Ajax File Upload",
885 text: "Could not find callback"
889 data = ufile.file.read()
890 args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
892 args = [False, e.message]
893 return out % (simplejson.dumps(callback), simplejson.dumps(args))
895 @openerpweb.httprequest
896 def upload_attachment(self, request, session_id, callback, model, id, ufile=None):
897 cherrypy.response.timeout = 500
898 context = request.session.eval_context(request.context)
899 Model = request.session.model('ir.attachment')
901 out = """<script language="javascript" type="text/javascript">
902 var win = window.top.window,
904 if (typeof(callback) === 'function') {
905 callback.call(this, %s);
908 attachment_id = Model.create({
909 'name': ufile.filename,
910 'datas': base64.encodestring(ufile.file.read()),
915 'filename': ufile.filename,
919 args = { 'error': e.message }
920 return out % (simplejson.dumps(callback), simplejson.dumps(args))
922 class Action(openerpweb.Controller):
923 _cp_path = "/base/action"
925 @openerpweb.jsonrequest
926 def load(self, req, action_id):
927 Actions = req.session.model('ir.actions.actions')
929 context = req.session.eval_context(req.context)
930 action_type = Actions.read([action_id], ['type'], context)
932 action = req.session.model(action_type[0]['type']).read([action_id], False,
935 value = clean_action(action[0], req.session)
936 return {'result': value}
938 @openerpweb.jsonrequest
939 def run(self, req, action_id):
940 return clean_action(req.session.model('ir.actions.server').run(
941 [action_id], req.session.eval_context(req.context)), req.session)