Django - reverse nested url with drf-nested-routers

2.2k views Asked by At

I configured my api url as

localhost:port/app_name/students/{student_id}/macro/{macro_id}/lto

using drf-nested-routers extension. Basically, each students has some macro categories assigned, that in turns have some Long Term Objectives (LTOs). I've tested it using curl and Postman and everything seems to work. Now I need to write a more precise test case for my LTO model. This is my urls.py

from django.urls import path, re_path
from django.conf.urls import include
from rest_framework import routers
from app_name.views.views import UserViewSet, StudentViewSet, MacroViewSet, LTOViewSet, MacroAssignmentViewSet
from rest_framework_nested import routers as nested_routers

# application namespace
app_name = 'app_name'

router = routers.DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
router.register(r'macro', MacroViewSet, basename='macro')
router.register(r'macro-assignments', MacroAssignmentViewSet, basename='macro-assignment')

student_router = routers.DefaultRouter()
student_router.register(r'students', StudentViewSet, basename='student')
lto_router = nested_routers.NestedSimpleRouter(student_router, r'students', lookup='student')
lto_router.register(r'macro/(?P<macro_pk>.+)/lto', LTOViewSet, basename='lto')


urlpatterns = [
    re_path('^', include(router.urls)),
    re_path('^', include(student_router.urls)),
    re_path('^', include(lto_router.urls)),
]

The issue is that I cannot use the reverse() method correctly to get the url of my LTOViewSet to test it.

self.url = reverse('app_name:student-detail:lto', {getattr(self.student, 'id'), getattr(self.macro, 'id')})

This gives the following error

django.urls.exceptions.NoReverseMatch: 'student-detail' is not a registered namespace inside 'app_name'

In other test cases, I use very similar sentences and those work fine

self.list_url = reverse('app_name:student-list')

reverse('app_name:student-detail', {post_response.data['id']})
1

There are 1 answers

3
AudioBubble On BEST ANSWER

So here's the minimally reproducible example:

# main/viewsets.py
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth.models import User, Group


class StudentViewSet(ModelViewSet):
    model = User


class LTOViewSet(ModelViewSet):
    model = Group
# main/urls.py
from django.urls import re_path, include
from rest_framework import routers

from rest_framework_nested import routers as nested_routers
from .viewsets import StudentViewSet, LTOViewSet

# application namespace
app_name = "main"

student_router = routers.DefaultRouter()
student_router.register(r"students", StudentViewSet, basename="student")
lto_router = nested_routers.NestedSimpleRouter(
    student_router, r"students", lookup="student"
)
lto_router.register(r"macro/(?P<macro_pk>.+)/lto", LTOViewSet, basename="lto")

urlpatterns = [
    re_path("^", include(student_router.urls)),
    re_path("^", include(lto_router.urls)),
]
reverse('main:lto-detail', args=(1,1,1))
Out[5]: '/api/students/1/macro/1/lto/1/'

So indeed your error was passing just the router basename not a final endpoint to reverse and because of the nesting we were thrown off by student-detail not reversing (which I still don't get).