Expanding on the Django REST Framework tutorial, I have switched to TemplateHTMLRenderer
to work with HTML.
class SnippetViewSet(viewsets.ModelViewSet):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
premission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
renderer_classes = (renderers.TemplateHTMLRenderer,)
def retrieve(self, request, *args, **kwargs):
snippet = get_object_or_404(Snippet, pk=kwargs.get('pk'))
serializer = SnippetSerializer(snippet, context={'request': request})
context = {'serializer': serializer, 'snippet': snippet }
return Response(context, template_name='snippets/snippet_retrieve.html')
def update(self, request, *args, **kwargs):
snippet = get_object_or_404(Snippet, pk=kwargs.get('pk'))
serializer = SnippetSerializer(snippet, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
#return redirect(reverse('snippet-list', kwargs=kwargs, request=request))
#serializer = SnippetSerializer()
#return redirect(reverse('snippet-list', request=request))
return HttpResponseRedirect(reverse('snippet-list'))
@action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
My retrieve
method renders the following template:
{% load rest_framework %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/htmx.org@1.9.5" integrity="sha384-xcuj3WpfgjlKF+FXhSQFQ0ZNr39ln+hwjN3npfM9VBnUskLolQAcN80McRIVOPuO" crossorigin="anonymous"></script>
<title>Snippet Retrieve</title>
</head>
<body hx-boost="true">
<h1>Snippet {{ snippet.pk }}</h1>
<hr />
<form>
{% csrf_token %}
{% render_form serializer %}
<button hx-put="{% url 'snippet-detail' pk=snippet.pk %}"
>Update
</button>
</form>
<hr />
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
})
</script>
</body>
</html>
Routing looks like this:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet, basename='snippet')
router.register(r'users', views.UserViewSet, basename='user')
The form sends a PUT
request to be handled by the update
method, and the data it updated correctly.
However, having updated the data, I should now return to the snippet-list
via an HTTP
redirection, but this fails with a 405
error:
[18/Sep/2023 19:08:46] "GET /snippets/5/ HTTP/1.1" 200 48984
[18/Sep/2023 19:08:50] "PUT /snippets/5/ HTTP/1.1" 302 0
Method Not Allowed: /snippets/
[18/Sep/2023 19:08:50] "PUT /snippets/ HTTP/1.1" 405 22
How do I correctly return a redirection to the list view from update
?
The problem was that the redirect based on a status code 302
issues a new PUT
request to the ViewSet
list
view. The list
view can not handle the PUT
request.
Sending a 303
lets the client know the redirect should happen via a GET
request, so the solution was:
return HttpResponseRedirect(reverse('snippet-list'), status=303)
That is, explicitly specify the 303
status code. Obviously, the list
view must be implemented to handle the request as HTML
.