torchbox / wagtail-grapple

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

Add support for snippets registered with `register_snippet` as a function instead of decorator (Wagtail 4.1+) #309

Open danmess opened 1 year ago

danmess commented 1 year ago

I am trying to customize snippet view following official docs ( https://docs.wagtail.org/en/stable/topics/snippets.html#customising-snippets-admin-views ), but getting a KeyError at /api/graphql/.

Code to reproduce: wagail_hooks.py

from wagtail.admin.filters import WagtailFilterSet
from wagtail.snippets.models import register_snippet
from wagtail.admin.ui.tables import UpdatedAtColumn
from wagtail.snippets.views.snippets import SnippetViewSet

from .models import Foo

class FooFilterSet(WagtailFilterSet):
    class Meta:
        model = Foo
        fields = ["name", "type"]

class FooViewSet(SnippetViewSet):
    list_display = ["name", "type", UpdatedAtColumn()]
    filterset_class = FooFilterSet

register_snippet(Foo, viewset=FooViewSet)

models.py

class Foo(models.Model):
    name = models.CharField(max_length=255, blank=False)
    type = models.CharField(max_length=255, blank=False)

wagtail = "4.2rc1" wagtail-grapple = "^0.18.1"

zerolab commented 1 year ago

please add the full traceback

danmess commented 1 year ago

@zerolab I apologize, here it is:

Traceback ``` Traceback (most recent call last): File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/usr/local/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view return view_func(*args, **kwargs) File "/usr/local/lib/python3.11/site-packages/django/views/generic/base.py", line 63, in view self = cls(**initkwargs) File "/usr/local/lib/python3.11/site-packages/graphene_django/views.py", line 104, in __init__ schema = graphene_settings.SCHEMA File "/usr/local/lib/python3.11/site-packages/graphene_django/settings.py", line 127, in __getattr__ val = perform_import(val, attr) File "/usr/local/lib/python3.11/site-packages/graphene_django/settings.py", line 66, in perform_import return import_from_string(val, setting_name) File "/usr/local/lib/python3.11/site-packages/graphene_django/settings.py", line 80, in import_from_string module = importlib.import_module(module_path) File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "", line 1206, in _gcd_import File "", line 1178, in _find_and_load File "", line 1149, in _find_and_load_unlocked File "", line 690, in _load_unlocked File "", line 940, in exec_module File "", line 241, in _call_with_frames_removed File "/usr/local/lib/python3.11/site-packages/grapple/schema.py", line 88, in schema = create_schema() File "/usr/local/lib/python3.11/site-packages/grapple/schema.py", line 79, in create_schema return graphene.Schema( File "/usr/local/lib/python3.11/site-packages/graphene/types/schema.py", line 78, in __init__ self.build_typemap() File "/usr/local/lib/python3.11/site-packages/graphene/types/schema.py", line 167, in build_typemap self._type_map = TypeMap( File "/usr/local/lib/python3.11/site-packages/graphene/types/typemap.py", line 80, in __init__ super(TypeMap, self).__init__(types) File "/usr/local/lib/python3.11/site-packages/graphql/type/typemap.py", line 31, in __init__ self.update(reduce(self.reducer, types, OrderedDict())) # type: ignore File "/usr/local/lib/python3.11/site-packages/graphene/types/typemap.py", line 88, in reducer return self.graphene_reducer(map, type) File "/usr/local/lib/python3.11/site-packages/graphene/types/typemap.py", line 117, in graphene_reducer return GraphQLTypeMap.reducer(map, internal_type) File "/usr/local/lib/python3.11/site-packages/graphql/type/typemap.py", line 109, in reducer field_map = type_.fields File "/usr/local/lib/python3.11/site-packages/graphql/pyutils/cached_property.py", line 22, in __get__ value = obj.__dict__[self.func.__name__] = self.func(obj) File "/usr/local/lib/python3.11/site-packages/graphql/type/definition.py", line 198, in fields return define_field_map(self, self._fields) File "/usr/local/lib/python3.11/site-packages/graphql/type/definition.py", line 212, in define_field_map field_map = field_map() File "/usr/local/lib/python3.11/site-packages/graphene/types/typemap.py", line 275, in construct_fields_for_type map = self.reducer(map, field.type) File "/usr/local/lib/python3.11/site-packages/graphene/types/field.py", line 119, in type return get_type(self._type) File "/usr/local/lib/python3.11/site-packages/graphene/types/utils.py", line 45, in get_type return _type() File "/usr/local/lib/python3.11/site-packages/grapple/models.py", line 68, in field_type = lambda: registry.snippets[mdl] # noqa: E731 Exception Type: KeyError at /api/graphql/ Exception Value: ```
danmess commented 1 year ago

Getting slightly different traceback after upgrading to wagtail-grapple = "^0.19.2". The issue resolves itself when I use @register_snippet decorator, but it seems there is no way to customize the admin views for snippets with this approach.

Traceback ``` Traceback (most recent call last): File "/usr/local/lib/python3.11/site-packages/graphql/type/definition.py", line 808, in fields fields = resolve_thunk(self._fields) File "/usr/local/lib/python3.11/site-packages/graphql/type/definition.py", line 300, in resolve_thunk return thunk() if callable(thunk) else thunk File "/usr/local/lib/python3.11/site-packages/graphene/types/schema.py", line 312, in create_fields_for_type field_type = create_graphql_type(field.type) File "/usr/local/lib/python3.11/site-packages/graphene/types/field.py", line 116, in type return get_type(self._type) File "/usr/local/lib/python3.11/site-packages/graphene/types/utils.py", line 42, in get_type return _type() File "/usr/local/lib/python3.11/site-packages/grapple/models.py", line 68, in field_type = lambda: registry.snippets[mdl] # noqa: E731 The above exception () was the direct cause of the following exception: File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/usr/local/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view return view_func(*args, **kwargs) File "/usr/local/lib/python3.11/site-packages/django/views/generic/base.py", line 63, in view self = cls(**initkwargs) File "/usr/local/lib/python3.11/site-packages/graphene_django/views.py", line 100, in __init__ schema = graphene_settings.SCHEMA File "/usr/local/lib/python3.11/site-packages/graphene_django/settings.py", line 125, in __getattr__ val = perform_import(val, attr) File "/usr/local/lib/python3.11/site-packages/graphene_django/settings.py", line 64, in perform_import return import_from_string(val, setting_name) File "/usr/local/lib/python3.11/site-packages/graphene_django/settings.py", line 78, in import_from_string module = importlib.import_module(module_path) File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "", line 1206, in _gcd_import File "", line 1178, in _find_and_load File "", line 1149, in _find_and_load_unlocked File "", line 690, in _load_unlocked File "", line 940, in exec_module File "", line 241, in _call_with_frames_removed File "/usr/local/lib/python3.11/site-packages/grapple/schema.py", line 88, in schema = create_schema() File "/usr/local/lib/python3.11/site-packages/grapple/schema.py", line 79, in create_schema return graphene.Schema( File "/usr/local/lib/python3.11/site-packages/graphene/types/schema.py", line 440, in __init__ self.graphql_schema = GraphQLSchema( File "/usr/local/lib/python3.11/site-packages/graphql/type/schema.py", line 218, in __init__ collect_referenced_types(type_) File "/usr/local/lib/python3.11/site-packages/graphql/type/schema.py", line 432, in collect_referenced_types for field in named_type.fields.values(): File "/usr/local/lib/python3.11/functools.py", line 1001, in __get__ val = self.func(instance) File "/usr/local/lib/python3.11/site-packages/graphql/type/definition.py", line 811, in fields raise cls(f"{self.name} fields cannot be resolved. {error}") from error Exception Type: TypeError at /api/graphql/ Exception Value: HeroInteriorBlock fields cannot be resolved. ```

My HeroInteriorBlock contains the following code:

@register_streamfield_block
class HeroInteriorBlock(BaseStructBlock):
    foo = SnippetChooserBlock("my_app.Foo", required=False)

    graphql_fields = BaseStructBlock.graphql_fields + [
        GraphQLSnippet("foo", "my_app.Foo"),
    ]
zerolab commented 1 year ago

Thanks for this @danmess. It most certainly helps.

Registering snippets via register_snippet as a function (with the ability to customise the viewsets) was added in Wagtail 4.1

We need to adjust our registry to support that model

danmess commented 1 year ago

Thank you for your quick replies, @zerolab!

For everyone else encountering this issue current workaround would be to serialize SnippetChooserBlock with GraphQLForeignKey.

@register_streamfield_block
class HeroInteriorBlock(BaseStructBlock):
    foo = SnippetChooserBlock("my_app.Foo", required=False)

    graphql_fields = BaseStructBlock.graphql_fields + [
        GraphQLForeignKey("foo", "my_app.Foo"),
    ]