Repository: wagtail/wagtail

Issue:
Setting WAGTAILIMAGES_RENDITION_STORAGE generates a migration in wagtailimages
### Issue Summary

Running `./manage.py makemigrations` while WAGTAILIMAGES_RENDITION_STORAGE is set to something other than the default storage causes a migration to be generated within the wagtailimages app

### Steps to Reproduce

1. (for example) Start a new project with `wagtail start myproject`
2. Run `./manage.py migrate` and `./manage.py makemigrations`; this outputs "No changes detected"
3. `pip install django-storages`
4. Add the line `WAGTAILIMAGES_RENDITION_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"` to myproject/settings/base.py
5. Run `./manage.py makemigrations`; this generates a migration `wagtail/images/migrations/0026_alter_rendition_file.py` that adds a `storage` argument to the Rendition.file field.

-   I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: yes

### Technical details

-   Python version: 3.8.0
-   Django version: 4.1.3
-   Wagtail version: main (4.2a0, 4b770784ca68f22d5ea58ecbd01e5c8c13882a3d)



Discussion:
It looks like this is caused by a logic error in https://github.com/django/django/commit/2d42e23b6d8fd76f93a96b2310b2c9dfd441d009. Adding the callable `storage` kwarg to Rendition.file should have generated a migration in wagtailimages regardless of the WAGTAILIMAGES_RENDITION_STORAGE setting - which we would have then shipped with Wagtail. However, the `if self.storage is not default_storage:` check is being called at a point where `self.storage` refers to the result of calling `get_rendition_storage`, which means that if `WAGTAILIMAGES_RENDITION_STORAGE` is left at the default, then the addition of `storage` to the deconstructed kwargs is skipped.
Issue opened against Django: https://code.djangoproject.com/ticket/34192
PR now opened on Django: https://github.com/django/django/pull/16493. I don't think there's any workaround we can apply until this lands - when it does (which will presumably be 5.0 now) we'll start seeing our [missing migrations test](https://github.com/wagtail/wagtail/blob/main/wagtail/tests/test_migrations.py) fail as it tries to create a migration in wagtailimages to add the missing `storages` kwarg. At that point we'll presumably have to add a Django version check in the existing migrations, to include or exclude the kwarg according to whether that Django version thinks it should be there, and thus preventing it from seeing any spurious state changes.

Resolving patch:
diff --git a/wagtail/images/migrations/0025_alter_image_file_alter_rendition_file.py b/wagtail/images/migrations/0025_alter_image_file_alter_rendition_file.py
--- a/wagtail/images/migrations/0025_alter_image_file_alter_rendition_file.py
+++ b/wagtail/images/migrations/0025_alter_image_file_alter_rendition_file.py
@@ -1,5 +1,6 @@
 # Generated by Django 4.0.7 on 2022-08-10 16:26
 
+from django import VERSION as DJANGO_VERSION
 from django.db import migrations
 import wagtail.images.models
 
@@ -10,6 +11,19 @@ class Migration(migrations.Migration):
         ("wagtailimages", "0024_index_image_file_hash"),
     ]
 
+    rendition_file_options = {
+        "height_field": "height",
+        "upload_to": wagtail.images.models.get_rendition_upload_to,
+        "width_field": "width",
+    }
+    # See https://code.djangoproject.com/ticket/34192 - prior to Django 4.2, a callable storage
+    # argument that returns default_storage would be incorrectly omitted from the deconstructed
+    # field. We need to match that behaviour and include/omit it accordingly to prevent
+    # makemigrations from seeing a difference and generating a spurious migration in
+    # wagtail.images.
+    if DJANGO_VERSION >= (4, 2):
+        rendition_file_options["storage"] = wagtail.images.models.get_rendition_storage
+
     operations = [
         migrations.AlterField(
             model_name="image",
@@ -24,10 +38,6 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="rendition",
             name="file",
-            field=wagtail.images.models.WagtailImageField(
-                height_field="height",
-                upload_to=wagtail.images.models.get_rendition_upload_to,
-                width_field="width",
-            ),
+            field=wagtail.images.models.WagtailImageField(**rendition_file_options),
         ),
     ]


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -26,8 +26,8 @@ concurrency:
 # - django 4.1, python 3.10, mysql
 # - django 4.0, python 3.10, postgres, USE_EMAIL_USER_MODEL=yes
 # - django 4.1, python 3.11, postgres, DISABLE_TIMEZONE=yes
-# - django stable/4.1.x, python 3.10, postgres (allow failures)
-# - django stable/4.1.x, python 3.11, mysql (allow failures)
+# - django stable/4.2.x, python 3.10, postgres (allow failures)
+# - django stable/4.2.x, python 3.11, mysql (allow failures)
 # - django main, python 3.10, postgres (allow failures)
 # - elasticsearch 5, django 3.2, python 3.7, sqlite
 # - elasticsearch 6, django 3.2, python 3.7, postgres
@@ -86,7 +86,7 @@ jobs:
             experimental: false
             emailuser: emailuser
           - python: '3.10'
-            django: 'git+https://github.com/django/django.git@stable/4.1.x#egg=Django'
+            django: 'git+https://github.com/django/django.git@stable/4.2.x#egg=Django'
             experimental: true
             postgres: 'postgres:12'
           - python: '3.10'
@@ -145,7 +145,7 @@ jobs:
             django: 'Django>=4.1,<4.2'
             experimental: false
           - python: '3.11'
-            django: 'git+https://github.com/django/django.git@stable/4.1.x#egg=Django'
+            django: 'git+https://github.com/django/django.git@stable/4.2.x#egg=Django'
             experimental: true
 
     services:
diff --git a/wagtail/test/testapp/migrations/0010_alter_customimage_file_and_more.py b/wagtail/test/testapp/migrations/0010_alter_customimage_file_and_more.py
--- a/wagtail/test/testapp/migrations/0010_alter_customimage_file_and_more.py
+++ b/wagtail/test/testapp/migrations/0010_alter_customimage_file_and_more.py
@@ -1,5 +1,6 @@
 # Generated by Django 4.0.7 on 2022-10-19 00:20
 
+from django import VERSION as DJANGO_VERSION
 from django.db import migrations
 import wagtail.images.models
 
@@ -10,6 +11,19 @@ class Migration(migrations.Migration):
         ("tests", "0009_alter_eventpage_options"),
     ]
 
+    rendition_file_options = {
+        "height_field": "height",
+        "upload_to": wagtail.images.models.get_rendition_upload_to,
+        "width_field": "width",
+    }
+    # See https://code.djangoproject.com/ticket/34192 - prior to Django 4.2, a callable storage
+    # argument that returns default_storage would be incorrectly omitted from the deconstructed
+    # field. We need to match that behaviour and include/omit it accordingly to prevent
+    # makemigrations from seeing a difference and generating a spurious migration in
+    # wagtail.images.
+    if DJANGO_VERSION >= (4, 2):
+        rendition_file_options["storage"] = wagtail.images.models.get_rendition_storage
+
     operations = [
         migrations.AlterField(
             model_name="customimage",
@@ -44,20 +58,12 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="customrendition",
             name="file",
-            field=wagtail.images.models.WagtailImageField(
-                height_field="height",
-                upload_to=wagtail.images.models.get_rendition_upload_to,
-                width_field="width",
-            ),
+            field=wagtail.images.models.WagtailImageField(**rendition_file_options),
         ),
         migrations.AlterField(
             model_name="customrenditionwithauthor",
             name="file",
-            field=wagtail.images.models.WagtailImageField(
-                height_field="height",
-                upload_to=wagtail.images.models.get_rendition_upload_to,
-                width_field="width",
-            ),
+            field=wagtail.images.models.WagtailImageField(**rendition_file_options),
         ),
         migrations.AlterField(
             model_name="customrestaurantimage",