Decorators

Grapple exposes a few fields for the root schema such as pages, images, documents, media and redirects.

Here are some useful decorators that allow you to expand your GraphQL schema further:

@register_query_field

class grapple.helpers.register_query_field(field_name, plural_field_name=None, query_params=None, required=False, plural_required=False, plural_item_required=False, middleware=None)

You can easily expose any Django model from your codebase by adding the @register_query_field decorator like so:

from django.db import models
from wagtail.admin.edit_handlers import FieldPanel

from grapple.helpers import register_query_field
from grapple.models import GraphQLString


@register_query_field("advert")
class Advert(models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [FieldPanel("url"), FieldPanel("text")]

    graphql_fields = [GraphQLString("url"), GraphQLString("text")]

    def __str__(self):
        return self.text

You can now query your adverts with the following query:

query {
    # Get all adverts
    adverts {
        url
        text
    }

    # Get a specific advert
    advert(id: 1) {
        url
        text
    }
}

You can add custom query parameters like so:

@register_query_field(
    "advert", "adverts", {"id": graphene.Int(), "url": graphene.String()}
)
class Advert(models.Model):
    pass  # your actual implementation here

and then use it in your queries:

query {
    # Get a specific advert
    advert(url: "some-unique-url") {
        url
        text
    }
}

You can make the singular query return type required like so:

@register_query_field("advert", required=True)
class Advert(models.Model):
    pass  # your actual implementation here

and then should look like this on your schema:

advert(id: Int): Advert!

instead of:

advert(id: Int): Advert

You can can also make the plural query return list type required:

@register_query_field("advert", plural_required=True)
class Advert(models.Model):
    pass  # your actual implementation here

making the plural query look like this on your schema:

adverts(id: Int, ...): [Advert]!

instead of the default:

adverts(id: Int, ...): [Advert]

If you want to make the plural query return list item type required:

@register_query_field("advert", plural_item_required=True)
class Advert(models.Model):
    pass  # your actual implementation here

making the plural query look like this:

adverts(id: Int, ...): [Advert!]

instead of the default:

adverts(id: Int, ...): [Advert]

You can add a middleware to the queries generated by the register_query_field decorator:

from grapple.middleware import IsAuthenticatedMiddleware


@register_query_field("advert", middleware=[IsAuthenticatedMiddleware])
class Advert(models.Model):
    pass  # your actual implementation here

Note that you must add GrappleMiddleware to the Graphene MIDDLEWARE setting. More information can be found in the middleware docs.

@register_paginated_query_field

class grapple.helpers.register_paginated_query_field(field_name, plural_field_name=None, query_params=None, required=False, plural_required=False, plural_item_required=False, middleware=None)

You can easily expose any Django model from your codebase by adding the @register_paginated_query_field decorator like so:

from grapple.helpers import register_paginated_query_field


@register_paginated_query_field("advert")
class Advert(models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [FieldPanel("url"), FieldPanel("text")]

    graphql_fields = [GraphQLString("url"), GraphQLString("text")]

    def __str__(self):
        return self.text

You can now query your adverts with the following query:

query {
    # Get adverts paginated
    adverts(page: 1, perPage: 10) {
        items {
            url
            text
        }
        pagination {
            total
            count
            perPage
            currentPage
            prevPage
            nextPage
            totalPages
        }
    }

    # Get a specific advert
    advert(id: 1) {
        url
        text
    }
}

The default per_page value is 10 and can be changed with the GRAPPLE["PAGE_SIZE"] setting. The per_page has a maximum value of 100 by default and can be changed with the GRAPPLE["MAX_PAGE_SIZE"] setting.

# settings.py
GRAPPLE = {
    # ...
    "PAGE_SIZE": 10,
    "MAX_PAGE_SIZE": 100,
}

You can add custom query parameters like so:

@register_paginated_query_field(
    "advert",
    "adverts",
    {
        "id": graphene.Int(),
        "url": graphene.String(),
    },
)
class Advert(models.Model):
    pass  # your actual implementation here

and then use it in your queries:

query {
    # Get a specific advert
    advert(url: "some-unique-url") {
        url
        text
    }
}

You can make the singular query return type required like so:

@register_paginated_query_field("advert", required=True)
class Advert(models.Model):
    pass  # your actual implementation here

and then should look like this on your schema:

advert(id: Int): Advert!

instead of:

advert(id: Int): Advert

You can can also make the plural query return list type required:

@register_paginated_query_field("advert", plural_required=True)
class Advert(models.Model):
    pass  # your actual implementation here

making the plural query look like this on your schema:

adverts(page: Int, perPage: Int, ...): AdvertPaginatedType!

Type AdvertPaginatedType {
    items: [Advert]!
    pagination: PaginationType!
}

instead of the default:

adverts(page: Int, perPage: Int, ...): AdvertPaginatedType

Type AdvertPaginatedType {
    items: [Advert]
    pagination: PaginationType
}

If you want to make the plural query return list item type required:

@register_paginated_query_field("advert", plural_item_required=True)
class Advert(models.Model):
    pass  # your actual implementation here

making the plural query look like this:

adverts(page: Int, perPage: Int, ...): AdvertPaginatedType

Type AdvertPaginatedType {
    items: [Advert!]
    pagination: PaginationType
}

instead of the default:

adverts(page: Int, perPage: Int, ...): AdvertPaginatedType

Type AdvertPaginatedType {
    items: [Advert]
    pagination: PaginationType
}

You can add middleware to the queries generated by the register_paginated_query_field decorator:

from grapple.middleware import IsAuthenticatedMiddleware


@register_paginated_query_field("advert", middleware=[IsAuthenticatedMiddleware])
class Advert(models.Model):
    pass  # your actual implementation here

More information can be found on middleware docs.

@register_singular_query_field

class grapple.helpers.register_singular_query_field(field_name, query_params=None, required=False, middleware=None)

Returns the first item of the given type using the Model ordering. You can expose any Django model by decorating it with @register_singular_query_field. This is especially useful when you have Wagtail Pages with max_count of one(Ref: Wagtail documentation), thus there is no need to query by id.

from grapple.helpers import register_singular_query_field


@register_singular_query_field("first_advert")
class Advert(models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [FieldPanel("url"), FieldPanel("text")]

    graphql_fields = [GraphQLString("url"), GraphQLString("text")]

    def __str__(self):
        return self.text

and then use it in your queries:

query {
    # Get the first advert
    firstAdvert {
        url
        text
    }
}

If you have multiple items, you could change the order:

query {
    # Get the first advert
    firstAdvert(order: "-id") {
        url
        text
    }
}

You can add middleware to the queries generated by the register_singular_query_field decorator:

from grapple.middleware import IsAuthenticatedMiddleware


@register_singular_query_field("first_advert", middleware=[IsAuthenticatedMiddleware])
class Advert(models.Model):
    pass  # your actual implementation here

More information can be found on middleware docs.

@register_streamfield_block

class grapple.helpers.register_streamfield_block(cls)

The register_streamfield_block decorator can be used to extend the schema with custom StreamField block types.

from wagtail import blocks
from grapple.helpers import register_streamfield_block


@register_streamfield_block
class CustomStreamBlock(blocks.StreamBlock):
    text = blocks.TextBlock()

    class Meta:
        graphql_description = "This is a streamblock with a textblock child"

If a block’s Meta class has a graphql_description attribute, this value will be exposed as the description in introspection queries.

To register additional interfaces for the block, add them with your block’s graphql_interfaces attribute:

import graphene
from wagtail import blocks

from grapple.helpers import register_streamfield_block


class CustomInterface(graphene.Interface):
    text = graphene.String()


@register_streamfield_block
class CustomInterfaceBlock(blocks.StructBlock):
    text = blocks.TextBlock()

    graphql_interfaces = (CustomInterface,)