I have 4 types of profiles. Some fields are the same, some are different. Each profile has its own url, so I use ContentType as a central place of mappring 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. Example of direct lookup can be found 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
is inline fragments (details).
As you can see, we query 2 fragments in the example above (however in my case it should be 4, according to the number of profiles type), but just one fragment/model returns data - the one which refers to the provided profileUrl
in the query.
From front end side based on the received modelName
we can handle fields according to our needs.
Also the query mentioned above with fragments > 1 throws 2 errors in browser's console (I use Apollo client on Front End):
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 has incorrect link. The correct one is this.)
WARNING: heuristic fragment matching going on!.
It seems this is a bug (github issue).
In order to fix it, in Apollo settings we need to add cache option, which should look like:
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(),
});