[REF] registry: now mapping model name to model class

This commit is contained in:
Raphael Collet
2016-08-31 17:21:59 +02:00
parent 75dc39e042
commit 70dec2d896
18 changed files with 203 additions and 173 deletions
+5 -4
View File
@@ -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
+1 -1
View File
@@ -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'},
+2 -2
View File
@@ -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 -4
View File
@@ -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
+3 -2
View File
@@ -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()
+53 -42
View File
@@ -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):
+3 -2
View File
@@ -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()
-1
View File
@@ -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,
+3 -2
View File
@@ -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
+45 -31
View File
@@ -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)
+7 -16
View File
@@ -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. """
+3 -2
View File
@@ -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()
+23 -18
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -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()
+2 -2
View File
@@ -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
+9 -1
View File
@@ -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.
+2 -2
View File
@@ -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,