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
This commit is contained in:
parent
98e1cc92f4
commit
79d2ee3b6d
|
@ -24,11 +24,10 @@ class GenericForeignKey(object):
|
||||||
self.fk_field = fk_field
|
self.fk_field = fk_field
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
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.name = name
|
||||||
self.model = cls
|
self.model = cls
|
||||||
self.cache_attr = "_%s_cache" % name
|
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.
|
# 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)
|
signals.pre_init.connect(self.instance_pre_init, sender=cls, weak=False)
|
||||||
|
|
|
@ -87,6 +87,14 @@ class ModelBase(type):
|
||||||
# Things without _meta aren't functional models, so they're
|
# Things without _meta aren't functional models, so they're
|
||||||
# uninteresting parents.
|
# uninteresting parents.
|
||||||
continue
|
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 not base._meta.abstract:
|
||||||
if base in o2o_map:
|
if base in o2o_map:
|
||||||
field = o2o_map[base]
|
field = o2o_map[base]
|
||||||
|
@ -98,13 +106,17 @@ class ModelBase(type):
|
||||||
auto_created=True, parent_link=True)
|
auto_created=True, parent_link=True)
|
||||||
new_class.add_to_class(attr_name, field)
|
new_class.add_to_class(attr_name, field)
|
||||||
new_class._meta.parents[base] = field
|
new_class._meta.parents[base] = field
|
||||||
|
|
||||||
|
# .. and abstract ones.
|
||||||
else:
|
else:
|
||||||
# The abstract base class case.
|
# Check for clashes between locally declared fields and those on the ABC.
|
||||||
names = set([f.name for f in new_class._meta.local_fields + new_class._meta.many_to_many])
|
parent_fields = base._meta.local_fields + base._meta.local_many_to_many
|
||||||
for field in base._meta.local_fields + base._meta.local_many_to_many:
|
for field in parent_fields:
|
||||||
if field.name in names:
|
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'
|
raise FieldError('Local field %r in class %r clashes '\
|
||||||
% (field.name, name, base.__name__))
|
'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))
|
new_class.add_to_class(field.name, copy.deepcopy(field))
|
||||||
|
|
||||||
# Inherit managers from the abstract base classes.
|
# Inherit managers from the abstract base classes.
|
||||||
|
@ -115,6 +127,16 @@ class ModelBase(type):
|
||||||
if not val or val is manager:
|
if not val or val is manager:
|
||||||
new_manager = manager._copy_to_model(new_class)
|
new_manager = manager._copy_to_model(new_class)
|
||||||
new_class.add_to_class(mgr_name, new_manager)
|
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:
|
if abstract:
|
||||||
# Abstract base models can't be instantiated and don't appear in
|
# 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
|
# the list of models for an app. We do the final setup for them a
|
||||||
|
|
|
@ -26,6 +26,7 @@ DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
|
||||||
class Options(object):
|
class Options(object):
|
||||||
def __init__(self, meta, app_label=None):
|
def __init__(self, meta, app_label=None):
|
||||||
self.local_fields, self.local_many_to_many = [], []
|
self.local_fields, self.local_many_to_many = [], []
|
||||||
|
self.virtual_fields = []
|
||||||
self.module_name, self.verbose_name = None, None
|
self.module_name, self.verbose_name = None, None
|
||||||
self.verbose_name_plural = None
|
self.verbose_name_plural = None
|
||||||
self.db_table = ''
|
self.db_table = ''
|
||||||
|
@ -155,6 +156,9 @@ class Options(object):
|
||||||
if hasattr(self, '_name_map'):
|
if hasattr(self, '_name_map'):
|
||||||
del self._name_map
|
del self._name_map
|
||||||
|
|
||||||
|
def add_virtual_field(self, field):
|
||||||
|
self.virtual_fields.append(field)
|
||||||
|
|
||||||
def setup_pk(self, field):
|
def setup_pk(self, field):
|
||||||
if not self.pk and field.primary_key:
|
if not self.pk and field.primary_key:
|
||||||
self.pk = field
|
self.pk = field
|
||||||
|
|
|
@ -27,6 +27,9 @@ class TaggedItem(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.tag
|
return self.tag
|
||||||
|
|
||||||
|
class ValuableTaggedItem(TaggedItem):
|
||||||
|
value = models.PositiveIntegerField()
|
||||||
|
|
||||||
class Comparison(models.Model):
|
class Comparison(models.Model):
|
||||||
"""
|
"""
|
||||||
A model that tests having multiple GenericForeignKeys
|
A model that tests having multiple GenericForeignKeys
|
||||||
|
@ -204,6 +207,12 @@ __test__ = {'API_TESTS':"""
|
||||||
>>> Comparison.objects.all()
|
>>> Comparison.objects.all()
|
||||||
[<Comparison: tiger is stronger than None>]
|
[<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 ##################################################
|
# GenericInlineFormSet tests ##################################################
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue