django-crispy-forms / django-crispy-forms/crispy-tailwind

|crispy not compatible with Django's custom form.Select create_option()

Open
#166 1 comment 0 reactions 0 assignees View on GitHub
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 }}


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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.