diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 4f55c3b626..7170924ebb 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -24,11 +24,10 @@ class GenericForeignKey(object): self.fk_field = fk_field def contribute_to_class(self, cls, name): - # Make sure the fields exist (these raise FieldDoesNotExist, - # which is a fine error to raise here) self.name = name self.model = cls self.cache_attr = "_%s_cache" % name + cls._meta.add_virtual_field(self) # For some reason I don't totally understand, using weakrefs here doesn't work. signals.pre_init.connect(self.instance_pre_init, sender=cls, weak=False) diff --git a/django/db/models/base.py b/django/db/models/base.py index 121626ca93..e23369bb67 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -87,6 +87,14 @@ class ModelBase(type): # Things without _meta aren't functional models, so they're # uninteresting parents. continue + + # All the fields of any type declared on this model + new_fields = new_class._meta.local_fields + \ + new_class._meta.many_to_many + \ + new_class._meta.virtual_fields + field_names = set([f.name for f in new_fields]) + + # Concrete classes... if not base._meta.abstract: if base in o2o_map: field = o2o_map[base] @@ -98,13 +106,17 @@ class ModelBase(type): auto_created=True, parent_link=True) new_class.add_to_class(attr_name, field) new_class._meta.parents[base] = field + + # .. and abstract ones. else: - # The abstract base class case. - names = set([f.name for f in new_class._meta.local_fields + new_class._meta.many_to_many]) - for field in base._meta.local_fields + base._meta.local_many_to_many: - if field.name in names: - raise FieldError('Local field %r in class %r clashes with field of similar name from abstract base class %r' - % (field.name, name, base.__name__)) + # Check for clashes between locally declared fields and those on the ABC. + parent_fields = base._meta.local_fields + base._meta.local_many_to_many + for field in parent_fields: + if field.name in field_names: + raise FieldError('Local field %r in class %r clashes '\ + 'with field of similar name from '\ + 'abstract base class %r' % \ + (field.name, name, base.__name__)) new_class.add_to_class(field.name, copy.deepcopy(field)) # Inherit managers from the abstract base classes. @@ -115,6 +127,16 @@ class ModelBase(type): if not val or val is manager: new_manager = manager._copy_to_model(new_class) new_class.add_to_class(mgr_name, new_manager) + + # Inherit virtual fields (like GenericForeignKey) from the parent class + for field in base._meta.virtual_fields: + if base._meta.abstract and field.name in field_names: + raise FieldError('Local field %r in class %r clashes '\ + 'with field of similar name from '\ + 'abstract base class %r' % \ + (field.name, name, base.__name__)) + new_class.add_to_class(field.name, copy.deepcopy(field)) + if abstract: # Abstract base models can't be instantiated and don't appear in # the list of models for an app. We do the final setup for them a diff --git a/django/db/models/options.py b/django/db/models/options.py index 6e14db2f94..6e0d6b5082 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -26,6 +26,7 @@ DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', class Options(object): def __init__(self, meta, app_label=None): self.local_fields, self.local_many_to_many = [], [] + self.virtual_fields = [] self.module_name, self.verbose_name = None, None self.verbose_name_plural = None self.db_table = '' @@ -155,6 +156,9 @@ class Options(object): if hasattr(self, '_name_map'): del self._name_map + def add_virtual_field(self, field): + self.virtual_fields.append(field) + def setup_pk(self, field): if not self.pk and field.primary_key: self.pk = field diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py index 2f36e268e0..db5ae47581 100644 --- a/tests/modeltests/generic_relations/models.py +++ b/tests/modeltests/generic_relations/models.py @@ -27,6 +27,9 @@ class TaggedItem(models.Model): def __unicode__(self): return self.tag +class ValuableTaggedItem(TaggedItem): + value = models.PositiveIntegerField() + class Comparison(models.Model): """ A model that tests having multiple GenericForeignKeys @@ -204,6 +207,12 @@ __test__ = {'API_TESTS':""" >>> Comparison.objects.all() [] +# GenericForeignKey should work with subclasses (see #8309) +>>> quartz = Mineral.objects.create(name="Quartz", hardness=7) +>>> valuedtag = ValuableTaggedItem(content_object=quartz, tag="shiny", value=10) +>>> valuedtag.save() +>>> valuedtag.content_object + # GenericInlineFormSet tests ##################################################