Skip to content

relay.Connection class cannot declare my own Paginator #573

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

Closed
japrogramer opened this issue Oct 18, 2017 · 4 comments
Closed

relay.Connection class cannot declare my own Paginator #573

japrogramer opened this issue Oct 18, 2017 · 4 comments

Comments

@japrogramer
Copy link

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

connection = connection_from_list(

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.

@japrogramer
Copy link
Author

japrogramer commented Oct 19, 2017

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.

https://github.com/graphql-python/graphql-relay-py/blob/17ce2efa3c396df42791ae00667120b5fae64610/graphql_relay/connection/arrayconnection.py#L32

@japrogramer
Copy link
Author

japrogramer commented Oct 20, 2017

**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"
            }
          }
        ]
      }
    }
  }
}

@oharlem
Copy link

oharlem commented Nov 10, 2017

@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!

@japrogramer
Copy link
Author

japrogramer commented Nov 11, 2017

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants