torchbox / wagtail-grapple

A Wagtail app that makes building GraphQL endpoints a breeze!
https://wagtail-grapple.readthedocs.io/en/latest/
Other
152 stars 57 forks source link

Unable to map ClusterTaggableManager #70

Closed SamuelTaylor001 closed 3 years ago

SamuelTaylor001 commented 4 years ago

Hi,

I'm using the tag manager for wagtail to tag some of my posts however, I've noticed that grapple doesn't know how to interpret ClusterTaggableManager.

This is how my class looks

class FactSheetTag(TaggedItemBase):
    content_object = ParentalKey(
        'FactSheet',
        related_name='tagged_items',
        on_delete=models.CASCADE
    )

This is how my field looks

keywords = ClusterTaggableManager(through=FactSheetTag, blank=True)

This is the output from graphene's converter.py file:

Exception: Don't know how to convert the Django field fact_sheet.FactSheet.keywords (<class 'modelcluster.contrib.taggit.ClusterTaggableManager'>)

When previously trying to implement my own Graphene coverter for Wagtail, I used the following code.

class FlatTags(graphene.String):
    @classmethod
    def serialize(cls, value):
        tagsList = []
        for tag in value.all():
            tagsList.append(tag.name)
        return tagsList

@convert_django_field.register(ClusterTaggableManager)
def convert_tag_field_to_string(field, registry=None):
    return graphene.Field(FlatTags,
        description=field.help_text,
        required=not field.null)

Does a similar implementation already exist in wagtail-grapple that I have missed or should this be a feature request?

Also, how might I implement this feature in the case that ClusterTaggableManager is not supported at all?

IAmNatch commented 4 years ago

@SamuelTaylor001 I have mapped ClustTaggableManager to a graphQL field in the past. I think I based most of the implementation of this article: https://jossingram.wordpress.com/2018/04/19/wagtail-and-graphql/

If it would be beneficial for you, I'd be happy to find out exactly how I implemented it and post here.

Note that implementing this will require you to have your own custom graphQL schema that you merge with grapples. It should be perfectly feasible, as outlined in https://github.com/torchbox/wagtail-grapple/issues/33

Let me know if this was useful!

tbrlpld commented 4 years ago

@IAmNatch Thanks for that. That works, but only gets me a string representation of the tags. I wish I could extracted the data in a more structured format.

tbrlpld commented 4 years ago

Ok, I was able to figure something out. To enable Grapple to make use of a custom type, the generic GraphQLField needs to be used with the custom type.

Here is how I put it to use:

# blog/schema.py

import graphene
from graphene_django.converter import convert_django_field
import grapple.models as gpl
import modelcluster.contrib.taggit as mct
import taggit.models as tgt

@convert_django_field.register(mct.ClusterTaggableManager)
def convert_tag_manager_to_string(field, registry=None):
    """Define converter type for ClusterTaggableManager."""
    return TagType()

class TagType(graphene.ObjectType):
    """Tag GraphQL Type."""

    tag_id = graphene.Int(name='id')
    name = graphene.String()

    def resolve_tag_id(self, _):  # noqa: D102
        return self.id

    def resolve_name(self, _):  # noqa: D102
        return self.name

class TagQuery(graphene.ObjectType):
    """Queries for the TagType."""

    tag = graphene.Field(TagType, id=graphene.Int())
    tags = graphene.List(TagType)

    def resolve_tag(self, _, id):  # noqa: D102
        return tgt.Tag.objects.get(pk=id)

    def resolve_tags(self, _):  # noqa: D102
        return tgt.Tag.objects.all().order_by('pk')

def GraphQLTags(field_name: str, **kwargs):  # noqa: N802
    """Custom grapple wrapper function for the TagType."""
    def Mixin():
        return gpl.GraphQLField(field_name, TagType, is_list=True, **kwargs)

    return Mixin

# blog/models.py

from blog.schema import GraphQLTags
...

class BlogPage(Page):
    ...
    tags = ClusterTaggableManager(
        through=BlogPageTag,
        blank=True,
    )

   ...
    graphql_fields = [
        ...
        GraphQLTags("tags"),
        ...
    ]