DRF Nested Routers
DRF Nested Routers is a Python library that extends Django REST Framework (DRF) to facilitate the creation of nested resources. It provides specialized routers and relation fields necessary for building a full REST URL structure where one resource hierarchically depends on another (e.g., `/authors/{author_pk}/books/`). This is crucial when child resources logically exist only within the context of their parent. The library is actively maintained, with the current version being 0.95.0, and typically releases updates to maintain compatibility with new Django and DRF versions.
Warnings
- breaking Version 0.94.2 and later dropped support for Python 3.8 and older, Django 4.1 and older, and DRF 3.13 and older. Version 0.93.5 dropped support for Python 3.6, Django 1.11, and DRF 3.6.
- gotcha The `lookup` parameter in `NestedSimpleRouter` or `NestedDefaultRouter` must match the URL keyword argument expected for the parent resource in the URL pattern and the corresponding lookup in the child ViewSet.
- gotcha When using `NestedHyperlinkedModelSerializer` for nested hyperlinks, you must define `parent_lookup_kwargs` in its `Meta` class to correctly build the parent resource's URL. This maps URL keyword arguments to model fields.
- gotcha When registering a ViewSet with `Nested*Router.register()`, particularly if the ViewSet does not have a `queryset` attribute or a clear `model` attribute (e.g., a custom `ViewSet`), you *must* provide a `basename` argument.
Install
-
pip install drf-nested-routers
Imports
- routers
from rest_framework_nested import routers
- NestedHyperlinkedModelSerializer
from rest_framework_nested.serializers import NestedHyperlinkedModelSerializer
- NestedViewSetMixin
from rest_framework_extensions.mixins import NestedViewSetMixin
Quickstart
from django.db import models
from rest_framework import viewsets, serializers
from rest_framework_nested import routers
from django.urls import path, include
# models.py
class Domain(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Nameserver(models.Model):
domain = models.ForeignKey(Domain, related_name='nameservers', on_delete=models.CASCADE)
hostname = models.CharField(max_length=255)
def __str__(self):
return f'{self.hostname} ({self.domain.name})'
# serializers.py (Optional, for hyperlinked relations)
class NameserverSerializer(serializers.ModelSerializer):
class Meta:
model = Nameserver
fields = ['id', 'hostname']
class DomainSerializer(serializers.ModelSerializer):
nameservers = NameserverSerializer(many=True, read_only=True)
class Meta:
model = Domain
fields = ['id', 'name', 'nameservers']
# views.py
class DomainViewSet(viewsets.ModelViewSet):
queryset = Domain.objects.all()
serializer_class = DomainSerializer
class NameserverViewSet(viewsets.ModelViewSet):
serializer_class = NameserverSerializer
def get_queryset(self):
return self.queryset.filter(domain=self.kwargs['domain_pk'])
def perform_create(self, serializer):
domain = Domain.objects.get(pk=self.kwargs['domain_pk'])
serializer.save(domain=domain)
# urls.py
router = routers.DefaultRouter()
router.register(r'domains', DomainViewSet, basename='domain')
domains_router = routers.NestedDefaultRouter(router, r'domains', lookup='domain')
domains_router.register(r'nameservers', NameserverViewSet, basename='domain-nameserver')
urlpatterns = [
path('', include(router.urls)),
path('', include(domains_router.urls)),
]
# Example usage in a Django project's main urls.py:
# from django.urls import path, include
# from myapp import urls as myapp_urls
# urlpatterns = [
# path('api/', include(myapp_urls)),
# ]