[IMP] orm: introduce cleaner class hierarchy for models
We'll now have a BaseModel with 3 subclasses, AbstractModel, TransientModel and Model. Model is for regular models, TransientModel for automatically-vacuumed models, and AbstractModel for common superclasses meant to be inherited by other models only, and not directly used. bzr revid: odo@openerp.com-20110923124525-jfzk55dk3ban2ps2
This commit is contained in:
parent
7d4cdb69f2
commit
2e7a134228
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
Loading…
Reference in New Issue