Re form validation: I’ve played around a bit with turbo streams and think this might be a starting point.
This is a subclass of TemplateResponse: obviously you can do something similar with a TemplateView or whatever. The main thing is to set the correct content type.
class TurboStreamResponse(TemplateResponse):
def __init__(self, request, template_name, context, *, action, target, **kwargs):
super().__init__(
request,
template_name,
context,
content_type="text/html; turbo-stream;",
**kwargs
)
self.context_data.update(
{
"turbo_stream_target": target,
"turbo_stream_action": action,
"is_turbo_stream": True,
}
)
This would be used in a Django view like this (I prefer FBV but this could be easily adapted into a mixin for CBVs):
@login_required
def user_preferences(request):
if request.method == "POST":
form = UserPreferencesForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
messages.success(request, "Your preferences have been saved")
return redirect(request.path)
return TurboStreamResponse(
request,
"account/_preferences.html",
{"form": form},
action="replace",
target="prefs-form",
)
form = UserPreferencesForm(instance=request.user)
return TemplateResponse(
request,
"account/preferences.html",
{"form": form},
)
So: render the full template in initial render, and a partial template if form validation fails, with a redirect on success.
Our full template looks like this:
{% extends "base.html" %}
{% block content %}
<div id="prefs-form">
{% include "account/_preferences.html" %}
</div>
{% endblock %}
and our partial template:
{% load i18n widget_tweaks %}
{% if is_turbo_stream %}
<turbo-stream action="{{ turbo_stream_action }}"
target="{{ turbo_stream_target }}">
<template>
{% endif %}
<form>
... rest of form lives here....
</form>
...
The important thing here is to set the correct tag and attibutes, and wrap in a tag if rendered as a stream. It would be easy enough to make a simple template tag to wrap the content.
There’s still an issue with forms from 3rd party packages and the like where it’s not so easy to rewire existing views (for example I usually just go with allauth for login/signup): unfortunately the current beta verson of Drive does not allow data-turbo=“false” on forms, which would allow you to ignore form submissions in these edge cases. That fix should be merged soon however.