I have been trying for hours but cannot figure out how to pass a url argument through an APIRequestFactory put request. I have tried it through Postman when running my server and the url variable is passed just fine, but when I run it in my tests it stops working.
What I mean is that when I send a Postman PUT request to ‘/litter/1/’ it will successfully take in the 1 as the variable litterId since my url is setup like this
path('litter/', include('apps.litter.urls')),
and
path('<int:litterId>/', LitterView.as_view(), name='litter-with-id')
But when I try and send an APIRequestFactory put request to that same url, for some reason the 1 will not go through as the litterId anymore.
Some relevant pieces of code…
My top level url.py
from rest_framework.authtoken import views from apps.litter.views import LitterView urlpatterns = [ path('admin/', admin.site.urls), path('auth/', include('apps.my_auth.urls')), path('litter/', include('apps.litter.urls')), ]
This is my app specific urls.py
from .views import LitterView urlpatterns = [ path('', LitterView.as_view(), name='standard-litter'), path('<int:litterId>/', LitterView.as_view(), name='litter-with-id'), ]
Here is my views.py
import json from django.contrib.auth.models import User from django.db import IntegrityError from django.views.decorators.csrf import csrf_exempt from rest_framework import authentication, permissions from rest_framework.parsers import JSONParser from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView from django.db import models from .models import Litter from .serializers import LitterSerializer #@csrf_exempt class LitterView(APIView): """ View for litter related requests * Requres token auth """ permission_classes = (IsAuthenticated,) authentication_classes = [authentication.TokenAuthentication] renderer_classes = [JSONRenderer] def put(self, request, litterId=0): """ Updates an old litter """ try: litterModel = Litter.objects.get(user=request.user, id=litterId) except Litter.DoesNotExist: returnData = {'status': 'fail', 'error': 'Could not find object with that id.'} return Response(returnData) serializer_class = LitterSerializer serialized = LitterSerializer(litterModel, data=request.data) if serialized.is_valid(): litterModel = serialized.save() returnData = {'status': 'okay', 'litter': [serialized.data]} return Response(returnData) else: return Response(serialized.errors, status=400)
And here is the relevant test.
def test_easy_successful_put_type(self): """ Testing a simple put """ user = UserFactory() amount = 40 amountChange = 20 litter = LitterFactory(user=user, amount=amount) data = {'typeOfLitter': litter.typeOfLitter, 'amount': litter.amount + amountChange, 'timeCollected': litter.timeCollected} url = '/litter/' + str(litter.id) + '/' request = self.factory.put(url, data, format='json') force_authenticate(request, user=user) view = LitterView.as_view() response = view(request).render() responseData = json.loads(response.content)
No matter what I do, I cannot get the int:litterId to get passed in, the put function always has the default value of 0. Any help would be greatly appreciated.
Advertisement
Answer
Your problem is here:
response = view(request).render()
You are manually passing the request to the view, also not passing the kwarg litterId, instead use APIClient
and make a put request to the url. First import the required modules:
from django.urls import reverse from rest_framework.test import APIClient
then:
user = UserFactory() amount = 40 amountChange = 20 litter = LitterFactory(user=user, amount=amount) data = { 'typeOfLitter': litter.typeOfLitter, 'amount': litter.amount + amountChange, 'timeCollected': litter.timeCollected } url = reverse('litter-with-id', kwargs={'litterId': litter.id}) client = APIClient() client.force_authenticate(user=user) response = client.put(url, data, format='json')