Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

offset pagination support for relay connection #809

Closed
marxlow opened this issue Nov 1, 2019 · 7 comments
Closed

offset pagination support for relay connection #809

marxlow opened this issue Nov 1, 2019 · 7 comments

Comments

@marxlow
Copy link

marxlow commented Nov 1, 2019

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?

@NateScarlet
Copy link

I've archive this by #563, but no one come to review.
You can try my https://github.com/NateScarlet/graphene-django-tools

@tpict
Copy link

tpict commented Dec 18, 2019

Our team would find #563 really helpful, is there anything we can do to help get it merged?

@stale
Copy link

stale bot commented Mar 17, 2020

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.

@stale stale bot added the wontfix label Mar 17, 2020
@tpict
Copy link

tpict commented Mar 17, 2020

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

@stale stale bot removed the wontfix label Mar 17, 2020
@HenryKuria
Copy link

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/

@tcleonard
Copy link
Collaborator

tcleonard commented Jul 27, 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.

@ja-vely
Copy link

ja-vely commented Aug 21, 2020

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.

@zbyte64 zbyte64 closed this as completed Feb 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants