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
parent
a87481b8b5
commit
6628145af7
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
name = model.__name__.lower()
|
- ModelB: modela_ptr, modelb, modelc
|
||||||
if as_ptr: name+='_ptr'
|
- ModelC: modela_ptr, modelb, modelb_ptr, modelc
|
||||||
result[name] = model
|
|
||||||
|
|
||||||
def add_all_base_models(model, result):
|
These accessors allow Django (and everyone else) to travel up and down
|
||||||
add_if_regular_sub_or_super_class(model, True, result)
|
the inheritance tree for the db object at hand.
|
||||||
for b in model.__bases__:
|
|
||||||
add_all_base_models(b, result)
|
|
||||||
|
|
||||||
def add_sub_models(model, result):
|
The original Django accessors use our polymorphic manager.
|
||||||
for b in model.__subclasses__():
|
But they should not. So we replace them with our own accessors that use
|
||||||
add_if_regular_sub_or_super_class(b, False, result)
|
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
|
||||||
|
|
||||||
result = {}
|
def create_accessor_function_for_model(model):
|
||||||
add_all_base_models(self.__class__,result)
|
def accessor_function(self):
|
||||||
add_sub_models(self.__class__,result)
|
attr = model.base_objects.get(pk=self.pk)
|
||||||
#print '##',self.__class__.__name__,' - ',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 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_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_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_model_if_regular(b, False, result)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
add_all_super_models(self.__class__,result)
|
||||||
|
add_all_sub_models(self.__class__,result)
|
||||||
|
return result
|
||||||
|
|
||||||
return super(PolymorphicModel, self).__getattribute__(name)
|
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue