Creating form widgets

I’m working on creating some templates for custom form widgets. Focusing on select (single and multi).

I’m curious if anyone here is familiar with crispy forms. I see they make use of select_option.html for each option of the select. And use the below comparison to mark as selected or not. But how can this be adapted so it handles both single and multi select? For multiselect, you would be comparing whether a single option is equal to a list of options.

{% if field.value|stringformat:'s' == value|stringformat:'s' %} selected{% endif %}

I guess you could use ‘in’, but then the opposite is a problem, where you might get substring matches.

Thoughts?

Would you honestly need to write something as long-winded as:

    {% for value, label in field.field.choices %}
      {% if field.field.widget.allow_multiple_selected %}
        {% if value in field.value %}
          {% with selected="selected" %}
            {% include "common/fields/option.html" with value=value label=label %}
          {% endwith %}
        {% else %}
          {% include "common/fields/option.html" with value=value label=label %}
        {% endif %}
      {% else %}
        {% if value == field.value %}
          {% with selected="selected" %}
            {% include "common/fields/option.html" with value=value label=label %}
          {% endwith %}
        {% else %}
          {% include "common/fields/option.html" with value=value label=label %}
        {% endif %}
      {% endif %}
    {% endfor %}

Actually, this fundamentally works as is.

Simple case:
forms.py

COLORS = [
    ('red', 'Red'), ('blue', 'Blue'), ('green', 'Green'), ('black', 'Black'),
]

class MultiForm(forms.Form):
    colors = forms.MultipleChoiceField(required=False,
        widget=forms.SelectMultiple, choices=COLORS)

and a template:

{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
  <div class="row">
    <div class="col-md-5">
      {% crispy form %}
    </div>
  </div>
</div>
{% endblock content %}

and a view:

def va(request):
    myform = MultiForm(initial={'colors':['red','green']})
    print(myform)
    context={'form':myform}
    return render(request,'df/ftest.html', context)

The printed output of the form shows that the initial items are marked as selected in the html.
(Note, this is using the bootstrap pack for Crispy.)

What I did notice is that the initial-selected values aren’t very distinct from the unselected values. In my case, I could tell a very slight different in the font, but the row wasn’t highlighted. I’m guessing that’s likely a CSS issue.

But, the base templates do work. They do render the proper html.

yeh, I agree that example in my second comment would work (not the first). But I was hoping for a simpler implementation. And can’t get my head around how crispy is doing it with code referenced in the first comment.

I ended up making two options –

<option value="{{ value|stringformat:'s' }}" {% if value in field.value %} selected{% endif %}>{{ label }}</option>
<option value="{{ value|stringformat:'s' }}" {% if value|stringformat:'s' == field.value|stringformat:'s' %} selected{% endif %}>{{ label }}</option>

and then use the appropriate depending on multi attr

  {% for value, label in field.field.choices %}
    {% if field.field.widget.attrs.multiple %}
      {% include "common/fields/multi-option.html" with value=value label=label %}
    {% else %}
      {% include "common/fields/option.html" with value=value label=label %}
    {% endif %}
  {% endfor %}

Note that I’m not using crispy. I’m working my own widgets. But do think they have the solution to this somewhere in their implementation.

Ok, I didn’t pick up on that originally. Note that the two are not necessarily mutually exclusive. (For example, you can use the crispy Layout objects with your custom widgets.)

What I have done in the past to help me understand the widget-rendering process is to configure DDT to show all the widget rendering steps by setting the SKIP_TEMPLATE_PREFIXES to []. (See https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html.)
You probably only want to do this with very limited test cases to explore individual widgets. This generates a lot of output - but it’s very informative.

yeh – I just did a double click on this and still can’t work it out. I might need to query one of the contributors to crispy at some point.