Graphene-django: How to declare custom global ID

Created on 19 Dec 2017  路  10Comments  路  Source: graphql-python/graphene-django

Code

class ShipNode(DjangoObjectType):
    class Meta:
        model = Ship
        filter_fields = ['id', 'size']
        interfaces = (relay.Node, )

    rowid = graphene.String()

    @resolve_only_args
    def resolve_rowid(self):
        return self.id

class Query(object):
    ship = relay.Node.Field(ShipNode)
    all_ships = DjangoFilterConnectionField(ShipNode)

I have an id field in my database which gets override when querying GraphQL server by the custom global ID. Using resolve_rowid, I can get the id I want, but I am unable to use it in the filters.

query {
    ship(id: "124567") {
    }
 }

How can I filter based on my database's primary key instead of the global ID? How can I rename global ID from id to _id?

wontfix 鉁╡nhancement 馃憖 more info needed

Most helpful comment

I think the people behind the graphene-django missed out how confusing, frustrating and all other dirty epithets is to work with shadowed-out id of an object.

All 10 comments

I'm in the same boat. Pretty certain it's a misunderstanding on our end of how relay works with its global id's, but still can't get my head around how to get an object with its model pk 馃槥

Since ShipNode implements the relay.Node interface you should get an id field on ShipNode which will be the global ID for that node (a global ID by default is just the base64("{type}:{id}")

So in your example you should be able to do:

query {
    allShips {
        edges {
            node {
                id
            }
        }
    }
}

which should give you something like:

'allShips': {
    'edges': [
        {
            'node': {
                'id': 'U2hpcE5vZGU6MTI0NTY3',
            }
        },
        // more nodes
    ]
}

Then using that ID you should be able to query the ship field:

query {
    ship(id: "U2hpcE5vZGU6MTI0NTY3") {
        ... on ShipNode {
            size
        }
    }
}

Notice that if you base64 decode U2hpcE5vZGU6MTI0NTY3 you get ShipNode:124567

@samkit-jain

How can I rename global ID from id to _id?

At this time, even the monkey-patching at runtime of classes Node, AbstractNode and NodeField from file _graphene/relay/node.py_ does not help (spent 2 days to figure it out).
You need to manually patch the graphene lib in this file and rename shadowing id to something you like (maybe, "global_id"?):

class NodeField(Field):
    def __init__(self, node, type=False, deprecation_reason=None, name=None, **kwargs):
        assert issubclass(node, Node), "NodeField can only operate in Nodes"
        self.node_type = node
        self.field_type = type
        super(NodeField, self).__init__(
            # If we don's specify a type, the field type will be the node
            # interface
            type or node,
            description="The ID of the object",
            global_id=ID(required=True),  # <<<<<<<<<<< there
        )

    def get_resolver(self, parent_resolver):
        return partial(self.node_type.node_resolver, get_type(self.field_type))


class AbstractNode(Interface):
    class Meta:
        abstract = True

    @classmethod
    def __init_subclass_with_meta__(cls, **options):
        _meta = InterfaceOptions(cls)
        _meta.fields = OrderedDict(
            global_id=GlobalID(cls, description="The ID of the object.")  # <<<<<<<< there
        )
        super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options)


class Node(AbstractNode):
    """An object with an ID"""

    @classmethod
    def Field(cls, *args, **kwargs):  # noqa: N802
        return NodeField(cls, *args, **kwargs)

    @classmethod
    def node_resolver(cls, only_type, root, info, global_id):  # <<<<<<<<<<<<<< there
        return cls.get_node_from_global_id(info, global_id, only_type=only_type)  # <<<<<<<<<<<<<< and there

then do this

class ShipNode(DjangoObjectType):
    def resolve_global_id(self, info):
        return self.pk

but even then the filtering by good old digital id will require additional work.

How can I filter based on my database's primary key instead of the global ID?

You need to give up using relay.Node and relay.Node.Field (and all their magic) and do something like

class ShipNode(DjangoObjectType):
    class Meta:
        model = Ship
        # filter_fields = ['id', 'size']
        # interfaces = (relay.Node, )

class Query(object):
    ship = graphene.Field(ShipNode, id=graphene.String(required=True))

    def resolve_ship(self, info, id):
        return Ship.objects.get(pk=id)

I think the people behind the graphene-django missed out how confusing, frustrating and all other dirty epithets is to work with shadowed-out id of an object.

@webel

how to get an object with its model pk

class ShipNode(DjangoObjectType):
    pk = graphene.Int(source='pk')

What's the workaround here? While I like the Global ID declaration of Graphene-Relay, I hate that it shadows the ID field and makes it impossible to filter & query Connection objects using ID in Relay.

One option is, of course, to redeclare the ID in a pk field, but this is tedious and verbose. Also, to get filtering to work, the FilterSet also needs an explicit pk declaration :(

Any ideas on how to enable:

  • The use of global IDs for single object fetching
  • While retaining the Django ORM object IDs for filtering

Also posted a question on StackOverflow here:
https://stackoverflow.com/questions/54328681/enable-pk-based-filtering-in-django-graphene-relay-while-retaining-global-ids/54344308#54344308

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Can we keep this issue open please? I'm vexed.

Can someone explain in an example, hopefully in a repo, with a CI test, how we override ID to return the database pk?

I have tried various stabs at monkeypatching and still can't get the actual ID to display. Lookups work. But I am using UUID Primary Keys and they're not showing in the results.

My temp solution is to rename all models' id's to uid = models.AutoField(primary_key=True)

I have added 2 PRs in the graphene repository that could solve this issue I think: https://github.com/graphql-python/graphene/issues/1276

Was this page helpful?
0 / 5 - 0 ratings

Related issues

licx picture licx  路  3Comments

eyalch picture eyalch  路  3Comments

hyusetiawan picture hyusetiawan  路  4Comments

amiyatulu picture amiyatulu  路  3Comments

dan-klasson picture dan-klasson  路  4Comments