Django Rest Framework Test Cases returning successful POSTs with HTTP 302 Response

I’m having a wee issue with my test suite with Django Rest Framework. Specifically, when doing a post with self.client = APIClient() . When I perform a post in the below code, I receive a redirect response of HTTP 302. The object is being created successfully, but my test is failing as 302 != 201

from rest_framework.test import APITestCase, APIClient, APIRequestFactory
from rest_framework.test import force_authenticate


class AlertTests(APITestCase):
    def setUp(self):
        user = get_user_model()
        self.alert = create_alert("4556")
        self.alert_data = {
            "id": "12ab34cd56",
            "acknowledged": True,
            "acknowledged_timestamp": timezone.now(),
            "acknowledged_by": "Lord Boring Henry",
            "created_timestamp": timezone.now(),
            "description": "Hello hello",
            # "group_name": create_item(Group, unique_identifier="1234556666").id,
            "severity": "MAJOR",
            "state": "OPEN",
            "type": "MAJOR",
            "timestamp": timezone.now(),
        }

        self.test_user = user(
            username="jim", password="monkey123", email="jim@jim.com"
        )
        self.test_user.save()

        # Build the viewsets
        self.alert_list_view = AlertViewSet.as_view(actions={"get": "list"})
        self.alert_detail_view = AlertViewSet.as_view(
            actions={"get": "retrieve"}
        )
        self.url = '/api/v1/alerts'
        self.list_url = reverse("alert-list")
        self.detail_url = reverse(
            "alert-detail", kwargs={"id": self.alert.id, }
        )
        self.factory = APIRequestFactory()
        self.client = APIClient()

  def test_alert_post(self):
    self.client.force_authenticate(user=self.test_user)
    response = self.client.post(
      self.url,
      self.alert_data,
      format="json",
      follow=True,
      secure=True
    )
    get = self.client.get(f'/api/v1/alerts/{self.alert.id}')
    print("# Response Content self.client.post()")
    print(response.render().content + "\n")
    
    print("# GET Response Content from self.client.get()")
    print(get.render().content + "\n")
    
    print("# Respone redirect chain from self.client.post()")
    print(response.redirect_chain)
    

And the output from my print statements
And the output from the print statement

 # Response Content self.client.post()
b'{"id":"12ab34cd56","url":"https://testserver/alerts/12ab34cd56","acknowledged":true,"acknowledged_by":"Bob","acknowledged_timestamp":"2020-04-21T09:52:30.057408Z","created_timestamp":"2020-04-21T09:52:30.057422Z","description":"Hello hello","severity":"MAJOR","state":"OPEN","timestamp":"2020-04-21T09:52:30.057431Z","type":"MAJOR","group_name":null,"labels":[]}'

# GET Response Content from self.client.get()
b'{"id":"4556","url":"http://testserver/alerts/4556","acknowledged":true,"acknowledged_by":"Bob","acknowledged_timestamp":"2020-04-21T09:52:30.053463Z","created_timestamp":"2020-04-21T09:52:30.053480Z","description":"Hello hello","severity":"MAJOR","state":"OPEN","timestamp":"2020-04-21T09:52:30.054900Z","type":"MAJOR","group_name":"http://testserver/groups/lovely-little-object-4556","labels":[]}'

# Respone redirect chain from self.client.post()
[]

I’ve set router = SimpleRouter(trailing_slash=False) for my Viewsets, and APPEND_SLASH=False in my settings.py in order to rule out redirects caused by appending the trailing slash.

My urls.py

from rest_framework.routers import SimpleRouter
from django.urls import path, include
from rest_framework.authtoken import views
from .views import APViewSet, LabelViewSet, SiteViewSet, GroupViewSet, AlertViewSet

router = SimpleRouter(trailing_slash=False)
router.register("aps", APViewSet, basename="aps")
router.register("labels", LabelViewSet, basename="label")
router.register("sites", SiteViewSet, basename="site")
router.register("groups", GroupViewSet, basename="group")
router.register("alerts", AlertViewSet, basename="alert")

urlpatterns = [
    path("token", views.obtain_auth_token, name="token"),
    path("", include(router.urls)),
  

This particular issue is affecting all my test cases for POST in all of my viewsets and I must admit I’m completey stumped. Is there anyone here who knows what’s going on?

Cheers,

C

Sorry, I don’t know enough about DRF to provide any real assistance, I can only offer some suggestions for gathering more information.

Have you tried accessing this url from a browser, using the browser’s developer tools to see all the responses you’re receiving from the server? (Or, using something like curl to see the transactions in more detail, or tcpdump or other packet capture tools to do the same thing but from the network layer, or basically anything that’s going to give you more information than what you’re seeing here.)

What I noticed is that you’re issuing requests to /api/v1/alerts/4556, but your response is saying the url was '/alerts/4556’ if I’m understanding your JSON response content correctly. (Again, my lack of DRF knowledge is showing through, because I don’t see where you’re defining an ‘/api/v1’ set of URLs in what you’ve posted here.)

1 Like

Hi Ken,

Thank you for the reply and well spotted with the URLs; It was in fact my issue. I have two Django apps, one for the API and one for the HTML side of things.

I had inadvertently used URL names in the HTML app such as alerts and alert-detail.

When you define a viewset with DRF, you define a basename, in my case alert for your viewset. DRF then creates some URLS specifically for the different types of actions you can perform on the API. As an example, if I have a basename of alert, DRF will create URLs of alert-list for GETing the list view, and alert-detail for the getting the single alert object. This alert-detail URL from the API clashed with the alert-detail URL from the HTML app.

Add to that that I am using HyperlinkedModelSerializers which use URLs for object lookups in the API, there was some sort of clash and it looks like the test was POSTing successfully to the HTML app (which it supports) and then being redirected back the HTML apps list view.

All tests pass now after I appended api to all of my API URLs.

Again, really well spotted. Often helps with a second pair of eyes.

Many thanks.

Cheers,

C

1 Like

Thank you for the follow-up and the explanation, I always enjoy learning something new.

Ken