Skip to content

Commit e9b6583

Browse files
jmsmkncodingjoe
andauthored
Fix #228 -- Skip migration for objects without files (#230)
Co-authored-by: Johannes Maron <[email protected]>
1 parent 32289a5 commit e9b6583

File tree

2 files changed

+128
-3
lines changed

2 files changed

+128
-3
lines changed

pictures/migrations.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def update_pictures(self, from_field: PictureField, to_model: type[models.Model]
5656
new_field_file.update_all(old_field_file)
5757

5858
def from_picture_field(self, from_model: type[models.Model]):
59-
for obj in from_model._default_manager.all().iterator():
59+
for obj in from_model._default_manager.exclude(
60+
Q(**{self.name: ""}) | Q(**{self.name: None})
61+
).iterator():
6062
field_file = getattr(obj, self.name)
6163
field_file.delete_all()
6264

@@ -66,9 +68,13 @@ def to_picture_field(
6668
from_field = from_model._meta.get_field(self.name)
6769
if hasattr(from_field.attr_class, "delete_variations"):
6870
# remove obsolete django-stdimage variations
69-
for obj in from_model._default_manager.all().iterator():
71+
for obj in from_model._default_manager.exclude(
72+
Q(**{self.name: ""}) | Q(**{self.name: None})
73+
).iterator():
7074
field_file = getattr(obj, self.name)
7175
field_file.delete_variations()
72-
for obj in to_model._default_manager.all().iterator():
76+
for obj in to_model._default_manager.exclude(
77+
Q(**{self.name: ""}) | Q(**{self.name: None})
78+
).iterator():
7379
field_file = getattr(obj, self.name)
7480
field_file.save_all()

tests/test_migrations.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,54 @@ class Meta:
171171

172172
assert not luke.picture
173173

174+
@pytest.mark.django_db
175+
def test_update_pictures__with_empty_pictures(
176+
self, request, stub_worker, image_upload_file
177+
):
178+
"""Test that update_pictures skips objects with empty/null pictures."""
179+
180+
class ToModel(models.Model):
181+
name = models.CharField(max_length=100)
182+
picture = PictureField(
183+
upload_to="testapp/profile/", aspect_ratios=[None, "21/9"], blank=True
184+
)
185+
186+
class Meta:
187+
app_label = request.node.name
188+
db_table = "testapp_profile"
189+
190+
# Create profiles with different picture states
191+
luke = Profile.objects.create(name="Luke", picture=image_upload_file)
192+
leia = Profile.objects.create(name="Leia", picture="")
193+
han = Profile.objects.create(name="Han", picture=None)
194+
stub_worker.join()
195+
196+
path = luke.picture.aspect_ratios["16/9"]["AVIF"][100].path
197+
assert path.exists()
198+
199+
migration = migrations.AlterPictureField("profile", "picture", PictureField())
200+
from_field = Profile._meta.get_field("picture")
201+
202+
# This should not fail despite empty/null pictures
203+
migration.update_pictures(from_field, ToModel)
204+
stub_worker.join()
205+
206+
# Verify old path was deleted and new one was created for luke
207+
assert not path.exists()
208+
luke.refresh_from_db()
209+
path = (
210+
ToModel.objects.get(pk=luke.pk)
211+
.picture.aspect_ratios["21/9"]["AVIF"][100]
212+
.path
213+
)
214+
assert path.exists()
215+
216+
# Verify empty profiles still exist and remain empty
217+
leia_profile = Profile.objects.get(pk=leia.pk)
218+
assert not leia_profile.picture
219+
han_profile = Profile.objects.get(pk=han.pk)
220+
assert not han_profile.picture
221+
174222
@pytest.mark.django_db
175223
def test_from_picture_field(self, stub_worker, image_upload_file):
176224
luke = Profile.objects.create(name="Luke", picture=image_upload_file)
@@ -182,6 +230,30 @@ def test_from_picture_field(self, stub_worker, image_upload_file):
182230
stub_worker.join()
183231
assert not path.exists()
184232

233+
@pytest.mark.django_db
234+
def test_from_picture_field__with_empty_pictures(
235+
self, stub_worker, image_upload_file
236+
):
237+
"""Test that from_picture_field skips objects with empty/null pictures."""
238+
# Create profiles with different picture states
239+
luke = Profile.objects.create(name="Luke", picture=image_upload_file)
240+
Profile.objects.create(name="Leia", picture="")
241+
Profile.objects.create(name="Han", picture=None)
242+
stub_worker.join()
243+
244+
path = luke.picture.aspect_ratios["16/9"]["AVIF"][100].path
245+
assert path.exists()
246+
247+
migration = migrations.AlterPictureField("profile", "picture", PictureField())
248+
# This should not fail despite empty/null pictures
249+
migration.from_picture_field(Profile)
250+
stub_worker.join()
251+
252+
assert not path.exists()
253+
# Verify other profiles still exist and weren't affected
254+
assert Profile.objects.filter(name="Leia").exists()
255+
assert Profile.objects.filter(name="Han").exists()
256+
185257
@pytest.mark.django_db
186258
def test_to_picture_field(self, request, stub_worker, image_upload_file):
187259
class FromModel(models.Model):
@@ -234,6 +306,53 @@ class Meta:
234306
migration = migrations.AlterPictureField("profile", "picture", PictureField())
235307
migration.to_picture_field(FromModel, Profile)
236308

309+
@pytest.mark.django_db
310+
def test_to_picture_field__with_empty_pictures(
311+
self, request, stub_worker, image_upload_file
312+
):
313+
"""Test that to_picture_field skips objects with empty/null pictures."""
314+
315+
class FromModel(models.Model):
316+
picture = models.ImageField(blank=True)
317+
318+
class Meta:
319+
app_label = request.node.name
320+
db_table = "testapp_profile"
321+
322+
class ToModel(models.Model):
323+
name = models.CharField(max_length=100)
324+
picture = models.ImageField(upload_to="testapp/profile/", blank=True)
325+
326+
class Meta:
327+
app_label = request.node.name
328+
db_table = "testapp_profile"
329+
330+
# Create profiles with different picture states
331+
luke = ToModel.objects.create(name="Luke", picture=image_upload_file)
332+
leia = ToModel.objects.create(name="Leia", picture="")
333+
han = ToModel.objects.create(name="Han", picture=None)
334+
stub_worker.join()
335+
336+
migration = migrations.AlterPictureField("profile", "picture", PictureField())
337+
# This should not fail despite empty/null pictures
338+
migration.to_picture_field(FromModel, Profile)
339+
stub_worker.join()
340+
341+
luke.refresh_from_db()
342+
# Verify only luke's picture was processed
343+
path = (
344+
Profile.objects.get(pk=luke.pk)
345+
.picture.aspect_ratios["16/9"]["AVIF"][100]
346+
.path
347+
)
348+
assert path.exists()
349+
350+
# Verify empty profiles still exist
351+
leia_profile = Profile.objects.get(pk=leia.pk)
352+
assert not leia_profile.picture
353+
han_profile = Profile.objects.get(pk=han.pk)
354+
assert not han_profile.picture
355+
237356
@pytest.mark.django_db
238357
def test_to_picture_field__from_stdimage(
239358
self, request, stub_worker, image_upload_file

0 commit comments

Comments
 (0)