diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 24b23720119..1bdbbe3d0be 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -340,7 +340,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): for (model, name) in cr.fetchall(): model_obj = pool.get(model) if model_obj and not model_obj.is_transient(): - logger.notifyChannel('init', netsvc.LOG_WARNING, 'The model %s (%s) has no access rules!' % (model, name)) + logger.notifyChannel('init', netsvc.LOG_WARNING, 'Model %s (%s) has no access rules!' % (model, name)) # Temporary warning while we remove access rights on osv_memory objects, as they have # been replaced by owner-only access rights @@ -348,7 +348,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False): for (model, name) in cr.fetchall(): model_obj = pool.get(model) if model_obj.is_transient(): - logger.notifyChannel('init', netsvc.LOG_WARNING, 'The transient model (formerly osv_memory) %s (%s) should not have explicit access rules!' % (model, name)) + import pdb + pdb.set_trace() + logger.notifyChannel('init', netsvc.LOG_WARNING, 'The transient model %s (%s) should not have explicit access rules!' % (model, name)) cr.execute("SELECT model from ir_model") for (model,) in cr.fetchall(): @@ -356,7 +358,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if obj: obj._check_removed_columns(cr, log=True) else: - logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model) + logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)" % model) # Cleanup orphan records pool.get('ir.model.data')._process_end(cr, 1, processed_modules) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 21163082ac6..b1d949765d8 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -45,24 +45,25 @@ import calendar import copy import datetime import logging -import warnings import operator import pickle import re +import simplejson import time import traceback import types -import simplejson - -import openerp.netsvc as netsvc +import warnings from lxml import etree -from openerp.tools.config import config -from openerp.tools.translate import _ +import expression import fields -from query import Query +import openerp +import openerp.netsvc as netsvc import openerp.tools as tools +from openerp.tools.config import config from openerp.tools.safe_eval import safe_eval as eval +from openerp.tools.translate import _ +from query import Query # List of etree._Element subclasses that we choose to ignore when parsing XML. from openerp.tools import SKIPPED_ELEMENT_TYPES @@ -585,22 +586,22 @@ class MetaModel(type): self.module_to_models.setdefault(self._module, []).append(self) -class Model(object): +class BaseModel(object): """ Base class for OpenERP models. - OpenERP models are created by inheriting from this class (or from - TransientModel) + OpenERP models are created by inheriting from this class' subclasses: - class user(Model): - ... + * Model: for regular database-persisted models + * TransientModel: for temporary data, stored in the database but automatically + vaccuumed every so often + * AbstractModel: for abstract super classes meant to be shared by multiple + _inheriting classes (usually Models or TransientModels) - The system will later instanciate the class once per database (on + The system will later instantiate the class once per database (on which the class' module is installed). - To create a class that should not be instanciated (because it is not a - model per se, but a class to be inherited to really create a model later), - the _register class attribute should be set to False. - + To create a class that should not be instantiated, the _register class attribute + may be set to False. """ __metaclass__ = MetaModel _register = False # Set to false if the model shouldn't be automatically discovered. @@ -847,6 +848,8 @@ class Model(object): as being part of the module where it is defined. """ + + # Set the module name (e.g. base, sale, accounting, ...) on the class. module = cls.__module__.split('.')[0] if not hasattr(cls, '_module'): @@ -4541,5 +4544,129 @@ class Model(object): """ return self._transient +class Model(BaseModel): + """Main super-class for regular database-persisted OpenERP models. + + OpenERP models are created by inheriting from this class:: + + class user(Model): + ... + + The system will later instantiate the class once per database (on + which the class' module is installed). + """ + _register = False # not visible in ORM registry, meant to be python-inherited only + +class TransientModel(BaseModel): + """Model super-class for transient records, meant to be temporarily + persisted, and regularly vaccuum-cleaned. + + A TransientModel has a simplified access rights management, + all users can create new records, and may only access the + records they created. The super-user has unrestricted access + to all TransientModel records. + """ + __metaclass__ = MetaModel + _register = False # not visible in ORM registry, meant to be python-inherited only + _transient = True + _max_count = None + _max_hours = None + _check_time = 20 + + def __init__(self, pool, cr): + super(TransientModel, self).__init__(pool, cr) + self.check_count = 0 + self._max_count = config.get('osv_memory_count_limit') + self._max_hours = config.get('osv_memory_age_limit') + cr.execute('delete from wkf_instance where res_type=%s', (self._name,)) + assert self._log_access, "TransientModels must have log_access turned on, "\ + "in order to implement their access rights policy" + + def _clean_transient_rows_older_than(self, cr, seconds): + if not self._log_access: + self.logger = logging.getLogger('orm').warning( + "Transient model without write_date: %s" % (self._name,)) + return + + cr.execute("SELECT id FROM " + self._table + " WHERE" + " COALESCE(write_date, create_date, now())::timestamp <" + " (now() - interval %s)", ("%s seconds" % seconds,)) + ids = [x[0] for x in cr.fetchall()] + self.unlink(cr, openerp.SUPERUSER, ids) + + def _clean_old_transient_rows(self, cr, count): + if not self._log_access: + self.logger = logging.getLogger('orm').warning( + "Transient model without write_date: %s" % (self._name,)) + return + + cr.execute( + "SELECT id, COALESCE(write_date, create_date, now())::timestamp" + " AS t FROM " + self._table + + " ORDER BY t LIMIT %s", (count,)) + ids = [x[0] for x in cr.fetchall()] + self.unlink(cr, openerp.SUPERUSER, ids) + + def vacuum(self, cr, uid, force=False): + """ Clean the TransientModel records. + + This unlinks old records from the transient model tables whenever the + "_max_count" or "_max_age" conditions (if any) are reached. + Actual cleaning will happen only once every "_check_time" calls. + This means this method can be called frequently called (e.g. whenever + a new record is created). + """ + self.check_count += 1 + if (not force) and (self.check_count % self._check_time): + self.check_count = 0 + return True + + # Age-based expiration + if self._max_hours: + self._clean_transient_rows_older_than(cr, self._max_hours * 60 * 60) + + # Count-based expiration + if self._max_count: + self._clean_old_transient_rows(cr, self._max_count) + + return True + + def check_access_rule(self, cr, uid, ids, operation, context=None): + # No access rules for transient models. + if uid != openerp.SUPERUSER: + cr.execute("SELECT distinct create_uid FROM " + self._table + " WHERE" + " id IN %s", (tuple(ids),)) + uids = [x[0] for x in cr.fetchall()] + if len(uids) != 1 or uids[0] != uid: + raise orm.except_orm(_('AccessError'), '%s access is ' + 'restricted to your own records for transient models ' + '(except for the super-user).' % operation.capitalize()) + + def create(self, cr, uid, vals, context=None): + self.vacuum(cr, uid) + return super(TransientModel, self).create(cr, uid, vals, context) + + def _search(self, cr, uid, domain, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None): + # Restrict acces to the current user, except for the super-user. + if self._log_access and uid != openerp.SUPERUSER: + domain = expression.AND(([('create_uid', '=', uid)], domain)) + + # TODO unclear: shoudl access_rights_uid be set to None (effectively ignoring it) or used instead of uid? + return super(TransientModel, self)._search(cr, uid, domain, offset, limit, order, context, count, access_rights_uid) + +class AbstractModel(BaseModel): + """Abstract Model super-class for creating an abstract class meant to be + inherited by regular models (Models or TransientModels) but not meant to + be usable on its own, or persisted. + + Technical note: we don't want to make AbstractModel the super-class of + Model or BaseModel because it would not make sense to put the main + definition of persistence methods such as create() in it, and still we + should be able to override them within an AbstractModel. + """ + _auto = False # don't create any database backend for AbstractModels + _register = False # not visible in ORM registry, meant to be python-inherited only + + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/osv/osv.py b/openerp/osv/osv.py index 84954dcea14..f827ff680fb 100644 --- a/openerp/osv/osv.py +++ b/openerp/osv/osv.py @@ -29,11 +29,9 @@ import openerp import openerp.netsvc as netsvc import openerp.pooler as pooler import openerp.sql_db as sql_db -import expression # must not be first local import! -from openerp.tools.config import config from openerp.tools.func import wraps from openerp.tools.translate import translate -from openerp.osv.orm import MetaModel, Model +from openerp.osv.orm import MetaModel, Model, TransientModel, AbstractModel class except_osv(Exception): def __init__(self, name, value, exc_type='warning'): @@ -201,106 +199,10 @@ class object_proxy(netsvc.Service): cr.close() return res - -class TransientModel(Model): - """ Model for transient records. - - A TransientModel works similarly to a regular Model but the assiociated - records will be cleaned automatically from the database after some time. - - A TransientModel has no access rules. - - """ - __metaclass__ = MetaModel - _register = False # Set to false if the model shouldn't be automatically discovered. - _transient = True - _max_count = None - _max_hours = None - _check_time = 20 - - def __init__(self, pool, cr): - super(TransientModel, self).__init__(pool, cr) - self.check_count = 0 - self._max_count = config.get('osv_memory_count_limit') - self._max_hours = config.get('osv_memory_age_limit') - cr.execute('delete from wkf_instance where res_type=%s', (self._name,)) - - def _clean_transient_rows_older_than(self, cr, seconds): - if not self._log_access: - self.logger = logging.getLogger('orm').warning( - "Transient model without write_date: %s" % (self._name,)) - return - - cr.execute("SELECT id FROM " + self._table + " WHERE" - " COALESCE(write_date, create_date, now())::timestamp <" - " (now() - interval %s)", ("%s seconds" % seconds,)) - ids = [x[0] for x in cr.fetchall()] - self.unlink(cr, openerp.SUPERUSER, ids) - - def _clean_old_transient_rows(self, cr, count): - if not self._log_access: - self.logger = logging.getLogger('orm').warning( - "Transient model without write_date: %s" % (self._name,)) - return - - cr.execute( - "SELECT id, COALESCE(write_date, create_date, now())::timestamp" - " AS t FROM " + self._table + - " ORDER BY t LIMIT %s", (count,)) - ids = [x[0] for x in cr.fetchall()] - self.unlink(cr, openerp.SUPERUSER, ids) - - def vacuum(self, cr, uid, force=False): - """ Clean the TransientModel records. - - This unlinks old records from the transient model tables whenever the - "_max_count" or "_max_age" conditions (if any) are reached. - Actual cleaning will happen only once every "_check_time" calls. - This means this method can be called frequently called (e.g. whenever - a new record is created). - """ - self.check_count += 1 - if (not force) and (self.check_count % self._check_time): - self.check_count = 0 - return True - - # Age-based expiration - if self._max_hours: - self._clean_transient_rows_older_than(cr, self._max_hours * 60 * 60) - - # Count-based expiration - if self._max_count: - self._clean_old_transient_rows(cr, self._max_count) - - return True - - def check_access_rule(self, cr, uid, ids, operation, context=None): - # No access rules for transient models. - if self._log_access and uid != openerp.SUPERUSER: - cr.execute("SELECT distinct create_uid FROM " + self._table + " WHERE" - " id IN %s", (tuple(ids),)) - uids = [x[0] for x in cr.fetchall()] - if len(uids) != 1 or uids[0] != uid: - raise orm.except_orm(_('AccessError'), '%s access is ' - 'restricted to your own records for transient models ' - '(except for the super-user).' % mode.capitalize()) - - def create(self, cr, uid, vals, context=None): - self.vacuum(cr, uid) - return super(TransientModel, self).create(cr, uid, vals, context) - - def _search(self, cr, uid, domain, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None): - # Restrict acces to the current user, except for the super-user. - if self._log_access and uid != openerp.SUPERUSER: - domain = expression.AND(([('create_uid', '=', uid)], domain)) - - # TODO unclear: shoudl access_rights_uid be set to None (effectively ignoring it) or used instead of uid? - return super(TransientModel, self)._search(cr, uid, domain, offset, limit, order, context, count, access_rights_uid) - - -# For backward compatibility. +# deprecated - for backward compatibility. osv = Model osv_memory = TransientModel +osv_abstract = AbstractModel # ;-) def start_object_proxy():