diff options
| author | Bharat R. Devnani (OpenERP) <bde@tinyerp.com> | 2014-03-04 11:08:49 +0530 |
|---|---|---|
| committer | Bharat R. Devnani (OpenERP) <bde@tinyerp.com> | 2014-03-04 11:08:49 +0530 |
| commit | 67b68d8e4e7015e368324dd0c02aa29fed71d359 (patch) | |
| tree | d72e75ef050bd65dcb53d2a27305a0285ca2963b /addons/web/controllers | |
| parent | 7c1af9ef1441b2097c815758670b70a690598d62 (diff) | |
| parent | e76c36d704c142fca08bf1297897d4091bf7a4cb (diff) | |
[MERGE] merged with main web
bzr revid: bde@tinyerp.com-20140304053849-wpvxs1ojl5jyjokt
Diffstat (limited to 'addons/web/controllers')
| -rw-r--r-- | addons/web/controllers/main.py | 333 | ||||
| -rw-r--r-- | addons/web/controllers/testing.py | 13 |
2 files changed, 241 insertions, 105 deletions
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 81990f633ad..a9f56a2099c 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -3,8 +3,10 @@ import ast import base64 import csv +import functools import glob import itertools +import jinja2 import logging import operator import datetime @@ -13,10 +15,7 @@ import os import re import simplejson import time -import urllib import urllib2 -import urlparse -import xmlrpclib import zlib from xml.etree import ElementTree from cStringIO import StringIO @@ -32,10 +31,17 @@ except ImportError: import openerp import openerp.modules.registry from openerp.tools.translate import _ -from openerp.tools import config from openerp import http -from openerp.http import request +from openerp.http import request, serialize_exception as _serialize_exception + +_logger = logging.getLogger(__name__) + +env = jinja2.Environment( + loader=jinja2.PackageLoader('openerp.addons.web', "views"), + autoescape=True +) +env.filters["json"] = simplejson.dumps #---------------------------------------------------------- # OpenERP Web helpers @@ -89,18 +95,79 @@ db_list = http.db_list db_monodb = http.db_monodb -def redirect_with_hash(url, code=303): - redirect_code = "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % url - if request.httprequest.user_agent.browser == 'msie': +def serialize_exception(f): + @functools.wraps(f) + def wrap(*args, **kwargs): try: - version = float(request.httprequest.user_agent.version) - if version < 10: - return redirect_code - except Exception: - pass - elif request.httprequest.user_agent.browser == 'safari': - return redirect_code - return werkzeug.utils.redirect(url, code) + return f(*args, **kwargs) + except Exception, e: + _logger.exception("An exception occured during an http request") + se = _serialize_exception(e) + error = { + 'code': 200, + 'message': "OpenERP Server Error", + 'data': se + } + return werkzeug.exceptions.InternalServerError(simplejson.dumps(error)) + return wrap + +def redirect_with_hash(*args, **kw): + """ + .. deprecated:: 8.0 + + Use the ``http.redirect_with_hash()`` function instead. + """ + return http.redirect_with_hash(*args, **kw) + +def abort_and_redirect(url): + r = request.httprequest + response = werkzeug.utils.redirect(url, 302) + response = r.app.get_response(r, response, explicit_session=False) + werkzeug.exceptions.abort(response) + +def ensure_db(redirect='/web/database/selector'): + # This helper should be used in web client auth="none" routes + # if those routes needs a db to work with. + # If the heuristics does not find any database, then the users will be + # redirected to db selector or any url specified by `redirect` argument. + # If the db is taken out of a query parameter, it will be checked against + # `http.db_filter()` in order to ensure it's legit and thus avoid db + # forgering that could lead to xss attacks. + db = request.params.get('db') + + # Ensure db is legit + if db and db not in http.db_filter([db]): + db = None + + if db and not request.session.db: + # User asked a specific database on a new session. + # That mean the nodb router has been used to find the route + # Depending on installed module in the database, the rendering of the page + # may depend on data injected by the database route dispatcher. + # Thus, we redirect the user to the same page but with the session cookie set. + # This will force using the database route dispatcher... + request.session.db = db + abort_and_redirect(request.httprequest.url) + + # if db not provided, use the session one + if not db: + db = request.session.db + + # if no database provided and no database in session, use monodb + if not db: + db = db_monodb(request.httprequest) + + # if no db can be found til here, send to the database selector + # the database selector will redirect to database manager if needed + if not db: + werkzeug.exceptions.abort(werkzeug.utils.redirect(redirect, 303)) + + # always switch the session to the computed db + if db != request.session.db: + request.session.logout() + abort_and_redirect(request.httprequest.url) + + request.session.db = db def module_topological_sort(modules): """ Return a list of module names sorted so that their dependencies of the @@ -304,9 +371,9 @@ def manifest_list(extension, mods=None, db=None, debug=False): if not debug: path = '/web/webclient/' + extension if mods is not None: - path += '?' + urllib.urlencode({'mods': mods}) + path += '?' + werkzeug.url_encode({'mods': mods}) elif db: - path += '?' + urllib.urlencode({'db': db}) + path += '?' + werkzeug.url_encode({'db': db}) remotes = [wp for fp, wp in files if fp is None] return [path] + remotes @@ -499,6 +566,8 @@ def content_disposition(filename): # OpenERP Web web Controllers #---------------------------------------------------------- +# TODO: to remove once the database manager has been migrated server side +# and `edi` + `pos` addons has been adapted to use render_bootstrap_template() html_template = """<!DOCTYPE html> <html style="height: 100%%"> <head> @@ -525,53 +594,70 @@ html_template = """<!DOCTYPE html> </html> """ -class Home(http.Controller): - - @http.route('/', type='http', auth="none") - def index(self, s_action=None, db=None, debug=False, **kw): - query = dict(urlparse.parse_qsl(request.httprequest.query_string, keep_blank_values=True)) - redirect = '/web' + '?' + urllib.urlencode(query) - return redirect_with_hash(redirect) +def render_bootstrap_template(template, values=None, debug=False, db=None, **kw): + if not db: + db = request.db + if request.debug: + debug = True + if values is None: + values = {} + values['debug'] = debug + values['current_db'] = db + try: + values['databases'] = http.db_list() + except openerp.exceptions.AccessDenied: + values['databases'] = None - @http.route('/web', type='http', auth="none") - def web_client(self, s_action=None, db=None, debug=False, **kw): - debug = debug != False + for res in ['js', 'css']: + if res not in values: + values[res] = manifest_list(res, db=db, debug=debug) - lst = http.db_list(True) - if db not in lst: - db = None - guessed_db = http.db_monodb(request.httprequest) - if guessed_db is None and len(lst) > 0: - guessed_db = lst[0] + if 'modules' not in values: + values['modules'] = module_boot(db=db) + values['modules'] = simplejson.dumps(values['modules']) - def redirect(db): - query = dict(urlparse.parse_qsl(request.httprequest.query_string, keep_blank_values=True)) - query.update({'db': db}) - redirect = request.httprequest.path + '?' + urllib.urlencode(query) - return redirect_with_hash(redirect) + return request.render(template, values, **kw) - if db is None and guessed_db is not None: - return redirect(guessed_db) +class Home(http.Controller): - if db is not None and db != request.session.db: - request.session.logout() - request.session.db = db - guessed_db = db + @http.route('/', type='http', auth="none") + def index(self, s_action=None, db=None, **kw): + return http.local_redirect('/web', query=request.params) - js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js', db=guessed_db, debug=debug)) - css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css', db=guessed_db, debug=debug)) + @http.route('/web', type='http', auth="none") + def web_client(self, s_action=None, **kw): + ensure_db() - r = html_template % { - 'js': js, - 'css': css, - 'modules': simplejson.dumps(module_boot(db=guessed_db)), - 'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));' - } - return request.make_response(r, {'Cache-Control': 'no-cache', 'Content-Type': 'text/html; charset=utf-8'}) + if request.session.uid: + headers = { + 'Cache-Control': 'no-cache', + 'Content-Type': 'text/html; charset=utf-8', + } + return render_bootstrap_template("web.webclient_bootstrap", headers=headers) + else: + return http.local_redirect('/web/login', query=request.params) + + @http.route('/web/login', type='http', auth="none") + def web_login(self, redirect=None, **kw): + ensure_db() + + if not request.uid: + request.uid = openerp.SUPERUSER_ID + + values = request.params.copy() + if not redirect: + redirect = '/web?' + request.httprequest.query_string + values['redirect'] = redirect + if request.httprequest.method == 'POST': + uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password']) + if uid is not False: + return http.redirect_with_hash(redirect) + values['error'] = "Wrong login/password" + return render_bootstrap_template('web.login', values) @http.route('/login', type='http', auth="none") - def login(self, db, login, key): - return login_and_redirect(db, login, key) + def login(self, db, login, key, redirect="/web", **kw): + return login_and_redirect(db, login, key, redirect_url=redirect) class WebClient(http.Controller): @@ -687,26 +773,28 @@ class WebClient(http.Controller): return {"modules": translations_per_module, "lang_parameters": None} - @http.route('/web/webclient/translations', type='json', auth="admin") + @http.route('/web/webclient/translations', type='json', auth="none") def translations(self, mods=None, lang=None): + request.disable_db = False + uid = openerp.SUPERUSER_ID if mods is None: m = request.registry.get('ir.module.module') - mods = [x['name'] for x in m.search_read(request.cr, request.uid, + mods = [x['name'] for x in m.search_read(request.cr, uid, [('state','=','installed')], ['name'])] if lang is None: lang = request.context["lang"] res_lang = request.registry.get('res.lang') - ids = res_lang.search(request.cr, request.uid, [("code", "=", lang)]) + ids = res_lang.search(request.cr, uid, [("code", "=", lang)]) lang_params = None if ids: - lang_params = res_lang.read(request.cr, request.uid, ids[0], ["direction", "date_format", "time_format", + lang_params = res_lang.read(request.cr, uid, ids[0], ["direction", "date_format", "time_format", "grouping", "decimal_point", "thousands_sep"]) # Regional languages (ll_CC) must inherit/override their parent lang (ll), but this is # done server-side when the language is loaded, so we only need to load the user's lang. ir_translation = request.registry.get('ir.translation') translations_per_module = {} - messages = ir_translation.search_read(request.cr, request.uid, [('module','in',mods),('lang','=',lang), + messages = ir_translation.search_read(request.cr, uid, [('module','in',mods),('lang','=',lang), ('comments','like','openerp-web'),('value','!=',False), ('value','!=','')], ['module','src','value','lang'], order='module') @@ -741,6 +829,37 @@ class Proxy(http.Controller): class Database(http.Controller): + @http.route('/web/database/selector', type='http', auth="none") + def selector(self, **kw): + try: + dbs = http.db_list() + if not dbs: + return http.local_redirect('/web/database/manager') + except openerp.exceptions.AccessDenied: + dbs = False + return env.get_template("database_selector.html").render({ + 'databases': dbs, + 'debug': request.debug, + }) + + @http.route('/web/database/manager', type='http', auth="none") + def manager(self, **kw): + # TODO: migrate the webclient's database manager to server side views + request.session.logout() + js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js', debug=request.debug)) + css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css', debug=request.debug)) + + r = html_template % { + 'js': js, + 'css': css, + 'modules': simplejson.dumps(module_boot()), + 'init': """ + var wc = new s.web.WebClient(null, { action: 'database_manager' }); + wc.appendTo($(document.body)); + """ + } + return r + @http.route('/web/database/get_list', type='json', auth="none") def get_list(self): # TODO change js to avoid calling this method if in monodb mode @@ -755,12 +874,15 @@ class Database(http.Controller): @http.route('/web/database/create', type='json', auth="none") def create(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) - return request.session.proxy("db").create_database( + db_created = request.session.proxy("db").create_database( params['super_admin_pwd'], params['db_name'], bool(params.get('demo_data')), params['db_lang'], params['create_admin_pwd']) + if db_created: + request.session.authenticate(params['db_name'], 'admin', params['create_admin_pwd']) + return db_created @http.route('/web/database/duplicate', type='json', auth="none") def duplicate(self, fields): @@ -892,17 +1014,7 @@ class Session(http.Controller): :return: A key identifying the saved action. :rtype: integer """ - saved_actions = request.httpsession.get('saved_actions') - if not saved_actions: - saved_actions = {"next":1, "actions":{}} - request.httpsession['saved_actions'] = saved_actions - # we don't allow more than 10 stored actions - if len(saved_actions["actions"]) >= 10: - del saved_actions["actions"][min(saved_actions["actions"])] - key = saved_actions["next"] - saved_actions["actions"][key] = the_action - saved_actions["next"] = key + 1 - return key + return request.httpsession.save_action(the_action) @http.route('/web/session/get_session_action', type='json', auth="user") def get_session_action(self, key): @@ -915,10 +1027,7 @@ class Session(http.Controller): :return: The saved action or None. :rtype: anything """ - saved_actions = request.httpsession.get('saved_actions') - if not saved_actions: - return None - return saved_actions["actions"].get(key) + return request.httpsession.get_action(key) @http.route('/web/session/check', type='json', auth="user") def check(self): @@ -929,9 +1038,9 @@ class Session(http.Controller): def destroy(self): request.session.logout() - @http.route('/web/session/logout', type='http', auth="user") + @http.route('/web/session/logout', type='http', auth="none") def logout(self, redirect='/web'): - request.session.logout() + request.session.logout(keep_db=True) return werkzeug.utils.redirect(redirect, 303) class Menu(http.Controller): @@ -1042,21 +1151,17 @@ class DataSet(http.Controller): """ Model = request.session.model(model) - ids = Model.search(domain, offset or 0, limit or False, sort or False, + records = Model.search_read(domain, fields, offset or 0, limit or False, sort or False, request.context) - if limit and len(ids) == limit: - length = Model.search_count(domain, request.context) - else: - length = len(ids) + (offset or 0) - if fields and fields == ['id']: - # shortcut read if we only want the ids + if not records: return { - 'length': length, - 'records': [{'id': id} for id in ids] + 'length': 0, + 'records': [] } - - records = Model.read(ids, fields or False, request.context) - records.sort(key=lambda obj: ids.index(obj['id'])) + if limit and len(records) == limit: + length = Model.search_count(domain, request.context) + else: + length = len(records) + (offset or 0) return { 'length': length, 'records': records @@ -1224,6 +1329,7 @@ class Binary(http.Controller): return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read() @http.route('/web/binary/saveas', type='http', auth="user") + @serialize_exception def saveas(self, model, field, id=None, filename_field=None, **kw): """ Download link for files stored as binary fields. @@ -1257,6 +1363,7 @@ class Binary(http.Controller): ('Content-Disposition', content_disposition(filename))]) @http.route('/web/binary/saveas_ajax', type='http', auth="user") + @serialize_exception def saveas_ajax(self, data, token): jdata = simplejson.loads(data) model = jdata['model'] @@ -1290,6 +1397,7 @@ class Binary(http.Controller): cookies={'fileToken': token}) @http.route('/web/binary/upload', type='http', auth="user") + @serialize_exception def upload(self, callback, ufile): # TODO: might be useful to have a configuration flag for max-length file uploads out = """<script language="javascript" type="text/javascript"> @@ -1305,6 +1413,7 @@ class Binary(http.Controller): return out % (simplejson.dumps(callback), simplejson.dumps(args)) @http.route('/web/binary/upload_attachment', type='http', auth="user") + @serialize_exception def upload_attachment(self, callback, model, id, ufile): Model = request.session.model('ir.attachment') out = """<script language="javascript" type="text/javascript"> @@ -1327,7 +1436,11 @@ class Binary(http.Controller): args = {'error': "Something horrible happened"} return out % (simplejson.dumps(callback), simplejson.dumps(args)) - @http.route('/web/binary/company_logo', type='http', auth="none") + @http.route([ + '/web/binary/company_logo', + '/logo', + '/logo.png', + ], type='http', auth="none") def company_logo(self, dbname=None): # TODO add etag, refactor to use /image code for etag uid = None @@ -1485,8 +1598,8 @@ class Export(http.Controller): model, map(operator.itemgetter('name'), export_fields_list)) return [ - {'name': field['name'], 'label': fields_data[field['name']]} - for field in export_fields_list + {'name': field_name, 'label': fields_data[field_name]} + for field_name in fields_data.keys() ] def fields_info(self, model, export_fields): @@ -1533,7 +1646,7 @@ class Export(http.Controller): fields[base]['relation'], base, fields[base]['string'], subfields )) - else: + elif base in fields: info[base] = fields[base]['string'] return info @@ -1594,6 +1707,7 @@ class ExportFormat(object): class CSVExport(ExportFormat, http.Controller): @http.route('/web/export/csv', type='http', auth="user") + @serialize_exception def index(self, data, token): return self.base(data, token) @@ -1631,6 +1745,7 @@ class CSVExport(ExportFormat, http.Controller): class ExcelExport(ExportFormat, http.Controller): @http.route('/web/export/xls', type='http', auth="user") + @serialize_exception def index(self, data, token): return self.base(data, token) @@ -1677,6 +1792,7 @@ class Reports(http.Controller): } @http.route('/web/report', type='http', auth="user") + @serialize_exception def index(self, action, token): action = simplejson.loads(action) @@ -1730,4 +1846,31 @@ class Reports(http.Controller): ('Content-Length', len(report))], cookies={'fileToken': token}) +class Apps(http.Controller): + @http.route('/apps/<app>', auth='user') + def get_app_url(self, req, app): + act_window_obj = request.session.model('ir.actions.act_window') + ir_model_data = request.session.model('ir.model.data') + try: + action_id = ir_model_data.get_object_reference('base', 'open_module_tree')[1] + action = act_window_obj.read(action_id, ['name', 'type', 'res_model', 'view_mode', 'view_type', 'context', 'views', 'domain']) + action['target'] = 'current' + except ValueError: + action = False + try: + app_id = ir_model_data.get_object_reference('base', 'module_%s' % app)[1] + except ValueError: + app_id = False + + if action and app_id: + action['res_id'] = app_id + action['view_mode'] = 'form' + action['views'] = [(False, u'form')] + + sakey = Session().save_session_action(action) + debug = '?debug' if req.debug else '' + return werkzeug.utils.redirect('/web{0}#sa={1}'.format(debug, sakey)) + + + # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py index 0160f0f28cd..c99efed4294 100644 --- a/addons/web/controllers/testing.py +++ b/addons/web/controllers/testing.py @@ -52,7 +52,6 @@ TESTING = Template(u"""<!DOCTYPE html> <script src="/web/static/lib/qunit/qunit.js"></script> <script type="text/javascript"> - var oe_db_info = ${db_info | n}; // List of modules, each module is preceded by its dependencies var oe_all_dependencies = ${dependencies | n}; QUnit.config.testTimeout = 5 * 60 * 1000; @@ -89,7 +88,7 @@ TESTING = Template(u"""<!DOCTYPE html> class TestRunnerController(http.Controller): @http.route('/web/tests', type='http', auth="none") - def index(self, req, mod=None, **kwargs): + def index(self, mod=None, **kwargs): ms = module.get_modules() manifests = dict( (name, desc) @@ -114,7 +113,7 @@ class TestRunnerController(http.Controller): to_test = sorted_mods if mod != '*': if mod not in manifests: - return req.not_found(NOTFOUND.render(module=mod)) + return request.not_found(NOTFOUND.render(module=mod)) idx = sorted_mods.index(mod) to_test = [None] * len(sorted_mods) to_test[idx] = mod @@ -134,16 +133,10 @@ class TestRunnerController(http.Controller): for mod, tests in itertools.izip(sorted_mods, tests) ] - # if all three db_info parameters are present, send them to the page - db_info = dict((k, v) for k, v in kwargs.iteritems() - if k in ['source', 'supadmin', 'password']) - if len(db_info) != 3: - db_info = None - return TESTING.render(files=files, dependencies=json.dumps( [name for name in sorted_mods if module.get_module_resource(name, 'static') - if manifests[name]['js']]), db_info=json.dumps(db_info)) + if manifests[name]['js']])) def load_manifest(self, name): manifest = module.load_information_from_description_file(name) |
