Added OrderingFilter

This commit is contained in:
Tom Christie 2013-05-14 11:27:03 +01:00
parent 08bc976269
commit 6a037f63ed
3 changed files with 186 additions and 3 deletions

View File

@ -190,7 +190,7 @@ The `SearchFilterBackend` class will only be applied if the view has a `search_f
filter_backends = (filters.SearchFilter,)
search_fields = ('username', 'email')
This will allow the client to filter the itemss in the list by making queries such as:
This will allow the client to filter the items in the list by making queries such as:
http://example.com/api/users?search=russell
@ -198,7 +198,7 @@ You can also perform a related lookup on a ForeignKey or ManyToManyField with th
search_fields = ('username', 'email', 'profile__profession')
By default, searches will use case-insensitive partial matches. The search parameter may contain multiple search terms, which should be whitespace and/or comma seperated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched.
By default, searches will use case-insensitive partial matches. The search parameter may contain multiple search terms, which should be whitespace and/or comma separated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched.
The search behavior may be restricted by prepending various characters to the `search_fields`.
@ -214,6 +214,34 @@ For more details, see the [Django documentation][search-django-admin].
---
## OrderingFilter
The `OrderingFilter` class supports simple query parameter controlled ordering of results. For example:
http://example.com/api/users?ordering=username
The client may also specify reverse orderings by prefixing the field name with '-', like so:
http://example.com/api/users?ordering=-username
Multiple orderings may also be specified:
http://example.com/api/users?ordering=account,username
If an `ordering` attribute is set on the view, this will be used as the default ordering.
Typicaly you'd instead control this by setting `order_by` on the initial queryset, but using the `ordering` parameter on the view allows you to specify the ordering in a way that it can then be passed automatically as context to a rendered template. This makes it possible to automatically render column headers differently if they are being used to order the results.
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer = UserSerializer
filter_backends = (filters.OrderingFilter,)
ordering = ('username',)
The `ordering` attribute may be either a string or a list/tuple of strings.
---
# Custom generic filtering
You can also provide your own generic filtering backend, or write an installable app for other developers to use.

View File

@ -4,7 +4,7 @@ returned by list views.
"""
from __future__ import unicode_literals
from django.db import models
from rest_framework.compat import django_filters
from rest_framework.compat import django_filters, six
from functools import reduce
import operator
@ -109,3 +109,42 @@ class SearchFilter(BaseFilterBackend):
queryset = queryset.filter(reduce(operator.or_, or_queries))
return queryset
class OrderingFilter(BaseFilterBackend):
ordering_param = 'order' # The URL query parameter used for the ordering.
def get_ordering(self, request):
"""
Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited.
"""
params = request.QUERY_PARAMS.get(self.ordering_param)
if params:
return [param.strip() for param in params.split(',')]
def get_default_ordering(self, view):
ordering = getattr(view, 'ordering', None)
if isinstance(ordering, six.string_types):
return (ordering,)
return ordering
def remove_invalid_fields(self, queryset, ordering):
field_names = [field.name for field in queryset.model._meta.fields]
return [term for term in ordering if term.lstrip('-') in field_names]
def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request)
if ordering:
# Skip any incorrect parameters
ordering = self.remove_invalid_fields(queryset, ordering)
if not ordering:
# Use 'ordering' attribtue by default
ordering = self.get_default_ordering(view)
if ordering:
return queryset.order_by(*ordering)
return queryset

View File

@ -335,3 +335,119 @@ class SearchFilterTests(TestCase):
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
)
class OrdringFilterModel(models.Model):
title = models.CharField(max_length=20)
text = models.CharField(max_length=100)
class OrderingFilterTests(TestCase):
def setUp(self):
# Sequence of title/text is:
#
# zyx abc
# yxw bcd
# xwv cde
for idx in range(3):
title = (
chr(ord('z') - idx) +
chr(ord('y') - idx) +
chr(ord('x') - idx)
)
text = (
chr(idx + ord('a')) +
chr(idx + ord('b')) +
chr(idx + ord('c'))
)
OrdringFilterModel(title=title, text=text).save()
def test_ordering(self):
class OrderingListView(generics.ListAPIView):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
view = OrderingListView.as_view()
request = factory.get('?order=text')
response = view(request)
self.assertEqual(
response.data,
[
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
]
)
def test_reverse_ordering(self):
class OrderingListView(generics.ListAPIView):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
view = OrderingListView.as_view()
request = factory.get('?order=-text')
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
)
def test_incorrectfield_ordering(self):
class OrderingListView(generics.ListAPIView):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
view = OrderingListView.as_view()
request = factory.get('?order=foobar')
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
)
def test_default_ordering(self):
class OrderingListView(generics.ListAPIView):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
view = OrderingListView.as_view()
request = factory.get('')
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
)
def test_default_ordering_using_string(self):
class OrderingListView(generics.ListAPIView):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = 'title'
view = OrderingListView.as_view()
request = factory.get('')
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
)