-
Notifications
You must be signed in to change notification settings - Fork 62
Description
The functionality
Django forms give the ability to override the create_option()
function for custom Select fields.
You can use this to set custom attributes for each select option.
The function is triggered when the form is called in the template. So if we have {{ form.custom_select_field }}
in the html file, create_option
is called when the file is rendered. This works fine with the above syntax, but when using {{ form.field|as_crispy_field }}
the create_option
no longer executes. (Note: though I verified that the CustomSelect
's __init__
method was still being called.
Source code
requirements.txt
python = "^3.9"
django = "^4.2.1"
django-widget-tweaks = "^1.4.12"
django-crispy-forms = "^2.0"
forms.py
class RegionSelect(forms.Select):
_region_map = None
@property
def region_map(self):
if self._region_map is None:
self._region_map = {r.id: r for r in Region.objects.all()}
return self._region_map
def create_option(
self, name, value, label, selected, index, subindex=None, attrs=None
):
option = super().create_option(
name, value, label, selected, index, subindex, attrs
)
region = self.region_map.get(value)
if region:
option["attrs"]["label"] = region.name
option["attrs"]["data-country-id"] = region.country_id
return option
class AddressForm(forms.ModelForm):
street = forms.CharField(
max_length=255,
required=False
)
city = forms.CharField(max_length=100, required=False)
state = forms.ModelChoiceField(
queryset=Region.objects.all(),
required=False,
widget=RegionSelect()
)
country = forms.ModelChoiceField(
queryset=Country.objects.all(),
required=False
)
postal_code = forms.CharField(max_length=20, required=False)
class Meta:
model = Address
fields = "__all__"
form.html
{% extends 'main/base.html' %}
{% load static %}
{% load widget_tweaks %}
{% load tailwind_filters %}
{% load crispy_forms_tags %}
{% block content %}
<form class="rounded-xl bg-white p-5 shadow-lg shadow-gray-500/40 sm:p-10 sm:px-20"
method="POST">
{% csrf_token %}
{{ form.street|as_crispy_field }}
<div id="hidden_address_fields" class="hidden">
<div class="grid grid-cols-1 sm:grid-cols-2 sm:gap-4">
<div>{{ form.country|as_crispy_field }}</div>
<div>{{ form.state }}</div>
</div>
{{ form.city|as_crispy_field }}
{{ form.postal_code|as_crispy_field }}
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endblock content %}
Notes
All the form fields render fine with the styling, but the custom select widget only works for {{ form.state }}
without the as_crispy_field
.
I put a print statement in RegionSelect.create_option()
and confirmed that it never even executes when we use crispy.
This behavior is also true with {{ form }}
working as expected, but {{ form|crispy }}
causing RegionSelect.create_option()
to not execute.