diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ff0a895..2259bd85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +### Changed + +- `SnippetObjectType` is replaced with `SnippetInterface` ([405](https://github.com/torchbox/wagtail-grapple/pull/405)) @mgax + ### Fixed - `value` not being queryable on `EmbedBlock` ([#399](https://github.com/torchbox/wagtail-grapple/pull/399))@JakubMastalerz diff --git a/docs/general-usage/decorators.rst b/docs/general-usage/decorators.rst index cf1f41b5..1423d783 100644 --- a/docs/general-usage/decorators.rst +++ b/docs/general-usage/decorators.rst @@ -433,12 +433,12 @@ To register additional interfaces for the block, add them with your block's ``gr from grapple.helpers import register_streamfield_block - class CustomInterface(graphene.Interface): + class MyInterface(graphene.Interface): text = graphene.String() @register_streamfield_block - class CustomInterfaceBlock(blocks.StructBlock): + class MyInterfaceBlock(blocks.StructBlock): text = blocks.TextBlock() - graphql_interfaces = (CustomInterface,) + graphql_interfaces = (MyInterface,) diff --git a/docs/general-usage/graphql-types.rst b/docs/general-usage/graphql-types.rst index ff441b6e..156397b0 100644 --- a/docs/general-usage/graphql-types.rst +++ b/docs/general-usage/graphql-types.rst @@ -102,33 +102,6 @@ The following fields are returned: fileHash: String - -SnippetObjectType -^^^^^^^^^^^^^^^^^ - -You won't see much of ``SnippetObjectType`` as it's only a Union type that -groups all your Snippet models together. You can query all the available snippets -under the ``snippets`` field under the root Query, The query is similar to -an interface but ``SnippetObjectType`` doesn't provide any fields itself. - -When snippets are attached to Pages you interact with your generated type itself -as opposed to an interface or base type. - -An example of querying all snippets: - -:: - - query { - snippets { - ...on Advert { - id - url - text - } - } - } - - SettingObjectType ^^^^^^^^^^^^^^^^^ diff --git a/docs/general-usage/interfaces.rst b/docs/general-usage/interfaces.rst index 32bf9542..2bd4a5a6 100644 --- a/docs/general-usage/interfaces.rst +++ b/docs/general-usage/interfaces.rst @@ -50,7 +50,7 @@ the name of the model: } You can change the default ``PageInterface`` to your own interface by changing the -:ref:`PAGE_INTERFACE` setting. +:ref:`PAGE_INTERFACE` setting. As mentioned above there is both a plural ``pages`` and singular ``page`` field on the root Query type that returns a ``PageInterface``. @@ -108,6 +108,37 @@ in the interface: +``SnippetInterface`` +-------------------- + +``SnippetInterface`` is the default interface for all Wagtail snippet models. It is accessible through the +``snippets`` field on the root query type. It exposes the following fields: + +:: + + snippetType: String! + contentType: String! + +An example of querying all snippets: + +:: + + query { + snippets { + snippetType + contentType + ...on Advert { + id + url + text + } + } + } + +You can change the default ``SnippetInterface`` to your own interface by changing the +:ref:`SNIPPET_INTERFACE` setting. + + Adding your own interfaces -------------------------- @@ -119,10 +150,7 @@ Given the following example interface: .. code-block:: python # interfaces.py - from .interfaces import CustomInterface - - - class CustomInterface(graphene.Interface): + class MyInterface(graphene.Interface): custom_field = graphene.String() you could add it to your Page model like so: @@ -130,12 +158,13 @@ you could add it to your Page model like so: .. code-block:: python from wagtail.models import Page + from .interfaces import MyInterface class MyPage(Page): # ... - graphql_interfaces = (CustomInterface,) + graphql_interfaces = (MyInterface,) or any Django model: @@ -143,13 +172,13 @@ or any Django model: # models.py from django.db import models + from .interfaces import MyInterface class MyModel(models.Model): # ... - graphql_interfaces = (CustomInterface,) - + graphql_interfaces = (MyInterface,) or a ``StreamField`` block: @@ -157,11 +186,12 @@ or a ``StreamField`` block: # blocks.py from wagtail.core import blocks + from .interfaces import MyInterface class MyStructBlock(blocks.StructBlock): # ... - graphql_interfaces = (CustomInterface,) + graphql_interfaces = (MyInterface,) The provided interfaces will be added to the base interfaces for the model. diff --git a/docs/getting-started/settings.rst b/docs/getting-started/settings.rst index 23f37179..ac245a5a 100644 --- a/docs/getting-started/settings.rst +++ b/docs/getting-started/settings.rst @@ -141,10 +141,10 @@ Limit the maximum number of items that ``QuerySetList`` and ``PaginatedQuerySet` Default: ``100`` -.. _page interface settings: +Wagtail model interfaces +^^^^^^^^^^^^^^^^^^^^^^^^ -Wagtail Page interface -^^^^^^^^^^^^^^^^^^^^^^ +.. _page interface setting: ``PAGE_INTERFACE`` ****************** @@ -153,3 +153,14 @@ Used to construct the schema for Wagtail Page-derived models. It can be overridd page models. Default: ``grapple.types.interfaces.PageInterface`` + + +.. _snippet interface setting: + +``SNIPPET_INTERFACE`` +********************* + +Used to construct the schema for Wagtail snippet models. It can be overridden to provide a custom interface for all +snippet models. + +Default: ``grapple.types.interfaces.SnippetInterface`` diff --git a/grapple/actions.py b/grapple/actions.py index b3601b2a..3c8332c2 100644 --- a/grapple/actions.py +++ b/grapple/actions.py @@ -27,6 +27,7 @@ from .types.images import ImageObjectType, ImageRenditionObjectType from .types.pages import Page, get_page_interface from .types.rich_text import RichText as RichTextType +from .types.snippets import get_snippet_interface from .types.streamfield import generate_streamfield_union @@ -612,7 +613,7 @@ def register_snippet_model(cls: Type[models.Model], type_prefix: str): return # Create a GQL type that implements Snippet Interface - snippet_node_type = build_node_type(cls, type_prefix, None) + snippet_node_type = build_node_type(cls, type_prefix, get_snippet_interface()) if snippet_node_type: registry.snippets[cls] = snippet_node_type diff --git a/grapple/settings.py b/grapple/settings.py index 3c5d10ae..3a2154f6 100644 --- a/grapple/settings.py +++ b/grapple/settings.py @@ -29,6 +29,7 @@ "MAX_PAGE_SIZE": 100, "RICHTEXT_FORMAT": "html", "PAGE_INTERFACE": "grapple.types.interfaces.PageInterface", + "SNIPPET_INTERFACE": "grapple.types.interfaces.SnippetInterface", } # List of settings that have been deprecated diff --git a/grapple/types/interfaces.py b/grapple/types/interfaces.py index 11acdf69..255ddea5 100644 --- a/grapple/types/interfaces.py +++ b/grapple/types/interfaces.py @@ -227,3 +227,25 @@ def resolve_raw_value(self, info, **kwargs): return self.value.source return self.value + + +def get_snippet_interface(): + return import_string(grapple_settings.SNIPPET_INTERFACE) + + +class SnippetInterface(graphene.Interface): + snippet_type = graphene.String(required=True) + content_type = graphene.String(required=True) + + @classmethod + def resolve_type(cls, instance, info, **kwargs): + return registry.snippets[type(instance)] + + def resolve_snippet_type(self, info, **kwargs): + return self.__class__.__name__ + + def resolve_content_type(self, info, **kwargs): + self.content_type = ContentType.objects.get_for_model(self) + return ( + f"{self.content_type.app_label}.{self.content_type.model_class().__name__}" + ) diff --git a/grapple/types/snippets.py b/grapple/types/snippets.py index 8e496943..b948fb78 100644 --- a/grapple/types/snippets.py +++ b/grapple/types/snippets.py @@ -1,49 +1,19 @@ import graphene from ..registry import registry - - -class SnippetTypes: - # SnippetObjectType class can only be created if - # registry.snippets.types is non-empty, and should only be created - # once (graphene complains if we register multiple type classes - # with identical names) - _SnippetObjectType = None - - @classmethod - def get_object_type(cls): - if cls._SnippetObjectType is None and registry.snippets: - - class SnippetObjectType(graphene.Union): - class Meta: - types = registry.snippets.types - - cls._SnippetObjectType = SnippetObjectType - return cls._SnippetObjectType +from .interfaces import get_snippet_interface def SnippetsQuery(): - SnippetObjectType = SnippetTypes.get_object_type() - - if SnippetObjectType is not None: - - class Mixin: - snippets = graphene.List(graphene.NonNull(SnippetObjectType), required=True) - # Return all snippets. - - def resolve_snippets(self, info, **kwargs): - snippet_objects = [] - for snippet in registry.snippets: - for object in snippet._meta.model.objects.all(): - snippet_objects.append(object) - - return snippet_objects - - return Mixin + class Mixin: + snippets = graphene.List(graphene.NonNull(get_snippet_interface), required=True) - else: + def resolve_snippets(self, info, **kwargs): + snippet_objects = [] + for snippet in registry.snippets: + for object in snippet._meta.model.objects.all(): + snippet_objects.append(object) - class Mixin: - pass + return snippet_objects - return Mixin + return Mixin diff --git a/grapple/types/streamfield.py b/grapple/types/streamfield.py index 7c2aa844..4e98fa3f 100644 --- a/grapple/types/streamfield.py +++ b/grapple/types/streamfield.py @@ -353,8 +353,7 @@ def resolve_items(self, info, **kwargs): def register_streamfield_blocks(): from .documents import get_document_type from .images import get_image_type - from .interfaces import get_page_interface - from .snippets import SnippetTypes + from .interfaces import get_page_interface, get_snippet_interface class PageChooserBlock(graphene.ObjectType): page = graphene.Field(get_page_interface(), required=False) @@ -391,20 +390,17 @@ def resolve_image(self, info, **kwargs): } ) - SnippetObjectType = SnippetTypes.get_object_type() - if SnippetObjectType is not None: + class SnippetChooserBlock(graphene.ObjectType): + snippet = graphene.Field(get_snippet_interface(), required=False) - class SnippetChooserBlock(graphene.ObjectType): - snippet = graphene.Field(SnippetObjectType, required=False) - - class Meta: - interfaces = (StreamFieldInterface,) + class Meta: + interfaces = (StreamFieldInterface,) - def resolve_snippet(self, info, **kwargs): - return self.value + def resolve_snippet(self, info, **kwargs): + return self.value - registry.streamfield_blocks.update( - { - wagtail.snippets.blocks.SnippetChooserBlock: SnippetChooserBlock, - } - ) + registry.streamfield_blocks.update( + { + wagtail.snippets.blocks.SnippetChooserBlock: SnippetChooserBlock, + } + ) diff --git a/tests/settings_custom_page_interface.py b/tests/settings_custom_interfaces.py similarity index 57% rename from tests/settings_custom_page_interface.py rename to tests/settings_custom_interfaces.py index 319403fb..31628ae1 100644 --- a/tests/settings_custom_page_interface.py +++ b/tests/settings_custom_interfaces.py @@ -2,3 +2,4 @@ GRAPPLE["PAGE_INTERFACE"] = "testapp.interfaces.CustomPageInterface" # noqa: F405 +GRAPPLE["SNIPPET_INTERFACE"] = "testapp.interfaces.CustomSnippetInterface" # noqa: F405 diff --git a/tests/test_grapple.py b/tests/test_grapple.py index 103587ed..23c96523 100644 --- a/tests/test_grapple.py +++ b/tests/test_grapple.py @@ -10,12 +10,13 @@ from django.db import connection from django.test import RequestFactory, TestCase, override_settings from graphene.test import Client -from testapp.factories import BlogPageFactory +from testapp.factories import AdvertFactory, BlogPageFactory, PersonFactory from testapp.models import GlobalSocialMediaSettings, HomePage, SocialMediaSettings from wagtail.documents import get_document_model from wagtail.models import Page, Site from wagtailmedia.models import get_media_model +from grapple.registry import RegistryItem from grapple.schema import create_schema @@ -1683,3 +1684,63 @@ def test_query_single_setting_without_site_filter_and_multiple_sites(self): "data": {"setting": None}, }, ) + + +class SnippetsTest(BaseGrappleTest): + def setUp(self): + super().setUp() + self.factory = RequestFactory() + self.advert = AdvertFactory() + self.person = PersonFactory() + + def test_snippets(self): + """ + Query for snippets of different types, they should all be returned in + the same response. + """ + + query = """ + { + snippets { + snippetType + contentType + } + } + """ + + executed = self.client.execute(query) + + self.assertEqual(type(executed["data"]), dict) + self.assertEqual(type(executed["data"]["snippets"]), list) + self.assertEqual(len(executed["data"]["snippets"]), 2) + self.assertEqual(type(executed["data"]["snippets"][0]), dict) + + snippets_data = sorted( + executed["data"]["snippets"], key=lambda s: s["snippetType"] + ) + self.assertEqual(snippets_data[0]["snippetType"], "Advert") + self.assertEqual(snippets_data[0]["contentType"], "testapp.Advert") + self.assertEqual(snippets_data[1]["snippetType"], "Person") + self.assertEqual(snippets_data[1]["contentType"], "testapp.Person") + + def test_no_snippet_classes_registered(self): + """ + If there are no registered snippet classes, the snippets query should + still work, and return nothing. + """ + + query = """ + { + snippets { + snippetType + contentType + } + } + """ + + with patch("grapple.registry.registry.snippets", RegistryItem()): + executed = self.client.execute(query) + + self.assertEqual(type(executed["data"]), dict) + self.assertEqual(type(executed["data"]["snippets"]), list) + self.assertEqual(len(executed["data"]["snippets"]), 0) diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py index 6922e35e..a1276010 100644 --- a/tests/test_interfaces.py +++ b/tests/test_interfaces.py @@ -4,15 +4,19 @@ from django.test import override_settings, tag from test_grapple import BaseGrappleTestWithIntrospection -from testapp.factories import BlogPageFactory, CustomInterfaceBlockFactory -from testapp.interfaces import CustomPageInterface +from testapp.factories import AdditionalInterfaceBlockFactory, BlogPageFactory +from testapp.interfaces import CustomPageInterface, CustomSnippetInterface -from grapple.types.interfaces import PageInterface, get_page_interface +from grapple.types.interfaces import ( + PageInterface, + get_page_interface, + get_snippet_interface, +) @skipIf( - os.getenv("DJANGO_SETTINGS_MODULE") == "settings_custom_page_interface", - "Cannot run with settings_custom_page_interface", + os.getenv("DJANGO_SETTINGS_MODULE") == "settings_custom_interfaces", + "Cannot run with settings_custom_interfaces", ) class InterfacesTestCase(BaseGrappleTestWithIntrospection): @classmethod @@ -21,7 +25,7 @@ def setUpTestData(cls): cls.blog_page = BlogPageFactory( body=[ - ("custom_interface_block", CustomInterfaceBlockFactory()), + ("additional_interface_block", AdditionalInterfaceBlockFactory()), ], parent=cls.home, ) @@ -41,15 +45,21 @@ def test_schema_with_default_page_interface(self): def test_get_page_interface_with_custom_page_interface(self): self.assertIs(get_page_interface(), CustomPageInterface) - def test_streamfield_block_with_custom_interface(self): + @override_settings( + GRAPPLE={"SNIPPET_INTERFACE": "testapp.interfaces.CustomSnippetInterface"} + ) + def test_get_snippet_interface_with_custom_page_interface(self): + self.assertIs(get_snippet_interface(), CustomSnippetInterface) + + def test_streamfield_block_with_additional_interface(self): query = """ query($id: ID) { page(id: $id) { ... on BlogPage { body { blockType - ...on CustomInterface { - customText + ...on AdditionalInterface { + additionalText } } } @@ -60,51 +70,58 @@ def test_streamfield_block_with_custom_interface(self): body = results["data"]["page"]["body"] for block in body: - if block["blockType"] == "CustomInterfaceBlock": + if block["blockType"] == "AdditionalInterfaceBlock": self.assertRegex( - block["customText"], r"^Block with custom interface \d+$" + block["additionalText"], r"^Block with additional interface \d+$" ) return self.fail("Query by interface didn't match anything") - def test_schema_for_streamfield_block_with_custom_interface(self): - results = self.introspect_schema_by_type("CustomInterfaceBlock") + def test_schema_for_streamfield_block_with_additional_interface(self): + results = self.introspect_schema_by_type("AdditionalInterfaceBlock") self.assertListEqual( sorted(results["data"]["__type"]["interfaces"], key=lambda x: x["name"]), - [{"name": "CustomInterface"}, {"name": "StreamFieldInterface"}], + [{"name": "AdditionalInterface"}, {"name": "StreamFieldInterface"}], ) def test_schema_for_page_with_graphql_interface(self): results = self.introspect_schema_by_type("AuthorPage") self.assertListEqual( sorted(results["data"]["__type"]["interfaces"], key=lambda x: x["name"]), - [{"name": "CustomInterface"}, {"name": "PageInterface"}], + [{"name": "AdditionalInterface"}, {"name": "PageInterface"}], ) def test_schema_for_snippet_with_graphql_interface(self): results = self.introspect_schema_by_type("Advert") self.assertListEqual( sorted(results["data"]["__type"]["interfaces"], key=lambda x: x["name"]), - [{"name": "CustomInterface"}], + [{"name": "AdditionalInterface"}, {"name": "SnippetInterface"}], ) def test_schema_for_django_model_with_graphql_interfaces(self): results = self.introspect_schema_by_type("SimpleModel") self.assertListEqual( sorted(results["data"]["__type"]["interfaces"], key=lambda x: x["name"]), - [{"name": "CustomInterface"}], + [{"name": "AdditionalInterface"}], ) @tag("needs-custom-settings") @skipUnless( - os.getenv("DJANGO_SETTINGS_MODULE") == "settings_custom_page_interface", - "Needs settings_custom_page_interface", + os.getenv("DJANGO_SETTINGS_MODULE") == "settings_custom_interfaces", + "Needs settings_custom_interfaces", ) -class CustomPageInterfaceTestCase(BaseGrappleTestWithIntrospection): +class CustomInterfacesTestCase(BaseGrappleTestWithIntrospection): def test_schema_with_custom_page_interface(self): results = self.introspect_schema_by_type("BlogPage") self.assertListEqual( results["data"]["__type"]["interfaces"], [{"name": "CustomPageInterface"}] ) + + def test_schema_with_custom_snippet_interface(self): + results = self.introspect_schema_by_type("Person") + self.assertListEqual( + results["data"]["__type"]["interfaces"], + [{"name": "CustomSnippetInterface"}], + ) diff --git a/tests/testapp/blocks.py b/tests/testapp/blocks.py index 0888e062..f5e606b4 100644 --- a/tests/testapp/blocks.py +++ b/tests/testapp/blocks.py @@ -24,7 +24,7 @@ GraphQLStreamfield, GraphQLString, ) -from testapp.interfaces import CustomInterface +from testapp.interfaces import AdditionalInterface if TYPE_CHECKING: @@ -294,17 +294,17 @@ def get_link_url( @register_streamfield_block -class CustomInterfaceBlock(blocks.StructBlock): +class AdditionalInterfaceBlock(blocks.StructBlock): """ - Specify a custom GraphQL interface for our block. + Specify an additional GraphQL interface for our block. """ - custom_text = blocks.TextBlock() + additional_text = blocks.TextBlock() graphql_fields = [ - GraphQLString("custom_text"), + GraphQLString("additional_text"), ] - graphql_interfaces = (CustomInterface,) + graphql_interfaces = (AdditionalInterface,) class StreamFieldBlock(blocks.StreamBlock): @@ -325,4 +325,4 @@ class StreamFieldBlock(blocks.StreamBlock): block_with_name = BlockWithName() advert = SnippetChooserBlock("testapp.Advert") person = SnippetChooserBlock("testapp.Person") - custom_interface_block = CustomInterfaceBlock() + additional_interface_block = AdditionalInterfaceBlock() diff --git a/tests/testapp/factories.py b/tests/testapp/factories.py index 35c46fde..5c66d7b7 100644 --- a/tests/testapp/factories.py +++ b/tests/testapp/factories.py @@ -9,7 +9,7 @@ from wagtail.contrib.redirects.models import Redirect from testapp.blocks import ( - CustomInterfaceBlock, + AdditionalInterfaceBlock, ImageGalleryBlock, ImageGalleryImage, ImageGalleryImages, @@ -88,11 +88,11 @@ class Meta: model = TextWithCallableBlock -class CustomInterfaceBlockFactory(wagtail_factories.StructBlockFactory): - custom_text = factory.Sequence(lambda n: f"Block with custom interface {n}") +class AdditionalInterfaceBlockFactory(wagtail_factories.StructBlockFactory): + additional_text = factory.Sequence(lambda n: f"Block with additional interface {n}") class Meta: - model = CustomInterfaceBlock + model = AdditionalInterfaceBlock class BlogPageRelatedLinkFactory(factory.django.DjangoModelFactory): diff --git a/tests/testapp/interfaces.py b/tests/testapp/interfaces.py index b473215d..41baec05 100644 --- a/tests/testapp/interfaces.py +++ b/tests/testapp/interfaces.py @@ -1,11 +1,18 @@ import graphene -from grapple.types.interfaces import PageInterface +from grapple.types.interfaces import PageInterface, SnippetInterface -class CustomInterface(graphene.Interface): - custom_text = graphene.String() +class AdditionalInterface(graphene.Interface): + additional_text = graphene.String() class CustomPageInterface(PageInterface): custom_text = graphene.String() + + +class CustomSnippetInterface(SnippetInterface): + custom_text = graphene.String() + + def resolve_custom_text(self, info, **kwargs): + return str(self) diff --git a/tests/testapp/migrations/0001_initial.py b/tests/testapp/migrations/0001_initial.py index d09e18b1..d405ab03 100644 --- a/tests/testapp/migrations/0001_initial.py +++ b/tests/testapp/migrations/0001_initial.py @@ -92,193 +92,123 @@ class Migration(migrations.Migration): "body", wagtail.fields.StreamField( [ - ( - "heading", - wagtail.blocks.CharBlock(form_classname="full title"), + ("heading", 0), + ("paragraph", 1), + ("image", 2), + ("decimal", 3), + ("date", 4), + ("datetime", 5), + ("gallery", 8), + ("video", 10), + ("objectives", 12), + ("carousel", 13), + ("callout", 14), + ("text_and_buttons", 20), + ("page", 21), + ("text_with_callable", 24), + ("block_with_name", 25), + ("advert", 26), + ("person", 27), + ("additional_interface_block", 28), + ], + block_lookup={ + 0: ( + "wagtail.blocks.CharBlock", + (), + {"form_classname": "full title"}, ), - ("paragraph", wagtail.blocks.RichTextBlock()), - ("image", wagtail.images.blocks.ImageChooserBlock()), - ("decimal", wagtail.blocks.DecimalBlock()), - ("date", wagtail.blocks.DateBlock()), - ("datetime", wagtail.blocks.DateTimeBlock()), - ( - "gallery", - wagtail.blocks.StructBlock( - [ - ( - "title", - wagtail.blocks.CharBlock( - form_classname="full title" - ), - ), - ( - "images", - wagtail.blocks.StreamBlock( - [ - ( - "image", - wagtail.blocks.StructBlock( - [ - ( - "caption", - wagtail.blocks.CharBlock( - form_classname="full title" - ), - ), - ( - "image", - wagtail.images.blocks.ImageChooserBlock(), - ), - ] - ), - ) - ] - ), - ), - ] - ), + 1: ("wagtail.blocks.RichTextBlock", (), {}), + 2: ("wagtail.images.blocks.ImageChooserBlock", (), {}), + 3: ("wagtail.blocks.DecimalBlock", (), {}), + 4: ("wagtail.blocks.DateBlock", (), {}), + 5: ("wagtail.blocks.DateTimeBlock", (), {}), + 6: ( + "wagtail.blocks.StructBlock", + [[("caption", 0), ("image", 2)]], + {}, ), - ( - "video", - wagtail.blocks.StructBlock( - [ - ( - "youtube_link", - wagtail.embeds.blocks.EmbedBlock( - required=False - ), - ) - ] - ), + 7: ("wagtail.blocks.StreamBlock", [[("image", 6)]], {}), + 8: ( + "wagtail.blocks.StructBlock", + [[("title", 0), ("images", 7)]], + {}, ), - ( - "objectives", - wagtail.blocks.ListBlock(wagtail.blocks.CharBlock()), + 9: ( + "wagtail.embeds.blocks.EmbedBlock", + (), + {"required": False}, ), - ( - "carousel", - wagtail.blocks.StreamBlock( - [ - ( - "text", - wagtail.blocks.CharBlock( - form_classname="full title" - ), - ), - ( - "image", - wagtail.images.blocks.ImageChooserBlock(), - ), - ("markup", wagtail.blocks.RichTextBlock()), - ] - ), + 10: ( + "wagtail.blocks.StructBlock", + [[("youtube_link", 9)]], + {}, ), - ( - "callout", - wagtail.blocks.StructBlock( - [ - ("text", wagtail.blocks.RichTextBlock()), - ( - "image", - wagtail.images.blocks.ImageChooserBlock(), - ), - ] - ), + 11: ("wagtail.blocks.CharBlock", (), {}), + 12: ("wagtail.blocks.ListBlock", (11,), {}), + 13: ( + "wagtail.blocks.StreamBlock", + [[("text", 0), ("image", 2), ("markup", 1)]], + {}, ), - ( - "text_and_buttons", - wagtail.blocks.StructBlock( - [ - ("text", wagtail.blocks.TextBlock()), - ( - "buttons", - wagtail.blocks.ListBlock( - wagtail.blocks.StructBlock( - [ - ( - "button_text", - wagtail.blocks.CharBlock( - label="Text", - max_length=50, - required=True, - ), - ), - ( - "button_link", - wagtail.blocks.CharBlock( - label="Link", - max_length=255, - required=True, - ), - ), - ] - ) - ), - ), - ( - "mainbutton", - wagtail.blocks.StructBlock( - [ - ( - "button_text", - wagtail.blocks.CharBlock( - label="Text", - max_length=50, - required=True, - ), - ), - ( - "button_link", - wagtail.blocks.CharBlock( - label="Link", - max_length=255, - required=True, - ), - ), - ] - ), - ), - ] - ), + 14: ( + "wagtail.blocks.StructBlock", + [[("text", 1), ("image", 2)]], + {}, + ), + 15: ("wagtail.blocks.TextBlock", (), {}), + 16: ( + "wagtail.blocks.CharBlock", + (), + {"label": "Text", "max_length": 50, "required": True}, + ), + 17: ( + "wagtail.blocks.CharBlock", + (), + {"label": "Link", "max_length": 255, "required": True}, ), - ("page", wagtail.blocks.PageChooserBlock()), - ( - "text_with_callable", - wagtail.blocks.StructBlock( + 18: ( + "wagtail.blocks.StructBlock", + [[("button_text", 16), ("button_link", 17)]], + {}, + ), + 19: ("wagtail.blocks.ListBlock", (18,), {}), + 20: ( + "wagtail.blocks.StructBlock", + [[("text", 15), ("buttons", 19), ("mainbutton", 18)]], + {}, + ), + 21: ("wagtail.blocks.PageChooserBlock", (), {}), + 22: ("wagtail.blocks.IntegerBlock", (), {}), + 23: ("wagtail.blocks.FloatBlock", (), {}), + 24: ( + "wagtail.blocks.StructBlock", + [ [ - ("text", wagtail.blocks.CharBlock()), - ("integer", wagtail.blocks.IntegerBlock()), - ("decimal", wagtail.blocks.FloatBlock()), - ("page", wagtail.blocks.PageChooserBlock()), + ("text", 11), + ("integer", 22), + ("decimal", 23), + ("page", 21), ] - ), + ], + {}, ), - ( - "block_with_name", - wagtail.blocks.StructBlock( - [("name", wagtail.blocks.TextBlock())] - ), + 25: ("wagtail.blocks.StructBlock", [[("name", 15)]], {}), + 26: ( + "wagtail.snippets.blocks.SnippetChooserBlock", + ("testapp.Advert",), + {}, ), - ( - "advert", - wagtail.snippets.blocks.SnippetChooserBlock( - "testapp.Advert" - ), + 27: ( + "wagtail.snippets.blocks.SnippetChooserBlock", + ("testapp.Person",), + {}, ), - ( - "person", - wagtail.snippets.blocks.SnippetChooserBlock( - "testapp.Person" - ), + 28: ( + "wagtail.blocks.StructBlock", + [[("additional_text", 15)]], + {}, ), - ( - "custom_interface_block", - wagtail.blocks.StructBlock( - [("custom_text", wagtail.blocks.TextBlock())] - ), - ), - ], - use_json_field=True, + }, ), ), ( diff --git a/tests/testapp/models/core.py b/tests/testapp/models/core.py index e010333f..732b1554 100644 --- a/tests/testapp/models/core.py +++ b/tests/testapp/models/core.py @@ -40,7 +40,7 @@ ) from grapple.utils import resolve_paginated_queryset from testapp.blocks import StreamFieldBlock -from testapp.interfaces import CustomInterface +from testapp.interfaces import AdditionalInterface document_model_string = getattr( @@ -50,7 +50,7 @@ @register_singular_query_field("simpleModel") class SimpleModel(models.Model): - graphql_interfaces = (CustomInterface,) + graphql_interfaces = (AdditionalInterface,) def custom_middleware_one(next, root, info, **args): @@ -83,7 +83,7 @@ class AuthorPage(Page): content_panels = Page.content_panels + [FieldPanel("name")] graphql_fields = [GraphQLString("name")] - graphql_interfaces = (CustomInterface,) + graphql_interfaces = (AdditionalInterface,) class BlogPageTag(TaggedItemBase): @@ -264,7 +264,7 @@ class Advert(models.Model): GraphQLString("string_rich_text", source="rich_text"), GraphQLString("extra_rich_text", deprecation_reason="Use rich_text instead"), ] - graphql_interfaces = (CustomInterface,) + graphql_interfaces = (AdditionalInterface,) def __str__(self): return self.text diff --git a/tox.ini b/tox.ini index 589f0915..30fa9bad 100644 --- a/tox.ini +++ b/tox.ini @@ -53,7 +53,7 @@ install_command = python -Im pip install -U {opts} {packages} commands = python -m coverage run manage.py test {posargs: -v1} --exclude-tag=needs-custom-settings - python manage.py test -v1 --tag=needs-custom-settings --settings=settings_custom_page_interface + python manage.py test -v1 --tag=needs-custom-settings --settings=settings_custom_interfaces [testenv:coverage-report] ; a bit of a hack - we keep deps to a minimum, and move coverage data to the tox root for easier excludes