summaryrefslogtreecommitdiff
path: root/openerp
diff options
context:
space:
mode:
authorRaphael Collet <rco@openerp.com>2016-08-26 13:26:05 +0200
committerRaphael Collet <rco@openerp.com>2016-08-31 17:21:59 +0200
commit70dec2d896af63e0e36fe3f5093676137501744d (patch)
tree79b6ad97c81f5af305a9bc138bef2308b111d598 /openerp
parent75dc39e0428465f38b2d3c4840764cdb0fac69db (diff)
[REF] registry: now mapping model name to model class
Diffstat (limited to 'openerp')
-rw-r--r--openerp/addons/base/ir/ir_actions.py1
-rw-r--r--openerp/addons/base/ir/ir_cron.py5
-rw-r--r--openerp/addons/base/ir/ir_http.py76
-rw-r--r--openerp/addons/base/ir/ir_model.py23
-rw-r--r--openerp/addons/base/res/res_font.py5
-rw-r--r--openerp/addons/base/res/res_users.py41
-rw-r--r--openerp/models.py71
-rw-r--r--openerp/modules/registry.py6
-rw-r--r--openerp/service/model.py4
-rw-r--r--openerp/tools/func.py10
-rw-r--r--openerp/tools/yaml_import.py4
11 files changed, 130 insertions, 116 deletions
diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py
index 6560ef98a59..d83ff6521a7 100644
--- a/openerp/addons/base/ir/ir_actions.py
+++ b/openerp/addons/base/ir/ir_actions.py
@@ -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,
diff --git a/openerp/addons/base/ir/ir_cron.py b/openerp/addons/base/ir/ir_cron.py
index 0e1e26986a4..c211cd12423 100644
--- a/openerp/addons/base/ir/ir_cron.py
+++ b/openerp/addons/base/ir/ir_cron.py
@@ -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
diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py
index ea6de3acbba..c63bd2f7226 100644
--- a/openerp/addons/base/ir/ir_http.py
+++ b/openerp/addons/base/ir/ir_http.py
@@ -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)
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index 6979d920a17..c4742ccb337 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -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. """
diff --git a/openerp/addons/base/res/res_font.py b/openerp/addons/base/res/res_font.py
index 4b200382d36..1ff4cd4f4c1 100644
--- a/openerp/addons/base/res/res_font.py
+++ b/openerp/addons/base/res/res_font.py
@@ -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()
diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py
index 221da71821d..e0f8a610b6f 100644
--- a/openerp/addons/base/res/res_users.py
+++ b/openerp/addons/base/res/res_users.py
@@ -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()
diff --git a/openerp/models.py b/openerp/models.py
index ce2d7cdb1ab..c3b683885c9 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -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
diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py
index e440f17ef84..c9660a5c965 100644
--- a/openerp/modules/registry.py
+++ b/openerp/modules/registry.py
@@ -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()
diff --git a/openerp/service/model.py b/openerp/service/model.py
index 02752355916..07897893abd 100644
--- a/openerp/service/model.py
+++ b/openerp/service/model.py
@@ -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
diff --git a/openerp/tools/func.py b/openerp/tools/func.py
index a615f64174d..8f49864eda2 100644
--- a/openerp/tools/func.py
+++ b/openerp/tools/func.py
@@ -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.
diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py
index 3c6480e400c..225c0388f8c 100644
--- a/openerp/tools/yaml_import.py
+++ b/openerp/tools/yaml_import.py
@@ -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,