1

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']})
dc_Bita98
  • 851
  • 2
  • 17
  • 35
  • Side Note: You don't have to create `routers.DefaultRouter` objects more than once – JPG Jul 25 '20 at 16:56
  • @ArakkalAbu Even if I don't want to have `student_router`'urls nested with those ones of `router`? – dc_Bita98 Jul 25 '20 at 17:02
  • So does knowing what [URL namespaces](https://docs.djangoproject.com/en/3.0/topics/http/urls/#url-namespaces) are, help you? You're trying to reference lto by namespace, which you haven't created. –  Jul 25 '20 at 17:03
  • @Melvyn from what I've understood, for each **namespace** corresponds a **app_name**. I have everything inside a single app, so I shouldn't use other namespaces – dc_Bita98 Jul 25 '20 at 17:07
  • The error message and reverse call don't match up: `reverse('app_name:student-detail:lto'` versus `Reverse for 'student-detail-lto'`. See colon versus -. So which of the two is it? –  Jul 25 '20 at 17:10
  • @Melvyn the second one, sorry. I've just updated the question. That was another attempt i made to find a solution – dc_Bita98 Jul 25 '20 at 17:17
  • I can't reproduce this, with a reduced version of what you have. You're also passing in a set, so even if you resolve your current issue you will get `TypeError: unhashable type: 'set'`. But to get to the bottom: add `print(student_router.urls)` above `urlpatterns` and then see if student-detail is there. –  Jul 25 '20 at 17:59
  • @Melvyn you're right. **student-detail** is included in **print(student_router.urls)**. Actually I found a possible solution: `self.url = reverse('app_name:lto-list', (getattr(self.student, 'id'), getattr(self.macro, 'id')))` As I understand, you must always define **-list** or **-detail** at the end of the url you want to reverse – dc_Bita98 Jul 25 '20 at 18:05

1 Answers1

2

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).

  • `student-detail` does not need to get reversed manually. Once Django knows the final endpoint ('lto-detail/list' in this case), he automatically reverse the entire nested url. The only things he requires are the arguments in the url --> the primary key of student and of a macro in this case – dc_Bita98 Jul 25 '20 at 18:16
  • Ah, I see now...when you updated the error message it complained about the namespace, not the url name anymore. All makes sense now! –  Jul 25 '20 at 18:18
  • Do you know whether I can use this approach to test nested urls? – Elias Prado Jun 06 '22 at 21:12