Commit 79d2ee3b authored by Jacob Kaplan-Moss's avatar Jacob Kaplan-Moss
Browse files

Fixed #8309: subclasses now inherit `GenericForeignKey` correctly. There's...

Fixed #8309: subclasses now inherit `GenericForeignKey` correctly. There's also now an internal API so that other "virtual fields" like GFK can be inherited as well. Thanks, msaelices.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@8855 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 98e1cc92
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -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)
+28 −6
Original line number Diff line number Diff line
@@ -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
+4 −0
Original line number Diff line number Diff line
@@ -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
+9 −0
Original line number Diff line number Diff line
@@ -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()
[<Comparison: tiger is stronger than None>]

# 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
<Mineral: Quartz>

# GenericInlineFormSet tests ##################################################