I'm trying to refactor a Django template which renders fields manually (cf. https://docs.djangoproject.com/en/2.0/topics/forms/#rendering-fields-manually). The labels are generated as follows:
<label for="{{ field.id_for_label }}"
class="{% if field.value %}active{% endif %} {% if field.errors %}invalid{% endif %}">
</label>
where the field
is looped over using {% for field in form %}
... {% endfor %}
.
I'm trying to refactor this by writing a custom filter (cf. https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/#writing-custom-template-filters). So far I've come up with the following. In the templatetags
directory, I've added a label_with_classes.py
which reads
from django import template
register = template.Library()
@register.filter(is_safe=True)
def label_with_classes(value, arg):
return value.label_tag(attrs={'class': arg})
which I use to replace the HTML above with
{{ field|label_with_classes:"active"}}
The problem is that this doesn't actually do what the original template does; it always labels it with the class "active"
and doesn't implement the conditional logic.
My question: is implementing this logic possible using a filter? What does the value
input argument to the filter function actually represent, is it the field.value
(as its name suggests) or the field
itself?
By dropping into the debugger while the development server was running and refreshing the page, I found that the value
is actually an instance of a BoundField
, which has a value()
method and an errors
attribute:
> /Users/kurtpeek/Documents/Dev/lucy/lucy-web/dashboard/templatetags/label_with_classes.py(8)label_with_classes()
7 import ipdb; ipdb.set_trace()
----> 8 return value.label_tag(attrs={'class': arg})
9
ipdb> value
<django.forms.boundfield.BoundField object at 0x113957eb8>
ipdb> dir(value)
['__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__html__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'as_hidden', 'as_text', 'as_textarea', 'as_widget', 'auto_id', 'build_widget_attrs', 'css_classes', 'data', 'errors', 'field', 'form', 'help_text', 'html_initial_id', 'html_initial_name', 'html_name', 'id_for_label', 'initial', 'is_hidden', 'label', 'label_tag', 'name', 'subwidgets', 'value']
ipdb> value.errors
[]
ipdb> value.value
<bound method BoundField.value of <django.forms.boundfield.BoundField object at 0x113957eb8>>
ipdb> value.value()
4
I was a bit confused by the use of the variable value
, and have renamed the dummy variable bound_field
instead.
Here is how I implemented the custom filter implementing the conditional classes (in templatetags/label_with_classes.py
):
from django import template
register = template.Library()
@register.filter(is_safe=True)
def label_with_classes(bound_field):
classes = f"{'active' if bound_field.value() else ''} {'invalid' if bound_field.errors else ''}"
return bound_field.label_tag(attrs={'class': classes.strip()})
after which the <label>
element can be replaced in the template by
{% load label_with_classes %}
{% for field in form %}
{{ field|label_with_classes }}
{% endfor %}