如何构建 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 客户端):
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。)
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(),
});
我有 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 客户端):
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。)
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(),
});