This package provides wonderfully simple integration with AG Grid and Django REST Framework. It handles AG Grid's query parameters for filtering, sorting, and pagination, transforming them into Django ORM compatible filtering.
This is an early release and work-in-progress.
pip install django-rest-framework-aggrid- Filtering based on AG Grid's
filterparameter - Sorting based on AG Grid's
sortparameter - Pagination based on AG Grid's
startRowandendRowparameters - Response formatting in the format expected by AG Grid
- Support for
format=aggridquery parameter to activate the filter backend - Automatic conversion of dot notation field names to Django ORM field names
AgGridFilterBackend: A filter backend that handles ag-grid's query parametersAgGridAutoPaginationMixin: A mixin to automatically handle paginationAgGridPaginationMixin: A mixin for views that use theAgGridFilterBackendAgGridPagination: A pagination class that handles ag-grid's pagination parametersAgGridRenderer: A custom renderer for ag-grid responses
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.filters import SearchFilter
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
from your_app.models import YourModel
from your_app.serializers import YourModelSerializer
from drf_aggrid import AgGridFilterBackend, AgGridAutoPaginationMixin
class YourModelViewSet(AgGridAutoPaginationMixin, viewsets.ModelViewSet):
queryset = YourModel.objects.all()
serializer_class = YourModelSerializer
permission_classes = [IsAuthenticated]
filter_backends = [
SearchFilter,
AgGridFilterBackend,
]
search_fields = ["name"]... or ...
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.filters import SearchFilter
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
from your_app.models import YourModel
from your_app.serializers import YourModelSerializer
from drf_aggrid import AgGridFilterBackend, AgGridPagination, AgGridRenderer
class YourModelViewSet(viewsets.ModelViewSet):
queryset = YourModel.objects.all()
serializer_class = YourModelSerializer
permission_classes = [IsAuthenticated]
filter_backends = [
SearchFilter,
AgGridFilterBackend,
]
renderer_classes = [JSONRenderer, BrowsableAPIRenderer, AgGridRenderer]
search_fields = ["name"]
pagination_class = AgGridPaginationfrom rest_framework.routers import DefaultRouter
from your_app.views import YourModelViewSet
router = DefaultRouter()
router.register(
r'your-model-ag-grid',
YourModelViewSet,
basename='your-model-ag-grid'
)
urlpatterns = router.urlsfrom django.urls import path, include
from your_app.routers import router as ag_grid_router
urlpatterns = [
# ... other URL patterns ...
path("api/ag-grid/", include(ag_grid_router.urls)),
]The filter backend and pagination can be activated in two ways:
-
By setting the
formatquery parameter toaggrid:GET /api/ag-grid/your-model-ag-grid/?format=aggrid -
By directly using ag-grid's query parameters:
GET /api/ag-grid/your-model-ag-grid/?filter={"name":{"filterType":"text","type":"contains","filter":"example"}}
The filter backend automatically converts ag-grid's dot notation field names to Django ORM compatible field names. For example:
event_type.namein ag-grid becomesevent_type__namein Django ORMuser.profile.emailin ag-grid becomesuser__profile__emailin Django ORM
This conversion is applied to both filtering and sorting parameters, allowing you to use the same field names in your ag-grid configuration and Django models.
Example:
GET /api/ag-grid/your-model-ag-grid/?filter={"event_type.name":{"filterType":"text","type":"contains","filter":"example"}}&sort=[{"colId":"event_type.name","sort":"asc"}]
The pagination is handled by the AgGridPagination class, which uses the startRow and endRow parameters to paginate the queryset. For example:
GET /api/ag-grid/your-model-ag-grid/?startRow=0&endRow=100
This will return rows 0-99 (100 rows total). The pagination is applied at the database level for efficiency, and the response includes the total count of rows in the totalCount field.
- The
AgGridPaginationclass extracts thestartRowandendRowparameters from the request. - It applies these parameters to the queryset using Django's slice notation:
queryset[start_row:end_row]. - The pagination is applied at the database level, which is more efficient than fetching all rows and then slicing them.
- The response includes the total count of rows in the
totalCountfield and the filtered count in therowCountfield.
- AgGridFilterBackend: Does NOT apply pagination. It only handles filtering and sorting.
- AgGridPagination: Applies pagination when the view uses it as the pagination_class.
- AgGridRenderer: Applies pagination only if the view doesn't have a paginator.
This separation of responsibilities ensures that pagination is applied correctly and only once.
The response format for ag-grid includes three main fields:
- totalCount: The total number of rows in the dataset before any filtering is applied.
- rowCount: The number of rows after filtering is applied (but before pagination).
- rows: The actual data rows after pagination is applied.
This format allows ag-grid to properly display pagination information and handle server-side operations.
{
"rowCount": 50, // 50 rows match the filter criteria
"totalCount": 1000, // 1000 total rows in the dataset
"rows": [
// Only the first 10 rows are returned due to pagination
{
"id": 1,
"name": "Example 1",
"event_type": {
"name": "Conference"
}
}
// ... more rows ...
]
}The AgGridFilterBackend supports the following filter types:
equalsnotEqualcontainsnotContainsstartsWithendsWith
equalsnotEquallessThanlessThanOrEqualgreaterThangreaterThanOrEqualinRange
equalsnotEquallessThangreaterThaninRange
values(list of values to filter by)
filter(boolean value to filter by)
GET /api/ag-grid/your-model-ag-grid/?format=aggrid&filter={"name":{"filterType":"text","type":"contains","filter":"example"}}&sort=[{"colId":"name","sort":"asc"}]&startRow=0&endRow=10
GET /api/ag-grid/your-model-ag-grid/?filter={"name":{"filterType":"text","type":"contains","filter":"example"}}&sort=[{"colId":"name","sort":"asc"}]&startRow=0&endRow=10
GET /api/ag-grid/your-model-ag-grid/?filter={"event_type.name":{"filterType":"text","type":"contains","filter":"example"}}&sort=[{"colId":"event_type.name","sort":"asc"}]&startRow=0&endRow=10
The AgGridFilterBackend provides a way to define custom filter functions for specific fields. This is useful when you need to implement complex filtering logic that isn't covered by the default filter types.
- Implement
get_aggrid_custom_filtersin your view:
def get_aggrid_custom_filters(self):
return {
'field_name': self.filter_field_name,
# Add more custom filters as needed
}- Implement filter functions:
def filter_field_name(self, field, filter_condition, queryset, request, view):
# Custom filtering logic here
# ...
return filtered_querysetfrom rest_framework import viewsets
from django.db.models import Q
from drf_aggrid import AgGridFilterBackend, AgGridPagination
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
filter_backends = [AgGridFilterBackend]
pagination_class = AgGridPagination
def get_aggrid_custom_filters(self):
return {
'internal_participants': self.filter_internal_participants,
}
def filter_internal_participants(self, field, filter_condition, queryset, request, view):
filter_type = filter_condition.get('filterType')
if filter_type == 'set':
values = filter_condition.get('values', [])
if not values:
return queryset
participant_q = Q()
for value in values:
participant_q |= Q(participants__is_internal=True, participants__id=value)
return queryset.filter(participant_q).distinct()
# Handle other filter types...
return querysetIf you're getting an empty error when using the format=aggrid parameter, make sure you've included the AgGridRenderer in your view's renderer_classes:
renderer_classes = [JSONRenderer, BrowsableAPIRenderer, AgGridRenderer]This renderer is responsible for properly formatting the response when the format=aggrid parameter is used.
If you're experiencing pagination issues (e.g., getting more rows than requested), make sure:
-
You're using the
AgGridPaginationclass as your pagination class:pagination_class = AgGridPagination
-
You're providing both
startRowandendRowparameters in your request:GET /api/ag-grid/your-model-ag-grid/?startRow=0&endRow=100 -
The
endRowparameter is exclusive, meaningstartRow=0&endRow=100will return rows 0-99 (100 rows total). -
You've overridden the
get_paginated_responsemethod in your view to ensure proper formatting:def get_paginated_response(self, data): if hasattr(self, 'paginator') and isinstance(self.paginator, AgGridPagination): return self.paginator.get_paginated_response(data) return Response({ 'rowCount': len(data), 'totalCount': len(data), 'rows': data })
-
The filter backend is not applying pagination. Only the pagination class or the renderer should apply pagination.
If rowCount and totalCount are showing the same value even when filters are applied, make sure:
-
The filter backend is properly setting both values:
_ag_grid_total_countshould be set to the count of the base queryset before any filtering_ag_grid_filtered_countshould be set to the count of the queryset after filtering
-
The pagination class is using these values correctly:
totalCountshould come from_ag_grid_total_countrowCountshould come from_ag_grid_filtered_count
-
You're not overriding these values elsewhere in your code.
If you're experiencing issues with field names, make sure:
-
You're using the correct field names in your ag-grid configuration. The field names should match the serializer field names.
-
For nested fields, use dot notation in your ag-grid configuration (e.g.,
event_type.name). The filter backend will automatically convert these to Django ORM field names (e.g.,event_type__name).
If you want your views to automatically use AgGridPagination for ag-grid requests and standard pagination for other requests, you can use the AutoAgGridPaginationMixin:
from drf_aggrid.mixins import AutoAgGridPaginationMixin
from rest_framework import viewsets
from rest_framework.pagination import PageNumberPagination
class MyViewSet(AutoAgGridPaginationMixin, viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
pagination_class = PageNumberPagination # This will be used for non-aggrid requestsThe AutoAgGridPaginationMixin will:
- Use
AgGridPaginationfor requests with?format=aggrid - Use your specified
pagination_classfor all other requests - Restore the original pagination class after the response is generated
You can also specify a different standard pagination class:
class MyViewSet(AutoAgGridPaginationMixin, viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
pagination_class = DefaultPaginationClass
standard_pagination_class = CustomPaginationClass # This will override the defaultThis mixin makes it easy to support both ag-grid and standard API clients with the same view.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.