Skip to content

How would one implement totalCount or count on DjangoFilterConnectionField? #636

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
changeling opened this issue May 13, 2019 · 16 comments
Closed

Comments

@changeling
Copy link
Contributor

With Connection, one can implement a count on edges like so:

from graphene import Connection, ConnectionField, Node, Int
from graphene_django import DjangoObjectType
from ..models import Place

class Thing_Type(DjangoObjectType):
    class Meta:
        model = Thing
        interfaces = (Node, )


class Thing_Connection(Connection):
    class Meta:
        node = Thing_Type
    count = Int()

    def resolve_count(root, info):
        return len(root.edges)


class Query(object):
    things = ConnectionField(Thing_Connection)

    def resolve_things(root, info, **kwargs):
        return Thing.objects.all()
   return len(root.edges)

Given that DjangoFilterConnectionField won't accept a Connection, but requires a DjangoObjectType, how would one implement an equivalent count?

@changeling changeling changed the title How to implement totalCount or count on DjangoFilterConnectionField? How would one implement totalCount or count on DjangoFilterConnectionField? May 13, 2019
@phalt
Copy link
Contributor

phalt commented May 13, 2019

Could you subclass DjangoFilterConnectionField?

@phalt
Copy link
Contributor

phalt commented May 13, 2019

Also, look at relay connections and pagination. You might be able to get your answer there.

@changeling
Copy link
Contributor Author

@phalt, any pointers? I'm going through issues and docs across graphene and graphene-django, and there seems to be some confusion regarding a best practice approach here. This is another item I'll add to the FAQ when I get my head around, as I said, best practice. It's trivial to implement using Connection and ConnectionField, but DjangoFilterConnectionField is proving to be a bear.

@changeling
Copy link
Contributor Author

changeling commented May 14, 2019

@phalt, never mind. Got it. I'll add this to the wiki FAQ once editing is enabled. Turns out the trick is to subclass Connection and declare that as a connection_class on the node type. DjangoFilterConnectionField then uses that declared connection_class class seamlessly, like so:

from graphene import ObjectType, Connection, Node, Int
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from ..models import Place


class ExtendedConnection(Connection):
    class Meta:
        abstract = True

    total_count = Int()
    edge_count = Int()

    def resolve_total_count(root, info, **kwargs):
        return root.length
    def resolve_edge_count(root, info, **kwargs):
        return len(root.edges)


class PlaceType(DjangoObjectType):
    class Meta:
        model = Place
        filter_fields = {
            'id':  ['exact', 'icontains'],
            'name': ['exact', 'icontains', 'istartswith', 'iendswith'],
            'date': ['exact', 'icontains', 'istartswith', 'iendswith'],
            'date_sort': ['exact', 'icontains', 'istartswith', 'iendswith'],
        }
        interfaces = (Node, )
        connection_class = ExtendedConnection


class Query(ObjectType):
    places = DjangoFilterConnectionField(PlaceType)

This allows, in my example here, querying:

{
  places(first: 2, name_Icontains: "Dallas" after:"YXJyYXljb25uZWN0aW9uOjE1") {
    totalCount
    edgeCount
    edges {
      cursor 
      node {
        id  
        name
      }
    }
  }
}

Which returns:

{
{
  "data": {
    "places": {
      "totalCount": 23,
      "edgeCount": 2,
      "edges": [
        {
          "cursor": "YXJyYXljb25uZWN0aW9uOjE2",
          "node": {
            "id": "UGxhY2VUeXBlOjUxOA==",
            "name": "Dallas, Texas, United States"
          }
        },
        {
          "cursor": "YXJyYXljb25uZWN0aW9uOjE3",
          "node": {
            "id": "UGxhY2VUeXBlOjU0Nw==",
            "name": "Dallas, Alabama, United States"
          }
        }
      ]
    }
  }
}

@phalt
Copy link
Contributor

phalt commented May 15, 2019

@changeling sorry I was being a bad contributor and replying on my phone. Your suggestion is great - wiki is now open :)

@changeling
Copy link
Contributor Author

No worries! I'll see about adding this as well.

@changeling
Copy link
Contributor Author

Added.

@phalt phalt closed this as completed May 16, 2019
@slorg1
Copy link

slorg1 commented Jun 24, 2020

Hi guys,

[adding @changeling ]
I implemented exactly that for the project I am working on and here is the error I am getting:

  File "/XXXXXX/lib/python3.7/site-packages/graphql/type/typemap.py", line 109, in reducer
    field_map = type_.fields
  File "/XXXXXX/lib/python3.7/site-packages/graphql/pyutils/cached_property.py", line 22, in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
  File "/XXXXXX/lib/python3.7/site-packages/graphql/type/definition.py", line 198, in fields
    return define_field_map(self, self._fields)
  File "/XXXXXX/lib/python3.7/site-packages/graphql/type/definition.py", line 212, in define_field_map
    field_map = field_map()
  File "/XXXXXX/lib/python3.7/site-packages/graphene/types/typemap.py", line 275, in construct_fields_for_type
    map = self.reducer(map, field.type)
  File "/XXXXXX/lib/python3.7/site-packages/graphene/relay/connection.py", line 129, in type
    if is_node(connection_type):
  File "/XXXXXX/lib/python3.7/site-packages/graphene/relay/node.py", line 22, in is_node
    for i in objecttype._meta.interfaces:
AttributeError: 'NoneType' object has no attribute 'interfaces'

I am using python3.7, graphene_django==2.10.1 and django 2.2

I see tests for this in the code base. So, I am thinking that I am doing something wrong.

from graphene import Int
from graphene import Connection
from graphene import ObjectType
from graphene_django.filter import DjangoFilterConnectionField
from graphene import relay
from graphene_django import DjangoObjectType

from project.models.Segment import Segment

class SegmentConnection(Connection):
    """
        Connection for relay segments.
    """

    class Meta:
        abstract = True

    count = Int(description="Number of segments in the system", required=True)

    def resolve_count(self, info, **kwarg):
        return Segment.objects.count()


class SegmentNode(DjangoObjectType,):
    """
        Serialized representation of a segment.
    """

    class Meta:
        model = Segment
        fields = (
            "id",
            "identifier",
            "description",
        )

        interfaces = (relay.Node,)
        connection_class = SegmentConnection

class Query(ObjectType):
    segments = DjangoFilterConnectionField(SegmentNode,)

Thank you for your help!

@ptadas
Copy link

ptadas commented Sep 21, 2020

Solution so that totalCount field is also discoverable by the schema:

class ExtendedConnection(graphene.relay.Connection):
    class Meta:
        abstract = True

    @classmethod
    def __init_subclass_with_meta__(cls, node=None, name=None, **options):
        result = super().__init_subclass_with_meta__(node=node, name=name, **options)
        cls._meta.fields["total_count"] = graphene.Field(
            type=graphene.Int,
            name="totalCount",
            description="Number of items in the queryset.",
            required=True,
            resolver=cls.resolve_total_count,
        )
        return result

    def resolve_total_count(self, *_) -> int:
        return self.iterable.count()


class Bla(DjangoObjectType):
    class Meta:
        connection_class = ExtendedConnection
query {
  something() {
    edges {
      node {
         ...
      }
    }
    totalCount,
    pageInfo {
      hasNextPage
    }
  }
}

@slorg1
Copy link

slorg1 commented Oct 22, 2020

Is this an official resolution to the issue? It looks like a very clever hack around the issue.

@kaojistream
Copy link

thx

@kimutaiRop
Copy link

kimutaiRop commented May 19, 2021

that was suppose to work but what if my final object is be a graphene.union with several objectTypes in resolve_type and I am using the graphene.Connection in a class to call the final object. how do I incorporate the filter in there

class AllObjectType(graphene.Union):
    @classmethod
    def resolve_type(cls, instance, info):
        if isinstance(instance,Object1):
            return ObjectType1
    class Meta:
        interfaces = (graphene.relay.Node, )
        types = [ObjectType1,]


class AllObjectTypeCollection(graphene.Connection):
    class Meta:
        node = AllObjectType

how would I filter in here

@Instrumedley
Copy link

any way I can have the totalCount without using that ExtendedConnection solution. The solution works but I'd like not to have my final data object cluttered with edges and nodes everywhere. For a big nested tree it becomes very hard to read

@ethagnawl
Copy link

Is anyone aware of a clean way to include this in a subclassed DjangoObjectType or related meta class so that you don't need to remember to include connection_class = ExtendedConnection in every one of your schema classes?

@ndpu
Copy link
Contributor

ndpu commented Jun 26, 2023

Is anyone aware of a clean way to include this in a subclassed DjangoObjectType or related meta class so that you don't need to remember to include connection_class = ExtendedConnection in every one of your schema classes?

Something like this should work:

class DjangoObjectTypeSubclass(DjangoObjectType):
    @classmethod
    def __init_subclass_with_meta__(cls, connection_class=ExtendedConnection, **options):
        super().__init_subclass_with_meta__(connection_class=connection_class, **options)

@ethagnawl
Copy link

Thank you, @ndpu. This looks like exactly what I need. I'll give it a try and follow up.

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

9 participants