Im working with a connection Class of this shape:
class WishConnection(relay.Connection):
class Meta:
node = ProductType
class Edge:
through = graphene.Field(WishReasonType, description='Connection reason')
def resolve_through(self, info, **args):
return self.node.preason[0]
The problem is that I want to use django own queryset paginator to resolve the list that gets passed by the resolve for where the connection is used.
products = relay.ConnectionField(WishConnection)
def resolve_products(self, info, **args):
qs = Reason.objects.filter(wish=self).only('reason', 'qty')
len = args.get
result = Paginator(self.products.prefetch_related(
Prefetch('reason_set', queryset=qs, to_attr='preason')).all(),
args.get('first', 20))
return result
my query looks like
{
wish(pk: "2") {
products(first: 1, after: "YXJyYXljb25uZWN0aW9uOjA=", ) {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
through {
reason
qty
}
node {
title
description
active
disabled
}
}
}
}
}
I would Like to mak my own PageInfo class that has the attributes that can be returned by djangos Paginator class like
Paginator.count
Paginator.num_pages
Paginator.page_range
and also the methods for each pages most notably has_next has_previous
https://docs.djangoproject.com/en/1.11/topics/pagination/#page-objects
In order to do that I need to be able to control how def connection_from_list_slice(...) resolves for PageInfo and for that matter the nodes, which means that I will need to change this line to call my own method
https://github.com/graphql-python/graphene/blob/c38ffa5ffdf726e1f84b908a7c1e3be25b88f1ed/graphene/relay/connection.py#L122
Simplest way to do that is to create a subclass of the ConnectionField and override the def resolve_connection method.
well looks like I just found a path to a solution to my own problem. Going to post to see if anyone has anything to ask or add, also might comeback to this issue if I find any more problems or difficult behavior I don't know how to implement while trying to work this out.
The reason I need to use the django Paginator with the connection is because if i slice the queryset in the resolver for that ConnectionField than the connection field will not know that it has already been sliced, the list, and it will try to slice the list again in the slice method, and the pageinfo in the connection will be wrong. Also PageInfo isn't really a page info more like a slice info.
*Well I did it 馃槃 *
This is how my queries look now
{
wish(pk: "2") {
title
products (page: 1) {
pageInfo {
Count
NumPages
hasNext
hasPrevious
hasOtherPages
nextPageNumber
previousPageNumber
startIndex
endIndex
}
edges {
through{
reason
qty
}
node {
active
title
}
}
}
}
}
and this is what I get back
{
"data": {
"wish": {
"title": "testme",
"products": {
"pageInfo": {
"Count": 2,
"NumPages": 1,
"hasNext": false,
"hasPrevious": false,
"hasOtherPages": false,
"nextPageNumber": null,
"previousPageNumber": null,
"startIndex": 1,
"endIndex": 2
},
"edges": [
{
"through": {
"reason": "Added this Car to test",
"qty": 1
},
"node": {
"active": false,
"title": "This is Item is a Car"
}
},
{
"through": {
"reason": "Needs more cowbell",
"qty": 7
},
"node": {
"active": true,
"title": "CowBell"
}
}
]
}
}
}
}
@japrogramer
Mind sharing how you added custom fields to pageInfo please?
I reached a point where I use a custom connection (class MyConnection(relay.Connection):) and my version of PageInfo, however I believe the questions is - how to pass data from a resolver to PageInfo? i.e. total counts, etc.
Thank you!
shure, I have a few extra stuff related to my implementation, here is the meat and potatoes of it tho simplified to work with your example here is a quick example.
Not aware of a simpler way, maybe to just use a resolver for page info, you could maybe pass the info you need into the resolver some other way.
33 class MyConnectionField(relay.ConnectionField): | |
32 | |
31 """PQCField Is a custom ConnectionField that works with a django Paginator """ | |
30 def __init__(self, *args, **kwargs): | |
29 kwargs.setdefault('page', Int(description='Page to retrieve')) | |
28 kwargs.setdefault('perpage', Int(description='Items per page')) | |
27 super().__init__(*args, **kwargs) | |
26 remove = ['before', 'after', 'first', 'last'] | |
25 for x in remove: | |
24 self.args.pop(x, None) | |
23 | |
22 @classmethod | |
21 def resolve_connection(cls, connection_type, args, resolved): | |
20 if isinstance(resolved, connection_type): |4|
19 return resolved | |
18 | |
17 assert isinstance(resolved, Iterable) or isinstance(resolved, Paginator), ( | |
16 'Resolved value from the connection field have to be iterable or instance of {}. ' | |
15 'or an instance of Paginator' | |
14 'Received "{}"' | |
13 ).format(connection_type, resolved) | |
12 | |
11 if isinstance(resolved, Iterable): | |
10 resolved = Paginator(resolved, 20) | |
9 | |
8 connection = connection_from_paginator( | |
7 resolved, | |
6 args, | |
5 connection_type=connection_type, | |
4 edge_type=connection_type.Edge, | |
3 pageinfo_type=PaginatorInfo | |
2 ) | |
1 connection.iterable = resolved | |
128 return connection
| 36 def connection_from_paginator(list_slice, args=None, connection_type=None, |
| 35 edge_type=None, pageinfo_type=None, **kwargs): |
| 34 ''' |
| 33 Given a slice (subset)from a Paginator, returns a connection object for use in |
| 32 GraphQL. |
| 31 This function is similar to `connectionFromArray`, but is intended for use |
| 30 cases where you know the cardinality of the connection, consider it too large |
| 29 to materialize the entire array, and instead wish pass in a slice of the |
| 28 total result large enough to cover the range specified in `args`. |
| 27 ''' |
| 26 connection_type = connection_type |
| 25 edge_type = edge_type or Edge |
| 24 pageinfo_type = pageinfo_type |
| 23 |
| 22 args = args or {} |
| 21 page = args.get('page', 1) |
| 20 |
| 19 try: |
| 18 _slice = list_slice.page(page) |
| 17 except PageNotAnInteger: |
| 16 # If page is not an integer, deliver first page. |
| 15 page = 1 |
| 14 _slice = list_slice.page(page) |
| 13 except InvalidPage: |
| 12 # If page is out of range (e.g. 9999), deliver last page of results. |
| 11 page = list_slice.num_pages |
| 10 _slice = list_slice.page(page) |
| 9 |
| 8 edges = [ |
| 7 edge_type( |
| 6 node=node, |
| 5 cursor=offset_to_cursor(page, page + i) |
| 4 ) |
| 3 for i, node in enumerate(_slice) |
| 2 ] |
| 1 page = pageinfo_type() |
|44 setattr(page, 'pcursor', list_slice) |
| 1 setattr(page, 'cpage', _slice) |
| 2 return connection_type( |
| 3 edges=edges, |
| 4 page_info=page |
| 5 )
class PaginatorInfo(graphene.ObjectType):
pcursor = None
cpage = None
count = graphene.Int(name='Count', description='Total count')
def resolve_count(self, info, **args):
return self.pcursor.count
class MyConnection(relay.Connection):
page_info = graphene.Field(PaginatorInfo, required=True)
class Meta:
node = SomeType
class MyType(DjangoObjectType):
somemodel = MyConnectionField(MyConnection)
def resolve_somemodel(self, info, **args):
return Paginator(somemodel.objects.all(), 30)
Most helpful comment
shure, I have a few extra stuff related to my implementation, here is the meat and potatoes of it tho simplified to work with your example here is a quick example.
Not aware of a simpler way, maybe to just use a resolver for page info, you could maybe pass the info you need into the resolver some other way.