Skip to content

Commit 6b69297

Browse files
paranderbenedicthsieh
authored andcommitted
Add optional column sorting to handle ties
If sorting is done on a single column with more duplicates than the page size it's possible than some rows are never retrieved as we traverse through our datatable. This is because of how order by together with limit and offset works in the database. As a workaround for this problem we add a second column to sort by in the case of ties.
1 parent 1b6fee4 commit 6b69297

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed

rest_framework_datatables/filters.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ def filter_queryset(self, request, queryset, view):
7474

7575
# order queryset
7676
if len(ordering):
77+
if hasattr(view, '_datatables_additional_order_by'):
78+
additional = view._datatables_additional_order_by
79+
# Django will actually only take the first occurrence if the
80+
# same column is added multiple times in an order_by, but it
81+
# feels cleaner to double check for duplicate anyway.
82+
if not any((o[1:] if o[0] == '-' else o) == additional
83+
for o in ordering):
84+
ordering.append(additional)
85+
7786
queryset = queryset.order_by(*ordering)
7887
return queryset
7988

tests/test_filter.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from albums.models import Album
2+
from albums.serializers import AlbumSerializer
3+
4+
from django.conf.urls import url
5+
from django.test.utils import override_settings
6+
from django.test import TestCase
7+
8+
from rest_framework.generics import ListAPIView
9+
from rest_framework.test import (
10+
APIClient,
11+
)
12+
from rest_framework_datatables.pagination import (
13+
DatatablesLimitOffsetPagination,
14+
)
15+
16+
class TestFilterTestCase(TestCase):
17+
class TestAPIView(ListAPIView):
18+
serializer_class = AlbumSerializer
19+
pagination_class = DatatablesLimitOffsetPagination
20+
_datatables_additional_order_by = 'year'
21+
22+
def get_queryset(self):
23+
return Album.objects.all()
24+
25+
fixtures = ['test_data']
26+
27+
def setUp(self):
28+
self.client = APIClient()
29+
30+
@override_settings(ROOT_URLCONF=__name__)
31+
def test_additional_order_by(self):
32+
response = self.client.get('/api/additionalorderby/?format=datatables&draw=1&columns[0][data]=rank&columns[0][name]=&columns[0][searchable]=true&columns[0][orderable]=true&columns[0][search][value]=&columns[0][search][regex]=false&columns[1][data]=artist_name&columns[1][name]=artist.name&columns[1][searchable]=true&columns[1][orderable]=true&columns[1][search][value]=&columns[1][search][regex]=false&columns[2][data]=name&columns[2][name]=&columns[2][searchable]=true&columns[2][orderable]=true&columns[2][search][value]=&columns[2][search][regex]=false&order[0][column]=1&order[0][dir]=desc&start=4&length=1&search[value]=&search[regex]=false')
33+
# Would be "Sgt. Pepper's Lonely Hearts Club Band" without the additional order by
34+
expected = (15, 15, 'Rubber Soul')
35+
result = response.json()
36+
self.assertEquals((result['recordsFiltered'], result['recordsTotal'], result['data'][0]['name']), expected)
37+
38+
39+
urlpatterns = [
40+
url('^api/additionalorderby', TestFilterTestCase.TestAPIView.as_view()),
41+
]

0 commit comments

Comments
 (0)