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) |