diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3378b1a..090a634 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,8 +29,8 @@ jobs: strategy: matrix: python-version: ["3.13"] - django: ["5.1"] - wagtail: ["6.3"] + django: ["5.2"] + wagtail: ["7.1"] db: ["postgres"] services: @@ -77,7 +77,7 @@ jobs: matrix: python-version: ["3.11", "3.12"] django: ["4.2"] - wagtail: ["5.2", "6.1", "6.2"] + wagtail: ["6.3", "7.0"] db: ["sqlite"] steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 11b694b..3b96c26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,30 +18,31 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Framework :: Django", "Framework :: Django :: 4.2", - "Framework :: Django :: 5.0", "Framework :: Django :: 5.1", + "Framework :: Django :: 5.2", "Framework :: Wagtail", - "Framework :: Wagtail :: 5", "Framework :: Wagtail :: 6", + "Framework :: Wagtail :: 7", ] dynamic = ["version"] requires-python = ">=3.11" dependencies = [ "Django>=4.2", - "Wagtail>=5.2", - "bynder-sdk>=1.1.5,<2.0" + "Wagtail>=6.3", + "bynder-sdk>=2.0.2,<3.0", ] [project.optional-dependencies] testing = [ - "dj-database-url>=2.1.0,<3.0", - "wagtail_factories>=4.1.0,<5.0", - "responses>=0.24,<1", - "coverage>=7.0,<8.0", - "freezegun>=1.1,<2", + "dj-database-url>=2.1.0,<3.1", + "wagtail-factories>=4.3.0,<5.0", + "responses>=0.25,<1", + "coverage>=7.10,<8.0", + "freezegun>=1.5,<2", ] [project.urls] diff --git a/src/wagtail_bynder/views/document.py b/src/wagtail_bynder/views/document.py index 6697252..27e7e8f 100644 --- a/src/wagtail_bynder/views/document.py +++ b/src/wagtail_bynder/views/document.py @@ -1,17 +1,9 @@ from typing import TYPE_CHECKING from django.conf import settings -from django.views.generic import UpdateView -from wagtail import VERSION as WAGTAIL_VERSION from wagtail.documents import get_document_model from wagtail.documents.views import chooser as chooser_views - - -if WAGTAIL_VERSION < (6, 3): - from wagtail.documents.views.documents import DeleteView - from wagtail.documents.views.documents import edit as document_edit -else: - from wagtail.documents.views.documents import DeleteView, EditView +from wagtail.documents.views.documents import DeleteView, EditView from .mixins import BynderAssetCopyMixin, RedirectToBynderMixin @@ -20,30 +12,8 @@ from django.http import HttpRequest, JsonResponse -if WAGTAIL_VERSION < (6, 3): - - class ClassBasedDocumentEditView(UpdateView): - """ - A class-based view that mimics the behaviour of wagtail's function-based - document edit view, and can be extended with view mixins. - """ - - # TODO: Use class from Wagtail once the documents app views are refactored - model = get_document_model() - pk_url_kwarg = "document_id" - - def get(self, request, *args, **kwargs): - return document_edit(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - return document_edit(request, *args, **kwargs) - - class DocumentEditView(RedirectToBynderMixin, ClassBasedDocumentEditView): - pass -else: - - class DocumentEditView(RedirectToBynderMixin, EditView): - pass +class DocumentEditView(RedirectToBynderMixin, EditView): + pass class DocumentDeleteView(RedirectToBynderMixin, DeleteView): diff --git a/src/wagtail_bynder/views/image.py b/src/wagtail_bynder/views/image.py index f07e346..4006cca 100644 --- a/src/wagtail_bynder/views/image.py +++ b/src/wagtail_bynder/views/image.py @@ -1,17 +1,9 @@ from typing import TYPE_CHECKING from django.conf import settings -from django.views.generic import UpdateView -from wagtail import VERSION as WAGTAIL_VERSION from wagtail.images import get_image_model from wagtail.images.views import chooser as chooser_views - - -if WAGTAIL_VERSION < (6, 3): - from wagtail.images.views.images import DeleteView - from wagtail.images.views.images import edit as image_edit -else: - from wagtail.images.views.images import DeleteView, EditView +from wagtail.images.views.images import DeleteView, EditView from .mixins import BynderAssetCopyMixin, RedirectToBynderMixin @@ -20,30 +12,8 @@ from django.http import HttpRequest, JsonResponse -if WAGTAIL_VERSION < (6, 3): - - class ClassBasedWagtailImageEditView(UpdateView): - """ - A class-based view that mimics the behaviour of wagtail's function-based - image edit view, and can be extended with view mixins. - """ - - # TODO: Use class from Wagtail once the image app views are refactored - model = get_image_model() - pk_url_kwarg = "image_id" - - def get(self, request, *args, **kwargs): - return image_edit(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - return image_edit(request, *args, **kwargs) - - class ImageEditView(RedirectToBynderMixin, ClassBasedWagtailImageEditView): - pass -else: - - class ImageEditView(RedirectToBynderMixin, EditView): - pass +class ImageEditView(RedirectToBynderMixin, EditView): + pass class ImageDeleteView(RedirectToBynderMixin, DeleteView): diff --git a/src/wagtail_bynder/widgets.py b/src/wagtail_bynder/widgets.py index cd2a6ac..b00d36f 100644 --- a/src/wagtail_bynder/widgets.py +++ b/src/wagtail_bynder/widgets.py @@ -3,8 +3,8 @@ from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from wagtail.admin.staticfiles import versioned_static +from wagtail.admin.telepath import register from wagtail.admin.widgets import BaseChooser, BaseChooserAdapter -from wagtail.telepath import register from wagtail_bynder import get_video_model diff --git a/tests/test_image_chooser_views.py b/tests/test_image_chooser_views.py index 22d1797..09e1cd6 100644 --- a/tests/test_image_chooser_views.py +++ b/tests/test_image_chooser_views.py @@ -3,6 +3,7 @@ from django.test import TestCase, TransactionTestCase, override_settings from django.urls import reverse, reverse_lazy from testapp.factories import CustomImageFactory +from wagtail import VERSION as WAGTAIL_VERSION from wagtail.test.utils import WagtailTestUtils from .utils import TEST_ASSET_ID @@ -40,18 +41,20 @@ def test_creates_image_if_asset_id_not_recognised(self): # Assertions create_object_mock.assert_called_once() self.assertEqual(response.status_code, 200) - self.assertEqual( - response.json(), - { - "step": "chosen", - "result": { - "id": str(image.id), - "title": image.title, - "preview": mock.ANY, - "edit_url": reverse("wagtailimages:edit", args=[image.id]), - }, + expected_json = { + "step": "chosen", + "result": { + "id": str(image.id), + "title": image.title, + "preview": mock.ANY, + "edit_url": reverse("wagtailimages:edit", args=[image.id]), }, - ) + } + + if WAGTAIL_VERSION >= (7, 0): + expected_json["result"].update({"default_alt_text": image.title}) + + self.assertEqual(response.json(), expected_json) @mock.patch("wagtail_bynder.views.image.ImageChosenView.update_object") def test_uses_existing_image_without_updating(self, update_object_mock): @@ -67,18 +70,20 @@ def test_uses_existing_image_without_updating(self, update_object_mock): # Check response content self.assertEqual(response.status_code, 200) - self.assertEqual( - response.json(), - { - "step": "chosen", - "result": { - "id": str(image.id), - "title": image.title, - "preview": mock.ANY, - "edit_url": reverse("wagtailimages:edit", args=[image.id]), - }, + expected_json = { + "step": "chosen", + "result": { + "id": str(image.id), + "title": image.title, + "preview": mock.ANY, + "edit_url": reverse("wagtailimages:edit", args=[image.id]), }, - ) + } + + if WAGTAIL_VERSION >= (7, 0): + expected_json["result"].update({"default_alt_text": image.title}) + + self.assertEqual(response.json(), expected_json) @override_settings(BYNDER_SYNC_EXISTING_IMAGES_ON_CHOOSE=True) @mock.patch("wagtail_bynder.views.image.ImageChosenView.update_object") @@ -94,15 +99,17 @@ def test_uses_existing_image_and_updates_it(self, update_object_mock): # Check response content self.assertEqual(response.status_code, 200) - self.assertEqual( - response.json(), - { - "step": "chosen", - "result": { - "id": str(image.id), - "title": image.title, - "preview": mock.ANY, - "edit_url": reverse("wagtailimages:edit", args=[image.id]), - }, + expected_json = { + "step": "chosen", + "result": { + "id": str(image.id), + "title": image.title, + "preview": mock.ANY, + "edit_url": reverse("wagtailimages:edit", args=[image.id]), }, - ) + } + + if WAGTAIL_VERSION >= (7, 0): + expected_json["result"].update({"default_alt_text": image.title}) + + self.assertEqual(response.json(), expected_json) diff --git a/tests/testapp/migrations/0001_initial.py b/tests/testapp/migrations/0001_initial.py index b33e57c..433cdb4 100644 --- a/tests/testapp/migrations/0001_initial.py +++ b/tests/testapp/migrations/0001_initial.py @@ -1,9 +1,9 @@ -# Generated by Django 5.0.4 on 2024-04-10 15:44 +# Generated by Django 4.2.24 on 2025-09-15 13:59 import django.db.models.deletion import taggit.managers import wagtail.images.models -import wagtail.models.collections +import wagtail.models.media import wagtail.search.index from django.conf import settings @@ -21,7 +21,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name="CustomDocument", + name="CustomImage", fields=[ ( "id", @@ -33,15 +33,39 @@ class Migration(migrations.Migration): ), ), ("title", models.CharField(max_length=255, verbose_name="title")), - ("file", models.FileField(upload_to="documents", verbose_name="file")), + ( + "file", + wagtail.images.models.WagtailImageField( + height_field="height", + upload_to=wagtail.images.models.get_upload_to, + verbose_name="file", + width_field="width", + ), + ), + ("width", models.IntegerField(editable=False, verbose_name="width")), + ("height", models.IntegerField(editable=False, verbose_name="height")), ( "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="created at"), + models.DateTimeField( + auto_now_add=True, db_index=True, verbose_name="created at" + ), + ), + ("focal_point_x", models.PositiveIntegerField(blank=True, null=True)), + ("focal_point_y", models.PositiveIntegerField(blank=True, null=True)), + ( + "focal_point_width", + models.PositiveIntegerField(blank=True, null=True), + ), + ( + "focal_point_height", + models.PositiveIntegerField(blank=True, null=True), ), ("file_size", models.PositiveIntegerField(editable=False, null=True)), ( "file_hash", - models.CharField(blank=True, editable=False, max_length=40), + models.CharField( + blank=True, db_index=True, editable=False, max_length=40 + ), ), ( "bynder_id", @@ -100,10 +124,22 @@ class Migration(migrations.Migration): default=False, verbose_name="asset is marked as public" ), ), + ( + "original_width", + models.IntegerField( + editable=False, null=True, verbose_name="original width" + ), + ), + ( + "original_height", + models.IntegerField( + editable=False, null=True, verbose_name="original height" + ), + ), ( "collection", models.ForeignKey( - default=wagtail.models.collections.get_root_collection_id, + default=wagtail.models.media.get_root_collection_id, on_delete=django.db.models.deletion.CASCADE, related_name="+", to="wagtailcore.collection", @@ -133,14 +169,16 @@ class Migration(migrations.Migration): ), ], options={ - "verbose_name": "document", - "verbose_name_plural": "documents", "abstract": False, }, - bases=(wagtail.search.index.Indexed, models.Model), + bases=( + wagtail.images.models.ImageFileMixin, + wagtail.search.index.Indexed, + models.Model, + ), ), migrations.CreateModel( - name="CustomImage", + name="Video", fields=[ ( "id", @@ -151,41 +189,6 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("title", models.CharField(max_length=255, verbose_name="title")), - ( - "file", - wagtail.images.models.WagtailImageField( - height_field="height", - upload_to=wagtail.images.models.get_upload_to, - verbose_name="file", - width_field="width", - ), - ), - ("width", models.IntegerField(editable=False, verbose_name="width")), - ("height", models.IntegerField(editable=False, verbose_name="height")), - ( - "created_at", - models.DateTimeField( - auto_now_add=True, db_index=True, verbose_name="created at" - ), - ), - ("focal_point_x", models.PositiveIntegerField(blank=True, null=True)), - ("focal_point_y", models.PositiveIntegerField(blank=True, null=True)), - ( - "focal_point_width", - models.PositiveIntegerField(blank=True, null=True), - ), - ( - "focal_point_height", - models.PositiveIntegerField(blank=True, null=True), - ), - ("file_size", models.PositiveIntegerField(editable=False, null=True)), - ( - "file_hash", - models.CharField( - blank=True, db_index=True, editable=False, max_length=40 - ), - ), ( "bynder_id", models.CharField( @@ -243,6 +246,19 @@ class Migration(migrations.Migration): default=False, verbose_name="asset is marked as public" ), ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "created_at", + models.DateTimeField( + auto_now_add=True, db_index=True, verbose_name="created at" + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, db_index=True, verbose_name="last updated at" + ), + ), ( "original_width", models.IntegerField( @@ -256,48 +272,41 @@ class Migration(migrations.Migration): ), ), ( - "collection", - models.ForeignKey( - default=wagtail.models.collections.get_root_collection_id, - on_delete=django.db.models.deletion.CASCADE, - related_name="+", - to="wagtailcore.collection", - verbose_name="collection", + "primary_source_url", + models.URLField( + help_text="A derivative using a WebM container using the VP9 codec for video and the Opus codec for audio. These are all open, royalty-free formats which are generally well-supported, although only in quite recent browsers, which is why a fallback is a good idea.", + verbose_name="primary source URL", ), ), ( - "tags", - taggit.managers.TaggableManager( + "fallback_source_url", + models.URLField( blank=True, - help_text=None, - through="taggit.TaggedItem", - to="taggit.Tag", - verbose_name="tags", + help_text="A derivative using an MP4 container and the AVC (H.264) video codec, ideally with AAC as your audio codec. This is because the MP4 container with AVC and AAC codecs within is a broadly-supported combination—by every major browser, in fact—and the quality is typically good for most use cases.", + verbose_name="fallback source URL", ), ), + ("poster_image_url", models.URLField(verbose_name="poster image URL")), ( - "uploaded_by_user", + "collection", models.ForeignKey( - blank=True, - editable=False, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="uploaded by user", + default=wagtail.models.media.get_root_collection_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtailcore.collection", + verbose_name="collection", ), ), ], options={ + "verbose_name": "video", + "verbose_name_plural": "videos", "abstract": False, }, - bases=( - wagtail.images.models.ImageFileMixin, - wagtail.search.index.Indexed, - models.Model, - ), + bases=(wagtail.search.index.Indexed, models.Model), ), migrations.CreateModel( - name="Video", + name="CustomDocument", fields=[ ( "id", @@ -308,6 +317,20 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ("file", models.FileField(upload_to="documents", verbose_name="file")), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="created at"), + ), + ( + "file_size", + models.PositiveBigIntegerField(editable=False, null=True), + ), + ( + "file_hash", + models.CharField(blank=True, editable=False, max_length=40), + ), ( "bynder_id", models.CharField( @@ -365,61 +388,41 @@ class Migration(migrations.Migration): default=False, verbose_name="asset is marked as public" ), ), - ("title", models.CharField(max_length=255, verbose_name="title")), ( - "created_at", - models.DateTimeField( - auto_now_add=True, db_index=True, verbose_name="created at" - ), - ), - ( - "updated_at", - models.DateTimeField( - auto_now=True, db_index=True, verbose_name="last updated at" - ), - ), - ( - "original_width", - models.IntegerField( - editable=False, null=True, verbose_name="original width" - ), - ), - ( - "original_height", - models.IntegerField( - editable=False, null=True, verbose_name="original height" - ), - ), - ( - "primary_source_url", - models.URLField( - help_text="A derivative using a WebM container using the VP9 codec for video and the Opus codec for audio. These are all open, royalty-free formats which are generally well-supported, although only in quite recent browsers, which is why a fallback is a good idea.", - verbose_name="primary source URL", + "collection", + models.ForeignKey( + default=wagtail.models.media.get_root_collection_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtailcore.collection", + verbose_name="collection", ), ), ( - "fallback_source_url", - models.URLField( + "tags", + taggit.managers.TaggableManager( blank=True, - help_text="A derivative using an MP4 container and the AVC (H.264) video codec, ideally with AAC as your audio codec. This is because the MP4 container with AVC and AAC codecs within is a broadly-supported combination—by every major browser, in fact—and the quality is typically good for most use cases.", - verbose_name="fallback source URL", + help_text=None, + through="taggit.TaggedItem", + to="taggit.Tag", + verbose_name="tags", ), ), - ("poster_image_url", models.URLField(verbose_name="poster image URL")), ( - "collection", + "uploaded_by_user", models.ForeignKey( - default=wagtail.models.collections.get_root_collection_id, - on_delete=django.db.models.deletion.CASCADE, - related_name="+", - to="wagtailcore.collection", - verbose_name="collection", + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="uploaded by user", ), ), ], options={ - "verbose_name": "video", - "verbose_name_plural": "videos", + "verbose_name": "document", + "verbose_name_plural": "documents", "abstract": False, }, bases=(wagtail.search.index.Indexed, models.Model), diff --git a/tox.ini b/tox.ini index 6b66acf..5548971 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,8 @@ min_version = 4.11 envlist = - py{3.11,3.12}-django{4.2,5.0}-wagtail{5.2,6.1,6.2,6.3} - py3.13-django5.1-wagtail6.3 + py{3.11,3.12}-django{4.2,5.1,5.2}-wagtail{6.3,7.0,7.1} + py3.13-django{5.1,5.2}-wagtail{6.3,7.0,7.1} [gh-actions] python = @@ -34,13 +34,12 @@ extras = testing deps = django4.2: Django>=4.2,<4.3 - django5.0: Django>=5.0,<5.1 django5.1: Django>=5.1,<5.2 + django5.2: Django>=5.2,<5.3 - wagtail5.2: wagtail>=5.2,<5.3 - wagtail6.1: wagtail>=6.1,<6.2 - wagtail6.2: wagtail>=6.2,<6.3 wagtail6.3: wagtail>=6.3,<6.4 + wagtail7.0: wagtail>=7.0,<7.1 + wagtail7.1: wagtail>=7.1,<7.2 wagtailmain: git+https://github.com/wagtail/wagtail.git postgres: psycopg2>=2.9