Django REST Framework: Ordering
For list endpoints, LDAP ORM provides ordering capabilities for REST framework
views through the LdapOrderingFilter filter backend. This filter leverages
LDAP ORM’s built-in ordering capabilities, which automatically use server-side
sorting when available and fall back to client-side sorting when the LDAP server
doesn’t support server-side sorting. See Ordering for more
details on server-side sorting.
Overview
The LdapOrderingFilter provides:
Server-side sorting: Uses LDAP’s server-side sorting when supported
Client-side fallback: Automatically falls back to client-side sorting when needed
Field validation: Validates ordering fields against the LDAP model
Multiple field support: Supports ordering by multiple fields
Ascending/descending: Supports both ascending and descending ordering
Default ordering: Falls back to model’s default ordering when no ordering is specified
Basic Usage
Add the LdapOrderingFilter to your ViewSet’s filter_backends:
from rest_framework import viewsets
from ldaporm.restframework import LdapOrderingFilter, LdapCursorPagination
from your_app.models import YourLdapModel
from your_app.serializers import YourLdapModelSerializer
class YourLdapModelViewSet(viewsets.ModelViewSet):
serializer_class = YourLdapModelSerializer
pagination_class = LdapCursorPagination
filter_backends = [LdapOrderingFilter]
ordering_fields = ['field1', 'field2', 'field3'] # Optional: restrict fields
ordering = ['field1'] # Default ordering
lookup_field = 'dn'
def get_queryset(self):
return YourLdapModel.objects.all()
Configuration Options
ordering_fields
A list of field names that can be used for ordering. If not specified, all model fields are allowed.
class UserViewSet(viewsets.ModelViewSet):
ordering_fields = ['uid', 'cn', 'mail', 'created']
# ... rest of configuration
ordering
The default ordering to use when no ordering parameter is provided. This can be a single field or a list of fields.
class UserViewSet(viewsets.ModelViewSet):
ordering = ['uid'] # Single field
# or
ordering = ['uid', 'cn'] # Multiple fields
ordering_param
The query parameter name for ordering (default: ‘ordering’).
class UserViewSet(viewsets.ModelViewSet):
filter_backends = [LdapOrderingFilter]
def get_ordering_param(self):
return 'sort' # Use 'sort' instead of 'ordering'
API Usage
Single Field Ordering
# Order by field ascending
GET /api/users/?ordering=uid
# Order by field descending (use '-' prefix)
GET /api/users/?ordering=-uid
Multiple Field Ordering
# Order by multiple fields
GET /api/users/?ordering=uid,-cn,mail
# This orders by:
# 1. uid ascending
# 2. cn descending
# 3. mail ascending
Combining with Other Filters
The ordering filter works alongside other filter backends:
# Filter + ordering
GET /api/users/?uid=john&ordering=cn
# Multiple filters + ordering
GET /api/users/?uid=john&is_active=true&ordering=-created
Error Handling
Invalid Field Names
If an invalid field name is provided, the API returns a 400 Bad Request with a clear error message:
{
"detail": "Invalid ordering field 'invalid_field'. Available fields: uid, cn, mail, created"
}
Empty or Invalid Parameters
Empty ordering parameter: Uses default ordering
Invalid field names: Returns 400 with error message
Malformed parameters: Gracefully handles and ignores invalid parts
Integration Examples
With Django Filter
from django_filters import rest_framework as filters
from rest_framework import viewsets
from ldaporm.restframework import LdapOrderingFilter, LdapCursorPagination
class UserFilter(filters.FilterSet):
uid = filters.CharFilter(field_name="uid", lookup_expr="icontains")
mail = filters.CharFilter(field_name="mail", lookup_expr="icontains")
class Meta:
model = User
fields = ['uid', 'mail']
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
pagination_class = LdapCursorPagination
filter_backends = [filters.DjangoFilterBackend, LdapOrderingFilter]
filterset_class = UserFilter
ordering_fields = ['uid', 'cn', 'mail', 'created']
ordering = ['uid']
lookup_field = 'dn'
def get_queryset(self):
return User.objects.all()
With Custom Filter Backends
from rest_framework import viewsets
from ldaporm.restframework import LdapOrderingFilter
class CustomFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# Custom filtering logic
return queryset
class UserViewSet(viewsets.ModelViewSet):
filter_backends = [CustomFilterBackend, LdapOrderingFilter]
ordering_fields = ['uid', 'cn', 'mail']
ordering = ['uid']
def get_queryset(self):
return User.objects.all()
Performance Considerations
Server-Side Sorting
When the LDAP server supports server-side sorting, the ordering is performed efficiently on the server side. This is the most efficient approach for large datasets.
Client-Side Sorting
When server-side sorting is not available, LDAP ORM automatically falls back to client-side sorting. This involves:
Fetching all results from LDAP
Sorting them in Python memory
Returning the sorted results
For large datasets, this can be less efficient, but it ensures compatibility with all LDAP servers.
Pagination with Ordering
When using LdapCursorPagination with ordering:
The ordering is applied first
Then pagination is applied to the ordered results
This ensures consistent ordering across pages
class UserViewSet(viewsets.ModelViewSet):
pagination_class = LdapCursorPagination
filter_backends = [LdapOrderingFilter]
ordering_fields = ['uid', 'cn', 'mail']
ordering = ['uid']
def get_queryset(self):
return User.objects.all()
Best Practices
Always specify ordering_fields: Restrict ordering to fields that make sense for your use case and have good performance characteristics.
Use server-side sorting when possible: The filter automatically uses server-side sorting when available, which is more efficient.
Combine with pagination: Always use pagination (like
LdapCursorPagination) when dealing with potentially large result sets.Test with your LDAP server: Different LDAP servers have different capabilities. Test ordering with your specific LDAP server to understand performance characteristics.
Monitor performance: For large datasets, monitor the performance of ordering operations, especially when client-side sorting is used.
Provide meaningful defaults: Set a sensible default ordering that matches user expectations.
Example Complete Implementation
from ldaporm import fields, models
from ldaporm.restframework import (
LdapModelSerializer, LdapOrderingFilter, LdapCursorPagination, LdapFilterBackend
)
from rest_framework import viewsets
# LDAP ORM Model
class User(models.Model):
uid = fields.CharField(max_length=50, primary_key=True)
cn = fields.CharField(max_length=100)
mail = fields.EmailField()
created = fields.DateTimeField(auto_now_add=True)
is_active = fields.BooleanField(default=True)
class Meta:
object_classes = ['person', 'organizationalPerson', 'inetOrgPerson']
ordering = ['uid'] # Default ordering
# Custom Filter Backend
class UserFilterBackend(LdapFilterBackend):
filter_fields = {
'uid': {'lookup': 'icontains'},
'cn': {'lookup': 'icontains'},
'mail': {'lookup': 'icontains'},
'is_active': {'lookup': 'exact'},
}
# Serializer
class UserSerializer(LdapModelSerializer):
class Meta:
model = User
# ViewSet
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
pagination_class = LdapCursorPagination
filter_backends = [UserFilterBackend, LdapOrderingFilter]
ordering_fields = ['uid', 'cn', 'mail', 'created', 'is_active']
ordering = ['uid'] # Default ordering
lookup_field = 'dn'
def get_queryset(self):
return User.objects.all()
# URL Configuration
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
urlpatterns = [
path('api/', include(router.urls)),
]
This implementation provides:
Full CRUD operations for LDAP users
Filtering by uid, cn, mail, and is_active using a custom UserFilterBackend
Ordering by any of the specified fields
Pagination with cursor-based navigation
Server-side sorting when available
Proper error handling for invalid ordering fields