This gives me an error in /graphql. Rest works like a charm though.
{
"errors": [
{
"message": "Cannot query field \"totalCount\" on type \"ProductNodeConnection\".",
"locations": [
{
"line": 3,
"column": 5
}
]
}
]
}
query{
allProducts(first:2 after:"YXJyYXljb25uZWN0aW9uOjM="){
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges{
node{
id
name
}
cursor
}
}
}
Connections don't expose their length attribute by default. But you can do this:
class Product(graphene_django.DjangoObjectType):
...
@classmethod
def get_connection(cls):
class CountableConnection(graphene.relay.Connection):
total_count = graphene.Int()
class Meta:
name = '{}Connection'.format(cls._meta.name)
node = cls
@staticmethod
def resolve_total_count(root, args, context, info):
return root.length
return CountableConnection
Not sure what you did there. If you could put it in the context of my code it would be more understandable, and if you provide some explanations, we could upgrade this question to the doc status :) Since Relay has the concept of _totalCount_, developers would expect it available in the query.
from graphene import relay, ObjectType, AbstractType, List
from graphene_django.types import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from .models import Product
class ProductNode(DjangoObjectType):
class Meta:
model = Product
filter_fields = {
'name': ['exact', 'icontains', 'istartswith'],
}
interfaces = (relay.Node, )
class Query(AbstractType):
all_products = DjangoFilterConnectionField(ProductNode)
def resolve_all_products(self, args, context, info):
return Product.objects.all()
Ok so here's what happens: when you implement the relay.Node interface, the Node.implements class method is called on the ObjectType you are defining, in your case it would be ProductNode.
Look at the method
def implements(cls, objecttype):
get_connection = getattr(objecttype, 'get_connection', None)
if not get_connection:
get_connection = partial(get_default_connection, objecttype)
objecttype.Connection = get_connection()
you'll notice it attemps to find a get_connection method from your ObjectType to define your Node's Connection custom implementation, and if it doesn't, it just uses the default Connection class, so if we take advantage of this then you can create a Connection that inherits from the default class and implement the totalCount attribute and resolver. (By the way, keep in mind that DjangoObjectType inherits from ObjectType)
...
from graphene import Int
class ProductNode(DjangoObjectType):
class Meta:
model = Product
filter_fields = {
'name': ['exact', 'icontains', 'istartswith'],
}
interfaces = (relay.Node, )
@classmethod
def get_connection(cls):
class CountableConnection(relay.Connection):
total_count = Int()
class Meta:
name = '{}Connection'.format(cls._meta.name)
node = cls
@staticmethod # Redundant since Graphene kinda does this automatically for all resolve_ methods.
def resolve_total_count(root, args, context, info):
return root.length
return CountableConnection
That's it... I hope. Also, thanks to how Python's scoping works, the outer cls method argument is passed to the class you define, which is why we are defining this subclass of Connection inside the ProductNode.get_connection method.
Also, the root.length attribute is defined here from here
Hope I cleared any doubts
Will try, but wouldn't it be easier to just stick is somewhere here in the Connection?
Yes, it definitely would. But I am merely proposing a solution that works with the current version of Graphene, though :)
I can wait for few days no worries :)
The solution worked for me.
Because this is scensial to paginate, I've done it as a Mixin to add it to multiple DjangoObjectType like this:
class TotalCountMixin(ObjectType):
@classmethod
def get_connection(cls):
class CountableConnection(relay.Connection):
total_count = Int()
class Meta:
name = '{}Connection'.format(cls._meta.name)
node = cls
@staticmethod
def resolve_total_count(root, args, context, info):
return root.length
return CountableConnection
Then I added it to the DjangoObjectType, in your case it would be:
class ProductNode(DjangoObjectType, TotalCountMixin):
class Meta:
model = Product
filter_fields = {
'name': ['exact', 'icontains', 'istartswith'],
}
interfaces = (relay.Node, )
Test it!
@Fercho191 Thank you for sharing your Mixin! :D
The get_connection is no more in v.2 🤔
This is how to do it in version 2 (graphene is woefully undocumented) :(
class CountableConnectionBase(relay.Connection):
class Meta:
abstract = True
total_count = Int()
def resolve_total_count(self, info, **kwargs):
return self.iterable.count()
class ProductNode(DjangoObjectType):
class Meta:
model = MyModel
interfaces = (relay.Node, )
connection_class = CountableConnectionBase
Thanks @aminland. Unfortunately when I try make it using abstract class I get TypeError: Error when calling the metaclass bases __init_subclass_with_meta__() got an unexpected keyword argument 'connection_class' Any ideas?
Meanwhile I was able to make it work with
class EventType(DjangoObjectType):
class Meta:
model = Event
interfaces = (graphene.relay.Node, )
class CountableConnection(graphene.relay.Connection):
total_count = graphene.Int()
class Meta:
node = EventType
def resolve_total_count(self, info):
return self.iterable.count()
class UserProfilType(DjangoObjectType):
events = graphene.relay.ConnectionField(CountableConnection)
class Meta:
model = UserProfil
interfaces = (graphene.relay.Node, )
@igo
Thank you, this works!
However, looks like it does not respect the first argument,
i.e. if I'm requesting just first 3 items, it will still get the full set.
Thoughts?
Are you using v2.0?
On Dec 2, 2017 4:23 AM, "Igor Urminček" notifications@github.com wrote:
Thanks @aminland https://github.com/aminland. Unfortunately when I try
make it using abstract class I get TypeError: Error when calling the
metaclass bases __init_subclass_with_meta__() got an unexpected keyword
argument 'connection_class' Any ideas?
Meanwhile I was able to make it work withclass EventType(DjangoObjectType):
pass
class CountableConnection(graphene.relay.Connection):
total_count = graphene.Int()class Meta: node = EventType def resolve_total_count(self, info): return self.iterable.count()class UserProfilType(DjangoObjectType):
events = graphene.relay.ConnectionField(CountableConnection)—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/graphql-python/graphene-django/issues/162#issuecomment-348679790,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAOFkIcoh9gLMhX4VDHgyr5JZh8eICweks5s8RccgaJpZM4NLXoS
.
@aminland
Sorry, got confused about the role of first. Working as expected.
(first should not be limiting the count value ).
@igo where does the Event model come from?
@igo This worked fine, for relay.ConnectionField, but didn't for DjangoConnectionField, because it fails an assert in DjangoConnectionField.type() (it expects the type argument to the constructor (to which you pass CountableConnection) to be a subclass of DjangoObjectType). I think that assert is wrong, but honestly don't know for sure. It works though when you override and skip the assert like this:
class CustomDjangoConnectionField(DjangoConnectionField):
@property
def type(self):
from .types import DjangoObjectType
_type = super(ConnectionField, self).type
# assert issubclass(_type, DjangoObjectType), "DjangoConnectionField only accepts DjangoObjectType types"
assert _type._meta.connection, "The type {} doesn't have a connection".format(_type.__name__)
return _type._meta.connection
Note the wonky call to super() to skip the baseclass.
I ended up implementing the connection type directly in type(), see my comment in #320 , as this is a common case in our code base.
@aminland I think the version on pypi (https://pypi.python.org/pypi/graphene-django/2.0.0) does not expose connection_class
@dani0805 good point, I didn't realize that the change in 2a39f5d8eaba3f7772c63b012a974bb9a841fb9f wasn't pushed to pypy yet. You can just change your requirements file and add -e git+https://github.com/graphql-python/graphene-django#egg=graphene-django to get the latest version.
Yep that’s what we did but would be nice to have it in a stable release as this is necessary for pagination.
Any reason why this hasn't been released on PyPI yet?
https://github.com/graphql-python/graphene-django/commit/2a39f5d8eaba3f7772c63b012a974bb9a841fb9f#diff-6926ea790e42fa924302b75462dfef72
It's been over 8 months, would be great to be able to use this sort of functionality.
This should have been released now so closing.
IMHO totalCount should be part of the codebase
Most helpful comment
This is how to do it in version 2 (graphene is woefully undocumented) :(