drf-flex-fields
drf-flex-fields (DRF-FF) is a Python package for Django REST Framework that provides flexible, dynamic fields and nested resources for serializers. It allows clients to control which fields are included or excluded, and to dynamically expand related models via URL parameters like `?fields=id,name` or `?expand=organization.owner.roles`. The library focuses on simplicity with minimal entanglement with DRF's core classes. It is actively maintained with regular updates, including bug fixes and new features, as seen in the recent 1.0.x releases.
Common errors
-
Expanded field 'book' is not showing in the response when using `?expand=book`.
cause If you are also using the `?fields=` query parameter, the expanded field 'book' must be explicitly included in the `fields` parameter as well.fixModify your request to include the expanded field in `fields`, e.g., `GET /api/templates/1/?fields=id,coupon_type,book&expand=book`. -
CircularDependencyError or similar import issues when referencing serializers in `expandable_fields`.
cause Python's import system can struggle with circular imports. Referencing serializers by direct class import can create these issues.fixUse string-based lazy evaluation for serializers in `expandable_fields`, ensuring the full app path is provided: `expandable_fields = {'category': ('myapp.serializers.CategorySerializer', {'many': True})}`. -
`AttributeError: 'Request' object has no attribute 'query_params'` or context missing in nested serializers.
cause When manually creating a serializer instance, especially within a custom view or `@action` method, the `request` context might not be automatically passed down to nested serializers, leading to `drf-flex-fields` not properly processing `expand` or `fields`.fixExplicitly pass the `request` object in the serializer context: `serializer = MySerializer(instance, context={'request': request})`.
Warnings
- gotcha By default, expanding fields is only allowed on detail (single object) views to prevent accidental over-fetching on list views. To enable expansion on list views (e.g., `GET /items/?expand=related_field`), you must explicitly add the field name to `permit_list_expands` on your `FlexFieldsMixin` based ViewSet.
- gotcha If both `fields` and `expand` query parameters are used simultaneously, the `fields` parameter takes precedence. An expanded field will not be included in the response if it's not also explicitly listed in the `fields` parameter. This can lead to unexpected missing data if not understood.
- gotcha Using deep or recursive expansions (`?expand=a.b.c`) can result in a large number of database queries (`N+1` problem) and significantly impact performance. This is especially true without proper database query optimization.
- gotcha When referencing serializers by a string name (lazy evaluation) in `expandable_fields`, ensure you include the full app path (e.g., `'myapp.serializers.MySerializer'`) to avoid import resolution issues, especially with complex project structures or circular dependencies.
- gotcha The `expand=*` or `expand=~all` wildcard options, while convenient, can expose the entire object graph and may lead to sensitive data exposure or performance degradation if not carefully controlled and restricted in a public API.
Install
-
pip install drf-flex-fields
Imports
- FlexFieldsModelSerializer
from rest_flex_fields import FlexFieldsModelSerializer
- FlexFieldsMixin
from rest_flex_fields.views import FlexFieldsMixin
- FlexFieldsFilterBackend
from rest_flex_fields.filter_backends import FlexFieldsFilterBackend
- FlexFieldsDocsFilterBackend
from rest_flex_fields.filter_backends import FlexFieldsDocsFilterBackend
Quickstart
import os
from django.db import models
from rest_framework import serializers, viewsets
from rest_flex_fields import FlexFieldsModelSerializer
from rest_flex_fields.views import FlexFieldsMixin
# Minimal Django setup for demonstration
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
# Define a simple Django model (e.g., in models.py)
class Country(models.Model):
name = models.CharField(max_length=100)
population = models.IntegerField()
def __str__(self):
return self.name
class State(models.Model):
name = models.CharField(max_length=100)
country = models.ForeignKey(Country, related_name='states', on_delete=models.CASCADE)
def __str__(self):
return self.name
# Define FlexFields serializers
class StateSerializer(FlexFieldsModelSerializer):
class Meta:
model = State
fields = ('id', 'name')
class CountrySerializer(FlexFieldsModelSerializer):
class Meta:
model = Country
fields = ('id', 'name', 'population', 'states')
expandable_fields = {
'states': (StateSerializer, {'many': True})
}
# Define a ViewSet using FlexFieldsMixin
class CountryViewSet(FlexFieldsMixin, viewsets.ModelViewSet):
queryset = Country.objects.all()
serializer_class = CountrySerializer
# Allow 'states' to be expanded on list views (GET /countries/)
permit_list_expands = ['states']
# Example usage (conceptual, in a Django/DRF app context):
# GET /countries/1/?expand=states
# GET /countries/?fields=id,name