question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

offset pagination support for relay connection

See original GitHub issue

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?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:7
  • Comments:7

github_iconTop GitHub Comments

6reactions
tpictcommented, Mar 17, 2020

Not stale! This is a very common requirement and @NateScarlet’s PR provides it.

5reactions
tcleonardcommented, Jul 28, 2020

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

9. Relay Support & Pagination - Join Monster
This approach is based of the OFFSET keyword in SQL – often used to get numbered pages. It uses a predictable, position-based integer...
Read more >
GraphQL Pagination Primer: Offset vs. Cursor vs. Relay-Style ...
Offset -based pagination is useful when you need to know the total number of pages available. It can also easily support bi-directional ...
Read more >
GraphQL Cursor Connections Specification - Relay
This specification aims to provide an option for GraphQL clients to consistently handle pagination best practices with support for related metadata via a ......
Read more >
Understanding pagination: REST, GraphQL, and Relay
A connection is a paginated field on an object — for example, the friends field on a user or the comments field on...
Read more >
Effortless Pagination with GraphQL and Relay? Really!
You start to realize that the cursor-based setup of a connection, along with a Relay pagination container, does not lend itself to this...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found