From 70dec2d896af63e0e36fe3f5093676137501744d Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 26 Aug 2016 13:26:05 +0200 Subject: [PATCH] [REF] registry: now mapping model name to model class --- addons/auth_ldap/models/res_users.py | 9 +- addons/base_import/tests/test_base_import.py | 2 +- addons/calendar/models/ir_http.py | 4 +- addons/utm/models/ir_http.py | 10 ++- addons/web_editor/models/ir_http.py | 5 +- addons/website/models/ir_http.py | 95 +++++++++++--------- addons/website_sale/models/ir_http.py | 5 +- openerp/addons/base/ir/ir_actions.py | 1 - openerp/addons/base/ir/ir_cron.py | 5 +- openerp/addons/base/ir/ir_http.py | 76 +++++++++------- openerp/addons/base/ir/ir_model.py | 23 ++--- openerp/addons/base/res/res_font.py | 5 +- openerp/addons/base/res/res_users.py | 41 +++++---- openerp/models.py | 71 +++++++-------- openerp/modules/registry.py | 6 +- openerp/service/model.py | 4 +- openerp/tools/func.py | 10 ++- openerp/tools/yaml_import.py | 4 +- 18 files changed, 203 insertions(+), 173 deletions(-) diff --git a/addons/auth_ldap/models/res_users.py b/addons/auth_ldap/models/res_users.py index e527f86f2a1..83887f802a0 100644 --- a/addons/auth_ldap/models/res_users.py +++ b/addons/auth_ldap/models/res_users.py @@ -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 diff --git a/addons/base_import/tests/test_base_import.py b/addons/base_import/tests/test_base_import.py index ee44734d29b..1a84d737391 100644 --- a/addons/base_import/tests/test_base_import.py +++ b/addons/base_import/tests/test_base_import.py @@ -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'}, diff --git a/addons/calendar/models/ir_http.py b/addons/calendar/models/ir_http.py index dc165a88ac2..d1d1e874a9c 100644 --- a/addons/calendar/models/ir_http.py +++ b/addons/calendar/models/ir_http.py @@ -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'] diff --git a/addons/utm/models/ir_http.py b/addons/utm/models/ir_http.py index e85210cef2d..4b4c275c4a7 100644 --- a/addons/utm/models/ir_http.py +++ b/addons/utm/models/ir_http.py @@ -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 diff --git a/addons/web_editor/models/ir_http.py b/addons/web_editor/models/ir_http.py index 5abe7862245..04845688ea9 100644 --- a/addons/web_editor/models/ir_http.py +++ b/addons/web_editor/models/ir_http.py @@ -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() diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 58e87bb8e20..b1a0fff4e10 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -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): diff --git a/addons/website_sale/models/ir_http.py b/addons/website_sale/models/ir_http.py index 9655a7b6223..d1f6e306ead 100644 --- a/addons/website_sale/models/ir_http.py +++ b/addons/website_sale/models/ir_http.py @@ -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() 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,