From 640ef30381e7c5a2ef0d92b6810f2a401ac64d5e Mon Sep 17 00:00:00 2001
From: Simon Charette <charette.s@gmail.com>
Date: Thu, 3 Oct 2013 13:44:10 -0400
Subject: [PATCH] Fixed #21217 -- Avoid connecting `(pre|post)_init` signals to
abstract senders.
---
django/contrib/contenttypes/generic.py | 8 +++---
django/db/models/fields/files.py | 4 ++-
tests/generic_relations/models.py | 30 ++++++++++++++++-----
tests/model_fields/models.py | 49 ++++++++++++++++++++++++++--------
tests/model_fields/test_imagefield.py | 6 ++---
5 files changed, 71 insertions(+), 26 deletions(-)
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index b34847b..1fdb1f1 100644
|
a
|
b
|
from django.db.models.fields.related import ForeignObject, ForeignObjectRel
|
| 14 | 14 | from django.db.models.related import PathInfo |
| 15 | 15 | from django.db.models.sql.where import Constraint |
| 16 | 16 | from django.forms import ModelForm, ALL_FIELDS |
| 17 | | from django.forms.models import (BaseModelFormSet, modelformset_factory, save_instance, |
| | 17 | from django.forms.models import (BaseModelFormSet, modelformset_factory, |
| 18 | 18 | modelform_defines_fields) |
| 19 | 19 | from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets |
| 20 | 20 | from django.contrib.contenttypes.models import ContentType |
| … |
… |
class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
|
| 46 | 46 | self.cache_attr = "_%s_cache" % name |
| 47 | 47 | cls._meta.add_virtual_field(self) |
| 48 | 48 | |
| 49 | | # For some reason I don't totally understand, using weakrefs here doesn't work. |
| 50 | | signals.pre_init.connect(self.instance_pre_init, sender=cls, weak=False) |
| | 49 | # Only run pre-initialization field assignment on non-abstract models |
| | 50 | if not cls._meta.abstract: |
| | 51 | signals.pre_init.connect(self.instance_pre_init, sender=cls) |
| 51 | 52 | |
| 52 | | # Connect myself as the descriptor for this field |
| 53 | 53 | setattr(cls, name, self) |
| 54 | 54 | |
| 55 | 55 | def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs): |
diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py
index 61e3eeb..557ec6e 100644
|
a
|
b
|
class ImageField(FileField):
|
| 358 | 358 | # Attach update_dimension_fields so that dimension fields declared |
| 359 | 359 | # after their corresponding image field don't stay cleared by |
| 360 | 360 | # Model.__init__, see bug #11196. |
| 361 | | signals.post_init.connect(self.update_dimension_fields, sender=cls) |
| | 361 | # Only run post-initialization dimension update on non-abstract models |
| | 362 | if not cls._meta.abstract: |
| | 363 | signals.post_init.connect(self.update_dimension_fields, sender=cls) |
| 362 | 364 | |
| 363 | 365 | def update_dimension_fields(self, instance, force=False, *args, **kwargs): |
| 364 | 366 | """ |
diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py
index d819a53..79eb59f 100644
|
a
|
b
|
class TaggedItem(models.Model):
|
| 32 | 32 | def __str__(self): |
| 33 | 33 | return self.tag |
| 34 | 34 | |
| | 35 | |
| 35 | 36 | class ValuableTaggedItem(TaggedItem): |
| 36 | 37 | value = models.PositiveIntegerField() |
| 37 | 38 | |
| 38 | | @python_2_unicode_compatible |
| 39 | | class Comparison(models.Model): |
| 40 | | """ |
| 41 | | A model that tests having multiple GenericForeignKeys |
| 42 | | """ |
| | 39 | |
| | 40 | class AbstractComparison(models.Model): |
| 43 | 41 | comparative = models.CharField(max_length=50) |
| 44 | 42 | |
| 45 | 43 | content_type1 = models.ForeignKey(ContentType, related_name="comparative1_set") |
| 46 | 44 | object_id1 = models.PositiveIntegerField() |
| 47 | 45 | |
| 48 | | content_type2 = models.ForeignKey(ContentType, related_name="comparative2_set") |
| | 46 | first_obj = generic.GenericForeignKey(ct_field="content_type1", fk_field="object_id1") |
| | 47 | |
| | 48 | |
| | 49 | @python_2_unicode_compatible |
| | 50 | class Comparison(AbstractComparison): |
| | 51 | """ |
| | 52 | A model that tests having multiple GenericForeignKeys. One is defined |
| | 53 | through an inherited abstract model and one defined directly on this class. |
| | 54 | """ |
| | 55 | content_type2 = models.ForeignKey(ContentType, related_name="comparative2_set") |
| 49 | 56 | object_id2 = models.PositiveIntegerField() |
| 50 | 57 | |
| 51 | | first_obj = generic.GenericForeignKey(ct_field="content_type1", fk_field="object_id1") |
| 52 | 58 | other_obj = generic.GenericForeignKey(ct_field="content_type2", fk_field="object_id2") |
| 53 | 59 | |
| 54 | 60 | def __str__(self): |
| 55 | 61 | return "%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj) |
| 56 | 62 | |
| | 63 | |
| 57 | 64 | @python_2_unicode_compatible |
| 58 | 65 | class Animal(models.Model): |
| 59 | 66 | common_name = models.CharField(max_length=150) |
| … |
… |
class Animal(models.Model):
|
| 67 | 74 | def __str__(self): |
| 68 | 75 | return self.common_name |
| 69 | 76 | |
| | 77 | |
| 70 | 78 | @python_2_unicode_compatible |
| 71 | 79 | class Vegetable(models.Model): |
| 72 | 80 | name = models.CharField(max_length=150) |
| … |
… |
class Vegetable(models.Model):
|
| 77 | 85 | def __str__(self): |
| 78 | 86 | return self.name |
| 79 | 87 | |
| | 88 | |
| 80 | 89 | @python_2_unicode_compatible |
| 81 | 90 | class Mineral(models.Model): |
| 82 | 91 | name = models.CharField(max_length=150) |
| … |
… |
class Mineral(models.Model):
|
| 87 | 96 | def __str__(self): |
| 88 | 97 | return self.name |
| 89 | 98 | |
| | 99 | |
| 90 | 100 | class GeckoManager(models.Manager): |
| 91 | 101 | def get_queryset(self): |
| 92 | 102 | return super(GeckoManager, self).get_queryset().filter(has_tail=True) |
| 93 | 103 | |
| | 104 | |
| 94 | 105 | class Gecko(models.Model): |
| 95 | 106 | has_tail = models.BooleanField(default=False) |
| 96 | 107 | objects = GeckoManager() |
| 97 | 108 | |
| | 109 | |
| 98 | 110 | # To test fix for #11263 |
| 99 | 111 | class Rock(Mineral): |
| 100 | 112 | tags = generic.GenericRelation(TaggedItem) |
| 101 | 113 | |
| | 114 | |
| 102 | 115 | class ManualPK(models.Model): |
| 103 | 116 | id = models.IntegerField(primary_key=True) |
| 104 | 117 | tags = generic.GenericRelation(TaggedItem) |
| … |
… |
class ForProxyModelModel(models.Model):
|
| 110 | 123 | obj = generic.GenericForeignKey(for_concrete_model=False) |
| 111 | 124 | title = models.CharField(max_length=255, null=True) |
| 112 | 125 | |
| | 126 | |
| 113 | 127 | class ForConcreteModelModel(models.Model): |
| 114 | 128 | content_type = models.ForeignKey(ContentType) |
| 115 | 129 | object_id = models.PositiveIntegerField() |
| 116 | 130 | obj = generic.GenericForeignKey() |
| 117 | 131 | |
| | 132 | |
| 118 | 133 | class ConcreteRelatedModel(models.Model): |
| 119 | 134 | bases = generic.GenericRelation(ForProxyModelModel, for_concrete_model=False) |
| 120 | 135 | |
| | 136 | |
| 121 | 137 | class ProxyRelatedModel(ConcreteRelatedModel): |
| 122 | 138 | class Meta: |
| 123 | 139 | proxy = True |
diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py
index a85dfc4..89ea77b 100644
|
a
|
b
|
class Foo(models.Model):
|
| 18 | 18 | a = models.CharField(max_length=10) |
| 19 | 19 | d = models.DecimalField(max_digits=5, decimal_places=3) |
| 20 | 20 | |
| | 21 | |
| 21 | 22 | def get_foo(): |
| 22 | 23 | return Foo.objects.get(id=1) |
| 23 | 24 | |
| | 25 | |
| 24 | 26 | class Bar(models.Model): |
| 25 | 27 | b = models.CharField(max_length=10) |
| 26 | 28 | a = models.ForeignKey(Foo, default=get_foo) |
| 27 | 29 | |
| | 30 | |
| 28 | 31 | class Whiz(models.Model): |
| 29 | 32 | CHOICES = ( |
| 30 | 33 | ('Group 1', ( |
| 31 | | (1,'First'), |
| 32 | | (2,'Second'), |
| | 34 | (1, 'First'), |
| | 35 | (2, 'Second'), |
| 33 | 36 | ) |
| 34 | 37 | ), |
| 35 | 38 | ('Group 2', ( |
| 36 | | (3,'Third'), |
| 37 | | (4,'Fourth'), |
| | 39 | (3, 'Third'), |
| | 40 | (4, 'Fourth'), |
| 38 | 41 | ) |
| 39 | 42 | ), |
| 40 | | (0,'Other'), |
| | 43 | (0, 'Other'), |
| 41 | 44 | ) |
| 42 | 45 | c = models.IntegerField(choices=CHOICES, null=True) |
| 43 | 46 | |
| | 47 | |
| 44 | 48 | class BigD(models.Model): |
| 45 | 49 | d = models.DecimalField(max_digits=38, decimal_places=30) |
| 46 | 50 | |
| | 51 | |
| 47 | 52 | class BigS(models.Model): |
| 48 | 53 | s = models.SlugField(max_length=255) |
| 49 | 54 | |
| | 55 | |
| 50 | 56 | class BigInt(models.Model): |
| 51 | 57 | value = models.BigIntegerField() |
| 52 | | null_value = models.BigIntegerField(null = True, blank = True) |
| | 58 | null_value = models.BigIntegerField(null=True, blank=True) |
| | 59 | |
| 53 | 60 | |
| 54 | 61 | class Post(models.Model): |
| 55 | 62 | title = models.CharField(max_length=100) |
| 56 | 63 | body = models.TextField() |
| 57 | 64 | |
| | 65 | |
| 58 | 66 | class NullBooleanModel(models.Model): |
| 59 | 67 | nbfield = models.NullBooleanField() |
| 60 | 68 | |
| | 69 | |
| 61 | 70 | class BooleanModel(models.Model): |
| 62 | 71 | bfield = models.BooleanField(default=None) |
| 63 | 72 | string = models.CharField(max_length=10, default='abc') |
| 64 | 73 | |
| | 74 | |
| 65 | 75 | class FksToBooleans(models.Model): |
| 66 | 76 | """Model wih FKs to models with {Null,}BooleanField's, #15040""" |
| 67 | 77 | bf = models.ForeignKey(BooleanModel) |
| 68 | 78 | nbf = models.ForeignKey(NullBooleanModel) |
| 69 | 79 | |
| | 80 | |
| 70 | 81 | class RenamedField(models.Model): |
| 71 | | modelname = models.IntegerField(name="fieldname", choices=((1,'One'),)) |
| | 82 | modelname = models.IntegerField(name="fieldname", choices=((1, 'One'),)) |
| | 83 | |
| 72 | 84 | |
| 73 | 85 | class VerboseNameField(models.Model): |
| 74 | 86 | id = models.AutoField("verbose pk", primary_key=True) |
| … |
… |
class VerboseNameField(models.Model):
|
| 99 | 111 | field21 = models.TimeField("verbose field21") |
| 100 | 112 | field22 = models.URLField("verbose field22") |
| 101 | 113 | |
| | 114 | |
| 102 | 115 | # This model isn't used in any test, just here to ensure it validates successfully. |
| 103 | 116 | # See ticket #16570. |
| 104 | 117 | class DecimalLessThanOne(models.Model): |
| 105 | 118 | d = models.DecimalField(max_digits=3, decimal_places=3) |
| 106 | 119 | |
| | 120 | |
| 107 | 121 | class DataModel(models.Model): |
| 108 | 122 | short_data = models.BinaryField(max_length=10, default=b'\x08') |
| 109 | 123 | data = models.BinaryField() |
| … |
… |
class DataModel(models.Model):
|
| 111 | 125 | ############################################################################### |
| 112 | 126 | # FileField |
| 113 | 127 | |
| | 128 | |
| 114 | 129 | class Document(models.Model): |
| 115 | 130 | myfile = models.FileField(upload_to='unused') |
| 116 | 131 | |
| … |
… |
if Image:
|
| 126 | 141 | """ |
| 127 | 142 | def __init__(self, *args, **kwargs): |
| 128 | 143 | self.was_opened = False |
| 129 | | super(TestImageFieldFile, self).__init__(*args,**kwargs) |
| | 144 | super(TestImageFieldFile, self).__init__(*args, **kwargs) |
| | 145 | |
| 130 | 146 | def open(self): |
| 131 | 147 | self.was_opened = True |
| 132 | 148 | super(TestImageFieldFile, self).open() |
| … |
… |
if Image:
|
| 146 | 162 | name = models.CharField(max_length=50) |
| 147 | 163 | mugshot = TestImageField(storage=temp_storage, upload_to='tests') |
| 148 | 164 | |
| 149 | | class PersonWithHeight(models.Model): |
| | 165 | class AbsctractPersonWithHeight(models.Model): |
| 150 | 166 | """ |
| 151 | | Model that defines an ImageField with only one dimension field. |
| | 167 | Abstract model that defines an ImageField with only one dimension field |
| | 168 | to make sure the dimension update is correctly run on concrete subclass |
| | 169 | instance post-initialization. |
| 152 | 170 | """ |
| 153 | | name = models.CharField(max_length=50) |
| 154 | 171 | mugshot = TestImageField(storage=temp_storage, upload_to='tests', |
| 155 | 172 | height_field='mugshot_height') |
| 156 | 173 | mugshot_height = models.PositiveSmallIntegerField() |
| 157 | 174 | |
| | 175 | class Meta: |
| | 176 | abstract = True |
| | 177 | |
| | 178 | class PersonWithHeight(AbsctractPersonWithHeight): |
| | 179 | """ |
| | 180 | Concrete model that subclass an abctract one with only on dimension |
| | 181 | field. |
| | 182 | """ |
| | 183 | name = models.CharField(max_length=50) |
| | 184 | |
| 158 | 185 | class PersonWithHeightAndWidth(models.Model): |
| 159 | 186 | """ |
| 160 | 187 | Model that defines height and width fields after the ImageField. |
diff --git a/tests/model_fields/test_imagefield.py b/tests/model_fields/test_imagefield.py
index ce7d33e..ec41247 100644
|
a
|
b
|
class ImageFieldTests(ImageFieldTestMixin, TestCase):
|
| 134 | 134 | p = self.PersonModel.objects.get(name="Joan") |
| 135 | 135 | path = p.mugshot.path |
| 136 | 136 | shutil.move(path, path + '.moved') |
| 137 | | p2 = self.PersonModel.objects.get(name="Joan") |
| | 137 | self.PersonModel.objects.get(name="Joan") |
| 138 | 138 | |
| 139 | 139 | def test_delete_when_missing(self): |
| 140 | 140 | """ |
| … |
… |
class TwoImageFieldTests(ImageFieldTestMixin, TestCase):
|
| 412 | 412 | # was opened. |
| 413 | 413 | self.assertEqual(p.mugshot.was_opened, False) |
| 414 | 414 | self.assertEqual(p.headshot.was_opened, False) |
| 415 | | self.check_dimensions(p, 4, 8,'mugshot') |
| | 415 | self.check_dimensions(p, 4, 8, 'mugshot') |
| 416 | 416 | self.check_dimensions(p, 8, 4, 'headshot') |
| 417 | 417 | # After checking dimensions on the image fields, the files will |
| 418 | 418 | # have been opened. |
| … |
… |
class TwoImageFieldTests(ImageFieldTestMixin, TestCase):
|
| 422 | 422 | # check dimensions again, the file should not have opened. |
| 423 | 423 | p.mugshot.was_opened = False |
| 424 | 424 | p.headshot.was_opened = False |
| 425 | | self.check_dimensions(p, 4, 8,'mugshot') |
| | 425 | self.check_dimensions(p, 4, 8, 'mugshot') |
| 426 | 426 | self.check_dimensions(p, 8, 4, 'headshot') |
| 427 | 427 | self.assertEqual(p.mugshot.was_opened, False) |
| 428 | 428 | self.assertEqual(p.headshot.was_opened, False) |