Graphene-django: Unable to subclass or emulate DjangoObjectType

Created on 8 Apr 2018  路  15Comments  路  Source: graphql-python/graphene-django

Subclassing DjangoObjectType so as to customise it for use in derived objects in the schema causes the execution of:

assert is_valid_django_model(model)

which fails the assertion due toclass MyNewClass(DjangoObjectType): not having a meta class, as it meant to be a subclass of DjangoObjectType for reuse later on, not and thus does not have a Meta class of its own.

Its also not possible to emulate DjangoObjectType by copying DjangoObjectType, subclassing the new class from ObjectType, this gets past assert is_valid_django_model(model) but then things run into trouble in graphene_django.registry.Registry which runs:

assert issubclass(cls, DjangoObjectType), 'Only DjangoObjectTypes can be registered, received "{}"'.format(cls.__name__)

Which fails due to the explicit check for DjangoObjectType.

It shouldn't require a fork of the entirety of graphene-django just to create a subclass of DjangoObjectType, either the Registry should be more flexible, or DjangoObjectType should be more flexible.

One option might be to use of Abstract Base Classes to enable a specific set of checks via https://docs.python.org/3/reference/datamodel.html?highlight=__subclass#class.__subclasscheck__

Another way would be to just drop these assertions and throw appropriate exceptions when we reach the situations they were designed to guard against. If we check that its a Django model to ensure that Meta exists, why not just throw a "Not A Django Model" exception when we try to do something later on that actually needs the Django model Meta information instead of asserting something way up the call stack that gets in the way of other things.

wontfix

Most helpful comment

I think this should probably stay open since it represents a rather significant limitation that deserves some kind of solution.

All 15 comments

I just hit a similar issue. That check in DjangoObjectType essentially forbids subclasses, and I was depending on that to implement authorization. I'm open to suggestions on how to implement filtering per user in a straightforward way.

@roddds At the moment, I've been forced to fork the entire graphene-django package to build on the features that I needed. I've built a solution for https://github.com/graphql-python/graphene/issues/572 which wasn't too hard once I worked out how everything was put together under the hood by django-parler and a little bit about how graphene-django inspects the model, but after working this out, I ran face first into this unable to extend/emulate issue.

Unfortunately, I haven't had the time to spend fully investigating how graphene and graphene-django work so that I can propose concrete changes to the code to resolve this. So for now I've stuck with the less than ideal, fork + branch with changes approach.

@techdragon Hi, could you please point out how you dealt with the specific django-parler issue (or share your fork). Thanks!

Ping on this, I've just hit this as a bit of a showstopper bug in our GraphQL implementation.

Is there any progress?

Any progress?

Also just ran into this issue trying to create an AutocompleteType that only allows two fields ("id" and "display"). Basically had to create a mixin I include with every Type that is supposed to be for the autocomplete query. Hopefully this gives another use case for this being useful.

A little ugly, but: personally I ~fixed~ worked around this by (in the subclass that's supposed to be a parent) referencing django.contrib.auth.models.User with a local registry that's never used again:

from django.contrib.auth.models import User

class MyDOTSubclass(DjangoObjectType):
    class Meta:
        model = User
        skip_registry = True

    additional_magic = ...

class SomeModelObjectType(MyDOTSubclass):
    class Meta:
        model = SomeModel

Why this works:

  • User is always a valid Django model
  • setting skip_registry = True makes it so that this ObjectType isn't registered for the User model, so you could still register a sensible ObjectType for that model if necessary anywhere in your application
  • the Meta class isn't inherited to children, so skip_registry won't be True for them and they will be registered

Obviously this isn't nice in a number of ways, but if it's a showstopper for you otherwise :)

@cobalamin thanks for this! I've just applied this technique on our codebase it it worked perfectly. Our previous hack was to use a dynamic base class which mypy then couldn't inspect, so we've now got more idiomatic Python, simpler code, better linting and better typechecking!

But who stops you from using

class Meta:
    abstract = True

in a subclass ?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

I think this should probably stay open since it represents a rather significant limitation that deserves some kind of solution.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

This should probably be reopened unless it's been fixed.

As mentioned by @techdragon this thing works for me:

class DjangoDynamicLoaderObjectType(DjangoObjectType):
    # ...
    class Meta:
        abstract = True

    #...
Was this page helpful?
0 / 5 - 0 ratings

Related issues

artofhuman picture artofhuman  路  3Comments

MythicManiac picture MythicManiac  路  3Comments

hyusetiawan picture hyusetiawan  路  4Comments

mraak picture mraak  路  3Comments

Eraldo picture Eraldo  路  3Comments