Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #8309: subclasses now inherit `GenericForeignKey` correctly. Th…

…ere'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
  • Loading branch information...
commit 79d2ee3b6d405674a2a49f0884312a8cdc82d809 1 parent 98e1cc9
Jacob Kaplan-Moss authored September 02, 2008
3  django/contrib/contenttypes/generic.py
@@ -24,11 +24,10 @@ def __init__(self, ct_field="content_type", fk_field="object_id"):
24 24
         self.fk_field = fk_field
25 25
 
26 26
     def contribute_to_class(self, cls, name):
27  
-        # Make sure the fields exist (these raise FieldDoesNotExist,
28  
-        # which is a fine error to raise here)
29 27
         self.name = name
30 28
         self.model = cls
31 29
         self.cache_attr = "_%s_cache" % name
  30
+        cls._meta.add_virtual_field(self)
32 31
 
33 32
         # For some reason I don't totally understand, using weakrefs here doesn't work.
34 33
         signals.pre_init.connect(self.instance_pre_init, sender=cls, weak=False)
34  django/db/models/base.py
@@ -87,6 +87,14 @@ def __new__(cls, name, bases, attrs):
87 87
                 # Things without _meta aren't functional models, so they're
88 88
                 # uninteresting parents.
89 89
                 continue
  90
+                
  91
+            # All the fields of any type declared on this model
  92
+            new_fields = new_class._meta.local_fields + \
  93
+                         new_class._meta.many_to_many + \
  94
+                         new_class._meta.virtual_fields
  95
+            field_names = set([f.name for f in new_fields])
  96
+                
  97
+            # Concrete classes...
90 98
             if not base._meta.abstract:
91 99
                 if base in o2o_map:
92 100
                     field = o2o_map[base]
@@ -98,13 +106,17 @@ def __new__(cls, name, bases, attrs):
98 106
                             auto_created=True, parent_link=True)
99 107
                     new_class.add_to_class(attr_name, field)
100 108
                 new_class._meta.parents[base] = field
  109
+            
  110
+            # .. and abstract ones.
101 111
             else:
102  
-                # The abstract base class case.
103  
-                names = set([f.name for f in new_class._meta.local_fields + new_class._meta.many_to_many])
104  
-                for field in base._meta.local_fields + base._meta.local_many_to_many:
105  
-                    if field.name in names:
106  
-                        raise FieldError('Local field %r in class %r clashes with field of similar name from abstract base class %r'
107  
-                                % (field.name, name, base.__name__))
  112
+                # Check for clashes between locally declared fields and those on the ABC.
  113
+                parent_fields = base._meta.local_fields + base._meta.local_many_to_many
  114
+                for field in parent_fields:
  115
+                    if field.name in field_names:
  116
+                        raise FieldError('Local field %r in class %r clashes '\
  117
+                                         'with field of similar name from '\
  118
+                                         'abstract base class %r' % \
  119
+                                            (field.name, name, base.__name__))
108 120
                     new_class.add_to_class(field.name, copy.deepcopy(field))
109 121
 
110 122
             # Inherit managers from the abstract base classes.
@@ -115,6 +127,16 @@ def __new__(cls, name, bases, attrs):
115 127
                 if not val or val is manager:
116 128
                     new_manager = manager._copy_to_model(new_class)
117 129
                     new_class.add_to_class(mgr_name, new_manager)
  130
+        
  131
+            # Inherit virtual fields (like GenericForeignKey) from the parent class
  132
+            for field in base._meta.virtual_fields:
  133
+                if base._meta.abstract and field.name in field_names:
  134
+                    raise FieldError('Local field %r in class %r clashes '\
  135
+                                     'with field of similar name from '\
  136
+                                     'abstract base class %r' % \
  137
+                                        (field.name, name, base.__name__))
  138
+                new_class.add_to_class(field.name, copy.deepcopy(field))
  139
+        
118 140
         if abstract:
119 141
             # Abstract base models can't be instantiated and don't appear in
120 142
             # the list of models for an app. We do the final setup for them a
4  django/db/models/options.py
@@ -26,6 +26,7 @@
26 26
 class Options(object):
27 27
     def __init__(self, meta, app_label=None):
28 28
         self.local_fields, self.local_many_to_many = [], []
  29
+        self.virtual_fields = []
29 30
         self.module_name, self.verbose_name = None, None
30 31
         self.verbose_name_plural = None
31 32
         self.db_table = ''
@@ -155,6 +156,9 @@ def add_field(self, field):
155 156
         if hasattr(self, '_name_map'):
156 157
             del self._name_map
157 158
 
  159
+    def add_virtual_field(self, field):
  160
+        self.virtual_fields.append(field)
  161
+
158 162
     def setup_pk(self, field):
159 163
         if not self.pk and field.primary_key:
160 164
             self.pk = field
9  tests/modeltests/generic_relations/models.py
@@ -27,6 +27,9 @@ class Meta:
27 27
     def __unicode__(self):
28 28
         return self.tag
29 29
 
  30
+class ValuableTaggedItem(TaggedItem):
  31
+    value = models.PositiveIntegerField()
  32
+
30 33
 class Comparison(models.Model):
31 34
     """
32 35
     A model that tests having multiple GenericForeignKeys
@@ -204,6 +207,12 @@ def __unicode__(self):
204 207
 >>> Comparison.objects.all()
205 208
 [<Comparison: tiger is stronger than None>]
206 209
 
  210
+# GenericForeignKey should work with subclasses (see #8309)
  211
+>>> quartz = Mineral.objects.create(name="Quartz", hardness=7)
  212
+>>> valuedtag = ValuableTaggedItem(content_object=quartz, tag="shiny", value=10)
  213
+>>> valuedtag.save()
  214
+>>> valuedtag.content_object
  215
+<Mineral: Quartz>
207 216
 
208 217
 # GenericInlineFormSet tests ##################################################
209 218
 

0 notes on commit 79d2ee3

Please sign in to comment.
Something went wrong with that request. Please try again.