From 690153c5b9651031850aa5f14a0b26e18ebe5712 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 11 Aug 2016 20:56:36 +0100 Subject: [PATCH] Add a filter_spec field on Rendition to track the filter spec string used This is an initial step towards retiring Filter as a model (#2881). We cannot go further than this in the present release, as we want to retain the ability for developers with custom image models to use the Django migration autodetector - this implies that all schema changes pertaining to a Wagtail point release must happen in one go (there's no possibility of applying a schema migration, then a data migration, then another schema migration). --- .../migrations/0014_add_filter_spec_field.py | 26 +++++++++++ .../migrations/0015_fill_filter_spec_field.py | 43 +++++++++++++++++++ wagtail/wagtailimages/models.py | 9 +++- wagtail/wagtailimages/tests/test_models.py | 6 +++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 wagtail/wagtailimages/migrations/0014_add_filter_spec_field.py create mode 100644 wagtail/wagtailimages/migrations/0015_fill_filter_spec_field.py diff --git a/wagtail/wagtailimages/migrations/0014_add_filter_spec_field.py b/wagtail/wagtailimages/migrations/0014_add_filter_spec_field.py new file mode 100644 index 000000000..d587f18e9 --- /dev/null +++ b/wagtail/wagtailimages/migrations/0014_add_filter_spec_field.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-08-11 12:03 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailimages', '0013_make_rendition_upload_callable'), + ] + + operations = [ + migrations.AddField( + model_name='rendition', + name='filter_spec', + field=models.CharField(blank=True, db_index=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='rendition', + name='filter', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailimages.Filter'), + ), + ] diff --git a/wagtail/wagtailimages/migrations/0015_fill_filter_spec_field.py b/wagtail/wagtailimages/migrations/0015_fill_filter_spec_field.py new file mode 100644 index 000000000..39755aeb3 --- /dev/null +++ b/wagtail/wagtailimages/migrations/0015_fill_filter_spec_field.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-08-11 13:25 +from __future__ import unicode_literals + +from django.db import migrations + + +def fill_filter_spec_forward(apps, schema_editor): + # Populate Rendition.filter_spec with the spec string of the corresponding Filter object + Rendition = apps.get_model('wagtailimages', 'Rendition') + db_alias = schema_editor.connection.alias + for rendition in Rendition.objects.using(db_alias).select_related('filter'): + rendition.filter_spec = rendition.filter.spec + rendition.save() + + +def fill_filter_spec_reverse(apps, schema_editor): + # Populate the Rendition.filter + Rendition = apps.get_model('wagtailimages', 'Rendition') + Filter = apps.get_model('wagtailimages', 'Filter') + db_alias = schema_editor.connection.alias + + filters_by_spec = {} + for rendition in Rendition.objects.using(db_alias): + try: + filter = filters_by_spec[rendition.filter_spec] + except KeyError: + filter, _ = Filter.objects.get_or_create(spec=rendition.filter_spec) + filters_by_spec[rendition.filter_spec] = filter + + rendition.filter = filter + rendition.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailimages', '0014_add_filter_spec_field'), + ] + + operations = [ + migrations.RunPython(fill_filter_spec_forward, fill_filter_spec_reverse), + ] diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index a4d59d09c..f2a13bd4b 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -458,7 +458,8 @@ class Filter(models.Model): class AbstractRendition(models.Model): - filter = models.ForeignKey(Filter, related_name='+') + filter = models.ForeignKey(Filter, related_name='+', null=True, blank=True) + filter_spec = models.CharField(max_length=255, db_index=True, null=True, blank=True) file = models.ImageField(upload_to=get_rendition_upload_to, width_field='width', height_field='height') width = models.IntegerField(editable=False) height = models.IntegerField(editable=False) @@ -505,6 +506,12 @@ class AbstractRendition(models.Model): filename = self.file.field.storage.get_valid_name(filename) return os.path.join(folder_name, filename) + def save(self, *args, **kwargs): + # populate the `filter_spec` field with the spec string of the filter. In Wagtail 1.8 + # Filter will be dropped as a model, and lookups will be done based on this string instead + self.filter_spec = self.filter.spec + return super(AbstractRendition, self).save(*args, **kwargs) + class Meta: abstract = True diff --git a/wagtail/wagtailimages/tests/test_models.py b/wagtail/wagtailimages/tests/test_models.py index 554a9282d..1d1fe98e9 100644 --- a/wagtail/wagtailimages/tests/test_models.py +++ b/wagtail/wagtailimages/tests/test_models.py @@ -207,6 +207,12 @@ class TestRenditions(TestCase): self.assertEqual(rendition.width, 400) self.assertEqual(rendition.height, 300) + # check that the rendition has been recorded under the correct filter, + # via both the Rendition.filter_spec attribute (which will come into action + # in Wagtail 1.8) and the linked Filter model (which will be retired in 1.8) + self.assertEqual(rendition.filter_spec, 'width-400') + self.assertEqual(rendition.filter.spec, 'width-400') + def test_resize_to_max(self): rendition = self.image.get_rendition('max-100x100')