1
0
mirror of https://github.com/django/django.git synced 2025-06-05 11:39:13 +00:00

Fixed #21217 -- Avoid connecting (pre|post)_init signals to abstract senders.

This commit is contained in:
Simon Charette 2013-10-03 13:44:10 -04:00
parent dc3d2ac98c
commit 948d209ada
5 changed files with 71 additions and 26 deletions

View File

@ -14,7 +14,7 @@ from django.db.models.fields.related import ForeignObject, ForeignObjectRel
from django.db.models.related import PathInfo from django.db.models.related import PathInfo
from django.db.models.sql.where import Constraint from django.db.models.sql.where import Constraint
from django.forms import ModelForm, ALL_FIELDS from django.forms import ModelForm, ALL_FIELDS
from django.forms.models import (BaseModelFormSet, modelformset_factory, save_instance, from django.forms.models import (BaseModelFormSet, modelformset_factory,
modelform_defines_fields) modelform_defines_fields)
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -46,10 +46,10 @@ class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
self.cache_attr = "_%s_cache" % name self.cache_attr = "_%s_cache" % name
cls._meta.add_virtual_field(self) cls._meta.add_virtual_field(self)
# For some reason I don't totally understand, using weakrefs here doesn't work. # Only run pre-initialization field assignment on non-abstract models
signals.pre_init.connect(self.instance_pre_init, sender=cls, weak=False) if not cls._meta.abstract:
signals.pre_init.connect(self.instance_pre_init, sender=cls)
# Connect myself as the descriptor for this field
setattr(cls, name, self) setattr(cls, name, self)
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs): def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):

View File

@ -358,7 +358,9 @@ class ImageField(FileField):
# Attach update_dimension_fields so that dimension fields declared # Attach update_dimension_fields so that dimension fields declared
# after their corresponding image field don't stay cleared by # after their corresponding image field don't stay cleared by
# Model.__init__, see bug #11196. # Model.__init__, see bug #11196.
signals.post_init.connect(self.update_dimension_fields, sender=cls) # Only run post-initialization dimension update on non-abstract models
if not cls._meta.abstract:
signals.post_init.connect(self.update_dimension_fields, sender=cls)
def update_dimension_fields(self, instance, force=False, *args, **kwargs): def update_dimension_fields(self, instance, force=False, *args, **kwargs):
""" """

View File

@ -32,28 +32,35 @@ class TaggedItem(models.Model):
def __str__(self): def __str__(self):
return self.tag return self.tag
class ValuableTaggedItem(TaggedItem): class ValuableTaggedItem(TaggedItem):
value = models.PositiveIntegerField() value = models.PositiveIntegerField()
@python_2_unicode_compatible
class Comparison(models.Model): class AbstractComparison(models.Model):
"""
A model that tests having multiple GenericForeignKeys
"""
comparative = models.CharField(max_length=50) comparative = models.CharField(max_length=50)
content_type1 = models.ForeignKey(ContentType, related_name="comparative1_set") content_type1 = models.ForeignKey(ContentType, related_name="comparative1_set")
object_id1 = models.PositiveIntegerField() object_id1 = models.PositiveIntegerField()
content_type2 = models.ForeignKey(ContentType, related_name="comparative2_set") first_obj = generic.GenericForeignKey(ct_field="content_type1", fk_field="object_id1")
@python_2_unicode_compatible
class Comparison(AbstractComparison):
"""
A model that tests having multiple GenericForeignKeys. One is defined
through an inherited abstract model and one defined directly on this class.
"""
content_type2 = models.ForeignKey(ContentType, related_name="comparative2_set")
object_id2 = models.PositiveIntegerField() object_id2 = models.PositiveIntegerField()
first_obj = generic.GenericForeignKey(ct_field="content_type1", fk_field="object_id1")
other_obj = generic.GenericForeignKey(ct_field="content_type2", fk_field="object_id2") other_obj = generic.GenericForeignKey(ct_field="content_type2", fk_field="object_id2")
def __str__(self): def __str__(self):
return "%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj) return "%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj)
@python_2_unicode_compatible @python_2_unicode_compatible
class Animal(models.Model): class Animal(models.Model):
common_name = models.CharField(max_length=150) common_name = models.CharField(max_length=150)
@ -67,6 +74,7 @@ class Animal(models.Model):
def __str__(self): def __str__(self):
return self.common_name return self.common_name
@python_2_unicode_compatible @python_2_unicode_compatible
class Vegetable(models.Model): class Vegetable(models.Model):
name = models.CharField(max_length=150) name = models.CharField(max_length=150)
@ -77,6 +85,7 @@ class Vegetable(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
@python_2_unicode_compatible @python_2_unicode_compatible
class Mineral(models.Model): class Mineral(models.Model):
name = models.CharField(max_length=150) name = models.CharField(max_length=150)
@ -87,18 +96,22 @@ class Mineral(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
class GeckoManager(models.Manager): class GeckoManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super(GeckoManager, self).get_queryset().filter(has_tail=True) return super(GeckoManager, self).get_queryset().filter(has_tail=True)
class Gecko(models.Model): class Gecko(models.Model):
has_tail = models.BooleanField(default=False) has_tail = models.BooleanField(default=False)
objects = GeckoManager() objects = GeckoManager()
# To test fix for #11263 # To test fix for #11263
class Rock(Mineral): class Rock(Mineral):
tags = generic.GenericRelation(TaggedItem) tags = generic.GenericRelation(TaggedItem)
class ManualPK(models.Model): class ManualPK(models.Model):
id = models.IntegerField(primary_key=True) id = models.IntegerField(primary_key=True)
tags = generic.GenericRelation(TaggedItem) tags = generic.GenericRelation(TaggedItem)
@ -110,14 +123,17 @@ class ForProxyModelModel(models.Model):
obj = generic.GenericForeignKey(for_concrete_model=False) obj = generic.GenericForeignKey(for_concrete_model=False)
title = models.CharField(max_length=255, null=True) title = models.CharField(max_length=255, null=True)
class ForConcreteModelModel(models.Model): class ForConcreteModelModel(models.Model):
content_type = models.ForeignKey(ContentType) content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
obj = generic.GenericForeignKey() obj = generic.GenericForeignKey()
class ConcreteRelatedModel(models.Model): class ConcreteRelatedModel(models.Model):
bases = generic.GenericRelation(ForProxyModelModel, for_concrete_model=False) bases = generic.GenericRelation(ForProxyModelModel, for_concrete_model=False)
class ProxyRelatedModel(ConcreteRelatedModel): class ProxyRelatedModel(ConcreteRelatedModel):
class Meta: class Meta:
proxy = True proxy = True

View File

@ -18,57 +18,69 @@ class Foo(models.Model):
a = models.CharField(max_length=10) a = models.CharField(max_length=10)
d = models.DecimalField(max_digits=5, decimal_places=3) d = models.DecimalField(max_digits=5, decimal_places=3)
def get_foo(): def get_foo():
return Foo.objects.get(id=1) return Foo.objects.get(id=1)
class Bar(models.Model): class Bar(models.Model):
b = models.CharField(max_length=10) b = models.CharField(max_length=10)
a = models.ForeignKey(Foo, default=get_foo) a = models.ForeignKey(Foo, default=get_foo)
class Whiz(models.Model): class Whiz(models.Model):
CHOICES = ( CHOICES = (
('Group 1', ( ('Group 1', (
(1,'First'), (1, 'First'),
(2,'Second'), (2, 'Second'),
) )
), ),
('Group 2', ( ('Group 2', (
(3,'Third'), (3, 'Third'),
(4,'Fourth'), (4, 'Fourth'),
) )
), ),
(0,'Other'), (0, 'Other'),
) )
c = models.IntegerField(choices=CHOICES, null=True) c = models.IntegerField(choices=CHOICES, null=True)
class BigD(models.Model): class BigD(models.Model):
d = models.DecimalField(max_digits=38, decimal_places=30) d = models.DecimalField(max_digits=38, decimal_places=30)
class BigS(models.Model): class BigS(models.Model):
s = models.SlugField(max_length=255) s = models.SlugField(max_length=255)
class BigInt(models.Model): class BigInt(models.Model):
value = models.BigIntegerField() value = models.BigIntegerField()
null_value = models.BigIntegerField(null = True, blank = True) null_value = models.BigIntegerField(null=True, blank=True)
class Post(models.Model): class Post(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
body = models.TextField() body = models.TextField()
class NullBooleanModel(models.Model): class NullBooleanModel(models.Model):
nbfield = models.NullBooleanField() nbfield = models.NullBooleanField()
class BooleanModel(models.Model): class BooleanModel(models.Model):
bfield = models.BooleanField(default=None) bfield = models.BooleanField(default=None)
string = models.CharField(max_length=10, default='abc') string = models.CharField(max_length=10, default='abc')
class FksToBooleans(models.Model): class FksToBooleans(models.Model):
"""Model wih FKs to models with {Null,}BooleanField's, #15040""" """Model wih FKs to models with {Null,}BooleanField's, #15040"""
bf = models.ForeignKey(BooleanModel) bf = models.ForeignKey(BooleanModel)
nbf = models.ForeignKey(NullBooleanModel) nbf = models.ForeignKey(NullBooleanModel)
class RenamedField(models.Model): class RenamedField(models.Model):
modelname = models.IntegerField(name="fieldname", choices=((1,'One'),)) modelname = models.IntegerField(name="fieldname", choices=((1, 'One'),))
class VerboseNameField(models.Model): class VerboseNameField(models.Model):
id = models.AutoField("verbose pk", primary_key=True) id = models.AutoField("verbose pk", primary_key=True)
@ -99,11 +111,13 @@ class VerboseNameField(models.Model):
field21 = models.TimeField("verbose field21") field21 = models.TimeField("verbose field21")
field22 = models.URLField("verbose field22") field22 = models.URLField("verbose field22")
# This model isn't used in any test, just here to ensure it validates successfully. # This model isn't used in any test, just here to ensure it validates successfully.
# See ticket #16570. # See ticket #16570.
class DecimalLessThanOne(models.Model): class DecimalLessThanOne(models.Model):
d = models.DecimalField(max_digits=3, decimal_places=3) d = models.DecimalField(max_digits=3, decimal_places=3)
class DataModel(models.Model): class DataModel(models.Model):
short_data = models.BinaryField(max_length=10, default=b'\x08') short_data = models.BinaryField(max_length=10, default=b'\x08')
data = models.BinaryField() data = models.BinaryField()
@ -111,6 +125,7 @@ class DataModel(models.Model):
############################################################################### ###############################################################################
# FileField # FileField
class Document(models.Model): class Document(models.Model):
myfile = models.FileField(upload_to='unused') myfile = models.FileField(upload_to='unused')
@ -126,7 +141,8 @@ if Image:
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.was_opened = False self.was_opened = False
super(TestImageFieldFile, self).__init__(*args,**kwargs) super(TestImageFieldFile, self).__init__(*args, **kwargs)
def open(self): def open(self):
self.was_opened = True self.was_opened = True
super(TestImageFieldFile, self).open() super(TestImageFieldFile, self).open()
@ -146,15 +162,26 @@ if Image:
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
mugshot = TestImageField(storage=temp_storage, upload_to='tests') mugshot = TestImageField(storage=temp_storage, upload_to='tests')
class PersonWithHeight(models.Model): class AbsctractPersonWithHeight(models.Model):
""" """
Model that defines an ImageField with only one dimension field. Abstract model that defines an ImageField with only one dimension field
to make sure the dimension update is correctly run on concrete subclass
instance post-initialization.
""" """
name = models.CharField(max_length=50)
mugshot = TestImageField(storage=temp_storage, upload_to='tests', mugshot = TestImageField(storage=temp_storage, upload_to='tests',
height_field='mugshot_height') height_field='mugshot_height')
mugshot_height = models.PositiveSmallIntegerField() mugshot_height = models.PositiveSmallIntegerField()
class Meta:
abstract = True
class PersonWithHeight(AbsctractPersonWithHeight):
"""
Concrete model that subclass an abctract one with only on dimension
field.
"""
name = models.CharField(max_length=50)
class PersonWithHeightAndWidth(models.Model): class PersonWithHeightAndWidth(models.Model):
""" """
Model that defines height and width fields after the ImageField. Model that defines height and width fields after the ImageField.

View File

@ -134,7 +134,7 @@ class ImageFieldTests(ImageFieldTestMixin, TestCase):
p = self.PersonModel.objects.get(name="Joan") p = self.PersonModel.objects.get(name="Joan")
path = p.mugshot.path path = p.mugshot.path
shutil.move(path, path + '.moved') shutil.move(path, path + '.moved')
p2 = self.PersonModel.objects.get(name="Joan") self.PersonModel.objects.get(name="Joan")
def test_delete_when_missing(self): def test_delete_when_missing(self):
""" """
@ -412,7 +412,7 @@ class TwoImageFieldTests(ImageFieldTestMixin, TestCase):
# was opened. # was opened.
self.assertEqual(p.mugshot.was_opened, False) self.assertEqual(p.mugshot.was_opened, False)
self.assertEqual(p.headshot.was_opened, False) self.assertEqual(p.headshot.was_opened, False)
self.check_dimensions(p, 4, 8,'mugshot') self.check_dimensions(p, 4, 8, 'mugshot')
self.check_dimensions(p, 8, 4, 'headshot') self.check_dimensions(p, 8, 4, 'headshot')
# After checking dimensions on the image fields, the files will # After checking dimensions on the image fields, the files will
# have been opened. # have been opened.
@ -422,7 +422,7 @@ class TwoImageFieldTests(ImageFieldTestMixin, TestCase):
# check dimensions again, the file should not have opened. # check dimensions again, the file should not have opened.
p.mugshot.was_opened = False p.mugshot.was_opened = False
p.headshot.was_opened = False p.headshot.was_opened = False
self.check_dimensions(p, 4, 8,'mugshot') self.check_dimensions(p, 4, 8, 'mugshot')
self.check_dimensions(p, 8, 4, 'headshot') self.check_dimensions(p, 8, 4, 'headshot')
self.assertEqual(p.mugshot.was_opened, False) self.assertEqual(p.mugshot.was_opened, False)
self.assertEqual(p.headshot.was_opened, False) self.assertEqual(p.headshot.was_opened, False)