For my Django project, I have a primary model, Book, and an associated BookViewSet (via 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.
Is this expected?
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.
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 redirecting from the PUT PageViewSet request to the BookViewSet:
(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 302
s.
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.
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 DRF
s natives:
url = reverse("book-detail", pk=page.book.id)
return Response(
status = status.HTTP_303_SEE_OTHER,
headers = {
'Location': url
}
)