diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 3f5bb172a..facb1b17a 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -185,7 +185,9 @@ class TagViewSet(NetBoxModelViewSet): class TaggedItemViewSet(RetrieveModelMixin, ListModelMixin, BaseViewSet): - queryset = TaggedItem.objects.prefetch_related('content_type', 'content_object', 'tag') + queryset = TaggedItem.objects.prefetch_related( + 'content_type', 'content_object', 'tag' + ).order_by('tag__weight', 'tag__name') serializer_class = serializers.TaggedItemSerializer filterset_class = filtersets.TaggedItemFilterSet diff --git a/netbox/netbox/api/exceptions.py b/netbox/netbox/api/exceptions.py index f552b06b5..c4d05d1a5 100644 --- a/netbox/netbox/api/exceptions.py +++ b/netbox/netbox/api/exceptions.py @@ -12,3 +12,7 @@ class SerializerNotFound(Exception): class GraphQLTypeNotFound(Exception): pass + + +class QuerySetNotOrdered(Exception): + pass diff --git a/netbox/netbox/api/pagination.py b/netbox/netbox/api/pagination.py index f1430a9fd..698e0a8dd 100644 --- a/netbox/netbox/api/pagination.py +++ b/netbox/netbox/api/pagination.py @@ -1,6 +1,7 @@ from django.db.models import QuerySet from rest_framework.pagination import LimitOffsetPagination +from netbox.api.exceptions import QuerySetNotOrdered from netbox.config import get_config @@ -15,6 +16,12 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination): def paginate_queryset(self, queryset, request, view=None): + if isinstance(queryset, QuerySet) and not queryset.ordered: + raise QuerySetNotOrdered( + "Paginating over an unordered queryset is unreliable. Ensure that a minimal " + "ordering has been applied to the queryset for this API endpoint." + ) + if isinstance(queryset, QuerySet): self.count = self.get_queryset_count(queryset) else: diff --git a/netbox/users/migrations/0010_add_token_meta_ordering.py b/netbox/users/migrations/0010_add_token_meta_ordering.py new file mode 100644 index 000000000..bb2be6c45 --- /dev/null +++ b/netbox/users/migrations/0010_add_token_meta_ordering.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.4 on 2025-07-23 17:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0009_update_group_perms'), + ] + + operations = [ + migrations.AlterModelOptions( + name='token', + options={'ordering': ('-created',)}, + ), + ] diff --git a/netbox/users/models/tokens.py b/netbox/users/models/tokens.py index 2e7040699..3c1284bc9 100644 --- a/netbox/users/models/tokens.py +++ b/netbox/users/models/tokens.py @@ -74,6 +74,7 @@ class Token(models.Model): class Meta: verbose_name = _('token') verbose_name_plural = _('tokens') + ordering = ('-created',) def __str__(self): return self.key if settings.ALLOW_TOKEN_RETRIEVAL else self.partial diff --git a/netbox/utilities/query.py b/netbox/utilities/query.py index b254df582..5eaff836f 100644 --- a/netbox/utilities/query.py +++ b/netbox/utilities/query.py @@ -67,5 +67,8 @@ def reapply_model_ordering(queryset: QuerySet) -> QuerySet: # MPTT-based models are exempt from this; use caution when annotating querysets of these models if any(isinstance(manager, TreeManager) for manager in queryset.model._meta.local_managers): return queryset + elif queryset.ordered: + return queryset + ordering = queryset.model._meta.ordering return queryset.order_by(*ordering)