django.urls.exceptions.NoReverseMatch:使用关键字参数反转 'votes-detail'

django.urls.exceptions.NoReverseMatch: Reverse for 'votes-detail' with keyword arguments

我正在尝试将 reverse() 添加到我的自动化测试 api 调用中,而不是对 url 进行硬编码。我的 urls.py 中有一个路由器指向 ModelViewSetModelSerializer.

这是我的 urls.py

router = DefaultRouter()
router.register('votes', VoteViewSet, basename='votes')

app_name = 'api'
urlpatterns = [
    path('', include(router.urls)),
]

这是我的views.py

class VoteViewSet(viewsets.ModelViewSet):
    queryset = Vote.objects.all()
    serializer_class = VoteSerializer
    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

这是我的 serializers.py

class VoteSerializer(serializers.ModelSerializer):
    entry = serializers.SlugField()

    def create(self, validated_data):
        user = self.context['request'].user
        entry_slug = validated_data['entry']
        entry_queryset = Entry.objects.filter(slug=entry_slug)
        entry = entry_queryset[0]
        response = {
            'user': user,
            'entry': entry_queryset[0]
        }

        if entry_queryset.filter(author=user):
            raise PermissionDenied("Cannot upvote your own entry")
        elif Vote.objects.filter(entry=entry, user=user):
            raise PermissionDenied("Already voted")

        return super().create(response)

    class Meta:
        model = Vote
        fields = '__all__'
        read_only_fields = ['user']

以下test.py失败

class EntryViewSetTestCase(APITestCase):

    @classmethod
    def setUpTestData(cls):
        user = get_user_model()
        cls.user_data = {
            'email': 'user@localhost.com',
            'password': 'hgEYqf$nQ8x5Pms'
        }
        cls.user = user.objects.create_user(
            username='user', 
            email='user@localhost.com', 
            password='hgEYqf$nQ8x5Pms'
        )        
        cls.other_user_data = {
            'email': 'otheruser@localhost.com',
            'password': 'hgEYqf$nQ8x5Pms'
        }
        cls.other_user = user.objects.create_user(
            username='otheruser', 
            email='otheruser@localhost.com', 
            password='hgEYqf$nQ8x5Pms'
        )
        cls.entry_data = {
            'title': 'title',
            'description': 'description',
        }
        cls.entry = Entry(
            title='title',
            description='description',
            author=cls.user
        )
        cls.entry.save()    

    def test_vote_entry_success(self):
        token = self.client.post(reverse('token_obtain_pair'), self.other_user_data)
        self.client.credentials(
            HTTP_AUTHORIZATION='Bearer ' + token.data['access'])
        entry = {'entry': self.entry.slug}
        response = self.client.post(reverse('api:votes-detail', kwargs=entry))
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

这个returns下面的错误

django.urls.exceptions.NoReverseMatch: Reverse for 'votes-detail' with keyword arguments '{'entry': 'd901e2-title'}' not found. 2 pattern(s) tried: ['api/votes/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$', 'api/votes/(?P<pk>[^/.]+)/$']

当我使用硬编码url时,测试通过

response = self.client.post('/api/votes/', entry)

你试过了吗reverse('api:votes-detail', kwargs=entry)?只有在使用 namespace= 参数注册路由器时才需要 api:

无论如何,我强烈建议使用 django-extension 的 show_urls。看看this SO thread.

至少对于自动化测试,看起来使用 *-list 而不是 *-detail 是解决方案。感谢 Tommy 推荐 django-extensions 来解决这个问题。

response = self.client.post(reverse('api:votes-list'), entry)