Hi all, I have a use case to paginate results based on an offset. I figured I would be able to add "offset" into my graphQL query to achieve this. (source: https://graphql.org/learn/pagination/)
Specifically, I want to implement fetching page 3 results of a list, where each page has a size of 20. Doing this query throws "unknown argument "offset" on field allEntityA"
query {
allEntityA(first: 20, offset: 40) {
edges {
node {
}
}
}
}
I have already trasnformed my Django model into a type relay.Node)
class entityANode(DjangoObjectType):
class Meta:
model = models.EntityA
interfaces = (relay.Node,)
How can I achieve jumping from page 1 to 3 without using cursor navigation?
I've archive this by #563, but no one come to review.
You can try my https://github.com/NateScarlet/graphene-django-tools
Our team would find #563 really helpful, is there anything we can do to help get it merged?
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.
Not stale! This is a very common requirement and @NateScarlet's PR provides it.
Hi all, I have a use case to paginate results based on an offset. I figured I would be able to add "offset" into my graphQL query to achieve this. (source: https://graphql.org/learn/pagination/)
Specifically, I want to implement fetching page 3 results of a list, where each page has a size of 20. Doing this query throws "unknown argument "offset" on field allEntityA"
query { allEntityA(first: 20, offset: 40) { edges { node { } } } }I have already trasnformed my Django model into a type
relay.Node)class entityANode(DjangoObjectType): class Meta: model = models.EntityA interfaces = (relay.Node,)How can I achieve jumping from page 1 to 3 without using cursor navigation?
I have written a blog to address this issue. It can be achieved using "first" and "last" arguments only
read more here https://cloudworks.dukamneti.co.ke/blog/go-specific-page-graphene-django/
This is actually very easy to implement considering the underlying pagination in Graphene Django is an offset pagination.
Indeed the cursor contains simply an offset already so you can rely on that.
Here is a simple implementation that can be used in case it does not get incorporated in the project (it could be incorporated in the DjangoFilterConnectionField class as described here):
from graphql_relay.connection.arrayconnection import cursor_to_offset, offset_to_cursor
from graphene_django.filter import DjangoFilterConnectionField
class OffsetConnectionField(DjangoFilterConnectionField):
聽 聽 def __init__(self, *args, **kwargs):
聽 聽 聽 聽 kwargs.setdefault("offset", graphene.Int())
聽 聽 聽 聽 super().__init__(*args, **kwargs)
聽 聽 @classmethod
聽 聽 def connection_resolver(
聽 聽 聽 聽 聽 聽 cls,
聽 聽 聽 聽 聽 聽 resolver,
聽 聽 聽 聽 聽 聽 connection,
聽 聽 聽 聽 聽 聽 default_manager,
聽 聽 聽 聽 聽 聽 queryset_resolver,
聽 聽 聽 聽 聽 聽 max_limit,
聽 聽 聽 聽 聽 聽 enforce_first_or_last,
聽 聽 聽 聽 聽 聽 root,
聽 聽 聽 聽 聽 聽 info,
聽 聽 聽 聽 聽 聽 **args
聽 聽 ):
聽 聽 聽 聽 """
聽 聽 聽 聽 Check parameter compatibility for `offset`.
聽 聽 聽 聽 Using offset with before could lead to negative indexing which is not supported by Relay so we forbid it altogether.
聽 聽 聽 聽 """
聽 聽 聽 聽 offset = args.get("offset")
聽 聽 聽 聽 before = args.get("before")
聽 聽 聽 聽 if offset is not None:
聽 聽 聽 聽 聽 聽 assert before is None, (
聽 聽 聽 聽 聽 聽 聽 聽 "You can't provide a `before` at the same time as an `offset` value to properly paginate the `{}` connection."
聽 聽 聽 聽 聽 聽 ).format(info.field_name)
聽 聽 聽 聽 return super().connection_resolver(
聽 聽 聽 聽 聽 聽 resolver,
聽 聽 聽 聽 聽 聽 connection,
聽 聽 聽 聽 聽 聽 default_manager,
聽 聽 聽 聽 聽 聽 queryset_resolver,
聽 聽 聽 聽 聽 聽 max_limit,
聽 聽 聽 聽 聽 聽 enforce_first_or_last,
聽 聽 聽 聽 聽 聽 root,
聽 聽 聽 聽 聽 聽 info,
聽 聽 聽 聽 聽 聽 **args
聽 聽 聽 聽 )
聽 聽 @classmethod
聽 聽 def resolve_connection(cls, connection, args, iterable, max_limit=None):
聽 聽 聽 聽 """
聽 聽 聽 聽 Take the offset out of the argument and if it is not `None` convert it to a `after` cursor.
聽 聽 聽 聽 """
聽 聽 聽 聽 offset = args.pop("offset", None)
聽 聽 聽 聽 after = args.get("after")
聽 聽 聽 聽 if offset:
聽 聽 聽 聽 聽 聽 if after:
聽 聽 聽 聽 聽 聽 聽 聽 offset += cursor_to_offset(after) + 1
# input offset starts at 1 while the graphene offset starts at 0
args["after"] = offset_to_cursor(offset - 1)
聽 聽 聽 聽 return super().resolve_connection(connection, args, iterable, max_limit)
and you just need to use OffsetConnectionField instead to define your query.
For example in the cookbook example of the documentation:
# cookbook/graphql/category.py
import graphene
from cookbook.models import Category, Ingredient
class CategoryNode(DjangoObjectType):
class Meta:
model = Category
interfaces = (graphene.relay.Node, )
class Query(graphene.ObjectType):
category = graphene.relay.Node.Field(CategoryNode)
all_categories = OffsetConnectionField(CategoryNode)
* Note *
@HenryKuria Your solution is not completely true. The combination of first and last does not completely cover the offset because of the RELAY_CONNECTION_MAX_LIMIT setting.
Indeed you can't set first: 310, last: 10 as it would raise an error.
While with the implementation that I proposed we use a cursor so we do not get this error when doing first: 10, offset: 300.
I think you can make a cursor by base64 encoding page offset like below
encode('arrayconnection:pageNumber')
It's not a robust solution but possible anyway.
Most helpful comment
Not stale! This is a very common requirement and @NateScarlet's PR provides it.