summaryrefslogtreecommitdiff
path: root/addons/web/controllers
diff options
context:
space:
mode:
authorBharat R. Devnani (OpenERP) <bde@tinyerp.com>2014-03-04 11:08:49 +0530
committerBharat R. Devnani (OpenERP) <bde@tinyerp.com>2014-03-04 11:08:49 +0530
commit67b68d8e4e7015e368324dd0c02aa29fed71d359 (patch)
treed72e75ef050bd65dcb53d2a27305a0285ca2963b /addons/web/controllers
parent7c1af9ef1441b2097c815758670b70a690598d62 (diff)
parente76c36d704c142fca08bf1297897d4091bf7a4cb (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.py333
-rw-r--r--addons/web/controllers/testing.py13
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)