Maxmind offers multiple ip-geolocalization databases, historically we have been using the City database which contains records on a city-basis. Many years later it turns out we are primary using geoip to know the country of the user. Geolocalization in the City database is considered slow by our standard and we have been clever in order not to geolocate each request by saving the info in the session. On the other hand, the Country database that is offered by Maxmind is much more lightweight and geoip using that country is considered a fast operation by our standard. In this work we make Odoo compatible with both the City and the Country databases. Using multiple database at the same time, we can be smart and only query each of the two on-demand. If a user ask for its country, we'll use the fast Country db. If a user ask for its city/timezone we'll use the slower City db. By default it loads both database from the `/usr/share/GeoIP/` folder, respectively the files `GeoLite2-City.mmdb` and `GeoLite2-Country.mmdb`, you can provide alternative paths using the `--geoip-city-db` and `--geoip-country-db` CLI options. In the same mindset as #86015, geoip is still lazy. It is done on-demand and the result is cached on the current request. The different with the related PR is that as we know consider geoip to be fast, we no longer cache the result in the session. Task: 2848206 Part-of: odoo/odoo#91337
176 lines
6.8 KiB
Python
176 lines
6.8 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import json
|
|
import logging
|
|
import werkzeug
|
|
from odoo import http
|
|
from odoo.exceptions import AccessError, UserError
|
|
from odoo.http import request
|
|
from odoo.tools import replace_exceptions
|
|
|
|
from odoo.addons.web.controllers.utils import ensure_db
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
CT_JSON = {'Content-Type': 'application/json; charset=utf-8'}
|
|
WSGI_SAFE_KEYS = {'PATH_INFO', 'QUERY_STRING', 'RAW_URI', 'SCRIPT_NAME', 'wsgi.url_scheme'}
|
|
|
|
|
|
class TestHttp(http.Controller):
|
|
|
|
# =====================================================
|
|
# Greeting
|
|
# =====================================================
|
|
@http.route(['/test_http/greeting', '/test_http/greeting-none'], type='http', auth='none')
|
|
def greeting_none(self):
|
|
return "Tek'ma'te"
|
|
|
|
@http.route('/test_http/greeting-public', type='http', auth='public')
|
|
def greeting_public(self):
|
|
assert request.env.user, "ORM should be initialized"
|
|
return "Tek'ma'te"
|
|
|
|
@http.route('/test_http/greeting-user', type='http', auth='user')
|
|
def greeting_user(self):
|
|
assert request.env.user, "ORM should be initialized"
|
|
return "Tek'ma'te"
|
|
|
|
@http.route('/test_http/wsgi_environ', type='http', auth='none')
|
|
def wsgi_environ(self):
|
|
environ = {
|
|
key: val for key, val in request.httprequest.environ.items()
|
|
if (key.startswith('HTTP_') # headers
|
|
or key.startswith('REMOTE_')
|
|
or key.startswith('REQUEST_')
|
|
or key.startswith('SERVER_')
|
|
or key.startswith('werkzeug.proxy_fix.')
|
|
or key in WSGI_SAFE_KEYS)
|
|
}
|
|
|
|
return request.make_response(
|
|
json.dumps(environ, indent=4),
|
|
headers=list(CT_JSON.items())
|
|
)
|
|
|
|
# =====================================================
|
|
# Echo-Reply
|
|
# =====================================================
|
|
@http.route('/test_http/echo-http-get', type='http', auth='none', methods=['GET'])
|
|
def echo_http_get(self, **kwargs):
|
|
return str(kwargs)
|
|
|
|
@http.route('/test_http/echo-http-post', type='http', auth='none', methods=['POST'], csrf=False)
|
|
def echo_http_post(self, **kwargs):
|
|
return str(kwargs)
|
|
|
|
@http.route('/test_http/echo-http-csrf', type='http', auth='none', methods=['POST'], csrf=True)
|
|
def echo_http_csrf(self, **kwargs):
|
|
return str(kwargs)
|
|
|
|
@http.route('/test_http/echo-json', type='json', auth='none', methods=['POST'], csrf=False)
|
|
def echo_json(self, **kwargs):
|
|
return kwargs
|
|
|
|
@http.route('/test_http/echo-json-context', type='json', auth='user', methods=['POST'], csrf=False)
|
|
def echo_json_context(self, **kwargs):
|
|
return request.env.context
|
|
|
|
@http.route('/test_http/echo-json-over-http', type='http', auth='none', methods=['POST'], csrf=False)
|
|
def echo_json_over_http(self):
|
|
try:
|
|
data = request.get_json_data()
|
|
except ValueError as exc:
|
|
raise werkzeug.exceptions.BadRequest("Invalid JSON data") from exc
|
|
return request.make_json_response(data)
|
|
|
|
# =====================================================
|
|
# Models
|
|
# =====================================================
|
|
@http.route('/test_http/<model("test_http.galaxy"):galaxy>', auth='public')
|
|
def galaxy(self, galaxy):
|
|
if not galaxy.exists():
|
|
raise UserError('The Ancients did not settle there.')
|
|
|
|
return http.request.render('test_http.tmpl_galaxy', {
|
|
'galaxy': galaxy,
|
|
'stargates': http.request.env['test_http.stargate'].search([
|
|
('galaxy_id', '=', galaxy.id)
|
|
]),
|
|
})
|
|
|
|
@http.route('/test_http/<model("test_http.galaxy"):galaxy>/<model("test_http.stargate"):gate>', auth='user')
|
|
def stargate(self, galaxy, gate):
|
|
if not gate.exists():
|
|
raise UserError("The goa'uld destroyed the gate")
|
|
|
|
return http.request.render('test_http.tmpl_stargate', {
|
|
'gate': gate
|
|
})
|
|
|
|
# =====================================================
|
|
# Cors
|
|
# =====================================================
|
|
@http.route('/test_http/cors_http_default', type='http', auth='none', cors='*')
|
|
def cors_http(self):
|
|
return "Hello"
|
|
|
|
@http.route('/test_http/cors_http_methods', type='http', auth='none', methods=['GET', 'PUT'], cors='*')
|
|
def cors_http_verbs(self, **kwargs):
|
|
return "Hello"
|
|
|
|
@http.route('/test_http/cors_json', type='json', auth='none', cors='*')
|
|
def cors_json(self, **kwargs):
|
|
return {}
|
|
|
|
# =====================================================
|
|
# Dual nodb/db
|
|
# =====================================================
|
|
@http.route('/test_http/ensure_db', type='http', auth='none')
|
|
def ensure_db_endpoint(self, db=None):
|
|
ensure_db()
|
|
assert request.db, "There should be a database"
|
|
return request.db
|
|
|
|
# =====================================================
|
|
# Session
|
|
# =====================================================
|
|
@http.route('/test_http/geoip', type='http', auth='none')
|
|
def geoip(self):
|
|
return json.dumps({
|
|
'city': request.geoip.city.name,
|
|
'country_code': request.geoip.country.iso_code or request.geoip.continent.code,
|
|
'country_name': request.geoip.country.name or request.geoip.continent.name,
|
|
'latitude': request.geoip.location.latitude,
|
|
'longitude': request.geoip.location.longitude,
|
|
'region': request.geoip.subdivisions[0].iso_code if request.geoip.subdivisions else None,
|
|
'time_zone': request.geoip.location.time_zone,
|
|
})
|
|
|
|
@http.route('/test_http/save_session', type='http', auth='none')
|
|
def touch(self):
|
|
request.session.touch()
|
|
return ''
|
|
|
|
# =====================================================
|
|
# Errors
|
|
# =====================================================
|
|
@http.route('/test_http/json_value_error', type='json', auth='none')
|
|
def json_value_error(self):
|
|
raise ValueError('Unknown destination')
|
|
|
|
@http.route('/test_http/hide_errors/decorator', type='http', auth='none')
|
|
@replace_exceptions(AccessError, by=werkzeug.exceptions.NotFound())
|
|
def hide_errors_decorator(self, error):
|
|
if error == 'AccessError':
|
|
raise AccessError("Wrong iris code")
|
|
if error == 'UserError':
|
|
raise UserError("Walter is AFK")
|
|
|
|
@http.route('/test_http/hide_errors/context-manager', type='http', auth='none')
|
|
def hide_errors_context_manager(self, error):
|
|
with replace_exceptions(AccessError, by=werkzeug.exceptions.NotFound()):
|
|
if error == 'AccessError':
|
|
raise AccessError("Wrong iris code")
|
|
if error == 'UserError':
|
|
raise UserError("Walter is AFK")
|