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 mcmd.py
dbconfig_local.py dbconfig_local.py
diffmanagement diffmanagement
scrapbook.py
pip-log.txt pip-log.txt
build build

View File

@ -23,19 +23,18 @@ class Command(NoArgsCommand):
print 'polycmd - sqlite test db is stored in:',settings.SQLITE_DB_PATH print 'polycmd - sqlite test db is stored in:',settings.SQLITE_DB_PATH
print 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() Project.objects.all().delete()
o=Project.objects.create(topic="John's gathering") a=Project.objects.create(topic="John's gathering")
o=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner") b=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
o=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") c=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
print Project.objects.all() print Project.objects.all()
print 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): class ModelC(ModelB):
field3 = models.CharField(max_length=10) 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 # for Django 1.2+, test models with same names in different apps
# (the other models with identical names are in polymorphic/tests.py) # (the other models with identical names are in polymorphic/tests.py)

View File

@ -73,6 +73,9 @@ class PolymorphicModelBase(ModelBase):
# validate resulting default manager # validate resulting default manager
self.validate_model_manager(new_class._default_manager, model_name, '_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 return new_class
def get_inherited_managers(self, attrs): def get_inherited_managers(self, attrs):
@ -153,6 +156,7 @@ class PolymorphicModelBase(ModelBase):
raise AssertionError(e) raise AssertionError(e)
return manager return manager
# hack: a small patch to Django would be a better solution. # hack: a small patch to Django would be a better solution.
# Django's management command 'dumpdata' relies on non-polymorphic # Django's management command 'dumpdata' relies on non-polymorphic
# behaviour of the _default_manager. Therefore, we catch any access to _default_manager # 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) return super(PolymorphicModelBase, self).__getattribute__(name)

View File

@ -114,48 +114,74 @@ class PolymorphicModel(models.Model):
if real_model == self.__class__: return self if real_model == self.__class__: return self
return real_model.objects.get(pk=self.pk) 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 def __init__(self, * args, ** kwargs):
# (do this only once and store the result into self.__class__.inheritance_relation_fields_dict) """Replace Django's inheritance accessors member functions for our model
if not self.__class__.__dict__.get('inheritance_relation_fields_dict', None): (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 we have inheritance of the form ModelA -> ModelB ->ModelC then
if ( issubclass(model, models.Model) and model != models.Model Django creates accessors like this:
and model != self.__class__ and model != PolymorphicModel): - 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() name = model.__name__.lower()
if as_ptr: name+='_ptr' if as_ptr: name+='_ptr'
result[name] = model result[name] = model
def add_all_base_models(model, result): def add_model_if_regular(model, as_ptr, result):
add_if_regular_sub_or_super_class(model, True, result) if ( issubclass(model, models.Model) and model != models.Model
for b in model.__bases__: and model != self.__class__
add_all_base_models(b, result) 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__(): for b in model.__subclasses__():
add_if_regular_sub_or_super_class(b, False, result) add_model_if_regular(b, False, result)
result = {} result = {}
add_all_base_models(self.__class__,result) add_all_super_models(self.__class__,result)
add_sub_models(self.__class__,result) add_all_sub_models(self.__class__,result)
#print '##',self.__class__.__name__,' - ',result return result
self.__class__.inheritance_relation_fields_dict = 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 >>> settings.DEBUG=True
>>> get_version() #>>> get_version()
'1.0 rc1' #'1.0 rc1'
### simple inheritance ### simple inheritance