Django rest framework 测试中的reverse()仅在django rest框架的测试中返回相对URL,这会导致404错误

Django rest framework 测试中的reverse()仅在django rest框架的测试中返回相对URL,这会导致404错误,django-rest-framework,Django Rest Framework,我正在尝试使用guide测试API的端点。具体来说,此块用于测试get请求: class GetAllPuppiesTest(TestCase): """ Test module for GET all puppies API """ def setUp(self): Puppy.objects.create( name='Casper', age=3, breed='Bull

我正在尝试使用guide测试API的端点。具体来说,此块用于测试get请求:

class GetAllPuppiesTest(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')
        Puppy.objects.create(
            name='Rambo', age=2, breed='Labrador', color='Black')
        Puppy.objects.create(
            name='Ricky', age=6, breed='Labrador', color='Brown')

    def test_get_all_puppies(self):
        # get API response
        response = client.get(reverse('get_post_puppies'))
        # get data from db
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
当我尝试将其适应我自己的测试时,它看起来是这样的:

from ..models import DemanderFeature, DemanderFeatureCollection

from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse

from ..serializers import DemanderFeatureCollectionSerializer

class GetAllDemanderFeatureCollections(TestCase):

    def setUp(self):
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0')
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1')

    def test_get_all_demandercollections(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("app.urls")),
    path('api-auth/', include('rest_framework.urls')),
]
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from app import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'demanders', views.DemanderFeatureViewSet)
router.register(r'demandercollections', views.DemanderFeatureCollectionViewSet, basename="demandercollections")
router.register(r'producers', views.ProducerFeatureViewSet)
router.register(r'producercollections', views.ProducerFeatureCollectionViewSet)
router.register(r'pathfinderrunconfigurations', views.PathfinderRunConfigurationViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router.
urlpatterns = [
    path('', include(router.urls)),
]
class DemanderFeatureCollectionViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet
):

    queryset = DemanderFeatureCollection.objects.all()
    serializer_class = DemanderFeatureCollectionSerializer
    lookup_field = 'name'

    @action(detail=True, methods=["get"])
    def geojson(self, request, *args, **kwargs):
        demanders = DemanderFeature.objects.filter(demandercollection=self.get_object())
        return Response(serialize('geojson', demanders, geometry_field='geom', fields=('name',)))

    @action(detail=True, methods=["patch"])
    def commit(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        if not request.data["committed"]:
            # User is trying to "uncommit", do not allow this
            return Response("You may not un-commit a DemanderCollection. You must copy it and make modifications on the copy.", status=status.HTTP_400_BAD_REQUEST)
        demandercollection.committed = True
        demandercollection.save()
        return Response(status=status.HTTP_204_NO_CONTENT)

    def get_queryset(self):
        user = get_object_or_404(User, username=self.request.user)
        return DemanderFeatureCollection.objects.filter(deleted=False).filter(owner=user).order_by("name")

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def destroy(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        demandercollection.deleted = True
        demandercollection.save()
        return Response(f"Successfully deleted DemanderCollection.")
class GetAllDemanderFeatureCollections(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        self.test_user = User.objects.create_user('test_user', 'a@b.com', 'test_user')
        self.other_user = User.objects.create_user('other_user', 'a@b.com', 'other_user')
        client.login(username="test_user", password="test_user")
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='otherdemanderfeaturecollection0', owner=self.other_user)

    def test_get_all_demandercollections_for_user(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.filter(owner=self.test_user).all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
但是问题是
reverse()
方法只返回相对URL(
/demandercollections/
),然后
client.get(reverse(…)
返回404。我不明白如何在测试期间强制它使用实际的显式URL

我用的是Django3

我的主URL.py如下所示:

from ..models import DemanderFeature, DemanderFeatureCollection

from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse

from ..serializers import DemanderFeatureCollectionSerializer

class GetAllDemanderFeatureCollections(TestCase):

    def setUp(self):
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0')
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1')

    def test_get_all_demandercollections(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("app.urls")),
    path('api-auth/', include('rest_framework.urls')),
]
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from app import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'demanders', views.DemanderFeatureViewSet)
router.register(r'demandercollections', views.DemanderFeatureCollectionViewSet, basename="demandercollections")
router.register(r'producers', views.ProducerFeatureViewSet)
router.register(r'producercollections', views.ProducerFeatureCollectionViewSet)
router.register(r'pathfinderrunconfigurations', views.PathfinderRunConfigurationViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router.
urlpatterns = [
    path('', include(router.urls)),
]
class DemanderFeatureCollectionViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet
):

    queryset = DemanderFeatureCollection.objects.all()
    serializer_class = DemanderFeatureCollectionSerializer
    lookup_field = 'name'

    @action(detail=True, methods=["get"])
    def geojson(self, request, *args, **kwargs):
        demanders = DemanderFeature.objects.filter(demandercollection=self.get_object())
        return Response(serialize('geojson', demanders, geometry_field='geom', fields=('name',)))

    @action(detail=True, methods=["patch"])
    def commit(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        if not request.data["committed"]:
            # User is trying to "uncommit", do not allow this
            return Response("You may not un-commit a DemanderCollection. You must copy it and make modifications on the copy.", status=status.HTTP_400_BAD_REQUEST)
        demandercollection.committed = True
        demandercollection.save()
        return Response(status=status.HTTP_204_NO_CONTENT)

    def get_queryset(self):
        user = get_object_or_404(User, username=self.request.user)
        return DemanderFeatureCollection.objects.filter(deleted=False).filter(owner=user).order_by("name")

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def destroy(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        demandercollection.deleted = True
        demandercollection.save()
        return Response(f"Successfully deleted DemanderCollection.")
class GetAllDemanderFeatureCollections(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        self.test_user = User.objects.create_user('test_user', 'a@b.com', 'test_user')
        self.other_user = User.objects.create_user('other_user', 'a@b.com', 'other_user')
        client.login(username="test_user", password="test_user")
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='otherdemanderfeaturecollection0', owner=self.other_user)

    def test_get_all_demandercollections_for_user(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.filter(owner=self.test_user).all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
我的模块url.py如下所示:

from ..models import DemanderFeature, DemanderFeatureCollection

from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse

from ..serializers import DemanderFeatureCollectionSerializer

class GetAllDemanderFeatureCollections(TestCase):

    def setUp(self):
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0')
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1')

    def test_get_all_demandercollections(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("app.urls")),
    path('api-auth/', include('rest_framework.urls')),
]
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from app import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'demanders', views.DemanderFeatureViewSet)
router.register(r'demandercollections', views.DemanderFeatureCollectionViewSet, basename="demandercollections")
router.register(r'producers', views.ProducerFeatureViewSet)
router.register(r'producercollections', views.ProducerFeatureCollectionViewSet)
router.register(r'pathfinderrunconfigurations', views.PathfinderRunConfigurationViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router.
urlpatterns = [
    path('', include(router.urls)),
]
class DemanderFeatureCollectionViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet
):

    queryset = DemanderFeatureCollection.objects.all()
    serializer_class = DemanderFeatureCollectionSerializer
    lookup_field = 'name'

    @action(detail=True, methods=["get"])
    def geojson(self, request, *args, **kwargs):
        demanders = DemanderFeature.objects.filter(demandercollection=self.get_object())
        return Response(serialize('geojson', demanders, geometry_field='geom', fields=('name',)))

    @action(detail=True, methods=["patch"])
    def commit(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        if not request.data["committed"]:
            # User is trying to "uncommit", do not allow this
            return Response("You may not un-commit a DemanderCollection. You must copy it and make modifications on the copy.", status=status.HTTP_400_BAD_REQUEST)
        demandercollection.committed = True
        demandercollection.save()
        return Response(status=status.HTTP_204_NO_CONTENT)

    def get_queryset(self):
        user = get_object_or_404(User, username=self.request.user)
        return DemanderFeatureCollection.objects.filter(deleted=False).filter(owner=user).order_by("name")

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def destroy(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        demandercollection.deleted = True
        demandercollection.save()
        return Response(f"Successfully deleted DemanderCollection.")
class GetAllDemanderFeatureCollections(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        self.test_user = User.objects.create_user('test_user', 'a@b.com', 'test_user')
        self.other_user = User.objects.create_user('other_user', 'a@b.com', 'other_user')
        client.login(username="test_user", password="test_user")
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='otherdemanderfeaturecollection0', owner=self.other_user)

    def test_get_all_demandercollections_for_user(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.filter(owner=self.test_user).all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
views.py中的
DemanderCollectionViewSet
如下所示:

from ..models import DemanderFeature, DemanderFeatureCollection

from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse

from ..serializers import DemanderFeatureCollectionSerializer

class GetAllDemanderFeatureCollections(TestCase):

    def setUp(self):
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0')
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1')

    def test_get_all_demandercollections(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("app.urls")),
    path('api-auth/', include('rest_framework.urls')),
]
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from app import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'demanders', views.DemanderFeatureViewSet)
router.register(r'demandercollections', views.DemanderFeatureCollectionViewSet, basename="demandercollections")
router.register(r'producers', views.ProducerFeatureViewSet)
router.register(r'producercollections', views.ProducerFeatureCollectionViewSet)
router.register(r'pathfinderrunconfigurations', views.PathfinderRunConfigurationViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router.
urlpatterns = [
    path('', include(router.urls)),
]
class DemanderFeatureCollectionViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet
):

    queryset = DemanderFeatureCollection.objects.all()
    serializer_class = DemanderFeatureCollectionSerializer
    lookup_field = 'name'

    @action(detail=True, methods=["get"])
    def geojson(self, request, *args, **kwargs):
        demanders = DemanderFeature.objects.filter(demandercollection=self.get_object())
        return Response(serialize('geojson', demanders, geometry_field='geom', fields=('name',)))

    @action(detail=True, methods=["patch"])
    def commit(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        if not request.data["committed"]:
            # User is trying to "uncommit", do not allow this
            return Response("You may not un-commit a DemanderCollection. You must copy it and make modifications on the copy.", status=status.HTTP_400_BAD_REQUEST)
        demandercollection.committed = True
        demandercollection.save()
        return Response(status=status.HTTP_204_NO_CONTENT)

    def get_queryset(self):
        user = get_object_or_404(User, username=self.request.user)
        return DemanderFeatureCollection.objects.filter(deleted=False).filter(owner=user).order_by("name")

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def destroy(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        demandercollection.deleted = True
        demandercollection.save()
        return Response(f"Successfully deleted DemanderCollection.")
class GetAllDemanderFeatureCollections(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        self.test_user = User.objects.create_user('test_user', 'a@b.com', 'test_user')
        self.other_user = User.objects.create_user('other_user', 'a@b.com', 'other_user')
        client.login(username="test_user", password="test_user")
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='otherdemanderfeaturecollection0', owner=self.other_user)

    def test_get_all_demandercollections_for_user(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.filter(owner=self.test_user).all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
回答后编辑 不仅公认的答案确实是罪魁祸首,而且它还揭示了正在创建的
DemanderFeatureCollection
对象也必须使用
owner
属性创建,并且
客户端
对象必须将其
登录()
方法调用到有效的用户凭据对

因此,测试类必须更新为如下所示:

from ..models import DemanderFeature, DemanderFeatureCollection

from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse

from ..serializers import DemanderFeatureCollectionSerializer

class GetAllDemanderFeatureCollections(TestCase):

    def setUp(self):
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0')
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1')

    def test_get_all_demandercollections(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("app.urls")),
    path('api-auth/', include('rest_framework.urls')),
]
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from app import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'demanders', views.DemanderFeatureViewSet)
router.register(r'demandercollections', views.DemanderFeatureCollectionViewSet, basename="demandercollections")
router.register(r'producers', views.ProducerFeatureViewSet)
router.register(r'producercollections', views.ProducerFeatureCollectionViewSet)
router.register(r'pathfinderrunconfigurations', views.PathfinderRunConfigurationViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router.
urlpatterns = [
    path('', include(router.urls)),
]
class DemanderFeatureCollectionViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet
):

    queryset = DemanderFeatureCollection.objects.all()
    serializer_class = DemanderFeatureCollectionSerializer
    lookup_field = 'name'

    @action(detail=True, methods=["get"])
    def geojson(self, request, *args, **kwargs):
        demanders = DemanderFeature.objects.filter(demandercollection=self.get_object())
        return Response(serialize('geojson', demanders, geometry_field='geom', fields=('name',)))

    @action(detail=True, methods=["patch"])
    def commit(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        if not request.data["committed"]:
            # User is trying to "uncommit", do not allow this
            return Response("You may not un-commit a DemanderCollection. You must copy it and make modifications on the copy.", status=status.HTTP_400_BAD_REQUEST)
        demandercollection.committed = True
        demandercollection.save()
        return Response(status=status.HTTP_204_NO_CONTENT)

    def get_queryset(self):
        user = get_object_or_404(User, username=self.request.user)
        return DemanderFeatureCollection.objects.filter(deleted=False).filter(owner=user).order_by("name")

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def destroy(self, request, *args, **kwargs):
        demandercollection = self.get_object()
        demandercollection.deleted = True
        demandercollection.save()
        return Response(f"Successfully deleted DemanderCollection.")
class GetAllDemanderFeatureCollections(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        self.test_user = User.objects.create_user('test_user', 'a@b.com', 'test_user')
        self.other_user = User.objects.create_user('other_user', 'a@b.com', 'other_user')
        client.login(username="test_user", password="test_user")
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1', owner=self.test_user)
        DemanderFeatureCollection.objects.create(name='otherdemanderfeaturecollection0', owner=self.other_user)

    def test_get_all_demandercollections_for_user(self):
        # get API response
        response = client.get(reverse('demandercollections-list'))
        # get data from db
        demanderfeaturecollections = DemanderFeatureCollection.objects.filter(owner=self.test_user).all()
        serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

获取
方法的
DemanderFeatureCollectionViewSet
类中,您使用
字段根据登录用户筛选模型实例


在您的测试用例中,您正在创建
DemanderFeatureCollection
实例,而没有链接
用户
,因此DRF会引发HTTP 404错误。因此,将用户附加到实例并使用同一用户发出请求将从API获得正确的响应。

添加您的
DemanderFeatureCollectionViewSet
类以及您一直使用的
reverse()
函数的引用(重点是,有两个
reverse()
fun)我将
reverse()
的导入语句添加到测试代码块,以及
DemanderFeatureCollectionViewSet
的源代码。在基本检查中,我可以看到您正在过滤
get\u queryset()
和一些
所有者
字段,这可能是404错误的原因,而404错误似乎是问题的症结所在。我不仅没有以测试用户的身份登录is,而且在创建
DemanderFeatureCollection
对象时没有关联的用户作为所有者(似乎我仍然允许创建无所有者的
DemanderFeatureCollection
模型对象,这也是一个问题)。我在
setUp()。如果你想把你的评论作为回答,我会接受的。