Add a generic form_field templatetag for daisyUI form rendering
_form_fields.html and club_form.html each hand-rolled the same
label/input/help-text/error markup per field, one copy per widget type,
which drifted out of sync between the two partials. {% form_field %}
picks the right daisyUI markup from the field's widget type in one
place, so every form that renders fields this way gets the same
behaviour (size modifiers, placeholder-as-label, required badges) for
free.
This commit is contained in:
@@ -12,18 +12,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div class="form-control my-3 w-full">
|
<div class="form-control my-3 w-full">
|
||||||
{% if field.field.widget.input_type == "checkbox" %}
|
{% form_field field %}
|
||||||
<label class="label cursor-pointer justify-start gap-3" for="{{ field.id_for_label }}">
|
|
||||||
{{ field|daisy }}
|
|
||||||
<span class="label-text">{{ field.label }}</span>
|
|
||||||
</label>
|
|
||||||
{% else %}
|
|
||||||
<label class="label" for="{{ field.id_for_label }}">
|
|
||||||
<span class="label-text">{{ field.label }}</span>
|
|
||||||
</label>
|
|
||||||
{{ field|daisy }}
|
|
||||||
{% endif %}
|
|
||||||
{% if field.help_text %}<span class="label-text-alt mt-1 text-xs block text-base-content/70">{{ field.help_text }}</span>{% endif %}
|
|
||||||
{% for error in field.errors %}<span class="label-text-alt text-xs mt-1 text-error">{{ error }}</span>{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -17,14 +17,7 @@
|
|||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div class="my-3 w-full">
|
{% form_field field %}
|
||||||
<label class="label" for="{{ field.id_for_label }}">
|
|
||||||
<span class="label-text">{{ field.label }}</span>
|
|
||||||
</label>
|
|
||||||
{{ field|daisy }}
|
|
||||||
{% if field.help_text and not field.errors %}<span class="label-text-alt mt-1 text-base-content/70">{{ field.help_text }}</span>{% endif %}
|
|
||||||
{% for error in field.errors %}<span class="label-text-alt mt-1 text-error">{{ error }}</span>{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
228
controlpanel/templates/templatetags/field.html
Normal file
228
controlpanel/templates/templatetags/field.html
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="w-full max-w-6xl {% if size_modifier %}lg:w-fit{% endif %}">
|
||||||
|
<div class="flex flex-col gap-y-2">
|
||||||
|
{% if show_label %}
|
||||||
|
<div class="flex flex-row items-end min-h-6 mb-1">
|
||||||
|
<div class="ml-1 grow text-sm font-semibold {% if field.errors %}text-error{% else %}text-base-content{% endif %}">
|
||||||
|
{{ field.label }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if field.field.required %}
|
||||||
|
<div class="mr-1">
|
||||||
|
<span class="badge badge-sm {% if field.errors %}badge-error text-error-content{% else %}badge-neutral text-neutral-content{% endif %}">{% translate "required" %}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if field_type == "input" %}
|
||||||
|
<input
|
||||||
|
type="{% if field.widget_type == "regionalphonenumber" %}tel{% elif field.widget_type == "datetime" %}datetime-local{% else %}{{ field.widget_type }}{% endif %}"
|
||||||
|
class="input w-full {% if field.errors %}input-error text-error{% endif %} {% if size_modifier %}input-{{ size_modifier }}{% endif %}"
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
value="{% if field.widget_type == "date" or field.widget_type == "datetime" %}{% if field.value|date:"c"|default:"" != "" %}{{ field.value|date:"c"|default:"" }}{% else %}{{ field.value|default:"" }}{% endif %}{% else %}{% if field.value == None %}{{ field.value|default:"" }}{% else %}{{ field.value }}{% endif %}{% endif %}"
|
||||||
|
{% if show_placeholder %}placeholder="{{ field.label|capfirst }}"{% endif %}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{% elif field_type == "checkbox" %}
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
{% if show_as_toggle %}
|
||||||
|
class="toggle {% if size_modifier %}toggle-{{ size_modifier }}{% endif %}"
|
||||||
|
{% else %}
|
||||||
|
class="checkbox {% if size_modifier %}checkbox-{{ size_modifier }}{% endif %}"
|
||||||
|
{% endif %}
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
{% if field.value %}checked="checked"{% endif %}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{% if show_help_text %}
|
||||||
|
<div class="text-sm">
|
||||||
|
{{ field.help_text }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% elif field_type == "textarea" %}
|
||||||
|
<textarea
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
class="{% if type == "markdownx" %}markdownx{% endif %} textarea textarea-bordered h-72 w-full {% if field.errors %}textarea-error text-error{% endif %} {% if size_modifier %}textarea-{{ size_modifier }}{% endif %}"
|
||||||
|
{% if show_placeholder %}placeholder="{{ field.label }}"{% endif %}
|
||||||
|
>{{ field.value|default:"" }}</textarea>
|
||||||
|
|
||||||
|
{% elif field_type == "select" %}
|
||||||
|
<select
|
||||||
|
class="select w-full {% if size_modifier %}select-{{ size_modifier }}{% endif %} {% if field.errors %}select-error text-error{% endif %}"
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
id="{{ field.auto_id }}"
|
||||||
|
{% if field.widget_type == "selectmultiple" %}multiple{% endif %}
|
||||||
|
>
|
||||||
|
|
||||||
|
{% if field.widget_type == "selectmultiple" %}
|
||||||
|
{% if not show_label %}
|
||||||
|
<option selected disabled>{{ field.label|capfirst }}</option>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for option_value, option_label in field.field.choices %}
|
||||||
|
<option value="{{ option_value }}" {% if option_value in field.value %}selected="selected"{% endif %}>
|
||||||
|
{{ option_label }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% if not show_label %}
|
||||||
|
<option selected disabled>{{ field.label|capfirst }}</option>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for option_value, option_label in field.field.choices %}
|
||||||
|
<option value="{{ option_value }}" {% if field.value|stringformat:"s" == option_value|stringformat:"s" %}selected="selected"{% endif %}>
|
||||||
|
{{ option_label }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{% elif field_type == "file" %}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
class="file-input w-full {% if size_modifier %}file-input-{{ size_modifier }}{% endif %}"
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
{% if field.field.widget.attrs.accept %}accept="{{ field.field.widget.attrs.accept }}"{% endif %}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{% if field.value %}
|
||||||
|
<span class="my-1 text-xs opacity-50">{% translate "Current file:" %} {{ field.value }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if field_type != "checkbox" and field.help_text and show_help_text or field.errors %}
|
||||||
|
<div class="flex flex-col gap-1 ml-2">
|
||||||
|
{% if field.errors %}
|
||||||
|
<div class="text-xs text-error">
|
||||||
|
{{ field.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if field.help_text %}
|
||||||
|
<div class="text-xs self-start opacity-50 {% if field.errors %}text-error{% endif %}">
|
||||||
|
{{ field.help_text }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% comment %} <div class="w-full max-w-6xl {% if size_modifier %}lg:w-fit{% endif %}">
|
||||||
|
<label class="w-full form-control">
|
||||||
|
{% if show_label %}
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text opacity-50 {% if field.errors %}text-error{% else %}text-base-content{% endif %}">{{ field.label }}</span>
|
||||||
|
|
||||||
|
{% if field.field.required %}
|
||||||
|
<span class="label-text-alt badge badge-sm {% if field.errors %}badge-error text-error-content{% else %}badge-neutral text-neutral-content{% endif %}">{% translate "required" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if field_type == "select" %}
|
||||||
|
<select
|
||||||
|
class="select select-bordered {% if size_modifier %}select-{{ size_modifier }}{% endif %} {% if field.errors %}select-error text-error{% endif %}"
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
id="{{ field.auto_id }}"
|
||||||
|
{% if field.widget_type == "selectmultiple" %}multiple{% endif %}
|
||||||
|
>
|
||||||
|
|
||||||
|
{% if field.widget_type == "selectmultiple" %}
|
||||||
|
{% if not show_label %}
|
||||||
|
<option selected disabled>{{ field.label|capfirst }}</option>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for option_value, option_label in field.field.choices %}
|
||||||
|
<option value="{{ option_value }}" {% if option_value in field.value %}selected="selected"{% endif %}>
|
||||||
|
{{ option_label }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% if not show_label %}
|
||||||
|
<option selected disabled>{{ field.label|capfirst }}</option>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for option_value, option_label in field.field.choices %}
|
||||||
|
<option value="{{ option_value }}" {% if field.value|stringformat:"s" == option_value|stringformat:"s" %}selected="selected"{% endif %}>
|
||||||
|
{{ option_label }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{% elif field_type == "checkbox" %}
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
{% if show_as_toggle %}
|
||||||
|
class="toggle {% if size_modifier %}toggle-{{ size_modifier }}{% endif %}"
|
||||||
|
{% else %}
|
||||||
|
class="checkbox {% if size_modifier %}checkbox-{{ size_modifier }}{% endif %}"
|
||||||
|
{% endif %}
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
{% if field.value %}checked="checked"{% endif %}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{% if show_help_text %}
|
||||||
|
<div class="label-text">
|
||||||
|
{{ field.help_text }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% elif field_type == "textarea" %}
|
||||||
|
<textarea
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
class="{% if type == "markdownx" %}markdownx{% endif %} textarea textarea-bordered h-72 {% if field.errors %}textarea-error text-error{% endif %} {% if size_modifier %}textarea-{{ size_modifier }}{% endif %}"
|
||||||
|
{% if show_placeholder %}placeholder="{{ field.label }}"{% endif %}
|
||||||
|
>{{ field.value|default:"" }}</textarea>
|
||||||
|
|
||||||
|
{% elif field_type == "file" %}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
class="file-input file-input-bordered {% if size_modifier %}file-input-{{ size_modifier }}{% endif %}"
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
/>
|
||||||
|
{% if field.value %}
|
||||||
|
<span class="my-1 text-sm text-gray-500">{% translate "Current file:" %} {{ field.value }}</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% elif field_type == "input" %}
|
||||||
|
<input
|
||||||
|
type="{% if field.widget_type == "regionalphonenumber" %}tel{% elif field.widget_type == "datetime" %}datetime-local{% else %}{{ field.widget_type }}{% endif %}"
|
||||||
|
class="input input-bordered {% if field.errors %}input-error text-error{% endif %} {% if size_modifier %}input-{{ size_modifier }}{% endif %}"
|
||||||
|
name="{{ field.html_name }}"
|
||||||
|
value="{% if field.widget_type == "date" or field.widget_type == "datetime" %}{{ field.value|date:"c"|default:"" }}{% else %}{{ field.value|default:"" }}{% endif %}"
|
||||||
|
{% if show_placeholder %}placeholder="{{ field.label|capfirst }}"{% endif %}
|
||||||
|
/>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if field.help_text and show_help_text or field.errors %}
|
||||||
|
<div class="label">
|
||||||
|
{% if field.errors %}
|
||||||
|
<div class="label-text-alt text-error">
|
||||||
|
{{ field.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if field.help_text and field_type != "checkbox" %}
|
||||||
|
<div class="label-text-alt self-start {% if field.errors %}text-error{% endif %}">
|
||||||
|
{{ field.help_text }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
</div> {% endcomment %}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Template helpers for the daisyUI-based UI."""
|
"""Template helpers for the daisyUI-based UI."""
|
||||||
|
|
||||||
from django import forms, template
|
from django import forms, template
|
||||||
|
from django.forms import BoundField
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@@ -130,3 +131,58 @@ def daisy(field, css=None):
|
|||||||
attrs = dict(widget.attrs)
|
attrs = dict(widget.attrs)
|
||||||
attrs["class"] = " ".join(part for part in [widget.attrs.get("class", ""), css] if part)
|
attrs["class"] = " ".join(part for part in [widget.attrs.get("class", ""), css] if part)
|
||||||
return field.as_widget(attrs=attrs)
|
return field.as_widget(attrs=attrs)
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag("templatetags/field.html")
|
||||||
|
def form_field(
|
||||||
|
field: BoundField,
|
||||||
|
label: str | None = None,
|
||||||
|
help_text: str | None = None,
|
||||||
|
show_label: bool = True,
|
||||||
|
show_help_text: bool = True,
|
||||||
|
show_placeholder: bool = True,
|
||||||
|
show_as_toggle: bool = False,
|
||||||
|
size: str = "full",
|
||||||
|
) -> dict:
|
||||||
|
if label is not None:
|
||||||
|
field.label = label
|
||||||
|
|
||||||
|
if help_text is not None:
|
||||||
|
field.help_text = help_text
|
||||||
|
|
||||||
|
field_type = None
|
||||||
|
match field.widget_type:
|
||||||
|
case "select" | "nullbooleanselect" | "radioselect":
|
||||||
|
field_type = "select"
|
||||||
|
case "checkbox":
|
||||||
|
field_type = "checkbox"
|
||||||
|
case "textarea" | "markdownx":
|
||||||
|
field_type = "textarea"
|
||||||
|
case "clearablefile":
|
||||||
|
field_type = "file"
|
||||||
|
case "selectmultiple":
|
||||||
|
field_type = "select"
|
||||||
|
case _:
|
||||||
|
field_type = "input"
|
||||||
|
|
||||||
|
size_modifier = None
|
||||||
|
match size:
|
||||||
|
case "extra-small":
|
||||||
|
size_modifier = "xs"
|
||||||
|
case "small":
|
||||||
|
size_modifier = "sm"
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# if field.widget_type == "number":
|
||||||
|
# field.value = str(field.value())
|
||||||
|
|
||||||
|
return {
|
||||||
|
"field": field,
|
||||||
|
"show_label": show_label,
|
||||||
|
"show_help_text": show_help_text,
|
||||||
|
"show_placeholder": show_placeholder,
|
||||||
|
"show_as_toggle": show_as_toggle,
|
||||||
|
"field_type": field_type,
|
||||||
|
"size_modifier": size_modifier,
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user