removed __getattribute__ hack from PolymorphicModel.

A somewhat cleaner solution is now used (through __init__) which
also completely removes the performance impact of __getattribute__.
fix_request_path_info
Bert Constantin 2010-10-18 12:06:50 +02:00
parent a87481b8b5
commit 6628145af7
6 changed files with 88 additions and 53 deletions

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ pbackup
mcmd.py
dbconfig_local.py
diffmanagement
scrapbook.py
pip-log.txt
build

View File

@ -23,19 +23,18 @@ class Command(NoArgsCommand):
print 'polycmd - sqlite test db is stored in:',settings.SQLITE_DB_PATH
print
"""
ModelA.objects.all().delete()
o=ModelA.objects.create(field1='A1')
o=ModelB.objects.create(field1='B1', field2='B2')
o=ModelC.objects.create(field1='C1', field2='C2', field3='C3')
print ModelA.objects.all()
print
"""
Project.objects.all().delete()
o=Project.objects.create(topic="John's gathering")
o=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
o=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
a=Project.objects.create(topic="John's gathering")
b=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
c=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
print Project.objects.all()
print
"""
ModelA.objects.all().delete()
a=ModelA.objects.create(field1='A1')
b=ModelB.objects.create(field1='B1', field2='B2')
c=ModelC.objects.create(field1='C1', field2='C2', field3='C3')
print ModelA.objects.all()
print
"""

View File

@ -20,6 +20,13 @@ class ModelB(ModelA):
class ModelC(ModelB):
field3 = models.CharField(max_length=10)
class nModelA(models.Model):
field1 = models.CharField(max_length=10)
class nModelB(nModelA):
field2 = models.CharField(max_length=10)
class nModelC(nModelB):
field3 = models.CharField(max_length=10)
# for Django 1.2+, test models with same names in different apps
# (the other models with identical names are in polymorphic/tests.py)

View File

@ -73,6 +73,9 @@ class PolymorphicModelBase(ModelBase):
# validate resulting default manager
self.validate_model_manager(new_class._default_manager, model_name, '_default_manager')
# for __init__ function of this class (monkeypatching inheritance accessors)
new_class.polymorphic_super_sub_accessors_replaced = False
return new_class
def get_inherited_managers(self, attrs):
@ -153,6 +156,7 @@ class PolymorphicModelBase(ModelBase):
raise AssertionError(e)
return manager
# hack: a small patch to Django would be a better solution.
# Django's management command 'dumpdata' relies on non-polymorphic
# behaviour of the _default_manager. Therefore, we catch any access to _default_manager
@ -174,5 +178,3 @@ class PolymorphicModelBase(ModelBase):
return super(PolymorphicModelBase, self).__getattribute__(name)

View File

@ -114,48 +114,74 @@ class PolymorphicModel(models.Model):
if real_model == self.__class__: return self
return real_model.objects.get(pk=self.pk)
# hack: a small patch to Django would be a better solution.
# For base model back reference fields (like basemodel_ptr),
# Django definitely must =not= use our polymorphic manager/queryset.
# For now, we catch objects attribute access here and handle back reference fields manually.
# This problem is triggered by delete(), like here:
# django.db.models.base._collect_sub_objects: parent_obj = getattr(self, link.name)
# TODO: investigate Django how this can be avoided
def __getattribute__(self, name):
if not name.startswith('__'): # do not intercept __class__ etc.
# for efficiency: create a dict containing all model attribute names we need to intercept
# (do this only once and store the result into self.__class__.inheritance_relation_fields_dict)
if not self.__class__.__dict__.get('inheritance_relation_fields_dict', None):
def __init__(self, * args, ** kwargs):
"""Replace Django's inheritance accessors member functions for our model
(self.__class__) with our own versions.
We monkey patch them until a patch can be added to Django
(which would probably be very small and make all of this obsolete).
def add_if_regular_sub_or_super_class(model, as_ptr, result):
if ( issubclass(model, models.Model) and model != models.Model
and model != self.__class__ and model != PolymorphicModel):
If we have inheritance of the form ModelA -> ModelB ->ModelC then
Django creates accessors like this:
- ModelA: modelb
- ModelB: modela_ptr, modelb, modelc
- ModelC: modela_ptr, modelb, modelb_ptr, modelc
These accessors allow Django (and everyone else) to travel up and down
the inheritance tree for the db object at hand.
The original Django accessors use our polymorphic manager.
But they should not. So we replace them with our own accessors that use
our appropriate base_objects manager.
"""
super(PolymorphicModel, self).__init__(*args, ** kwargs)
if self.__class__.polymorphic_super_sub_accessors_replaced: return
self.__class__.polymorphic_super_sub_accessors_replaced = True
def create_accessor_function_for_model(model):
def accessor_function(self):
attr = model.base_objects.get(pk=self.pk)
return attr
return accessor_function
subclasses_and_superclasses_accessors = self.get_inheritance_relation_fields_and_models()
#print '###',self.__class__.__name__,subclasses_and_superclasses_accessors
from django.db.models.fields.related import SingleRelatedObjectDescriptor, ReverseSingleRelatedObjectDescriptor
for name,model in subclasses_and_superclasses_accessors.iteritems():
orig_accessor = getattr(self.__class__, name, None)
if type(orig_accessor) in [SingleRelatedObjectDescriptor,ReverseSingleRelatedObjectDescriptor]:
#print 'replacing',name, orig_accessor
setattr(self.__class__, name, property(create_accessor_function_for_model(model)) )
def get_inheritance_relation_fields_and_models(self):
"""helper function for __init__:
determine names of all Django inheritance accessor member functions for type(self)"""
def add_model(model, as_ptr, result):
name = model.__name__.lower()
if as_ptr: name+='_ptr'
result[name] = model
def add_all_base_models(model, result):
add_if_regular_sub_or_super_class(model, True, result)
for b in model.__bases__:
add_all_base_models(b, result)
def add_model_if_regular(model, as_ptr, result):
if ( issubclass(model, models.Model) and model != models.Model
and model != self.__class__
and model != PolymorphicModel ):
add_model(model,as_ptr,result)
def add_sub_models(model, result):
def add_all_super_models(model, result):
add_model_if_regular(model, True, result)
for b in model.__bases__:
add_all_super_models(b, result)
def add_all_sub_models(model, result):
for b in model.__subclasses__():
add_if_regular_sub_or_super_class(b, False, result)
add_model_if_regular(b, False, result)
result = {}
add_all_base_models(self.__class__,result)
add_sub_models(self.__class__,result)
#print '##',self.__class__.__name__,' - ',result
self.__class__.inheritance_relation_fields_dict = result
add_all_super_models(self.__class__,result)
add_all_sub_models(self.__class__,result)
return result
model = self.__class__.inheritance_relation_fields_dict.get(name, None)
if model:
id = super(PolymorphicModel, self).__getattribute__('id')
attr = model.base_objects.get(id=id)
#print '---',self.__class__.__name__,name
return attr
return super(PolymorphicModel, self).__getattribute__(name)

View File

@ -249,8 +249,8 @@ __test__ = {"doctest": """
>>> settings.DEBUG=True
>>> get_version()
'1.0 rc1'
#>>> get_version()
#'1.0 rc1'
### simple inheritance