HTMX - Forms in Bootstrap

Following pattern uses Partial Page Load, it uses Bootsrap modals and is compatible with No-JS browser environments. it follows HTMX - Partial page load pattern to load edit form inside a modal.


Code for view.py for model called Corpora and a model form called CorpusForm:

from datacore.models import Corpora, Document
from datacore.forms import CorpusForm
corpus = Corpora.objects.get(id=id)

# Handle edit request
if (request.method == 'POST' and request.POST.get('edit')=='1') or (request.htmx and request.htmx.trigger_name=='edit'):
	form = CorpusForm(instance=corpus)
	context = {
		'corpus': corpus,
		'form': form,
	}
	context['base_template'] = "partial.html" if request.htmx else "base.html"
	return render(request, "corpus-edit.html", context)
if request.method == 'POST' and request.POST.get('save')=='1':
	form = CorpusForm(request.POST or None, instance=corpus)
	corpus = form.save()

# Handle delete request
if request.method == "DELETE" or (request.method == 'POST' and request.POST.get('delete')=='1'):
	corpus.delete()
	if request.htmx:
		return corpora(request)
	else:
		return HttpResponseRedirect(reverse('datacore:corpora'))

context = {
		'corpus': corpus,
	}
	context['base_template'] = "partial.html" if request.htmx else "base.html"
	return render(request, "corpus.html", context)

corpus-edit.html file called from view.

{% extends base_template %}
{% load i18n %}
{% load widget_tweaks %}

{% block title %}{% trans "Corpora" %}{% endblock %}

{% block content %}
<form action="{% url 'datacore:corpus' corpus.id %}" method="post">
	{% csrf_token %}
	<div class="modal-header">
		<h1 class="modal-title fs-5" id="modal-label">Edit Corpus</h1>
		{% if base_template != 'base.html' %}<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>{% endif %}
	</div>
	<div class="modal-body">
		{% for hidden_field in form.hidden_fields %}
			{{ hidden_field }}
		{% endfor %}
		{% if form.non_field_errors %}
			<div class="alert alert-danger" role="alert">
				{% for error in form.non_field_errors %}
					{{ error }}
				{% endfor %}
			</div>
		{% endif %}
		{% for field in form.visible_fields	%}
			<div class="mb-3">
				<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
				{% if field|field_type == 'charfield' and field|widget_type == 'textarea' %}
					{# render textarea #}
					{% render_field field class="form-control rounded-0" %}
				{% elif field|field_type == 'modelmultiplechoicefield' and field|widget_type == 'checkboxselectmultiple' %}
					{# render checkbox #}
					{% for choice in field %}
						<div class="form-check">
							<input class="form-check-input" type="checkbox" value="{{ choice.data.value }}" id="{{ choice.id_for_label }}" name="{{ choice.data.name }}"{% if choice.data.selected %} checked{% endif %}>
							<label for="{{ choice.id_for_label }}" class="form-check-label">
								{{ choice.choice_label }}
							</label>
						</div>
					{% endfor %}
				{% else %}
					{# default render for most fields #}
					{% render_field field class="form-control" %}
				{% endif %}
				{% if field.help_text %}
					<div id="{{ field.id_for_label }}-help" class="form-text">{{ field.help_text|safe }}</div>
				{% endif %}
			</div>
		{% endfor %}
	</div>
	<div class="modal-footer gap-2">
		<a href="{% url 'datacore:corpus' corpus.id %}" class="btn btn-secondary" {% if base_template != 'base.html' %} data-bs-dismiss="modal"{% endif %}>Back</a>
		<button type="submit" class="btn btn-primary" name="save" value="1">Save</button>
	</div>
</form>
{% endblock %}

corpora.html which shows buttons which perform deleting action, opening edit form(in modal, or in case of No-JS in new page):

<div id="loading-indicator" class="htmx-indicator spinner-border" role="status">
	<span class="visually-hidden">Loading...</span>
</div>

<form method="post" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
	{% csrf_token %}
	<div class="btn-group float-end" role="group" aria-label="Basic example">
		<button hx-indicator="#loading-indicator" hx-post="{% url 'datacore:corpus' corpus.id %}" hx-target="#modal-container" type="submit" name="edit" value="1" class="btn btn-outline-info" data-bs-toggle="modal" data-bs-target="#page-modal"><i class="fa fa-pen"></i></button>
		<button hx-indicator="#loading-indicator" hx-delete="{% url 'datacore:corpus' corpus.id %}" hx-target="#main-container" hx-confirm="Are you sure you?" type="submit" name="delete" value="1" class="btn btn-outline-danger"><i class="fa fa-trash-can"></i></button>
	</div>
</form>