Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
danmess opened this issue Jan 31, 2023 · 5 comments

Comments

@danmess
Copy link

danmess commented Jan 31, 2023

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
Copy link
Member

zerolab commented Jan 31, 2023

please add the full traceback

@danmess
Copy link
Author

danmess commented Jan 31, 2023

@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 "<frozen importlib._bootstrap>", line 1206, in _gcd_import
    <source code not available>
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
    <source code not available>
  File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
    <source code not available>
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
    <source code not available>
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
    <source code not available>
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
    <source code not available>
  File "/usr/local/lib/python3.11/site-packages/grapple/schema.py", line 88, in <module>
    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 <lambda>
    field_type = lambda: registry.snippets[mdl]  # noqa: E731

Exception Type: KeyError at /api/graphql/
Exception Value: <class 'apps.my_app.models.Foo'>

@danmess
Copy link
Author

danmess commented Jan 31, 2023

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 <lambda>
    field_type = lambda: registry.snippets[mdl]  # noqa: E731

The above exception (<class 'apps.my_app.models.Foo'>) 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 "<frozen importlib._bootstrap>", line 1206, in _gcd_import
    <source code not available>
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
    <source code not available>
  File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
    <source code not available>
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
    <source code not available>
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
    <source code not available>
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
    <source code not available>
  File "/usr/local/lib/python3.11/site-packages/grapple/schema.py", line 88, in <module>
    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. <class 'apps.my_app.models.Foo'>

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 zerolab changed the title Snippets are not registering using wagtail hooks Add support for snippets registerd with register_snippet as a function instead of decorator Jan 31, 2023
@zerolab
Copy link
Member

zerolab commented Jan 31, 2023

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

@zerolab zerolab changed the title Add support for snippets registerd with register_snippet as a function instead of decorator Add support for snippets registered with register_snippet as a function instead of decorator (Wagtail 4.1+) Jan 31, 2023
@danmess
Copy link
Author

danmess commented Jan 31, 2023

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"),
    ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants