Redirecting after making PUT request: can I redirect with GET request instead?

For my Django project, I have a primary model, Book, and an associated BookViewSet (from Django REST Framework). From the frontend, it's possible to edit other objects like Page, which POSTs to a corresponding PageViewSet.

Changes to Page objects will also affect the Book, so I want to return an updated BookViewSet after processing changes to the Page.

After processing a PUT request for the Page via the PageViewSet, I've tried leveraging the redirect shortcut to send the request to the BookViewSet, like this:

return redirect('/books/10', pk=book_id)

I expected (and hoped) this would make a GET request so that the backend would return an updated Book via the BookViewSet using the "retrieve" method.

However, it seems like the BookViewSet still receives PUT request, which means the request will be sent to the BookViewSet's "update" method.

  1. Is this expected?

  2. If so, is there a way I can "get" an updated Book view after making changes to Page? I can achieve this by putting that logic into the BookViewSet's "update" method, but I already have that logic in the "retrieve" method and don't want to duplicate it.

I recently found this pattern described as "post, redirect, get" and am not sure if there's something else I should to achieve that GET request.

PageViewSet

The specific operation in this case is an update to the page's "layout":

class PageViewSet(LoginRequiredMixin, viewsets.ModelViewSet):
    queryset = Page.objects.all()
    renderer_classes = [TemplateHTMLRenderer, JSONRenderer]
    serializer_class = PageSerializer

    def update(self, request, pk=None):
        page = self.get_object()
        page.update_layout(
            Layout.objects.get(
                id=request.data[f"{page.id}-layout"]
            )  # Form field prefix is set to page ID
        )

        return redirect("book-detail", pk=page.book.id)

The console shows the following after the GET request:

(django.request) log/log_response: Bad Request: /books/11/
[15/Oct/2024 13:14:40] "PUT /books/11/ HTTP/1.1" 400 15

Yes, this is expected behavior.

Fixing it depends a little on how you are consuming the backend.

Django's redirect() defaults to HTTP 302, which if you look at the link, means that HTTP spec-compliant clients will not change their HTTP method for (meaning the PUT gets preserved). This is the behavior you are seeing.

If you're using a frontend that you have control over, it will be trivial for you to write a special case, either for that specific page or as a middleware, where you force-change it to a GET whenever you follow 302s.

But your post kind of also indicates that you're either using a browser or someone else's client and their default behavior. If that is the case, maybe it is spec compliant? If it is, changing the backend's response to HTTP 303 will probably resolve the issue, as 303 specifically is made for the scenario you are describing in this post.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections

Django's redirect() doesn't have any built-in classes that will emit HTTP 303, however.

You can try passing status=303 as an arg, but I'm not sure that will work.

If that doesn't work, my next suggestion would be to hand-craft an appropriate redirect using DRFs natives:

url = reverse("book-detail", pk=page.book.id)

return Response(
    status  = status.HTTP_303, 
    headers = {
        'Location': url
    }
)
Вернуться на верх