Skip to content

Instantly share code, notes, and snippets.

@mbrochh
Last active September 24, 2023 10:45
Show Gist options
  • Star 51 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save mbrochh/f92594ab8188393bd83c892ef2af25e6 to your computer and use it in GitHub Desktop.
Save mbrochh/f92594ab8188393bd83c892ef2af25e6 to your computer and use it in GitHub Desktop.
Using pagination with Django, graphene and Apollo
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
# First we create a little helper function, becase we will potentially have many PaginatedTypes
# and we will potentially want to turn many querysets into paginated results:
def get_paginator(qs, page_size, page, paginated_type, **kwargs):
p = Paginator(qs, page_size)
try:
page_obj = p.page(page)
except PageNotAnInteger:
page_obj = p.page(1)
except EmptyPage:
page_obj = p.page(p.num_pages)
return paginated_type(
page=page_obj.number,
pages=p.num_pages,
has_next=page_obj.has_next(),
has_prev=page_obj.has_previous(),
objects=page_obj.object_list,
**kwargs
)
from theartling.utils import get_paginator
from . import models
# Let's assume you have some ObjectType for one of your models:
class ProductType(DjangoObjectType):
class Meta:
model = models.Product
# Now we create a corresponding PaginatedType for that object type:
class ProductPaginatedType(graphene.ObjectType):
page = graphene.Int()
pages = graphene.Int()
has_next = graphene.Boolean()
has_prev = graphene.Boolean()
objects = graphene.List(ProductType)
class Query(object):
products = graphene.Field(ProductPaginatedType, page=graphene.Int())
# Now, in your resolver functions, you just query your objects and turn the queryset into the PaginatedType using the helper function:
def resolve_products(self, info, page):
page_size = 10
qs = models.Product.objects.all()
return get_paginator(qs, page_size, page, ProductPaginatedType)
// In your frontend, you just query your endpoint and request all the fields from the PaginatedType:
const gql = `
{
products(page: 1) {
page
pages
has_next
has_prev
objects {
id
name
slug
whatever
}
}
}
`
@PhilippeFerreiraDeSousa

It's hasNext and hasPrev in the request frontend.

@carlossacorrea
Copy link

Hey its a great option only I have 1 question with this implementation we can filter by Title or whatever?

@knavels
Copy link

knavels commented Jan 31, 2020

Thank you for sharing that, the thing is that how can we refactor these?! because in this case there will be a lot of repeated codes, I tried to refactor paginate class but it caused error

@Zagrebelin
Copy link

Good job, but can i ask you, what is CustomNode in these snippets?

@mbrochh
Copy link
Author

mbrochh commented Jun 24, 2020

@Zagrebelin forget about CustomNode... when I started out with graphene my understanding of GraphQL was quite poor and I found some hack online that made sure that the GraphQL ID is equal to the Django PK, instead of being some weird hash value. I think this is now the default behaviour for graphene in Django, I'm not using this CustomNode thing any more in my more recent projects.

(I have updated the gist and removed that line)

@Cimmanuel
Copy link

@mbrochh this is really nice. How would you make PaginatedType reusable though? I mean instead of creating a PaginatedType for each DjangoObjectType, how do you think the DRY principle can be honoured? I'm trying to figure something out

@mbrochh
Copy link
Author

mbrochh commented Jul 6, 2020

@Cimmanuel so far I haven't bothered to figure out a reusable way 🙈

@Cimmanuel
Copy link

Cimmanuel commented Jul 6, 2020

@mbrochh
Ouch!
Here's what I did so far:

class PaginatedType(graphene.ObjectType):
    page = graphene.Int()
    pages = graphene.Int()
    has_next = graphene.Boolean()
    has_prev = graphene.Boolean()

class ProductPaginatedType(PaginatedType):
    objects = graphene.List(ProductType)

I did this just to remove redundancy and make it a little neat. I feel there's more that can be done. Please let me know when you figure something out. Thanks!

@alfredrumss
Copy link

@mbrochh @Cimmanuel

how to handle this solution when you have filters in a class level filterset_class in the Query.

Basically doing this you would have to do all filters by hand and then paginate them as I haven't figured it out how to fetch in resolve methods what the filters have filtered before.

Actually, I've got lots of filters in the all app with filterset_class, so it shouldn't be nice removing all of them to do it in the resolvers side.

Any idea?

@cyrilmarceau
Copy link

cyrilmarceau commented Jan 24, 2022

Hello,

Thanks for this code. It working fine. I try to set up this pagination with DjangoFilterConnectionField but I get an error.

I try to set the pagination before the filter end. Do you have an idea on how to do setup this pagination with DjangoFilterConnectionField ?

@Lyrics2000
Copy link

thanks alot for this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment