Fixed ContentType related field accessor clash (an error emitted by model validation),

by adding related_name to the ContentType ForeignKey. Thanks to  Andrew Ingram.
This happened if a polymorphc model used a ContentType ForeignKey.
Plus minor documentation updates.
This commit is contained in:
Bert Constantin
2010-01-29 14:53:53 +01:00
parent 2055d03688
commit c7ac78e08d
4 changed files with 61 additions and 61 deletions
+29 -19
View File
@@ -79,10 +79,17 @@ class PolymorphicQuerySet(QuerySet):
return self.filter(not_instance_of=args)
def _filter_or_exclude(self, negate, *args, **kwargs):
_translate_polymorphic_filter_specs_in_args(self.model, args)
additional_args = _translate_polymorphic_filter_specs_in_kwargs(self.model, kwargs)
""" we override this internal Django functon since it is used for all filtering """
_translate_polymorphic_filter_defnitions_in_args(self.model, args) # the Q objects
additional_args = _translate_polymorphic_filter_defnitions_in_kwargs(self.model, kwargs) # filter_field='data'
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs)
# these queryset functions are not yet supported
def defer(self, *args, **kwargs): raise NotImplementedError
def only(self, *args, **kwargs): raise NotImplementedError
def aggregate(self, *args, **kwargs): raise NotImplementedError
def annotate(self, *args, **kwargs): raise NotImplementedError
def _get_real_instances(self, base_result_objects):
"""
Polymorphic object loader
@@ -95,8 +102,9 @@ class PolymorphicQuerySet(QuerySet):
base class query. The class of all of them is self.model (our base model).
Some, many or all of these objects were not created and stored as
class self.model, but as a class derived from self.model. We want to fetch
these objects from the db so we can return them just as they were saved.
class self.model, but as a class derived from self.model. We want to re-fetch
these objects from the db as their original class so we can return them
just as they were created/saved.
We identify these objects by looking at o.polymorphic_ctype, which specifies
the real class of these objects (the class at the time they were saved).
@@ -145,7 +153,11 @@ class PolymorphicQuerySet(QuerySet):
def iterator(self):
"""
This function does the same as:
This function is used by Django for all object retrieval.
By overriding it, we modify the objects that ths queryset returns
when it is evaluated (or it's get method or other object-returning methods are called).
Here we do the same as:
base_result_objects=list(super(PolymorphicQuerySet, self).iterator())
real_results=self._get_get_real_instances(base_result_objects)
@@ -173,12 +185,6 @@ class PolymorphicQuerySet(QuerySet):
if reached_end: raise StopIteration
# these queryset functions are not yet supported
def defer(self, *args, **kwargs): raise NotImplementedError
def only(self, *args, **kwargs): raise NotImplementedError
def aggregate(self, *args, **kwargs): raise NotImplementedError
def annotate(self, *args, **kwargs): raise NotImplementedError
def __repr__(self):
result = [ repr(o) for o in self.all() ]
return '[ ' + ',\n '.join(result) + ' ]'
@@ -192,7 +198,7 @@ class PolymorphicQuerySet(QuerySet):
# functionality to filters and Q objects.
# Probably a more general queryset enhancement class could be made out them.
def _translate_polymorphic_filter_specs_in_kwargs(queryset_model, kwargs):
def _translate_polymorphic_filter_defnitions_in_kwargs(queryset_model, kwargs):
"""
Translate the keyword argument list for PolymorphicQuerySet.filter()
@@ -209,7 +215,7 @@ def _translate_polymorphic_filter_specs_in_kwargs(queryset_model, kwargs):
additional_args = []
for field_path, val in kwargs.items():
# normal filter expression => ignore
new_expr = _translate_polymorphic_filter_spec(queryset_model, field_path, val)
new_expr = _translate_polymorphic_filter_defnition(queryset_model, field_path, val)
if type(new_expr) == tuple:
# replace kwargs element
del(kwargs[field_path])
@@ -221,7 +227,7 @@ def _translate_polymorphic_filter_specs_in_kwargs(queryset_model, kwargs):
return additional_args
def _translate_polymorphic_filter_specs_in_args(queryset_model, args):
def _translate_polymorphic_filter_defnitions_in_args(queryset_model, args):
"""
Translate the non-keyword argument list for PolymorphicQuerySet.filter()
@@ -229,6 +235,8 @@ def _translate_polymorphic_filter_specs_in_args(queryset_model, args):
polymorphic functionality with their vanilla django equivalents.
We traverse the Q object tree for this (which is simple).
TODO: investigate: we modify the Q-objects ina args in-place. Is this OK?
Modifies: args list
"""
@@ -240,7 +248,7 @@ def _translate_polymorphic_filter_specs_in_args(queryset_model, args):
if type(child) == tuple:
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
key, val = child
new_expr = _translate_polymorphic_filter_spec(queryset_model, key, val)
new_expr = _translate_polymorphic_filter_defnition(queryset_model, key, val)
if new_expr:
node.children[i] = new_expr
else:
@@ -251,7 +259,7 @@ def _translate_polymorphic_filter_specs_in_args(queryset_model, args):
if isinstance(q, models.Q):
tree_node_correct_field_specs(q)
def _translate_polymorphic_filter_spec(queryset_model, field_path, field_val):
def _translate_polymorphic_filter_defnition(queryset_model, field_path, field_val):
"""
Translate a keyword argument (field_path=field_val), as used for
PolymorphicQuerySet.filter()-like functions (and Q objects).
@@ -531,7 +539,10 @@ class PolymorphicModel(models.Model):
class Meta:
abstract = True
polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False)
# TODO: %(class)s alone is not really enough, we also need to include app_label - patch for Django needed?
# see: django/db/models/fields/related.py/RelatedField
polymorphic_ctype = models.ForeignKey(ContentType,
null=True, editable=False, related_name='polymorphic_%(class)s_set')
# some applications want to know the name of the fields that are added to its models
polymorphic_internal_model_fields = [ 'polymorphic_ctype' ]
@@ -540,8 +551,7 @@ class PolymorphicModel(models.Model):
base_objects = models.Manager()
def pre_save_polymorphic(self):
"""
Normally not needed.
"""Normally not needed.
This function may be called manually in special use-cases. When the object
is saved for the first time, we store its real class in polymorphic_ctype.
When the object later is retrieved by PolymorphicQuerySet, it uses this
+7
View File
@@ -6,6 +6,7 @@ from django.test import TestCase
from django.db.models.query import QuerySet
from django.db.models import Q
from django.db import models
from django.contrib.contenttypes.models import ContentType
from models import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet, ShowFields, ShowFieldsAndTypes
@@ -88,6 +89,12 @@ class MgrInheritB(MgrInheritA):
class MgrInheritC(ShowFieldsAndTypes, MgrInheritB):
pass
# validation error: "polymorphic.relatednameclash: Accessor for field 'polymorphic_ctype' clashes
# with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram)
# fixed with related_name
class RelatedNameClash(PolymorphicModel):
ctype = models.ForeignKey(ContentType, null=True, editable=False)
class testclass(TestCase):
def test_diamond_inheritance(self):