Skip to content

DjangoObjectType using the same django model do not resolve to correct relay object #1291

Open
@tony

Description

@tony

Note

This issue is a duplicate of #971 but includes a full description for searchability and links to history on the tracker itself.

What is the Current Behavior?

Assume a fixed schema with two (or more) different GraphQL object types using graphene_django.DjangoObjectType linked to the same Django model:

import graphene_django
from .models import Org as OrgModel

class Org(graphene_django.DjangoObjectType):
    class Meta:
        model = OrgModel
        fields = (
            "id",
            "name",
            "billing"
        )

class AnonymousOrg(graphene_django.DjangoObjectType):
    class Meta:
        model = OrgModel
        fields = (
            "id",
            "name",
        )

Assume a query to Org of ID 7eca71ed-ff04-4473-9fd1-0a587705f885.

btoa('Org:7eca71ed-ff04-4473-9fd1-0a587705f885')
'T3JnOjdlY2E3MWVkLWZmMDQtNDQ3My05ZmQxLTBhNTg3NzA1Zjg4NQ=='
{
  node(id: "T3JnOjdlY2E3MWVkLWZmMDQtNDQ3My05ZmQxLTBhNTg3NzA1Zjg4NQ==") {
    id
    __typename
    ... on Org {
      id
    }
  }
}

Response (incorrect):

{
  "data": {
    "node": {
      "id": "QW5vbnltb3VzT3JnOjdlY2E3MWVkLWZmMDQtNDQ3My05ZmQxLTBhNTg3NzA1Zjg4NQ==",
      "__typename": "AnonymousOrg"
    }
  }
}

It returns the other object type 'AnonymousOrg:7eca71ed-ff04-4473-9fd1-0a587705f885', despite the relay ID specifying it was an Org object.

What is the Expected Behavior?

Should return the object type specified in the relay ID.

Return (expected):

{
  "data": {
    "node": {
      "id": "T3JnOjdlY2E3MWVkLWZmMDQtNDQ3My05ZmQxLTBhNTg3NzA1Zjg4NQ==",
      "__typename": "Org"
    }
  }
}

Motivation / Use Case for Changing the Behavior

  • For node(id: "") based queries to handle object types based on the same Django model.
  • To resolve miscommunication and confusion between other issues and StackOverflow.

Environment

  • Version: 2.4.0
  • Platform: graphene 2.1.4

History

Other

Workaround

Graphene 2

Version 1

@boolangery posted a workaround on May 25, 2020:

class FixRelayNodeResolutionMixin:
    @classmethod
    def get_node(cls, info, pk):
        instance = super(FixRelayNodeResolutionMixin, cls).get_node(info, pk)
        setattr(instance, "graphql_type", cls.__name__)
        return instance

    @classmethod
    def is_type_of(cls, root, info):
        if hasattr(root, "graphql_type"):
            return getattr(root, "graphql_type") == cls.__name__
        return super(FixRelayNodeResolutionMixin, cls).is_type_of(root, info)

class PublicUserType(FixRelayNodeResolutionMixin, DjangoObjectType):
    class Meta:
        model = User
        interfaces = (graphene.relay.Node,)
        fields = ['id', 'first_name', 'last_name']

class UserType(FixRelayNodeResolutionMixin, DjangoObjectType):
    class Meta:
        model = User
        interfaces = (graphene.relay.Node,)
        fields = ['id', 'first_name', 'last_name', 'profile']

Version 2

ass FixRelayNodeResolutionMixin:
    """
    Fix issue where DjangoObjectType using same model aren't returned in node(id: )

    WARNING: This needs to be listed _before_ SecureDjangoObjectType when inherited.

    Credit: https://github.com/graphql-python/graphene-django/issues/971#issuecomment-633507631
    Bug: https://github.com/graphql-python/graphene-django/issues/1291
    """

    @classmethod
    def is_type_of(cls, root: Any, info: graphene.ResolveInfo) -> bool:
        # Special handling for the Relay `Node`-field, which lives at the root
        # of the schema. Inside the `graphene_django` type resolution logic
        # we have very little type information available, and therefore it'll
        # often resolve to an incorrect type. For example, a query for `Book:<UUID>`
        # would return a `LibraryBook`-object, because `graphene_django` simply
        # looks at `LibraryBook._meta.model` and sees that it is a `Book`.
        #
        # Here we use the `id` variable from the query to figure out which type
        # to return.
        #
        # See: https://github.com/graphql-python/graphene-django/issues/1291

        # Check if the current path is evaluating a relay Node field
        if info.path == ['node'] and info.field_asts:
            # Support variable keys other than id. E.g., 'node(id: $userId)'
            # Since `node(id: ...)` is a standard relay idiom we can depend on `id` being present
            # and the value field's name being the key we need from info.variable_values.
            argument_nodes = info.field_asts[0].arguments
            if argument_nodes:
                for arg in argument_nodes:
                    if arg.name.value == 'id':
                        # Catch direct ID lookups, e.g. 'node(id: "U3RvcmU6MQ==")'
                        if isinstance(arg.value, graphql.language.ast.StringValue):
                            global_id = arg.value.value
                            _type, _id = from_global_id(global_id)
                            return _type == cls.__name__

                        # Catch variable lookups, e.g. 'node(id: $projectId)'
                        variable_name = arg.value.name.value
                        if variable_name in info.variable_values:
                            global_id = info.variable_values[variable_name]
                            _type, _id = from_global_id(global_id)
                            return _type == cls.__name__

        return super().is_type_of(root, info)

Graphene 3

via August 19th, 2024, adaptation of above:

class FixRelayNodeResolutionMixin:
    """
    Fix issue where DjangoObjectType using same model aren't returned in node(id: )

    Credit: https://github.com/graphql-python/graphene-django/issues/971#issuecomment-633507631
    Bug: https://github.com/graphql-python/graphene-django/issues/1291
    """

    @classmethod
    def is_type_of(cls, root: Any, info: graphene.ResolveInfo) -> bool:
        # Special handling for the Relay `Node`-field, which lives at the root
        # of the schema. Inside the `graphene_django` type resolution logic
        # we have very little type information available, and therefore it'll
        # often resolve to an incorrect type. For example, a query for `Book:<UUID>`
        # would return a `LibaryBook`-object, because `graphene_django` simply
        # looks at `LibraryBook._meta.model` and sees that it is a `Book`.
        #
        # Here we use the `id` variable from the query to figure out which type
        # to return.
        #
        # See: https://github.com/graphql-python/graphene-django/issues/1291

        # Check if the current path is evaluating a relay Node field
        if info.path.as_list() == ['node'] and info.field_nodes:
            # Support variable keys other than id. E.g., 'node(id: $userId)'
            # Since `node(id: ...)` is a standard relay idiom we can depend on `id` being present
            # and the value field's name being the key we need from info.variable_values.
            argument_nodes = info.field_nodes[0].arguments
            if argument_nodes:
                for arg in argument_nodes:
                    if arg.name.value == 'id':
                        # Catch direct ID lookups, e.g. 'node(id: "U3RvcmU6MQ==")'
                        if isinstance(arg.value, graphql.language.ast.StringValueNode):
                            global_id = arg.value.value
                            _type, _id = from_global_id(global_id)
                            return _type == cls.__name__

                        # Catch variable lookups, e.g. 'node(id: $projectId)'
                        variable_name = arg.value.name.value
                        if variable_name in info.variable_values:
                            global_id = info.variable_values[variable_name]
                            _type, _id = from_global_id(global_id)
                            return _type == cls.__name__

        return super().is_type_of(root, info)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions