graphql-python / graphene-django

Build powerful, efficient, and flexible GraphQL APIs with seamless Django integration.

Home Page:http://docs.graphene-python.org/projects/django/en/latest/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Automatic object type field for reverse one-to-one model fields cannot resolve when model field has `related_query_name` and object type has custom `get_queryset`

MrThearMan opened this issue · comments

What is the current behavior?

Given the following models:

from django.db import models

class Example(models.Model):
    pass

class Related(models.Model):
    example = models.OneToOneField(
        Example,
        on_delete=models.CASCADE,
        related_name="related",
        # Important!
        related_query_name="related_item",
    )

... and the following ObjectTypes:

import graphene
from graphene_django import DjangoObjectType
from graphene_django.fields import DjangoConnectionField

class ExampleType(DjangoObjectType):
    class Meta:
        model = Example
        fields = [
            "id",
            "related",
        ]
        connection_class = graphene.Connection
        interfaces = (graphene.relay.Node,)

class RelatedType(DjangoObjectType):
    class Meta:
        model = Related
        fields = [
            "id",
            "example",
        ]
        connection_class = graphene.Connection
        interfaces = (graphene.relay.Node,)
        
    # Important!
    @classmethod
    def get_queryset(cls, queryset, info):
        return queryset

... and the following query definitions:

import graphene
from graphene_django.fields import DjangoConnectionField

class Query(graphene.ObjectType):
    examples = DjangoConnectionField(ExampleType)
    related = DjangoConnectionField(RelatedType)

Now, trying to query like this:

query {
  examples {
    edges {
      node {
        id
        related {
          id
        }
      }
    }
  }
}

... will result in an error like this:

[
  {
    "locations": [
      {
        "column": 9,
        "line": 3,
      },
    ],
    "message": "Example has no field named 'related'",
    "path": [
      "examples",
      "edges",
      0,
      "node",
      "related",
    ],
  }
]

What causes the current behavior?

This happens because of two things:

  1. The Related model field example is a OneToOneField, which has defined a related_query_name different from the field's related_name.

  2. The RelatedType ObjectType defined the get_queryset class method.

When graphene_django.converter.convert_onetoone_field_to_djangomodel creates a the ObjectType for the ExampleType field related, it uses the RelatedType ObjectType's queryset for this check: graphene_django.converter.py:283

Since the RelatedType's get_queryset was modified, this check does not early return, thus we use the custom_resolver below that.

When custom_resolver resolves, it tries to fetch reversed_field_name from the class fields (uses django.db.models.options.py:649). Since related is a reverse relation, it does not exist in _forward_fields_map. However, it's not found from fields_map either, since the keys in fields_map use fields' related_query_names instead of related_names.

Therefore, automatic field object type creation fails in this case (reverse one-to-one fields with get_queryset defined).


What is the expected behavior?

I should be able to make the query described above with the given object type configuration.


What is the motivation / use case for changing the behavior?

I should be able to rely on automatic field generaton for reverse one-to-one relations with related_query_name to object types that have defined get_queryset. This reduces boilerplate code, since otherwise I would need to define the field and resolver manually.


Please tell us about your environment:

  • Version: v3.1.5
  • Platform: Windows 11