Graphene-django: Defaulting the first param / paging by default

Created on 7 Mar 2018  路  15Comments  路  Source: graphql-python/graphene-django

I have the following snippet:

def resolve_products(self, info, **kwargs):
    return Product.objects.all()

I want to return 100 results by default. I notice that if I specify say first: 30 in my query

{ products (first: 30 ){} }
that first appears in kwargs.

I then tried setting kwargs['first'] to 100 if no first key is found, but it has no effect.

How do I default first so all results do not get returned by default

Most helpful comment

@muriloacs the easiest way that I've found to make this happen is to create a new connection field with an overridden connection_resolver. This is untested as I don't need it right now but it should be about right.

from graphene_django.filter import DjangoFilterConnectionField


class AutoPagedDjangoFilterConnectionField(DjangoFilterConnectionField):
    @classmethod
    def connection_resolver(
        cls,
        resolver,
        connection,
        default_manager,
        max_limit,
        enforce_first_or_last,
        filterset_class,
        filtering_args,
        root,
        info,
        **args
    ):
        args['first'] = args.get("first", 40)
        return super().connection_resolver(
            cls,
            resolver,
            connection,
            default_manager,
            max_limit,
            enforce_first_or_last,
            filterset_class,
            filtering_args,
            root,
            info,
            **args
        )

All 15 comments

Are you using the DjangoFilterConnectionField ? It handles taking into account the settings

  • RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST
  • RELAY_CONNECTION_MAX_LIMIT

I turn on both to ensure that all the items are output and response times are fast.

DjangoFilterConnectionField should resolve this for you.

Stumbled on this and DjangoFilterConnectionField doesn't solve this issue, it just forces the user to input a first. I don't want my API to enforce that necessarily

@gabelimon you can set a default value of a connection field like this:

from graphene import ObjectType, Int

class Query(ObjectType):
    users = DjangoFilterConnectionField(User, first=Int(default_value=100))

@jkimbo on literally every query? That doesn't seem useful.

You would have to add it to every definition of DjangoFilterConnectionField yes. It might be that different connections have different defaults so I don't think that adding a global setting is very useful. You can also always create your own Connection field that subclasses the DjangoFilterConnectionField and defaults the first (or any argument) to whatever you like.

Giving a settings param would mean I could use said param and count on it in perpetuity. Monkey-patching this library to set defaults is not very consistent with Django patterns and it means I have to keep up to date with the internals of this library.

@gabelimon you can set a default value of a connection field like this:

from graphene import ObjectType, Int

class Query(ObjectType):
  users = DjangoFilterConnectionField(User, first=Int(default_value=100))

This does not work :(
kwargs.get('first') is always None

@muriloacs the easiest way that I've found to make this happen is to create a new connection field with an overridden connection_resolver. This is untested as I don't need it right now but it should be about right.

from graphene_django.filter import DjangoFilterConnectionField


class AutoPagedDjangoFilterConnectionField(DjangoFilterConnectionField):
    @classmethod
    def connection_resolver(
        cls,
        resolver,
        connection,
        default_manager,
        max_limit,
        enforce_first_or_last,
        filterset_class,
        filtering_args,
        root,
        info,
        **args
    ):
        args['first'] = args.get("first", 40)
        return super().connection_resolver(
            cls,
            resolver,
            connection,
            default_manager,
            max_limit,
            enforce_first_or_last,
            filterset_class,
            filtering_args,
            root,
            info,
            **args
        )

@gabelimon thanks for the answer man! I'll try to implement it. I let you know the results :)

@gabelimon it works! 馃殌

As I needed to do it on several Queries (each one with a different first value) then I decided to use middleware. That's my solution:

settings.py

GRAPHENE = {
    'MIDDLEWARE': (
        'myapp.middleware.default_args_middleware',
    )
}
GRAPHENE_DEFAULT_ARGS = {
    'queryFoo': {
        'first': 5
    },
    'queryBar': {
        'first': 10
    }
}

myapp/middleware.py

def default_args_middleware(next, root, info, **args):
    if info.operation.operation == 'query':
        if info.field_name in settings.GRAPHENE_DEFAULT_ARGS:
            default_args = settings.GRAPHENE_DEFAULT_ARGS[info.field_name]
            for k, v in default_args.items():
                args[k] = args.get(k, v)
    return next(root, info, **args)

This approach allows us to set a default value for any graphene argument. Other than that, it does not require the addition of new classes. Just keep in mind that your middleware must be lightweight since it's called for every node/request. That's why I tried to keep it as simple as possible.

@muriloacs thanks for posting your final solution. We have a TON of connections so this is exactly what we've been looking for. Thanks!

Thank you @gabelimon! Really cool that this solution helps you :)

All of the solutions above failed for me, so I figured I would share what worked:

class MyFilterConnectionField(DjangoFilterConnectionField):
    @classmethod
    def connection_resolver(
        cls,
        *args,
        first=settings.GRAPHENE['DEFAULT_PAGE_SIZE'],
        **kargs
    ):
        return super(MyFilterConnectionField, cls).connection_resolver(*args, first=first, **kargs)

This class allowed me to easily override all my connections and use one central setting for everything.

Was this page helpful?
0 / 5 - 0 ratings