Search code examples
pythondjangodjango-formsdjango-filterdjango-taggit

Django-Filter: {{ field.value }} is Empty when rendering form fields


I'm using the Django-Filter library !important. For tags, I'm using Django-taggit.

I built the following filter.py:

class TaskFilter(django_filters.FilterSet):
    tags = django_filters.ModelMultipleChoiceFilter(widget=forms.CheckboxSelectMultiple, queryset=Task.tags.most_common())

    class Meta:
        model = Task
        fields = ['tags']

However, when I pass this filter to the template, it doesn't render the tags properly. In particular {{ field.value }} is empty. Let's look at the following cases:


CASE 1.

# template.html
{{ filter.form.tags.errors }}
{% for field in filter.form.tags %}
<label for="{{ field.id_for_label }}"></label>
{{ field.value }}
{% endfor %}

# out
<label for="id_tags_0"></label>
<label for="id_tags_1"></label>
<label for="id_tags_2"></label>

CASE 2.

# template.html
{{ filter.form.tags.errors }}
{% for field in filter.form.tags %}
<label for="{{ field.id_for_label }}"></label>
{{ field }}
{% endfor %}

# out
<label for="id_tags_0"></label>
<label for="id_tags_0"><input type="checkbox" name="tags" value="4" id="id_tags_0">Tag 1</label>
<label for="id_tags_1"></label>
<label for="id_tags_1"><input type="checkbox" name="tags" value="1" id="id_tags_1">Tag 2</label>
<label for="id_tags_2"></label>
<label for="id_tags_2"><input type="checkbox" name="tags" value="2" id="id_tags_2">Tag 3</label>

CASE 3.

# template.html
{{ filter.form.tags.errors }}
{% for field in filter.form.tags %}
{{ field }}
{{ field.label_tag }}
{% endfor %}

#out
<label for="id_tags_0"><input type="checkbox" name="tags" value="4" id="id_tags_0">Tag 1</label>
<label for="id_tags_1"><input type="checkbox" name="tags" value="1" id="id_tags_1">Tag 2</label>
<label for="id_tags_2"><input type="checkbox" name="tags" value="2" id="id_tags_2">Tag 3</label>

I'm trying to understand why this happens. Why can't I get the values as stated in the docs

STEPS TO REPRODUCE

pip install django-filter + add 'django_filters' to APPS
pip install django-taggit + add 'taggit' to APPS

# models.py
class Task(models.Model):
    title = models.CharField(max_length=100, blank=False)
    tags = TaggableManager()

# Use the API to create an object.
t = Task.objects.create(title="Title")
t.tags.add("Tag 1","Tag 2")

# views.py
def view(request):
    f = TaskFilter(request.GET, queryset=Task.objects.all())
    return render(request, 'template.html', {'filter': f}

Solution

  • When you iterate over filter.form.tags you're not iterating over a set of form fields, but instead over a set of individual choices for the tags field. This is why field.value doesn't work.

    This should work instead:

    {{ filter.form.tags.errors }}
    {% for choice in filter.form.tags %}
        <label for="{{ choice.id_for_label }}"></label>
        {{ choice.tag }}
    {% endfor %}
    

    Where tag is an attribute that exists on each choice, which will render the checkbox input for that choice.

    This is documented in the documentation for RadioSelect:

    To get more granular, you can use each radio button’s tag, choice_label and id_for_label attributes.

    Further down, the documentation for CheckBoxSelectMultiple says that the same logic applies for it too.