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?
Could you subclass DjangoFilterConnectionField?
Also, look at relay connections and pagination. You might be able to get your answer there.
@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.
@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"
}
}
]
}
}
}
@changeling sorry I was being a bad contributor and replying on my phone. Your suggestion is great - wiki is now open :)
No worries! I'll see about adding this as well.
Added.
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!
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
}
}
}
Is this an official resolution to the issue? It looks like a very clever hack around the issue.
thx
Most helpful comment
@phalt, never mind. Got it. I'll add this to the wiki FAQ once editing is enabled. Turns out the trick is to subclass
Connectionand declare that as aconnection_classon the node type.DjangoFilterConnectionFieldthen uses that declaredconnection_classclass seamlessly, like so:This allows, in my example here, querying:
Which returns: