Django: custom widget I use a material-kit library for django which offers a design for rendering form fields except for selects which is only available in the PRO version. I coded a custom selectpicker widget which renders similar to the selectpicker of the PRO version of material-kit.

In a form, I have a lot of "select" fields to render with my selectpicker widget. To limit the repetition of the code I defined a template select_field.html which displays the selectpicker.html of my widget when I insert a field having this widget in my final template using an include. Isn't there a more efficient and/or elegant way to achieve the same result?
#model.py
dem_sex = models.IntegerField('Sexe', null=True, blank=True)
#forms.py
dem_sex = forms.TypedChoiceField(widget=Selectpicker(label='Sexe - SELECTPICKER',attrs={'class': ''}),choices=SEX_CHOICES, empty_value=None,required=False)
#widget.py
from django.forms.widgets import TextInput
class Selectpicker(TextInput):
template_name = 'widgets/selectpicker.html'
def __init__(self, label=None, choices=None, *args, **kwargs):
self.choices = choices
self.label = label
super().__init__(*args, **kwargs)
def get_context(self, name, value, attrs=None):
context = super().get_context(name, value, attrs)
if self.choices:
context['choices'] = self.choices
if self.label:
context['label'] = self.label
return context
#selectpicker.html
{% with css_classes=widget.attrs.class|default:'' %}
<div class="row ms-3 me-3">
<div class="col-md-12 selectpicker-div">
<div class="selectpicker">
<div class="label floating-label">{{label}}</div>
<input class="textBox hidden" type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value is None %}value=""{% else %}value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
<div class="input" class="numBox">
{% for choice in choices %}
{% if widget.value|stringformat:'s' == choice.0|stringformat:'s' %} {{ choice.1 }} {% endif %}
{% endfor %}
</div>
<div class="option">
{% for choice in choices %}
<div value="{{ choice.1 }}" {% if choice.0 is None %}code=""{% else %}code="{{ choice.0 }}"{% endif %}{% if choice.0|stringformat:'s' == widget.value|stringformat:'s' %}class="selected"{% endif %}>{{ choice.1 }}</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endwith %}
#select_field.html
<div class="row mb-2">
<div class="col-md-12">
{{ field }}
{% for error in field.errors %}
<div class="invalid-feedback d-block text-danger mt-n3 ms-4 mb-3 ps-1">
{{ error }}
</div>
{% endfor %}
</div>
</div>
#template.html
<form data-model="{{model_name}}" data-ide="{{form.instance.ide}}" data-is-locked="{{is_locked}}" role="form" method="post" autocomplete="off">
{% csrf_token %}
{% include 'widgets/select_field.html' with field=form.dem_sex %}
<button id= "save" type="submit" class="btn bg-gradient-dark w-100">Enregistrer</button>
</form>
EDIT - 2023-07-04
Some clarifications about expected result. I want:
myfield = forms.TypedChoiceField(label='Sexe - FIELD',widget=Selectpicker(label='Sexe - WIDGET',attrs={'class': ''}),choices=SEX_CHOICES, empty_value=None,required=False) 2. be able to render fields with selectpicker in my template using {{ form.dem_sex }} syntaxe instead of using an include {% include 'widgets/select_field.html' with field=form.dem_sex %} 3. be able to render fields errors
I try to use template inheritance like below. But I did not have access to {{field.label }} and {{ field.errors }} in selectpicker.html nor in select_field.html
Hope someone could me clarify
#select_field.html
<div class="row mb-2">
{% block field %}{% endblock %}
{% for error in field.errors %}
<div class="invalid-feedback d-block text-danger mt-n3 ms-4 mb-3 ps-1">
{{ error }}
</div>
{% endfor %}
</div>
#selectpicker.html
{% extends 'widgets/select_field.html' %}
{% block field %}
<div class="col-md-12">
<div class="row ms-3 me-3">
<div class="col-md-12 selectpicker-div">
<div class="selectpicker">
<div class="label floating-label">{{ label }}</div>
<input class="textBox hidden" type="{{ widget.type }}" name="{{ widget.name }}" {% if widget.value is None %}value=""{% else %}value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
<div class="input" class="numBox">
{% for choice in choices %}
{% if widget.value|stringformat:'s' == choice.0|stringformat:'s' %} {{ choice.1 }} {% endif %}
{% endfor %}
</div>
<div class="option">
{% for choice in choices %}
<div value="{{ choice.1 }}" {% if choice.0 is None %}code=""{% else %}code="{{ choice.0 }}"{% endif %}{% if choice.0|stringformat:'s' == widget.value|stringformat:'s' %}class="selected"{% endif %}>{{ choice.1 }}</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
For those or maybe interested, below the way I solve my problem using templatetag.
I would be interested by your comments to improve this approach if needed.
#select_field.html
<div class="row mb-2">
<div class="col-md-12">
<div class="row ms-3 me-3">
<div class="col-md-12 selectpicker-div">
<div class="selectpicker">
<div class="label floating-label">{{field.label}}</div>
{{ field }} <!-- return the selectpicker widget -->
</div>
</div>
</div>
{% for error in field.errors %}
<div class="invalid-feedback d-block text-danger mt-n3 ms-4 mb-3 ps-1">
{{ error }}
</div>
{% endfor %}
</div>
</div>
#selectpicker.html
{% with css_classes=widget.attrs.class|default:'' %}
<input class="textBox hidden" type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value is None %}value=""{% else %}value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
<div class="input" class="numBox">
{% for choice in choices %}
{% if widget.value|stringformat:'s' == choice.0|stringformat:'s' %} {{ choice.1 }} {% endif %}
{% endfor %}
</div>
<div class="option">
{% for choice in choices %}
<div value="{{ choice.1 }}" {% if choice.0 is None %}code=""{% else %}code="{{ choice.0 }}"{% endif %}{% if choice.0|stringformat:'s' == widget.value|stringformat:'s' %}class="selected"{% endif %}>{{ choice.1 }}</div>
{% endfor %}
</div>
{% endwith %}
#forms.py
my_field = forms.TypedChoiceField(label='my_label',widget=Selectpicker(attrs={'class': ''}),choices=MY_CHOICES, empty_value=None,required=False)
#widget.py
from django.forms.widgets import TextInput
class Selectpicker(TextInput):
template_name = 'widgets/selectpicker.html'
def __init__(self, choices=None, *args, **kwargs):
self.choices = choices
super().__init__(*args, **kwargs)
def get_context(self, name, value, attrs=None):
context = super().get_context(name, value, attrs)
if self.choices:
context['choices'] = self.choices
return context
#templatetags
from django.template.loader import render_to_string
@register.filter(name='selectpicker')
def selectpicker(widget):
rendered_template = render_to_string('widgets/select_field.html', {'field': widget.form[widget.name]})
return rendered_template
#template.html
{{ form.<my_field>|selectpicker }}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With