简体   繁体   中英

Django ForeignKey field ignored by graphene-django when "to" value is a string

Versions:

Python: 3.8
Django: 3.2.2
graphene-django: 2.15.0

The below issue worked was observed after upgrading to Django 3.2.2 from a 3.0 version.

I have an issue when using graphene-django with a ForeignKey field, it is ignored because the value of to is a string. By ignored, I mean it isn't converted to a GraphQL field. Here's the Django model:

class CableTermination(models.Model):
    cable = models.ForeignKey(
        to='dcim.Cable',
        on_delete=models.SET_NULL,
        related_name='+',
        blank=True,
        null=True
    )

The value of to is a string to avoid a circular import. This is also the only field (apart from pk ) on this model.

I've created a DjangoObjectType from this class:

class CableTerminationNodeType(DjangoObjectType):
    class Meta:
        model = CableTermination

I also have a type for Cable:

class CableNodeType(DjangoObjectType):
    class Meta:
        model = Cable

But on startup I see this error:

env/lib/python3.8/site-packages/graphql/type/definition.py", line 214, in define_field_map
    assert isinstance(field_map, Mapping) and len(field_map) > 0, (
AssertionError: CableTerminationNodeType fields must be a mapping (dict / OrderedDict) with field names as keys or a function which returns such a mapping.

I've tracked this down to field_map having length 0. I have also observed that the converter for the above cable field is called but returns None.

This is because field.related_model returns the string dcim.Cable but the registry can only lookup by class. So ultimately, _type is None below:

@convert_django_field.register(models.OneToOneField)
@convert_django_field.register(models.ForeignKey)
def convert_field_to_djangomodel(field, registry=None):
    model = field.related_model

    def dynamic_type():
        _type = registry.get_type_for_model(model)
        if not _type:
            return

        return Field(_type, description=field.help_text, required=not field.null)

    return Dynamic(dynamic_type)

Anyone come across a similar issue? Or is there something I should be doing differently?

It appears I can workaround this by overriding the converter and loading the model with django.apps . So I'm wondering if this is a valid bug and fix (which I should raise a PR for) or something going wrong on my end.

@convert_django_field.register(models.OneToOneField)
@convert_django_field.register(models.ForeignKey)
def convert_field_to_djangomodel(field, registry=None):
    model = field.related_model
    if isinstance(model, str):
        split = model.split('.', 1)
        model = apps.get_model(app_label=split[0], model_name=split[1])

    def dynamic_type():
        _type = registry.get_type_for_model(model)
        if not _type:
            return

        return Field(_type, description=field.help_text, required=not field.null)

    return Dynamic(dynamic_type)

It seems this is the problem, but it isn't.

The problem is that you didn't register a Cable type. When you look into the debugger you will see that field.related_model is already resolved: Django does this, graphene doesn't even see the string reference.

But if you don't get the Cable model into the registry by creating a DjangoObjectType for it and import it into the schema, graphene_django cannot find it.

To make it clear and assuming the app with the CableTermination model is called "main" and your settings file is in project/ :

# file: dcim/schema.py

from graphene_django import DjangoObjectType
from .models import Cable


class CableType(DjangoObjectType):
    class Meta:
        model = Cable

# main/schema.py

import graphene
from graphene_django import DjangoObjectType
from .models import CableTermination


class CableTerminationType(DjangoObjectType):
    class Meta:
        model = CableTermination


class Query(graphene.ObjectType):
    terminations = graphene.List(CableTerminationType)

    @staticmethod
    def resolve_terminations(root, info, **kwargs):
        return CableTermination.objects.all()

#project/schema.py:

import graphene
import main.schema

# import the schema with the CableType, so it registers itself
import dcim.schema


class Query(main.schema.Query, graphene.ObjectType):
    pass


schema = graphene.Schema(query=Query)

# project/settings.py

GRAPHENE = {
    "SCHEMA": 'project.schema.schema'
}

This is the minimal setup. Of course, if you create queries and/or mutations for the Cable model, this will resolve itself, since you will need to create a type for it.

Update:

Can't reproduce what you're describing with Django 3.2.3. Above solution still works for me.

调试器显示已解决的 field.related_model

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM