Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django custom widget: best approach

Tags:

django

widget

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.

enter image description here

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:

  1. be able to use form field label attribute instead of passing label as widget attribute

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 %}
like image 662
SLATER Avatar asked Dec 11 '25 11:12

SLATER


1 Answers

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 }}
like image 96
SLATER Avatar answered Dec 13 '25 04:12

SLATER