POST being redirected to GET during testing

I’m trying to create a unit test for a POST request. If I don’t allow the request to follow, I get a 302 with the URL pointing to the same URL I’m trying to post to. If I do allow the request to follow it is redirected to the view I’m trying to hit but loses all data and becomes a GET request.

Here is my unit test:

import uuid

from django.contrib.auth import get_user
from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework.test import APITestCase, force_authenticate


class MeetingChatAPITest(APITestCase):
    USERNAME = "test_username"
    EMAIL = "a@b.com"
    PASSWORD = "password"

    def setUp(self):
        self.user = User.objects.get(username=self.USERNAME)

    def test_meeting_chat_with_text_input(self):
        url = "/meeting-chat/"

        request_payload = {
                "foo": "bar"
        }

        self.asertTrue(self.client.login(username=self.USERNAME, password=self.PASSWORD))

        response = self.client.post(url, request_payload, format="json", follow=True)

        self.assertEqual(response.status_code, 200)

And my urls:

# core.urls

app_name = 'core'

urlpatterns = [
    path('meeting-chat/', chat_meeting, name='meeting_chat'),
]

# <project>urls

urlpatterns = [

    path('', include('core.urls')),
]

And my view function:

@login_required
@require_POST
@csrf_exempt
def chat_meeting(request):
    try:
        payload = json.loads(request.body.decode('utf-8'))
    except json.JSONDecodeError:
        return JsonResponse({'error': 'Invalid JSON'}, status=400)  # This line is only hit if I do follow=True on the request and remove the @require_POST decoration

Didn’t you send the request to meeting-chat instead of meeting-chat/ ?

Check your URL formatting & APPEND_SLASH
If the URL pattern uses a trailing slash (path('meeting-chat/', ...)), the test must post to /meeting-chat/
Conversely, if there’s no trailing slash, make your test use /meeting-chat and you should set APPEND_SLASH = False to avoid Django auto-redirecting POSTs
By default, APPEND_SLASH = True — a POST without the slash triggers a redirect with status 301/302, stripping the body and changing to a GET

Hope that helps

Is this the complete view function you are showing here?

If so, this is an invalid view. A Django view must return a response, which you are not showing here.

If not, please show the complete view.

I don’t think so, unless I’m misunderstanding what you mean. The request in the test is being sent to url = "/meeting-chat/". I did try all 4 combinations of slashes or no slashes at either end, all of which result in the same response of 302.

Thank you for your explanation. I have confirmed the following:

  • APPEND_SLASH is not set anywhere, thus is set to True
  • The URL pattern ends in a slash path('meeting-chat/', ...)
  • The test posts to /meeting-chat/ , but I did try all 4 combinations of slashes.

If I’m understanding your response correctly this configuration should work.

All tests still return a 302 (or allowing redirect, return a 405 since the POST is changed to a GET).

1 Like

For brevity and confidentiality, I did not include the full view, but I doubt the issue is within the view as with a debugger I can confirm that no line is executed (it is only executed if I remove the require_POST decorator and allow follow=True). The full view always returns a JsonResponse as the one shown, or a one with a 200 status code in the case of the happy path.

You’re using login_required . The remaining possibility is that the request is being made with an anonymous session, which is causing a redirect to the login page. Try either removing login_required or making the request with a logged-in session.

I’m logging in just above the request line, using:
self.asertTrue(self.client.login(username=self.USERNAME, password=self.PASSWORD))

I have also tried to remove the login-required decorator, to no success. And the location being redirected to is the original url itself (/meeting_chat/), not the login page.

I can’t tell anything more from the content so far. Please disclose the full code of that view.

I apologize if my need for confidentiality makes it seem like I’m not cooperating. It is not my intention. While I can’t disclose the full code of the view, I can confirm that the problem persists when the code is reduced to what has been shown here before:

@login_required
@require_POST
@csrf_exempt
def chat_meeting(request):
    print("Entered the view")

    try:
        payload = json.loads(request.body.decode('utf-8'))
    except json.JSONDecodeError:
        return JsonResponse({'error': 'Invalid JSON'}, status=400)

    response = {"foo": "bar"}
    return JsonResponse(response)

Additionally, the print at the top of the view is not printed (and breakpoints placed by the debugger are not caught), so I don’t believe the code of the view is the cause.
Thank you again for your time in your responses.

I have since also tested the same view function but replacing @login_required with @login_not_required and the same redirect happens, so the problem is likely not due to login.

I’ve found the root cause of this. It’s caused by the core.middlewares.AllowedUsersMiddleware middleware.

Edit: It’s also possible to fix this by making a get request before the post.

So the problem was because of the middleware you created?