[REF] registry: now mapping model name to model class
This commit is contained in:
@@ -9,21 +9,22 @@ from odoo import api, models, registry, SUPERUSER_ID
|
||||
class Users(models.Model):
|
||||
_inherit = "res.users"
|
||||
|
||||
def _login(self, db, login, password):
|
||||
user_id = super(Users, self)._login(db, login, password)
|
||||
@classmethod
|
||||
def _login(cls, db, login, password):
|
||||
user_id = super(Users, cls)._login(db, login, password)
|
||||
if user_id:
|
||||
return user_id
|
||||
with registry(db).cursor() as cr:
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
cr.execute("SELECT id FROM res_users WHERE lower(login)=%s", (login,))
|
||||
res = cr.fetchone()
|
||||
if res:
|
||||
return False
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
Ldap = env['res.company.ldap']
|
||||
for conf in Ldap.get_ldap_dicts():
|
||||
entry = Ldap.authenticate(conf, login, password)
|
||||
if entry:
|
||||
user_id = Ldap.sudo().get_or_create_user(conf, login, entry)
|
||||
user_id = Ldap.get_or_create_user(conf, login, entry)
|
||||
if user_id:
|
||||
break
|
||||
return user_id
|
||||
|
||||
@@ -172,7 +172,7 @@ class TestMatchHeadersMultiple(TransactionCase):
|
||||
|
||||
def test_mixed(self):
|
||||
self.assertEqual(
|
||||
self.registry('base_import.import')._match_headers(
|
||||
self.env['base_import.import']._match_headers(
|
||||
iter(['foo bar baz qux/corge'.split()]),
|
||||
[
|
||||
{'name': 'bar', 'string': 'Bar'},
|
||||
|
||||
@@ -10,10 +10,10 @@ from werkzeug.exceptions import BadRequest
|
||||
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def _auth_method_calendar(self):
|
||||
@classmethod
|
||||
def _auth_method_calendar(cls):
|
||||
token = request.params['token']
|
||||
dbname = request.params['db']
|
||||
|
||||
|
||||
@@ -6,14 +6,16 @@ from odoo import models
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def get_utm_domain_cookies(self):
|
||||
@classmethod
|
||||
def get_utm_domain_cookies(cls):
|
||||
return request.httprequest.host
|
||||
|
||||
def _dispatch(self):
|
||||
response = super(IrHttp, self)._dispatch()
|
||||
@classmethod
|
||||
def _dispatch(cls):
|
||||
response = super(IrHttp, cls)._dispatch()
|
||||
if isinstance(response, Exception):
|
||||
return response
|
||||
for var, dummy, cook in request.env['utm.mixin'].tracking_fields():
|
||||
if var in request.params and request.httprequest.cookies.get(var) != request.params[var]:
|
||||
response.set_cookie(cook, request.params[var], domain=self.get_utm_domain_cookies())
|
||||
response.set_cookie(cook, request.params[var], domain=cls.get_utm_domain_cookies())
|
||||
return response
|
||||
|
||||
@@ -8,7 +8,8 @@ from odoo.http import request
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def _dispatch(self):
|
||||
@classmethod
|
||||
def _dispatch(cls):
|
||||
context = dict(request.context)
|
||||
if 'editable' in request.httprequest.args and 'editable' not in context:
|
||||
context['editable'] = True
|
||||
@@ -17,4 +18,4 @@ class IrHttp(models.AbstractModel):
|
||||
if context.get('lang') != "en_US" and 'translatable' not in context:
|
||||
context['translatable'] = True
|
||||
request.context = context
|
||||
return super(IrHttp, self)._dispatch()
|
||||
return super(IrHttp, cls)._dispatch()
|
||||
|
||||
@@ -31,23 +31,24 @@ class RequestUID(object):
|
||||
|
||||
|
||||
class Http(models.AbstractModel):
|
||||
|
||||
_inherit = 'ir.http'
|
||||
|
||||
rerouting_limit = 10
|
||||
_geoip_resolver = None
|
||||
|
||||
def _get_converters(self):
|
||||
@classmethod
|
||||
def _get_converters(cls):
|
||||
""" Get the converters list for custom url pattern werkzeug need to
|
||||
match Rule. This override adds the website ones.
|
||||
"""
|
||||
return dict(
|
||||
super(Http, self)._get_converters(),
|
||||
super(Http, cls)._get_converters(),
|
||||
model=ModelConverter,
|
||||
page=PageConverter,
|
||||
)
|
||||
|
||||
def _auth_method_public(self):
|
||||
@classmethod
|
||||
def _auth_method_public(cls):
|
||||
""" If no user logged, set the public user of current website, or default
|
||||
public user as request uid.
|
||||
After this method `request.env` can be called, since the `request.uid` is
|
||||
@@ -65,16 +66,18 @@ class Http(models.AbstractModel):
|
||||
|
||||
bots = "bot|crawl|slurp|spider|curl|wget|facebookexternalhit".split("|")
|
||||
|
||||
def is_a_bot(self):
|
||||
@classmethod
|
||||
def is_a_bot(cls):
|
||||
# We don't use regexp and ustr voluntarily
|
||||
# timeit has been done to check the optimum method
|
||||
user_agent = request.httprequest.environ.get('HTTP_USER_AGENT', '').lower()
|
||||
try:
|
||||
return any(bot in user_agent for bot in self.bots)
|
||||
return any(bot in user_agent for bot in cls.bots)
|
||||
except UnicodeDecodeError:
|
||||
return any(bot in user_agent.encode('ascii', 'ignore') for bot in self.bots)
|
||||
return any(bot in user_agent.encode('ascii', 'ignore') for bot in cls.bots)
|
||||
|
||||
def get_nearest_lang(self, lang):
|
||||
@classmethod
|
||||
def get_nearest_lang(cls, lang):
|
||||
# Try to find a similar lang. Eg: fr_BE and fr_FR
|
||||
short = lang.partition('_')[0]
|
||||
short_match = False
|
||||
@@ -85,32 +88,36 @@ class Http(models.AbstractModel):
|
||||
short_match = code
|
||||
return short_match
|
||||
|
||||
def _geoip_setup_resolver(self):
|
||||
if self._geoip_resolver is None:
|
||||
@classmethod
|
||||
def _geoip_setup_resolver(cls):
|
||||
if cls._geoip_resolver is None:
|
||||
try:
|
||||
import GeoIP
|
||||
# updated database can be downloaded on MaxMind website
|
||||
# http://dev.maxmind.com/geoip/legacy/install/city/
|
||||
geofile = config.get('geoip_database')
|
||||
if os.path.exists(geofile):
|
||||
self._geoip_resolver = GeoIP.open(geofile, GeoIP.GEOIP_STANDARD)
|
||||
cls._geoip_resolver = GeoIP.open(geofile, GeoIP.GEOIP_STANDARD)
|
||||
else:
|
||||
self._geoip_resolver = False
|
||||
cls._geoip_resolver = False
|
||||
logger.warning('GeoIP database file %r does not exists, apt-get install geoip-database-contrib or download it from http://dev.maxmind.com/geoip/legacy/install/city/', geofile)
|
||||
except ImportError:
|
||||
self._geoip_resolver = False
|
||||
cls._geoip_resolver = False
|
||||
|
||||
def _geoip_resolve(self):
|
||||
@classmethod
|
||||
def _geoip_resolve(cls):
|
||||
if 'geoip' not in request.session:
|
||||
record = {}
|
||||
if self._geoip_resolver and request.httprequest.remote_addr:
|
||||
record = self._geoip_resolver.record_by_addr(request.httprequest.remote_addr) or {}
|
||||
if cls._geoip_resolver and request.httprequest.remote_addr:
|
||||
record = cls._geoip_resolver.record_by_addr(request.httprequest.remote_addr) or {}
|
||||
request.session['geoip'] = record
|
||||
|
||||
def get_page_key(self):
|
||||
return (self._name, "cache", request.uid, request.lang, request.httprequest.full_path)
|
||||
@classmethod
|
||||
def get_page_key(cls):
|
||||
return (cls._name, "cache", request.uid, request.lang, request.httprequest.full_path)
|
||||
|
||||
def _dispatch(self):
|
||||
@classmethod
|
||||
def _dispatch(cls):
|
||||
""" Before executing the endpoint method, add website params on request, such as
|
||||
- current website (record)
|
||||
- multilang support (set on cookies)
|
||||
@@ -126,7 +133,7 @@ class Http(models.AbstractModel):
|
||||
if request.httprequest.method == 'GET' and '//' in request.httprequest.path:
|
||||
new_url = request.httprequest.path.replace('//', '/') + '?' + request.httprequest.query_string
|
||||
return werkzeug.utils.redirect(new_url, 301)
|
||||
func, arguments = self._find_handler()
|
||||
func, arguments = cls._find_handler()
|
||||
request.website_enabled = func.routing.get('website', False)
|
||||
except werkzeug.exceptions.NotFound:
|
||||
# either we have a language prefixed route, either a real 404
|
||||
@@ -138,19 +145,19 @@ class Http(models.AbstractModel):
|
||||
func and func.routing.get('multilang', func.routing['type'] == 'http')
|
||||
)
|
||||
|
||||
self._geoip_setup_resolver()
|
||||
self._geoip_resolve()
|
||||
cls._geoip_setup_resolver()
|
||||
cls._geoip_resolve()
|
||||
|
||||
# For website routes (only), add website params on `request`
|
||||
cook_lang = request.httprequest.cookies.get('website_lang')
|
||||
if request.website_enabled:
|
||||
try:
|
||||
if func:
|
||||
self._authenticate(func.routing['auth'])
|
||||
cls._authenticate(func.routing['auth'])
|
||||
elif request.uid is None:
|
||||
self._auth_method_public()
|
||||
cls._auth_method_public()
|
||||
except Exception as e:
|
||||
return self._handle_exception(e)
|
||||
return cls._handle_exception(e)
|
||||
|
||||
request.redirect = lambda url, code=302: werkzeug.utils.redirect(url_for(url), code)
|
||||
request.website = request.env['website'].get_current_website() # can use `request.env` since auth methods are called
|
||||
@@ -159,13 +166,13 @@ class Http(models.AbstractModel):
|
||||
langs = [lg[0] for lg in request.website.get_languages()]
|
||||
path = request.httprequest.path.split('/')
|
||||
if first_pass:
|
||||
nearest_lang = not func and self.get_nearest_lang(path[1])
|
||||
nearest_lang = not func and cls.get_nearest_lang(path[1])
|
||||
url_lang = nearest_lang and path[1]
|
||||
preferred_lang = ((cook_lang if cook_lang in langs else False)
|
||||
or self.get_nearest_lang(request.lang)
|
||||
or cls.get_nearest_lang(request.lang)
|
||||
or request.website.default_lang_code)
|
||||
|
||||
is_a_bot = self.is_a_bot()
|
||||
is_a_bot = cls.is_a_bot()
|
||||
|
||||
request.lang = context['lang'] = nearest_lang or preferred_lang
|
||||
# if lang in url but not the displayed or default language --> change or remove
|
||||
@@ -189,7 +196,7 @@ class Http(models.AbstractModel):
|
||||
request.uid = None
|
||||
path.pop(1)
|
||||
request.context = context
|
||||
return self.reroute('/'.join(path) or '/')
|
||||
return cls.reroute('/'.join(path) or '/')
|
||||
if path[1] == request.website.default_lang_code:
|
||||
context['edit_translations'] = False
|
||||
if not context.get('tz'):
|
||||
@@ -200,29 +207,31 @@ class Http(models.AbstractModel):
|
||||
|
||||
# removed cache for auth public
|
||||
request.cache_save = False
|
||||
resp = super(Http, self)._dispatch()
|
||||
resp = super(Http, cls)._dispatch()
|
||||
|
||||
if request.website_enabled and cook_lang != request.lang and hasattr(resp, 'set_cookie'):
|
||||
resp.set_cookie('website_lang', request.lang)
|
||||
return resp
|
||||
|
||||
def reroute(self, path):
|
||||
@classmethod
|
||||
def reroute(cls, path):
|
||||
if not hasattr(request, 'rerouting'):
|
||||
request.rerouting = [request.httprequest.path]
|
||||
if path in request.rerouting:
|
||||
raise Exception("Rerouting loop is forbidden")
|
||||
request.rerouting.append(path)
|
||||
if len(request.rerouting) > self.rerouting_limit:
|
||||
if len(request.rerouting) > cls.rerouting_limit:
|
||||
raise Exception("Rerouting limit exceeded")
|
||||
request.httprequest.environ['PATH_INFO'] = path
|
||||
# void werkzeug cached_property. TODO: find a proper way to do this
|
||||
for key in ('path', 'full_path', 'url', 'base_url'):
|
||||
request.httprequest.__dict__.pop(key, None)
|
||||
|
||||
return self._dispatch()
|
||||
return cls._dispatch()
|
||||
|
||||
def _postprocess_args(self, arguments, rule):
|
||||
super(Http, self)._postprocess_args(arguments, rule)
|
||||
@classmethod
|
||||
def _postprocess_args(cls, arguments, rule):
|
||||
super(Http, cls)._postprocess_args(arguments, rule)
|
||||
|
||||
for key, val in arguments.items():
|
||||
# Replace uid placeholder by the current request.uid
|
||||
@@ -233,7 +242,7 @@ class Http(models.AbstractModel):
|
||||
_, path = rule.build(arguments)
|
||||
assert path is not None
|
||||
except Exception, e:
|
||||
return self._handle_exception(e, code=404)
|
||||
return cls._handle_exception(e, code=404)
|
||||
|
||||
if getattr(request, 'website_multilang', False) and request.httprequest.method in ('GET', 'HEAD'):
|
||||
generated_path = werkzeug.url_unquote_plus(path)
|
||||
@@ -245,14 +254,15 @@ class Http(models.AbstractModel):
|
||||
path += '?' + request.httprequest.query_string
|
||||
return werkzeug.utils.redirect(path, code=301)
|
||||
|
||||
def _handle_exception(self, exception, code=500):
|
||||
@classmethod
|
||||
def _handle_exception(cls, exception, code=500):
|
||||
is_website_request = bool(getattr(request, 'website_enabled', False) and request.website)
|
||||
if not is_website_request:
|
||||
# Don't touch non website requests exception handling
|
||||
return super(Http, self)._handle_exception(exception)
|
||||
return super(Http, cls)._handle_exception(exception)
|
||||
else:
|
||||
try:
|
||||
response = super(Http, self)._handle_exception(exception)
|
||||
response = super(Http, cls)._handle_exception(exception)
|
||||
if isinstance(response, Exception):
|
||||
exception = response
|
||||
else:
|
||||
@@ -300,7 +310,7 @@ class Http(models.AbstractModel):
|
||||
)
|
||||
|
||||
if not request.uid:
|
||||
self._auth_method_public()
|
||||
cls._auth_method_public()
|
||||
|
||||
try:
|
||||
html = request.env['ir.ui.view'].render_template('website.%s' % code, values)
|
||||
@@ -308,7 +318,8 @@ class Http(models.AbstractModel):
|
||||
html = request.env['ir.ui.view'].render_template('website.http_error', values)
|
||||
return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
|
||||
|
||||
def binary_content(self, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None):
|
||||
@classmethod
|
||||
def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None):
|
||||
env = env or request.env
|
||||
obj = None
|
||||
if xmlid:
|
||||
@@ -318,7 +329,7 @@ class Http(models.AbstractModel):
|
||||
if obj and 'website_published' in obj._fields:
|
||||
if env[obj._name].sudo().search([('id', '=', obj.id), ('website_published', '=', True)]):
|
||||
env = env(user=SUPERUSER_ID)
|
||||
return super(Http, self).binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype=default_mimetype, env=env)
|
||||
return super(Http, cls).binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype=default_mimetype, env=env)
|
||||
|
||||
|
||||
class ModelConverter(ir.ir_http.ModelConverter):
|
||||
|
||||
@@ -7,8 +7,9 @@ from odoo.http import request
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def _dispatch(self):
|
||||
@classmethod
|
||||
def _dispatch(cls):
|
||||
affiliate_id = request.httprequest.args.get('affiliate_id')
|
||||
if affiliate_id:
|
||||
request.session['affiliate_id'] = int(affiliate_id)
|
||||
return super(IrHttp, self)._dispatch()
|
||||
return super(IrHttp, cls)._dispatch()
|
||||
|
||||
@@ -919,7 +919,6 @@ class IrActionsServer(models.Model):
|
||||
'object': obj,
|
||||
'obj': obj,
|
||||
# Deprecated use env or model instead
|
||||
'self': model._model,
|
||||
'pool': self.pool,
|
||||
'cr': self._cr,
|
||||
'context': self._context,
|
||||
|
||||
@@ -125,7 +125,8 @@ class ir_cron(models.Model):
|
||||
except Exception, e:
|
||||
self._handle_callback_exception(model_name, method_name, args, job_id, e)
|
||||
|
||||
def _process_job(self, job_cr, job, cron_cr):
|
||||
@classmethod
|
||||
def _process_job(cls, job_cr, job, cron_cr):
|
||||
""" Run a given job taking care of the repetition.
|
||||
|
||||
:param job_cr: cursor to use to execute the job, safe to commit/rollback
|
||||
@@ -135,7 +136,7 @@ class ir_cron(models.Model):
|
||||
"""
|
||||
try:
|
||||
with api.Environment.manage():
|
||||
cron = api.Environment(job_cr, job['user_id'], {})[self._name]
|
||||
cron = api.Environment(job_cr, job['user_id'], {})[cls._name]
|
||||
# Use the user's timezone to compare and compute datetimes,
|
||||
# otherwise unexpected results may appear. For instance, adding
|
||||
# 1 month in UTC to July 1st at midnight in GMT+2 gives July 30
|
||||
|
||||
@@ -70,27 +70,33 @@ class IrHttp(models.AbstractModel):
|
||||
_name = 'ir.http'
|
||||
_description = "HTTP routing"
|
||||
|
||||
def _get_converters(self):
|
||||
@classmethod
|
||||
def _get_converters(cls):
|
||||
return {'model': ModelConverter, 'models': ModelsConverter, 'int': SignedIntConverter}
|
||||
|
||||
def _find_handler(self, return_rule=False):
|
||||
return self.routing_map().bind_to_environ(request.httprequest.environ).match(return_rule=return_rule)
|
||||
@classmethod
|
||||
def _find_handler(cls, return_rule=False):
|
||||
return cls.routing_map().bind_to_environ(request.httprequest.environ).match(return_rule=return_rule)
|
||||
|
||||
def _auth_method_user(self):
|
||||
@classmethod
|
||||
def _auth_method_user(cls):
|
||||
request.uid = request.session.uid
|
||||
if not request.uid:
|
||||
raise http.SessionExpiredException("Session expired")
|
||||
|
||||
def _auth_method_none(self):
|
||||
@classmethod
|
||||
def _auth_method_none(cls):
|
||||
request.uid = None
|
||||
|
||||
def _auth_method_public(self):
|
||||
@classmethod
|
||||
def _auth_method_public(cls):
|
||||
if not request.session.uid:
|
||||
request.uid = request.env.ref('base.public_user').id
|
||||
else:
|
||||
request.uid = request.session.uid
|
||||
|
||||
def _authenticate(self, auth_method='user'):
|
||||
@classmethod
|
||||
def _authenticate(cls, auth_method='user'):
|
||||
try:
|
||||
if request.session.uid:
|
||||
try:
|
||||
@@ -103,7 +109,7 @@ class IrHttp(models.AbstractModel):
|
||||
# let them bubble up
|
||||
request.session.logout(keep_db=True)
|
||||
if request.uid is None:
|
||||
getattr(self, "_auth_method_%s" % auth_method)()
|
||||
getattr(cls, "_auth_method_%s" % auth_method)()
|
||||
except (AccessDenied, http.SessionExpiredException, werkzeug.exceptions.HTTPException):
|
||||
raise
|
||||
except Exception:
|
||||
@@ -111,7 +117,8 @@ class IrHttp(models.AbstractModel):
|
||||
raise AccessDenied()
|
||||
return auth_method
|
||||
|
||||
def _serve_attachment(self):
|
||||
@classmethod
|
||||
def _serve_attachment(cls):
|
||||
env = api.Environment(request.cr, SUPERUSER_ID, request.context)
|
||||
domain = [('type', '=', 'binary'), ('url', '=', request.httprequest.path)]
|
||||
fields = ['__last_update', 'datas', 'name', 'mimetype', 'checksum']
|
||||
@@ -144,13 +151,14 @@ class IrHttp(models.AbstractModel):
|
||||
response.data = datas.decode('base64')
|
||||
return response
|
||||
|
||||
def _handle_exception(self, exception):
|
||||
@classmethod
|
||||
def _handle_exception(cls, exception):
|
||||
# If handle_exception returns something different than None, it will be used as a response
|
||||
|
||||
# This is done first as the attachment path may
|
||||
# not match any HTTP controller
|
||||
if isinstance(exception, werkzeug.exceptions.HTTPException) and exception.code == 404:
|
||||
attach = self._serve_attachment()
|
||||
attach = cls._serve_attachment()
|
||||
if attach:
|
||||
return attach
|
||||
|
||||
@@ -162,21 +170,22 @@ class IrHttp(models.AbstractModel):
|
||||
except AccessDenied:
|
||||
return werkzeug.exceptions.Forbidden()
|
||||
|
||||
def _dispatch(self):
|
||||
@classmethod
|
||||
def _dispatch(cls):
|
||||
# locate the controller method
|
||||
try:
|
||||
rule, arguments = self._find_handler(return_rule=True)
|
||||
rule, arguments = cls._find_handler(return_rule=True)
|
||||
func = rule.endpoint
|
||||
except werkzeug.exceptions.NotFound, e:
|
||||
return self._handle_exception(e)
|
||||
return cls._handle_exception(e)
|
||||
|
||||
# check authentication level
|
||||
try:
|
||||
auth_method = self._authenticate(func.routing["auth"])
|
||||
auth_method = cls._authenticate(func.routing["auth"])
|
||||
except Exception as e:
|
||||
return self._handle_exception(e)
|
||||
return cls._handle_exception(e)
|
||||
|
||||
processing = self._postprocess_args(arguments, rule)
|
||||
processing = cls._postprocess_args(arguments, rule)
|
||||
if processing:
|
||||
return processing
|
||||
|
||||
@@ -187,40 +196,45 @@ class IrHttp(models.AbstractModel):
|
||||
if isinstance(result, Exception):
|
||||
raise result
|
||||
except Exception, e:
|
||||
return self._handle_exception(e)
|
||||
return cls._handle_exception(e)
|
||||
|
||||
return result
|
||||
|
||||
def _postprocess_args(self, arguments, rule):
|
||||
@classmethod
|
||||
def _postprocess_args(cls, arguments, rule):
|
||||
""" post process arg to set uid on browse records """
|
||||
for name, arg in arguments.items():
|
||||
if isinstance(arg, models.BaseModel) and arg._uid is UID_PLACEHOLDER:
|
||||
arguments[name] = arg.sudo(request.uid)
|
||||
if not arg.exists():
|
||||
return self._handle_exception(werkzeug.exceptions.NotFound())
|
||||
return cls._handle_exception(werkzeug.exceptions.NotFound())
|
||||
|
||||
def routing_map(self):
|
||||
if not hasattr(self, '_routing_map'):
|
||||
@classmethod
|
||||
def routing_map(cls):
|
||||
if not hasattr(cls, '_routing_map'):
|
||||
_logger.info("Generating routing map")
|
||||
installed = request.registry._init_modules - {'web'}
|
||||
if tools.config['test_enable']:
|
||||
installed.add(odoo.modules.module.current_test)
|
||||
mods = [''] + odoo.conf.server_wide_modules + sorted(installed)
|
||||
# Note : when routing map is generated, we put it on the class `type(self)`
|
||||
# Note : when routing map is generated, we put it on the class `cls`
|
||||
# to make it available for all instance. Since `env` create an new instance
|
||||
# of the model, each instance will regenared its own routing map and thus
|
||||
# regenerate its EndPoint. The routing map should be static.
|
||||
type(self)._routing_map = http.routing_map(mods, False, converters=self._get_converters())
|
||||
return self._routing_map
|
||||
cls._routing_map = http.routing_map(mods, False, converters=cls._get_converters())
|
||||
return cls._routing_map
|
||||
|
||||
def _clear_routing_map(self):
|
||||
if hasattr(self, '_routing_map'):
|
||||
del type(self)._routing_map
|
||||
@classmethod
|
||||
def _clear_routing_map(cls):
|
||||
if hasattr(cls, '_routing_map'):
|
||||
del cls._routing_map
|
||||
|
||||
def content_disposition(self, filename):
|
||||
@classmethod
|
||||
def content_disposition(cls, filename):
|
||||
return content_disposition(filename)
|
||||
|
||||
def binary_content(self, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None):
|
||||
@classmethod
|
||||
def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None):
|
||||
""" Get file, attachment or downloadable content
|
||||
|
||||
If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the
|
||||
@@ -317,7 +331,7 @@ class IrHttp(models.AbstractModel):
|
||||
|
||||
# content-disposition default name
|
||||
if download:
|
||||
headers.append(('Content-Disposition', self.content_disposition(filename)))
|
||||
headers.append(('Content-Disposition', cls.content_disposition(filename)))
|
||||
return (status, headers, content)
|
||||
|
||||
|
||||
|
||||
@@ -779,16 +779,15 @@ class IrModelAccess(models.Model):
|
||||
|
||||
return bool(r)
|
||||
|
||||
__cache_clearing_methods = []
|
||||
__cache_clearing_methods = set()
|
||||
|
||||
def register_cache_clearing_method(self, model, method):
|
||||
self.__cache_clearing_methods.append((model, method))
|
||||
@classmethod
|
||||
def register_cache_clearing_method(cls, model, method):
|
||||
cls.__cache_clearing_methods.add((model, method))
|
||||
|
||||
def unregister_cache_clearing_method(self, model, method):
|
||||
try:
|
||||
self.__cache_clearing_methods.remove((model, method))
|
||||
except ValueError:
|
||||
pass
|
||||
@classmethod
|
||||
def unregister_cache_clearing_method(cls, model, method):
|
||||
cls.__cache_clearing_methods.discard((model, method))
|
||||
|
||||
@api.model_cr
|
||||
def call_cache_clearing_methods(self):
|
||||
@@ -979,14 +978,6 @@ class IrModelData(models.Model):
|
||||
pass
|
||||
return False
|
||||
|
||||
def clear_caches(self):
|
||||
""" Clears all orm caches on the object's methods
|
||||
|
||||
:returns: itself
|
||||
"""
|
||||
self.xmlid_lookup.clear_cache(self)
|
||||
return self
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
""" Regular unlink method, but make sure to clear the caches. """
|
||||
|
||||
@@ -125,7 +125,8 @@ class ResFont(models.Model):
|
||||
builtin_font_family, mode)
|
||||
return True
|
||||
|
||||
def clear_caches(self):
|
||||
@classmethod
|
||||
def clear_caches(cls):
|
||||
"""Force worker to resync at next report loading by setting an empty font list"""
|
||||
customfonts.CustomTTFonts = []
|
||||
return super(ResFont, self).clear_caches()
|
||||
return super(ResFont, cls).clear_caches()
|
||||
|
||||
@@ -438,23 +438,26 @@ class Users(models.Model):
|
||||
# extra records will be deleted by the periodical garbage collection
|
||||
self.env['res.users.log'].create({}) # populated by defaults
|
||||
|
||||
def _login(self, db, login, password):
|
||||
@classmethod
|
||||
def _login(cls, db, login, password):
|
||||
if not password:
|
||||
return False
|
||||
user_id = False
|
||||
try:
|
||||
with self.pool.cursor() as cr:
|
||||
res = self.search(cr, SUPERUSER_ID, [('login','=',login)])
|
||||
if res:
|
||||
user_id = res[0]
|
||||
self.check_credentials(cr, user_id, password)
|
||||
self._update_last_login(cr, user_id)
|
||||
with cls.pool.cursor() as cr:
|
||||
self = api.Environment(cr, SUPERUSER_ID, {})[cls._name]
|
||||
user = self.search([('login', '=', login)])
|
||||
if user:
|
||||
user_id = user.id
|
||||
user.sudo(user_id).check_credentials(password)
|
||||
user.sudo(user_id)._update_last_login()
|
||||
except AccessDenied:
|
||||
_logger.info("Login failed for db:%s login:%s", db, login)
|
||||
user_id = False
|
||||
return user_id
|
||||
|
||||
def authenticate(self, db, login, password, user_agent_env):
|
||||
@classmethod
|
||||
def authenticate(cls, db, login, password, user_agent_env):
|
||||
"""Verifies and returns the user ID corresponding to the given
|
||||
``login`` and ``password`` combination, or False if there was
|
||||
no matching user.
|
||||
@@ -464,33 +467,35 @@ class Users(models.Model):
|
||||
:param dict user_agent_env: environment dictionary describing any
|
||||
relevant environment attributes
|
||||
"""
|
||||
uid = self._login(db, login, password)
|
||||
uid = cls._login(db, login, password)
|
||||
if uid == SUPERUSER_ID:
|
||||
# Successfully logged in as admin!
|
||||
# Attempt to guess the web base url...
|
||||
if user_agent_env and user_agent_env.get('base_location'):
|
||||
try:
|
||||
with self.pool.cursor() as cr:
|
||||
with cls.pool.cursor() as cr:
|
||||
base = user_agent_env['base_location']
|
||||
ICP = self.pool['ir.config_parameter']
|
||||
if not ICP.get_param(cr, uid, 'web.base.url.freeze'):
|
||||
ICP.set_param(cr, uid, 'web.base.url', base)
|
||||
ICP = api.Environment(cr, uid, {})['ir.config_parameter']
|
||||
if not ICP.get_param('web.base.url.freeze'):
|
||||
ICP.set_param('web.base.url', base)
|
||||
except Exception:
|
||||
_logger.exception("Failed to update web.base.url configuration parameter")
|
||||
return uid
|
||||
|
||||
def check(self, db, uid, passwd):
|
||||
@classmethod
|
||||
def check(cls, db, uid, passwd):
|
||||
"""Verifies that the given (uid, password) is authorized for the database ``db`` and
|
||||
raise an exception if it is not."""
|
||||
if not passwd:
|
||||
# empty passwords disallowed for obvious security reasons
|
||||
raise AccessDenied()
|
||||
if self.__uid_cache[db].get(uid) == passwd:
|
||||
if cls.__uid_cache[db].get(uid) == passwd:
|
||||
return
|
||||
cr = self.pool.cursor()
|
||||
cr = cls.pool.cursor()
|
||||
try:
|
||||
self.check_credentials(cr, uid, passwd)
|
||||
self.__uid_cache[db][uid] = passwd
|
||||
self = api.Environment(cr, uid, {})[cls._name]
|
||||
self.check_credentials(passwd)
|
||||
cls.__uid_cache[db][uid] = passwd
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
|
||||
+33
-38
@@ -46,7 +46,8 @@ from . import api
|
||||
from . import tools
|
||||
from .exceptions import AccessError, MissingError, ValidationError, UserError
|
||||
from .osv.query import Query
|
||||
from .tools import frozendict, lazy_property, ormcache, Collector, LastOrderedSet, OrderedSet
|
||||
from .tools import frozendict, lazy_classproperty, lazy_property, ormcache, \
|
||||
Collector, LastOrderedSet, OrderedSet
|
||||
from .tools.config import config
|
||||
from .tools.func import frame_codeinfo
|
||||
from .tools.misc import CountingStream, DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
|
||||
@@ -267,6 +268,11 @@ class BaseModel(object):
|
||||
_group_by_full = {} # {field: method}, where method returns (records
|
||||
# name_get, {id: fold}) used by read_group()
|
||||
|
||||
# default values for _transient_vacuum()
|
||||
_transient_check_count = 0
|
||||
_transient_max_count = lazy_classproperty(lambda _: config.get('osv_memory_count_limit'))
|
||||
_transient_max_hours = lazy_classproperty(lambda _: config.get('osv_memory_age_limit'))
|
||||
|
||||
CONCURRENCY_CHECK_FIELD = '__last_update'
|
||||
|
||||
@api.model
|
||||
@@ -523,7 +529,7 @@ class BaseModel(object):
|
||||
if name in parents:
|
||||
if name not in pool:
|
||||
raise TypeError("Model %r does not exist in registry." % name)
|
||||
ModelClass = type(pool[name])
|
||||
ModelClass = pool[name]
|
||||
ModelClass._build_model_check_base(cls)
|
||||
check_parent = ModelClass._build_model_check_parent
|
||||
else:
|
||||
@@ -541,7 +547,7 @@ class BaseModel(object):
|
||||
for parent in parents:
|
||||
if parent not in pool:
|
||||
raise TypeError("Model %r inherits from non-existing model %r." % (name, parent))
|
||||
parent_class = type(pool[parent])
|
||||
parent_class = pool[parent]
|
||||
if parent == name:
|
||||
for base in parent_class.__bases__:
|
||||
bases.add(base)
|
||||
@@ -554,10 +560,23 @@ class BaseModel(object):
|
||||
# determine the attributes of the model's class
|
||||
ModelClass._build_model_attributes(pool)
|
||||
|
||||
# instantiate the model, and initialize it
|
||||
check_pg_name(ModelClass._table)
|
||||
|
||||
# Transience
|
||||
if ModelClass._transient:
|
||||
assert ModelClass._log_access, \
|
||||
"TransientModels must have log_access turned on, " \
|
||||
"in order to implement their access rights policy"
|
||||
|
||||
# link the class to the registry, and update the registry
|
||||
ModelClass.pool = pool
|
||||
pool.add(ModelClass._name, ModelClass)
|
||||
|
||||
# backward compatibility: instantiate the model, and initialize it
|
||||
model = object.__new__(ModelClass)
|
||||
model.__init__(pool, cr)
|
||||
return model
|
||||
|
||||
return ModelClass
|
||||
|
||||
@classmethod
|
||||
def _build_model_check_base(model_class, cls):
|
||||
@@ -622,7 +641,7 @@ class BaseModel(object):
|
||||
|
||||
# recompute attributes of children models
|
||||
for child_name in cls._inherit_children:
|
||||
child_class = type(pool[child_name])
|
||||
child_class = pool[child_name]
|
||||
child_class._build_model_attributes(pool)
|
||||
|
||||
@api.model
|
||||
@@ -727,37 +746,11 @@ class BaseModel(object):
|
||||
def __new__(cls):
|
||||
# In the past, this method was registering the model class in the server.
|
||||
# This job is now done entirely by the metaclass MetaModel.
|
||||
#
|
||||
# Do not create an instance here. Model instances are created by method
|
||||
# _build_model().
|
||||
return None
|
||||
|
||||
def __init__(self, pool, cr):
|
||||
""" Initialize a model and make it part of the given registry.
|
||||
|
||||
- copy the stored fields' functions in the registry,
|
||||
- retrieve custom fields and add them in the model,
|
||||
- ensure there is a many2one for each _inherits'd parent,
|
||||
- give a chance to each field to initialize itself.
|
||||
|
||||
"""
|
||||
cls = type(self)
|
||||
|
||||
# link the class to the registry, and update the registry
|
||||
cls.pool = pool
|
||||
cls._model = self # backward compatibility
|
||||
pool.add(cls._name, self)
|
||||
|
||||
check_pg_name(cls._table)
|
||||
|
||||
# Transience
|
||||
if cls.is_transient():
|
||||
cls._transient_check_count = 0
|
||||
cls._transient_max_count = config.get('osv_memory_count_limit')
|
||||
cls._transient_max_hours = config.get('osv_memory_age_limit')
|
||||
assert cls._log_access, \
|
||||
"TransientModels must have log_access turned on, " \
|
||||
"in order to implement their access rights policy"
|
||||
""" Deprecated method to initialize the model. """
|
||||
pass
|
||||
|
||||
@api.model
|
||||
@ormcache()
|
||||
@@ -912,7 +905,8 @@ class BaseModel(object):
|
||||
|
||||
ids = []
|
||||
messages = []
|
||||
ModelData = self.env['ir.model.data'].clear_caches()
|
||||
ModelData = self.env['ir.model.data']
|
||||
ModelData.clear_caches()
|
||||
extracted = self._extract_records(fields, data, log=messages.append)
|
||||
converted = self._convert_records(extracted, log=messages.append)
|
||||
for id, xid, record, info in converted:
|
||||
@@ -1658,15 +1652,16 @@ class BaseModel(object):
|
||||
defaults.update(values)
|
||||
return defaults
|
||||
|
||||
def clear_caches(self):
|
||||
@classmethod
|
||||
def clear_caches(cls):
|
||||
""" Clear the caches
|
||||
|
||||
This clears the caches associated to methods decorated with
|
||||
``tools.ormcache`` or ``tools.ormcache_multi``.
|
||||
"""
|
||||
try:
|
||||
self.pool.cache.clear()
|
||||
self.pool._any_cache_cleared = True
|
||||
cls.pool.cache.clear()
|
||||
cls.pool._any_cache_cleared = True
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ _logger = logging.getLogger(__name__)
|
||||
class Registry(Mapping):
|
||||
""" Model registry for a particular database.
|
||||
|
||||
The registry is essentially a mapping between model names and model
|
||||
instances. There is one registry instance per database.
|
||||
The registry is essentially a mapping between model names and model classes.
|
||||
There is one registry instance per database.
|
||||
|
||||
"""
|
||||
|
||||
@@ -184,7 +184,7 @@ class Registry(Mapping):
|
||||
ir_model._instanciate(model_data)
|
||||
|
||||
# prepare the setup on all models
|
||||
models = [env[model_name] for model_name in self.models]
|
||||
models = env.values()
|
||||
for model in models:
|
||||
model._prepare_setup()
|
||||
|
||||
|
||||
@@ -153,8 +153,8 @@ def check(f):
|
||||
model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
|
||||
model = table.replace("_",".")
|
||||
if model in registry:
|
||||
model_obj = registry[model]
|
||||
model_name = model_obj._description or model_obj._name
|
||||
model_class = registry[model]
|
||||
model_name = model_class._description or model_class._name
|
||||
msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
__all__ = ['synchronized', 'lazy_property', 'classproperty', 'conditional']
|
||||
__all__ = ['synchronized', 'lazy_classproperty', 'lazy_property',
|
||||
'classproperty', 'conditional']
|
||||
|
||||
from functools import wraps
|
||||
from inspect import getsourcefile
|
||||
@@ -36,6 +37,13 @@ class lazy_property(object):
|
||||
if isinstance(getattr(cls, name, None), lazy_property):
|
||||
obj_dict.pop(name)
|
||||
|
||||
class lazy_classproperty(lazy_property):
|
||||
""" Similar to :class:`lazy_property`, but for classes. """
|
||||
def __get__(self, obj, cls):
|
||||
val = self.fget(cls)
|
||||
setattr(cls, self.fget.__name__, val)
|
||||
return val
|
||||
|
||||
def conditional(condition, decorator):
|
||||
""" Decorator for a conditionally applied decorator.
|
||||
|
||||
|
||||
@@ -588,7 +588,7 @@ class YamlInterpreter(object):
|
||||
python, statements = node.items()[0]
|
||||
assert python.model or python.id, "!python node must have attribute `model` or `id`"
|
||||
if python.id is None:
|
||||
record = self.env[python.model]._model
|
||||
record = self.env[python.model]
|
||||
elif isinstance(python.id, basestring):
|
||||
record = self.get_record(python.id)
|
||||
else:
|
||||
@@ -598,7 +598,7 @@ class YamlInterpreter(object):
|
||||
statements = "\n" * python.first_line + statements.replace("\r\n", "\n")
|
||||
code_context = {
|
||||
'self': record,
|
||||
'model': record._model,
|
||||
'model': record,
|
||||
'cr': self.cr,
|
||||
'uid': self.uid,
|
||||
'log': self._log,
|
||||
|
||||
Reference in New Issue
Block a user