Problem
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
Solution 1
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.
Solution 2
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
Update on 2
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
Request
I am stuck at the moment. I have a few questions:
- Is this an unusual requirement to begin with? Am I asking the wrong question when I wish to retain the ability to filter by pk
- Is there a standard pattern to solve this problem?
Related Issue on Github
https://github.com/graphql-python/graphene-django/issues/349
Versions
- graphene-django==2.1.0
- django==1.9.12
- django-filter==1.0.1
- python==2.7.13