diff options
| author | Raphael Collet <rco@openerp.com> | 2016-08-26 13:26:05 +0200 |
|---|---|---|
| committer | Raphael Collet <rco@openerp.com> | 2016-08-31 17:21:59 +0200 |
| commit | 70dec2d896af63e0e36fe3f5093676137501744d (patch) | |
| tree | 79b6ad97c81f5af305a9bc138bef2308b111d598 /openerp | |
| parent | 75dc39e0428465f38b2d3c4840764cdb0fac69db (diff) | |
[REF] registry: now mapping model name to model class
Diffstat (limited to 'openerp')
| -rw-r--r-- | openerp/addons/base/ir/ir_actions.py | 1 | ||||
| -rw-r--r-- | openerp/addons/base/ir/ir_cron.py | 5 | ||||
| -rw-r--r-- | openerp/addons/base/ir/ir_http.py | 76 | ||||
| -rw-r--r-- | openerp/addons/base/ir/ir_model.py | 23 | ||||
| -rw-r--r-- | openerp/addons/base/res/res_font.py | 5 | ||||
| -rw-r--r-- | openerp/addons/base/res/res_users.py | 41 | ||||
| -rw-r--r-- | openerp/models.py | 71 | ||||
| -rw-r--r-- | openerp/modules/registry.py | 6 | ||||
| -rw-r--r-- | openerp/service/model.py | 4 | ||||
| -rw-r--r-- | openerp/tools/func.py | 10 | ||||
| -rw-r--r-- | openerp/tools/yaml_import.py | 4 |
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, |
