Repository: encode/django-rest-framework

Issue:
Adding the `DjangoFilterBackend` filter changes queryset ordering.
Tried posting on StackOverflow with no reply (http://stackoverflow.com/questions/21848095/adding-filtering-changes-ordering) => decided to open bug here

I have a ModelViewSet that I want to add filtering to. My simple model looks like

```
class Article(models.Model):
    date = = models.DateField()
    language = models.CharField(max_length=10)

    class Meta:
        ordering = ['-date']
```

And the ModelViewSet (read only):

```
class ArticleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
```

Articles on the API are now ordered by date descending as I would expect. Now I wich to allow filtering on language. I've set the filter backend to `DjangoFilterBackend` in settings.py. My updated ModelViewSet now looks like:

```
class ArticleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    filter_fields = ['language']
```

This changes the ordering to language ASC. Adding `order_by('-date')` to queryset does not change anything. Adding `ordering = ('-date', )` does not change anything. => How do I specify both filtering and ordering (or simply use default ordering while allowing filtering)?

**EDIT:**
Current functionality seems to come from AutoFilterSet created in Rest Framework by default:
https://github.com/tomchristie/django-rest-framework/blob/822eb39599b248c68573c3095639a831ab6df99a/rest_framework/filters.py#L53
... where `order_by=True` and the handing of this in django-filter `get_ordering_field` here: https://github.com/alex/django-filter/blob/d88b98dd2b70551deb9c128b209fcf783b325acc/django_filters/filterset.py#L325

=> Seems I have to create a FilterSet class:

```
class LanguageFilter(django_filters.FilterSet):
    class Meta:
        model = Article
        fields = ['language']
        order_by = model()._meta.ordering

class ArticleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    filter_class = LanguageFilter
```

Does this look correct? Seems a bit "much"/verbose/counter-intuitive to retain default ordering.



Discussion:
If simply adding DjangoFilterBackend quietly & unexpectedly changes the default ordering then I think we can prob treat that as a bug, yup.

Just ran into this behaviour and found it odd. How about making `order_by` in `AutoFilterSet` default to `filter_fields` if no `model()._meta.ordering` is set? I think that should work. Thoughts?

Update: Or maybe just appending both iterables together.

As far as I can see `order_by` on the `AutoFilterSet` should simply _not_ be set.
There's no documentation against `DjangoFilterBackend` noting that behavior.

I imagine that it's just a left-over from when Django REST framework didn't support pluggable filters and `OrderingFilter` didn't exist.

Of course we would want to call it out loudly if we remove it.  It's possible that some users are relying on the undocumented behavior there.

@tomchristie any specific way you want to tackle it? Anyway I can help?

Well, start with a PR removing it and check if any of the existing test cases break I guess.

+1

@jpadilla Okay so that looks great - next thing is to properly describe the change in a way that's easy for users to understand so that we have something we can add to the release notes.  What does `order_by` on the `FilterSet` mean and do, how does the change impact the ordering and how do users switch back to the old behavior if they need too?

If someone wants to pull that text together it could just be as a comment on this thread.

DjangoCon sprint guidance - this already has a pull request, but needs release notes and test, [see my comment above from Apr 9](https://github.com/tomchristie/django-rest-framework/issues/1432#issuecomment-40005139).

I'm picking this one up again


Resolving patch:
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -56,7 +56,6 @@ class AutoFilterSet(self.default_filter_set):
                 class Meta:
                     model = queryset.model
                     fields = filter_fields
-                    order_by = True
             return AutoFilterSet
 
         return None


Test patch:
diff --git a/tests/test_filters.py b/tests/test_filters.py
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -408,16 +408,61 @@ class SearchListView(generics.ListAPIView):
             )
 
 
-class OrdringFilterModel(models.Model):
+class OrderingFilterModel(models.Model):
     title = models.CharField(max_length=20)
     text = models.CharField(max_length=100)
 
 
 class OrderingFilterRelatedModel(models.Model):
-    related_object = models.ForeignKey(OrdringFilterModel,
+    related_object = models.ForeignKey(OrderingFilterModel,
                                        related_name="relateds")
 
 
+class DjangoFilterOrderingModel(models.Model):
+    date = models.DateField()
+    text = models.CharField(max_length=10)
+
+    class Meta:
+        ordering = ['-date']
+
+
+class DjangoFilterOrderingTests(TestCase):
+    def setUp(self):
+        data = [{
+            'date': datetime.date(2012, 10, 8),
+            'text': 'abc'
+        }, {
+            'date': datetime.date(2013, 10, 8),
+            'text': 'bcd'
+        }, {
+            'date': datetime.date(2014, 10, 8),
+            'text': 'cde'
+        }]
+
+        for d in data:
+            DjangoFilterOrderingModel.objects.create(**d)
+
+    def test_default_ordering(self):
+        class DjangoFilterOrderingView(generics.ListAPIView):
+            model = DjangoFilterOrderingModel
+            filter_backends = (filters.DjangoFilterBackend,)
+            filter_fields = ['text']
+            ordering = ('-date',)
+
+        view = DjangoFilterOrderingView.as_view()
+        request = factory.get('/')
+        response = view(request)
+
+        self.assertEqual(
+            response.data,
+            [
+                {'id': 3, 'date': datetime.date(2014, 10, 8), 'text': 'cde'},
+                {'id': 2, 'date': datetime.date(2013, 10, 8), 'text': 'bcd'},
+                {'id': 1, 'date': datetime.date(2012, 10, 8), 'text': 'abc'}
+            ]
+        )
+
+
 class OrderingFilterTests(TestCase):
     def setUp(self):
         # Sequence of title/text is:
@@ -436,11 +481,11 @@ def setUp(self):
                 chr(idx + ord('b')) +
                 chr(idx + ord('c'))
             )
-            OrdringFilterModel(title=title, text=text).save()
+            OrderingFilterModel(title=title, text=text).save()
 
     def test_ordering(self):
         class OrderingListView(generics.ListAPIView):
-            model = OrdringFilterModel
+            model = OrderingFilterModel
             filter_backends = (filters.OrderingFilter,)
             ordering = ('title',)
             ordering_fields = ('text',)
@@ -459,7 +504,7 @@ class OrderingListView(generics.ListAPIView):
 
     def test_reverse_ordering(self):
         class OrderingListView(generics.ListAPIView):
-            model = OrdringFilterModel
+            model = OrderingFilterModel
             filter_backends = (filters.OrderingFilter,)
             ordering = ('title',)
             ordering_fields = ('text',)
@@ -478,7 +523,7 @@ class OrderingListView(generics.ListAPIView):
 
     def test_incorrectfield_ordering(self):
         class OrderingListView(generics.ListAPIView):
-            model = OrdringFilterModel
+            model = OrderingFilterModel
             filter_backends = (filters.OrderingFilter,)
             ordering = ('title',)
             ordering_fields = ('text',)
@@ -497,7 +542,7 @@ class OrderingListView(generics.ListAPIView):
 
     def test_default_ordering(self):
         class OrderingListView(generics.ListAPIView):
-            model = OrdringFilterModel
+            model = OrderingFilterModel
             filter_backends = (filters.OrderingFilter,)
             ordering = ('title',)
             oredering_fields = ('text',)
@@ -516,7 +561,7 @@ class OrderingListView(generics.ListAPIView):
 
     def test_default_ordering_using_string(self):
         class OrderingListView(generics.ListAPIView):
-            model = OrdringFilterModel
+            model = OrderingFilterModel
             filter_backends = (filters.OrderingFilter,)
             ordering = 'title'
             ordering_fields = ('text',)
@@ -536,7 +581,7 @@ class OrderingListView(generics.ListAPIView):
     def test_ordering_by_aggregate_field(self):
         # create some related models to aggregate order by
         num_objs = [2, 5, 3]
-        for obj, num_relateds in zip(OrdringFilterModel.objects.all(),
+        for obj, num_relateds in zip(OrderingFilterModel.objects.all(),
                                      num_objs):
             for _ in range(num_relateds):
                 new_related = OrderingFilterRelatedModel(
@@ -545,11 +590,11 @@ def test_ordering_by_aggregate_field(self):
                 new_related.save()
 
         class OrderingListView(generics.ListAPIView):
-            model = OrdringFilterModel
+            model = OrderingFilterModel
             filter_backends = (filters.OrderingFilter,)
             ordering = 'title'
             ordering_fields = '__all__'
-            queryset = OrdringFilterModel.objects.all().annotate(
+            queryset = OrderingFilterModel.objects.all().annotate(
                 models.Count("relateds"))
 
         view = OrderingListView.as_view()
@@ -567,7 +612,7 @@ class OrderingListView(generics.ListAPIView):
     def test_ordering_with_nonstandard_ordering_param(self):
         with temporary_setting('ORDERING_PARAM', 'order', filters):
             class OrderingListView(generics.ListAPIView):
-                model = OrdringFilterModel
+                model = OrderingFilterModel
                 filter_backends = (filters.OrderingFilter,)
                 ordering = ('title',)
                 ordering_fields = ('text',)