Datatable Serverside Processing
Server-side processing means DataTables delegates filtering, sorting, and pagination to your backend, sending these parameters via AJAX. DRF then handles these to return a paginated, filtered, and sorted response.
๐๏ธ 1. DataTables Frontend Setup
Example JS:
$('#clientTable').DataTable({
serverSide: true,
processing: true,
ajax: {
url: '/api/clients/',
type: 'GET',
,
}columns: [
data: 'first_name' },
{ data: 'last_name' },
{ data: 'treatment_name' },
{ data: 'appointments_upcoming' },
{ // ...
]; })
Ensure
data
matches the field names in your serializer.
๐ 2. What DataTables Sends to the Backend
When serverSide mode is on, it sends:
Param | Example | Meaning |
---|---|---|
draw |
1 |
Draw counter |
start |
0 |
Offset for pagination |
length |
10 |
Page size |
search[value] |
john |
Global search value |
order[0][column] |
2 |
Column index to sort on |
order[0][dir] |
asc or desc |
Sorting direction |
columns[i][data] |
treatment_name |
Field name for column i |
columns[i][searchable] |
true |
Can this column be searched? |
โ๏ธ 3. Backend: Parse and Respond to DataTables
๐ฆ Custom DRF Filter Backend
from rest_framework.filters import BaseFilterBackend
from django.db.models import Q
import operator
from functools import reduce
class CustomDatatablesFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
= int(request.GET.get('draw', 1))
draw = int(request.GET.get('start', 0))
start = int(request.GET.get('length', 10))
length
# Global search
= request.GET.get('search[value]')
search_value if search_value:
= [col['name'][0] for col in view.get_datatables_fields()]
fields = reduce(operator.or_, [Q(**{f"{field}__icontains": search_value}) for field in fields], Q())
q = queryset.filter(q)
queryset
# Ordering
= request.GET.get('order[0][column]')
order_col_index = request.GET.get('order[0][dir]')
order_dir if order_col_index is not None:
= request.GET.get(f'columns[{order_col_index}][data]')
col_name if col_name:
= f"-{col_name}" if order_dir == 'desc' else col_name
order = queryset.order_by(order)
queryset
# Pagination
return queryset[start:start+length]
๐งฉ 4. Serializer
Support computed/annotated fields:
class ClientListSerializer(serializers.ModelSerializer):
= serializers.SerializerMethodField()
appointments_upcoming = serializers.SerializerMethodField()
treatment_name
class Meta:
= Client
model = ['first_name', 'last_name', 'appointments_upcoming', 'treatment_name']
fields
def get_appointments_upcoming(self, obj):
return getattr(obj, 'appointments_upcoming', 0)
def get_treatment_name(self, obj):
return getattr(obj, 'treatment_name', '')
๐งฎ 5. ViewSet with Annotated QuerySet
class ClientViewSet(viewsets.ReadOnlyModelViewSet):
= ClientListSerializer
serializer_class = [CustomDatatablesFilterBackend]
filter_backends
def get_queryset(self):
= timezone.now()
now = Treatment.objects.filter(client=OuterRef("pk")).order_by("-start_date")
latest_treatment
return Client.objects.annotate(
=Subquery(latest_treatment.values("name")[:1]),
treatment_name=Count(
appointments_upcoming"appointments", filter=Q(appointments__when__gte=now)
)
)
def get_datatables_fields(self):
return [
"name": ["first_name"]},
{"name": ["last_name"]},
{"name": ["treatment_name"]},
{"name": ["appointments_upcoming"]},
{ ]
๐ 6. Return the Right Response Shape
DataTables expects:
{
"draw": 1,
"recordsTotal": 100,
"recordsFiltered": 25,
"data": [{...}, {...}, ...]
}
If using DRF pagination, override paginate_queryset()
and get_paginated_response()
in a custom pagination class to return this shape.
๐ ๏ธ 7. Optional Enhancements
Feature | How to Implement |
---|---|
Column-specific search | Parse columns[i][search][value] |
Multi-column sort | Parse all order[i] fields |
Date range filters | Use query params like start_date_from , start_date_to |
Fuzzy search | Use __icontains , __regex , or trigram similarity |
Permissions | Restrict queryset with .for_user(user) or custom managers |
Large datasets | Use values() or only() to reduce query cost |
๐ง Tips
- Use
.annotate()
for computed fields. - Avoid N+1 queries by using
select_related()
andprefetch_related()
. - Use
Subquery()
+OuterRef()
to include related model fields efficiently. - Validate fields used for filtering and sorting to avoid injection attacks.
๐งช Testing & Debugging
Enable logging of SQL queries to debug performance:
import logging
= logging.getLogger('django.db.backends')
logger
logger.setLevel(logging.DEBUG) logger.addHandler(logging.StreamHandler())