Build template-based form rendering with bootstrap attrs

This commit is contained in:
Tobias Kunze 2025-03-17 22:38:26 +01:00
parent eb91f59e09
commit 78119dc6b3
5 changed files with 79 additions and 1 deletions

View file

@ -0,0 +1,28 @@
from django.forms.renderers import TemplatesSetting
def inject_class(f, class_name):
def inner(*args, **kwargs):
result = f(*args, **kwargs)
class_list = result.get("class", "")
class_list = f"{class_list} {class_name}".strip()
result["class"] = class_list
return result
return inner
class VerticalFormRenderer(TemplatesSetting):
form_template_name = "frontend/forms/form.html"
field_template_name = "frontend/forms/vertical_field.html"
def render(self, template_name, context, request=None):
if field := context.get("field"):
if field.field.widget.input_type == "checkbox":
class_name = "form-check-input"
else:
class_name = "form-control"
field.build_widget_attrs = inject_class(
field.build_widget_attrs, class_name
)
return super().render(template_name, context, request)

View file

@ -37,7 +37,10 @@
<i class="bi bi-chevron-right me-2 ms-1 mb-2 collapse-icon"></i> <i class="bi bi-chevron-right me-2 ms-1 mb-2 collapse-icon"></i>
{% translate "Log in with email and password instead" %} {% translate "Log in with email and password instead" %}
</a> </a>
<div class="collapse mt-3" id="login-form"> <div class="collapse mt-3 ms-3"
id="login-form"
class="form form-vertical"
style="max-width: 400px">
<form method="post" action="{% url 'account_login' %}"> <form method="post" action="{% url 'account_login' %}">
{% csrf_token %} {% csrf_token %}
{{ form }} {{ form }}

View file

@ -0,0 +1,19 @@
{% if errors %}
<div class="alert alert-danger" role="alert">
<div>
{% if errors|length > 1 %}
<ul>
{% for error in errors %}<li>{{ error }}</li>{% endfor %}
</ul>
{% else %}
{{ errors.0 }}
{% endif %}
</div>
</div>
{% endif %}
<div class="form-body">
<div class="row">
{% for field, errors in fields %}{{ field.as_field_group }}{% endfor %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
</div>
</div>

View file

@ -0,0 +1,26 @@
{% load i18n %}
<div class="col-12">
<div class="form-group{% with classes=field.css_classes %}{% if classes %} {{ classes }}{% endif %}{% endwith %}">
{% if field.field.widget.input_type != "checkbox" or field.field.widget.allow_multiple_selected %}
<label for="{{ field.auto_id }}" class="{{ label_class }}">
{{ field.label }}
{% if not field.field.required %}
<span class="optional">{% translate "Optional" %}</span>
{% endif %}
</label>
{% endif %}
{% if field.use_fieldset %}
<fieldset {% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}>
{% endif %}
{{ field }}
{% if field.field.widget.input_type == "checkbox" and not field.field.widget.allow_multiple_selected %}
<label for="{{ field.auto_id }}">{{ field.label }}</label>
{% endif %}
{% if field.use_fieldset %}</fieldset>{% endif %}
{% for text in field.errors %}<div class="invalid-feedback">{{ text }}</div>{% endfor %}
{% if field.help_text %}
<small class="form-text text-muted"
{% if field.auto_id %}id="{{ field.auto_id }}_helptext"{% endif %}>{{ field.help_text|safe }}</small>
{% endif %}
</div>
</div>

View file

@ -95,6 +95,7 @@ INSTALLED_APPS = [
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django.forms",
"servala.frontend", "servala.frontend",
"allauth", "allauth",
"allauth.account", "allauth.account",
@ -151,6 +152,7 @@ TEMPLATES = [
}, },
] ]
FORM_RENDERER = "servala.frontend.forms.renderers.VerticalFormRenderer"
MESSAGE_TAGS = { MESSAGE_TAGS = {
messages.ERROR: "danger", messages.ERROR: "danger",
} }