django-crispy-forms / django-crispy-forms/crispy-tailwind
|crispy not compatible with Django's custom form.Select create_option()
- Dominant language
- HTML
- Stars
- 423
- Forks
- 64
- PR merge metrics
- No merged PRs in 30d
Description
# The functionality
Django forms give the [ability](https://docs.djangoproject.com/en/5.0/ref/forms/fields/#iterating-relationship-choices) 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
```bash
python = "^3.9"
django = "^4.2.1"
django-widget-tweaks = "^1.4.12"
django-crispy-forms = "^2.0"
```
## forms.py
```python
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
```html
{% extends 'main/base.html' %}
{% load static %}
{% load widget_tweaks %}
{% load tailwind_filters %}
{% load crispy_forms_tags %}
{% block content %}
{% csrf_token %}
{{ form.street|as_crispy_field }}
{{ form.city|as_crispy_field }}
{{ form.postal_code|as_crispy_field }}
Submit
{% 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.
Contributor guide
Assessment
This issue has not been assessed yet.