如何构建 GraphQL 模式以从内容类型模型到普通模型进行反向查找?

How to build GraphQL schema for reverse lookup from Content Type model to usual model?

我有 4 种类型的配置文件。有些字段相同,有些字段不同。每个配置文件都有自己的 url,因此我使用 ContentType 作为映射的中心位置 urls<->profiles.

# profiles/models.py
class Sport(models.Model):
    pass

class ProfileAbstract(models.Model):
    pass

class ProfileOrganization(ProfileAbstract):
    pass

class ProfilePlace(ProfileAbstract):
    pass

class ProfileTeam(ProfileAbstract):
    pass

class ProfileUser(ProfileAbstract):
    pass

class ProfileURL(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    slug = models.SlugField(max_length=30)  # Url

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.CharField(max_length=36)  # CharField because I use UUID
    content_object = GenericForeignKey('content_type', 'object_id')

P.S。可以找到直接查找的示例 in this github issue.

# profiles/schema.py
import graphene

from graphene_django.types import DjangoObjectType

from .models import (ProfileOrganization, ProfilePlace, ProfileTeam, ProfileUser,ProfileURL, Sport)

# [ START: Types ]
class SportType(DjangoObjectType):
    class Meta:
        model = Sport


class ProfileURLType(DjangoObjectType):
    class Meta:
        model = ProfileURL

    slug = graphene.String()
    model_name = graphene.String()

    def resolve_slug(self, info):
        return self.slug

    def resolve_model_name(self, info):
        return self.content_object.__class__.__name__  # ex. 'ProfileTeam'


class ProfileOrganizationType(DjangoObjectType):
    """
    Use this class as a basic class for other Profile Types classes
    """

    class Meta:
        model = ProfileOrganization
        fields = ('name', 'logo_data', 'profile_url')

    profile_url = graphene.Field(ProfileURLType)

    def resolve_profile_url(self, args):
        return self.profile_url.first()


class ProfilePlaceType(ProfileOrganizationType):
    class Meta:
        model = ProfilePlace


class ProfileTeamType(ProfileOrganizationType):
    class Meta:
        model = ProfileTeam


class ProfileUserType(ProfileOrganizationType):
    class Meta:
        model = ProfileUser


class ProfileTypeUnion(graphene.Union):
    class Meta:
        types = (ProfileOrganizationType, ProfileTeamType, ProfilePlaceType, ProfileUserType)
# [ END: Types ]


# [ START: Queries ]
class Query(graphene.ObjectType):
    """
    EXAMPLE OF QUERY:

    query profileDetails {
      profileDetails(profileUrl: "8auB-pMH-6Sh") {
        ... on ProfileTeamType {
          id,
          name,
          skillLevel,
          sport {
            name
          },
          profileUrl {
            slug,
            modelName
          }
        }

        ... on ProfilePlaceType {
          id,
          name,
          sports {
            name
          },
          profileUrl {
            slug,
            modelName
          }
        }
      }
    }
    """

    profile_details = graphene.Field(ProfileTypeUnion, required=True, profile_url=graphene.String())

    def resolve_profile_details(self, info, profile_url):
        profile_url_type = ContentType.objects.get(app_label='profiles', model='profileurl')
        profile_url_inst = profile_url_type.get_object_for_this_type(slug=profile_url)

        return profile_url_inst.content_object
# [ END: Queries ]

... on ProfileTeamType 是内联片段 (details)。 如您所见,我们在上面的示例中查询了 2 个片段(但在我的例子中,根据配置文件类型的数量,它应该是 4 个),但只有一个 fragment/model returns 数据 - 一个引用查询中提供的profileUrl


前端根据收到的modelName我们可以根据需要处理字段。

上面提到的片段 > 1 的查询也会在浏览器的控制台中抛出 2 个错误(我在前端使用 Apollo 客户端):

  1. You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types. Apollo Client will not be able to accurately map fragments. To make this error go away, use the IntrospectionFragmentMatcher as described in the docs: https://www.apollographql.com/docs/react/advanced/fragments.html#fragment-matcher

(error有不正确的link。正确的是this。)

  1. WARNING: heuristic fragment matching going on!.

这似乎是一个错误 (github issue)。

为了修复它,我们需要在 Apollo 设置中添加缓存选项,它应该如下所示:

import ApolloClient from 'apollo-client';
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http';

const fragmentMatcher = new IntrospectionFragmentMatcher({
  introspectionQueryResultData: {
    __schema: {
      types: []
    }
  }
})

const cache = new InMemoryCache({ fragmentMatcher });

const client = new ApolloClient({
  cache,
  link: new HttpLink(),
});