question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`order_by` clause issue with django & graphql

See original GitHub issue

Hi,

When using the order_by clause in graphql,

  • the graphql representation is correctly orderBy (camelCase).
  • the value however is expected in snake case, which does not match the graphql auto generated code (it should match the API exposed field in all logic).

I tried to fix this but there are 2 places I could find where the order by is injected in a djgango qs:

  1. In graphene.contrib.django.filter.fields.DjangoFilterConnectionField
    def get_order(self, args):
        return args.get('order_by', None)

Which does not tackle the snake case at all but at least the return is correctly handled by (in case of None (same file):

    def get_queryset(self, qs, args, info):
        filterset_class = self.filterset_class
        filter_kwargs = self.get_filter_kwargs(args)
        order = self.get_order(args)
        if order:
            qs = qs.order_by(order)
        unknown = filterset_class(data=filter_kwargs, queryset=qs)
        return unknown

This in also (not sure why, maybe I did something wrong) pushed to: django_filters.filterset.BaseFilterSet

    @property
    def qs(self):
        if not hasattr(self, '_qs'):
            valid = self.is_bound and self.form.is_valid()

            if self.strict and self.is_bound and not valid:
                self._qs = self.queryset.none()
                return self._qs

            # start with all the results and filter from there
            qs = self.queryset.all()
            for name, filter_ in six.iteritems(self.filters):
                value = None
                if valid:
                    value = self.form.cleaned_data[name]
                else:
                    raw_value = self.form[name].value()
                    try:
                        value = self.form.fields[name].clean(raw_value)
                    except forms.ValidationError:
                        # for invalid values either:
                        # strictly "apply" filter yielding no results and get outta here
                        if self.strict:
                            self._qs = self.queryset.none()
                            return self._qs
                        else:  # or ignore this filter altogether
                            pass

                if value is not None:  # valid & clean data
                    qs = filter_.filter(qs, value)

            if self._meta.order_by:
                order_field = self.form.fields[self.order_by_field]
                data = self.form[self.order_by_field].data
                ordered_value = None
                try:
                    ordered_value = order_field.clean(data)
                except forms.ValidationError:
                    pass

                if ordered_value in EMPTY_VALUES and self.strict:
                    ordered_value = self.form.fields[self.order_by_field].choices[0][0]

                if ordered_value:
                    qs = qs.order_by(*self.get_order_by(ordered_value))

            self._qs = qs

        return self._qs

Which does not handle at all the None return for get_order_by.

  1. When I tested this, I got it not to crash turning the camelCase to snake case in the get_order method, however after that, while the qs was correct (I printed it out) the return from the API was systematically empty… (in that case #2 did not seem to kick in)
  2. When I tested sorting by a composite field (field that I had defined on the Node manually and not part of the django model) is when I hit #1 and #2 here. I could not get this working at all.
  3. When the system finds an order_by it does not know: it prints the django error which exposes all the fields of the raw django model to the API: not super secure… It seems like the system does not validate the order_by “clause” / arguments before sending it to django.

This is what I found after a couple days of debugging and trying to go around the issue. Please let me know if I can help in any way. The doc was very light on the subject and I understand from slack that @syrusakbary has code changes.

I did not know the best way to help you/us resolve this. Thank you.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:15 (4 by maintainers)

github_iconTop GitHub Comments

5reactions
Mhs-220commented, Jul 8, 2019

@farnoodma Hey there! I had the same problem but finally, I fix it! Here is my code

class OrderedDjangoFilterConnectionField(DjangoFilterConnectionField):
	@classmethod
	def connection_resolver(cls, resolver, connection, default_manager, max_limit,
							enforce_first_or_last, filterset_class, filtering_args,
							root, info, **args):
		filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
		qs = filterset_class(
			data=filter_kwargs,
			queryset=default_manager.get_queryset(),
			request=info.context
		).qs
		order = args.get('orderBy', None)
		if order:
			qs = qs.order_by(*order)
		return super(DjangoFilterConnectionField, cls).connection_resolver(
			resolver,
			connection,
			qs,
			max_limit,
			enforce_first_or_last,
			root,
			info,
			**args
	)

and then, just use it instead of DjangoFilterConnectionField, like this:

person = OrderedDjangoFilterConnectionField( PersonNode, orderBy=graphene.List(of_type=graphene.String) )

and then, the query is something like this:

query {
    person (orderBy: ["field1","-field2"...]){
        edges{
            node{
                name
            }
        }
    }
}

P.S: fix pagination bug

4reactions
alokRamtekecommented, May 2, 2020

Update:

Mhs-220 solution won’t work with the current graphene-django version(2.9.1) or greater than graphene-django 2.6.0 version.

DjangoFilterConnectionField methods are changed in the 2.7.0 version. For more details, you can check the changelogs here

The solution will generate error, connection_resolver() missing 1 required positional argument: 'info’.

I have modified the solution and it worked perfectly.

from graphene_django.filter import DjangoFilterConnectionField
from graphene.utils.str_converters import to_snake_case


class OrderedDjangoFilterConnectionField(DjangoFilterConnectionField):

    @classmethod
    def resolve_queryset(
        cls, connection, iterable, info, args, filtering_args, filterset_class
    ):
        qs = super(DjangoFilterConnectionField, cls).resolve_queryset(
            connection, iterable, info, args
        )
        filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
        qs = filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs

        order = args.get('orderBy', None)
        if order:
            if type(order) is str:
                snake_order = to_snake_case(order)
            else:
                snake_order = [to_snake_case(o) for o in order]
            qs = qs.order_by(*snake_order)
        return qs
Read more comments on GitHub >

github_iconTop Results From Across the Web

Add ordering to your Django-Graphene GraphQL API
If the GraphQL query is sent without the orderBy argument then we never enter the if order block and instantly return the queryset...
Read more >
Django Graphene Relay order_by (OrderingFilter)
Eric's solution won't work with the current graphene-django version(2.9.1) or greater than graphene-django 2.6.0 version.
Read more >
[LIVE] Django GraphQL API with Python Graphene - YouTube
This is an edited livestream where I build a GraphQL API in Python's Django framework.It's a spaced repetition learning back-end to compare ...
Read more >
Sorting - Graphene-Python
Graphene framework for Python. ... schema = graphene. ... The query has a sort argument which allows to sort over a different column(s)....
Read more >
Integrating GraphQL API into a Django application - Section.io
This tutorial will focus on integrating a GraphQL API into a Django project and effectively using it to query data. GraphQL provides a ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found