Revert OrderingFilter

This commit is contained in:
Jeremy Stretch 2026-08-05 16:16:01 -04:00
parent 17b2017e6b
commit a633e80804
3 changed files with 3 additions and 64 deletions

View File

@ -1,7 +1,6 @@
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django_filters.rest_framework import DjangoFilterBackend
from django_rq.queues import get_redis_connection
from django_rq.settings import get_queues_list
from django_rq.utils import get_statistics
@ -20,7 +19,6 @@ from core.jobs import SyncDataSourceJob
from core.models import *
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs, requeue_rq_job, stop_rq_job
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
from netbox.api.filter_backends import OrderingFilter
from netbox.api.metadata import ContentTypeMetadata
from netbox.api.pagination import LimitOffsetListPagination
from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
@ -73,11 +71,6 @@ class JobViewSet(NetBoxReadOnlyModelViewSet):
queryset = Job.objects.all()
serializer_class = serializers.JobSerializer
filterset_class = filtersets.JobFilterSet
filter_backends = (DjangoFilterBackend, OrderingFilter)
# Order by elapsed time for jobs which are still running, matching the jobs table in the UI
ordering_expressions = {
'execution_time': Job.elapsed_time_expression(),
}
class ObjectChangeViewSet(NetBoxReadOnlyModelViewSet):

View File

@ -212,7 +212,7 @@ class JobTestCase(
)
def test_list_objects_by_execution_time(self):
"""The Job list endpoint supports filtering by execution_time."""
"""The Job list endpoint supports filtering and ordering by execution_time."""
self.add_permissions('core.view_job')
url = reverse('core-api:job-list')
@ -221,30 +221,10 @@ class JobTestCase(
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
def test_ordering_by_execution_time(self):
"""
Ordering by execution_time must place jobs with no execution time last in both directions,
and must rank a running job by its elapsed time (matching the jobs table in the UI).
"""
self.add_permissions('core.view_job')
url = reverse('core-api:job-list')
# 'Job 2' is running; give it a start time so it has an elapsed time exceeding Job 3's 90s
Job.objects.filter(name='Job 2').update(started=timezone.now() - timezone.timedelta(hours=1))
response = self.client.get(f'{url}?ordering=-execution_time', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(
[job['name'] for job in response.data['results']],
['Job 2', 'Job 3', 'Job 1'],
)
# Ordering by execution_time should be accepted (NULLs sort to one end)
response = self.client.get(f'{url}?ordering=execution_time', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(
[job['name'] for job in response.data['results']],
['Job 3', 'Job 2', 'Job 1'],
)
self.assertEqual(response.data['count'], 3)
class BackgroundTaskTestCase(RQQueueTestMixin, TestCase):

View File

@ -1,34 +0,0 @@
from django.db.models import F
from rest_framework import filters
__all__ = (
'OrderingFilter',
)
class OrderingFilter(filters.OrderingFilter):
"""
Extends DRF's OrderingFilter to sort null values last irrespective of the sort direction, and to
append a stable tiebreaker so that paginating through tied rows cannot skip or repeat them.
(PostgreSQL sorts nulls first when ordering descending, which pushes rows with no value to the
top of a descending sort.)
A viewset may map a field name to a query expression via `ordering_expressions` to order by
something other than the named column; this is used where the value presented to the user is
computed rather than stored.
"""
def filter_queryset(self, request, queryset, view):
if not (ordering := self.get_ordering(request, queryset, view)):
return queryset
expressions = getattr(view, 'ordering_expressions', {})
terms = []
for term in ordering:
if descending := term.startswith('-'):
term = term[1:]
expression = expressions[term] if term in expressions else F(term)
terms.append(
expression.desc(nulls_last=True) if descending else expression.asc(nulls_last=True)
)
return queryset.order_by(*terms, 'pk')