mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-03-03 19:00:17 +03:00
Added OrderingFilter
This commit is contained in:
parent
08bc976269
commit
6a037f63ed
|
@ -190,7 +190,7 @@ The `SearchFilterBackend` class will only be applied if the view has a `search_f
|
||||||
filter_backends = (filters.SearchFilter,)
|
filter_backends = (filters.SearchFilter,)
|
||||||
search_fields = ('username', 'email')
|
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
|
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')
|
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`.
|
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
|
# Custom generic filtering
|
||||||
|
|
||||||
You can also provide your own generic filtering backend, or write an installable app for other developers to use.
|
You can also provide your own generic filtering backend, or write an installable app for other developers to use.
|
||||||
|
|
|
@ -4,7 +4,7 @@ returned by list views.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from rest_framework.compat import django_filters
|
from rest_framework.compat import django_filters, six
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
|
@ -109,3 +109,42 @@ class SearchFilter(BaseFilterBackend):
|
||||||
queryset = queryset.filter(reduce(operator.or_, or_queries))
|
queryset = queryset.filter(reduce(operator.or_, or_queries))
|
||||||
|
|
||||||
return queryset
|
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
|
||||||
|
|
|
@ -335,3 +335,119 @@ class SearchFilterTests(TestCase):
|
||||||
{'id': 2, 'title': 'zz', 'text': 'bcd'}
|
{'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'},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user