I am using django-graphene with Relay on our GraphQL Server. The implementation imposes a Global ID requirement in the graphene.relay.Node
class that overrides and hides Django's ID field.
As a result, I can query like this:
{
allBatches(id:"QmF0Y2hOb2RlOjE=") {
edges {
node {
id
pk
}
}
}
}
And get this response:
{
"data": {
"allBatches": {
"edges": [
{
"node": {
"id": "QmF0Y2hOb2RlOjE=",
"pk": 1
}
}
]
}
}
}
However, what I lose is the ability to filter by the original ID (or PK) field of the Object itself:
{
allBatches(id:1) {
edges {
node {
id
pk
}
}
}
}
In fact, I simply cannot filter objects by ID.
I can think of two possible work-arounds to this:
1. Prevent django-graphene-relay from hijacking and shadowing the id
field, perhaps force it to use a different field name such as gid
2. Find a way to include pk
as a special field that is available both as a property and in filter
I have made no progress on 1 since it appears as though django-graphene
(and perhaps the relay standard) imposes a limitation that this field be called id
. I see that id
has been used as a Magic String in multiple places and there does not appear to be a standard way to change the field name.
On 2, I can get the property to work with a Mixin
like this:
class PKMixin(object):
pk = graphene.Field(type=graphene.Int, source='pk')
However, I am unable to get the filtering via django-filter
to work, since the FilterSet
does not have the field pk
declared and breaks with the following error
'Meta.fields' contains fields that are not defined on this FilterSet: pk
I tried the following:
class PKFilteringNode(Node):
@classmethod
def get_node_from_global_id(cls, info, global_id, only_type=None):
# So long as only_type is set; if we detect that the global_id is a pk and not a global ID;
# then coerce it to be a proper global ID before fetching
if only_type:
try:
int(global_id)
global_id = cls.to_global_id(only_type._meta.name, global_id)
return super(PKFilteringNode, cls).get_node_from_global_id(info, global_id, only_type)
except ValueError:
pass
return super(PKFilteringNode, cls).get_node_from_global_id(info, global_id, only_type)
And now I can get GraphQL to do this:
{
batchA: batch(id: "QmF0Y2hOb2RlOjE=") {
id
name
}
batchB: batch(id: 1) {
id
name
}
}
{
"data": {
"batchA": {
"id": "QmF0Y2hOb2RlOjE=",
"name": "Default Batch"
},
"batchB": {
"id": "QmF0Y2hOb2RlOjE=",
"name": "Default Batch"
}
}
}
But I have a fairly strong fear this will break something downstream, at the level of caching perhaps? Also this does not allow filtering by ID still since filtering depends on
DjangoFilterConnectionField
I am stuck at the moment. I have a few questions:
https://github.com/graphql-python/graphene-django/issues/349
I am not sure that you still want the answer or not, but at least let me try to answer to your question. Correct if my understanding is wrong. I just willing to help
Actually pk
supposed to be DetailView
not ListView
that being used with filter
.
requirements.txt
graphene-django==2.7.1
django==3.0.1
django-filter==2.2.0
python==3.8.1
models.py
from django.contrib.auth import get_user_model
from django.db import models
User = get_user_model()
class Objection(models.Model):
detail = models.TextField(null=True, blank=True)
hidden = models.BooleanField(default=False)
report = models.BooleanField(default=False)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='objections',
related_query_name='objection')
nodes.py
import django_filters
import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from multy_herr.objections.models import Objection
class ObjectionFilter(django_filters.FilterSet):
pk = django_filters.NumberFilter(field_name='pk')
class Meta:
model = Objection
fields = [
'pk',
]
class ObjectionNode(DjangoObjectType):
pk = graphene.Field(type=graphene.Int, source='id')
class Meta:
model = Objection
fields = [
'id',
'pk',
'detail',
'hidden',
'report',
]
filter_fields = {
'pk': ['exact'],
'detail': ['icontains', 'istartswith'],
'created_by__name': ['icontains', ],
'hidden': ['exact'],
'report': ['exact'],
}
interfaces = (relay.Node,)
queries.py
import graphene
from graphene import relay
from graphene_django.filter import DjangoFilterConnectionField
from multy_herr.objections.grapheql.nodes import ObjectionNode, ObjectionFilter
from multy_herr.objections.models import Objection
class ObjectionQuery(graphene.ObjectType):
objection = relay.Node.Field(ObjectionNode)
all_objections = DjangoFilterConnectionField(ObjectionNode,
filterset_class=ObjectionFilter)
def resolve_all_objections(self, info, **kwargs):
if info.context.user.is_authenticated is False:
return Objection.objects.none()
return Objection.objects.filter(created_by=info.context.user)
I leave comment in query
here for analogy. With my hackish solution Insomnia
application will warns me with Unknown argument pk ...
. But works
query
query{
# objection(id: "T2JqZWN0aW9uTm9kZTo1"){
# id
# report
# hidden
# }
allObjections(pk: 5){
edges{
node{
id
pk
hidden
report
}
}
}
}
response
{
"data": {
"allObjections": {
"edges": [
{
"node": {
"id": "T2JqZWN0aW9uTm9kZTo1",
"pk": 5,
"hidden": false,
"report": false
}
}
]
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With