Django - обратный вложенный URL-адрес с drf-nested-router

Я настроил свой URL-адрес API как

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

с помощью расширения drf-nested-routers. По сути, каждому учащемуся назначены некоторые макрокатегории, которые, в свою очередь, имеют некоторые долгосрочные цели (ДН). Я тестировал его с помощью curl и Postman, и, похоже, все работает. Теперь мне нужно написать более точный тестовый пример для моей модели LTO. Это мой 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)),
]

Проблема в том, что я не могу правильно использовать метод reverse (), чтобы получить URL-адрес своего LTOViewSet для его проверки.

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

Это дает следующую ошибку

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

В других тестовых случаях я использую очень похожие предложения, и они работают нормально

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

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

person dc_Bita98    schedule 25.07.2020    source источник
comment
Боковое примечание: вам не нужно создавать routers.DefaultRouter объектов более одного раза   -  person JPG    schedule 25.07.2020
comment
@ArakkalAbu Даже если я не хочу, чтобы student_router'urls были вложены в эти router?   -  person dc_Bita98    schedule 25.07.2020
comment
Зная, что такое пространства имен URL, поможет Вы? Вы пытаетесь ссылаться на lto по пространству имен, которое вы не создали.   -  person Melvyn    schedule 25.07.2020
comment
@Melvyn из того, что я понял, для каждого пространства имен соответствует app_name. У меня все внутри одного приложения, поэтому мне не следует использовать другие пространства имен   -  person dc_Bita98    schedule 25.07.2020
comment
Сообщение об ошибке и обратный вызов не совпадают: reverse('app_name:student-detail:lto' против Reverse for 'student-detail-lto'. См. Двоеточие в сравнении с -. Так что это из двух?   -  person Melvyn    schedule 25.07.2020
comment
@ Мелвин второй, извини. Я только что обновил вопрос. Это была еще одна попытка найти решение.   -  person dc_Bita98    schedule 25.07.2020
comment
Я не могу воспроизвести это с уменьшенной версией того, что у вас есть. Вы также передаете набор, поэтому даже если вы решите текущую проблему, вы получите TypeError: unhashable type: 'set'. Но чтобы добраться до сути: добавьте print(student_router.urls) выше urlpatterns, а затем посмотрите, есть ли там сведения о студенте.   -  person Melvyn    schedule 25.07.2020
comment
@ Мелвин, ты прав. Сведения о студенте включены в print (student_router.urls). На самом деле я нашел возможное решение: self.url = reverse('app_name:lto-list', (getattr(self.student, 'id'), getattr(self.macro, 'id'))) Насколько я понимаю, вы всегда должны указывать -list или -detail в конце URL-адреса, который вы хотите изменить.   -  person dc_Bita98    schedule 25.07.2020


Ответы (1)


Итак, вот минимально воспроизводимый пример:

# 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/'

Итак, действительно, ваша ошибка передавала только базовое имя маршрутизатора, а не конечную конечную точку, которую нужно изменить, и из-за вложенности мы были отброшены из-за того, что student-detail не реверсировал (чего я до сих пор не понимаю).

person Melvyn    schedule 25.07.2020
comment
student-detail не нужно реверсировать вручную. Как только Django знает конечную точку (в данном случае lto-detail / list), он автоматически меняет весь вложенный URL-адрес. Единственное, что ему нужно, это аргументы в URL-адресе - ›первичный ключ студента и макроса в данном случае. - person dc_Bita98; 25.07.2020
comment
А, теперь я понимаю ... когда вы обновили сообщение об ошибке, оно больше жаловалось на пространство имен, а не на имя URL. Теперь все имеет смысл! - person Melvyn; 25.07.2020