Skip to content

DjangoFilterConnectionField and resolve support #30

Closed
@drakon

Description

@drakon

I found that the following example from the docs doesn't work correctly.

The thing is that if there is a resolver for a DjangoFilterConnectionField like in the example, the filtering and ordering created by DjangoFilterConnectionField will be ignored. Details below.

class Query(ObjectType):
    my_posts = DjangoFilterConnectionField(CategoryNode)

    def resolve_my_posts(self, args, context, info):
        # context will reference to the Django request
        if not context.user.is_authenticated():
            return Post.objects.none()
        else:
            return Post.objects.filter(owner=context.user)

The issues seems to be that DjangoFilterConnectionField ignores what the resolver returns.

@staticmethod
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
                        root, args, context, info):
    filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
    order = args.get('order_by', None)
    qs = default_manager.get_queryset()
    if order:
        qs = qs.order_by(order)
    qs = filterset_class(data=filter_kwargs, queryset=qs)

    return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info)

Here the QuerySet is built just fine but the resolver does not have an impact on the QuerySet (which is fine if no resolver is used anyway).

The problem arrises when the DjangoConnectionField resolves the query.

@staticmethod
def connection_resolver(resolver, connection, default_manager, root, args, context, info):
    iterable = resolver(root, args, context, info)
    if iterable is None:
        iterable = default_manager
    iterable = maybe_queryset(iterable)
    if isinstance(iterable, QuerySet):
        _len = iterable.count()
    else:
        _len = len(iterable)
    connection = connection_from_list_slice(
        iterable,
        args,
        slice_start=0,
        list_length=_len,
        list_slice_length=_len,
        connection_type=connection,
        edge_type=connection.Edge,
        pageinfo_type=PageInfo,
    )
    connection.iterable = iterable
    connection.length = _len
    return connection

Here we consider the default_manager (which is the QuerySet that was built by DjangoFilterConnectionField) only if there resolver is None.

So you either can have the DjangoFilterConnection field or a resolver. But it should be possible to use both (to limit an initial QuerySet and check of authorisation, etc.).

Solution

The solution I use now locally by changing DjangoFilterConnectionField is the following:

@staticmethod
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
                        root, args, context, info):
    filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
    order = args.get('order_by', None)

    def new_resolver(root, args, context, info):
        qs = resolver(root, args, context, info)
        if qs is None:
            qs = default_manager.get_queryset()
        if order:
            qs = qs.order_by(order)
        qs = filterset_class(data=filter_kwargs, queryset=qs)
        return qs

    return DjangoConnectionField.connection_resolver(new_resolver, connection, None, root, args, context, info)

Basically it wraps the resolver and uses its QuerySet if available or create one with the default_manager. It also guarantees that the resolver returns something and therefore we can set the default_manager to None just to make sure it there is no other way to get to the QuerySet.

I would have created a PR but I couldn't get the tests of the repo running locally. :(

PS: I'd also really like to get the tests running so I can contribute to the code directly. Any pointers how to get it running are really appreciated as I didn't have luck by following the instructions in "Contributing".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions