Skip to content

Commit

Permalink
Replace SnippetObjectType with SnippetInterface (#405)
Browse files Browse the repository at this point in the history
* Rename CustomInterface to AdditionalInterface

The name `CustomInterface` is too similar with `CustomPageInterface` and
the upcoming `CustomSnippetInterface` and their custom interfaces
settings file. Better rename it to `AdditionalInterface` so there is
less confusion.

* Rename the custom interface settings file

The settings file will contain an override for the default snippet
interface, in addition to the default page interface, so we're renaming
it up front here.

* Replace SnippetObjectType with SnippetInterface

Fixes #386

API clients could use a field on snippet objects to determine the type
of snippet they are looking at. Therefore, we change the snippet type to
an interface, similar to the page interface, so it can expose a new
field called `snippetType`.

* Update changelog
* Add contentType field on SnippetInterface
* Test for no registered snippets
* Create an instance each of both snippet types
  • Loading branch information
mgax authored Sep 24, 2024
1 parent 6cebc7b commit 82717eb
Show file tree
Hide file tree
Showing 19 changed files with 337 additions and 313 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 3 additions & 3 deletions docs/general-usage/decorators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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,)
27 changes: 0 additions & 27 deletions docs/general-usage/graphql-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^^

Expand Down
48 changes: 39 additions & 9 deletions docs/general-usage/interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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<page interface settings>` setting.
:ref:`PAGE_INTERFACE<page interface setting>` setting.

As mentioned above there is both a plural ``pages`` and singular ``page``
field on the root Query type that returns a ``PageInterface``.
Expand Down Expand Up @@ -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<snippet interface setting>` setting.


Adding your own interfaces
--------------------------

Expand All @@ -119,49 +150,48 @@ 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:

.. 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:

.. code-block:: python
# 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:

.. code-block:: python
# 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.
17 changes: 14 additions & 3 deletions docs/getting-started/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``
******************
Expand All @@ -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``
3 changes: 2 additions & 1 deletion grapple/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions grapple/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions grapple/types/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__}"
)
50 changes: 10 additions & 40 deletions grapple/types/snippets.py
Original file line number Diff line number Diff line change
@@ -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
28 changes: 12 additions & 16 deletions grapple/types/streamfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@


GRAPPLE["PAGE_INTERFACE"] = "testapp.interfaces.CustomPageInterface" # noqa: F405
GRAPPLE["SNIPPET_INTERFACE"] = "testapp.interfaces.CustomSnippetInterface" # noqa: F405
Loading

0 comments on commit 82717eb

Please sign in to comment.