How to loop over form field choices and display associated model instance fields
Solution 1
Easiest would be if you define the whole form in a HTML template. You should be able to iterate over a field's values in a template like that:
{% for value, text in form.hikers.field.choices %}
{{ value }}: {{ text }}
{% endfor %}
Solution 2
Try this solution :
<ul>
{% for choice in form.my_choice_field.field.choices %}
<li>
<input type="radio" name="my_choice_field" value="{{choice.0}}"
{% ifequal form.my_choice_field.data choice.0 %}
checked="checked"
{% endifequal %}/>
<label for="">{{choice.1}}</label>
</li>
{% endfor %}
</ul>
see this link : http://www.ilian.io/django-forms-choicefield-and-custom-html-output/
Solution 3
This is surprisingly tricky, but you can do it using ModelMultipleChoiceField
, CheckboxSelectMultiple
, and a custom template filter. The form and widget classes get most of the way there, but the template filter works out which widget to give you for each instance in the queryset. See below...
Generic solution
# forms.py
from django import forms
from .models import MyModel
class MyForm(forms.Form):
my_models = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=None)
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_models'].queryset = MyModel.objects.all()
# myapp/templatetags/myapp.py
from django import template
from copy import copy
register = template.Library()
@register.filter
def instances_and_widgets(bound_field):
"""Returns a list of two-tuples of instances and widgets, designed to
be used with ModelMultipleChoiceField and CheckboxSelectMultiple widgets.
Allows templates to loop over a multiple checkbox field and display the
related model instance, such as for a table with checkboxes.
Usage:
{% for instance, widget in form.my_field_name|instances_and_widgets %}
<p>{{ instance }}: {{ widget }}</p>
{% endfor %}
"""
instance_widgets = []
index = 0
for instance in bound_field.field.queryset.all():
widget = copy(bound_field[index])
# Hide the choice label so it just renders as a checkbox
widget.choice_label = ''
instance_widgets.append((instance, widget))
index += 1
return instance_widgets
# template.html
{% load myapp %}
<form method='post'>
{% csrf_token %}
<table>
{% for instance, widget in form.job_applications|instances_and_widgets %}
<tr>
<td>{{ instance.pk }}, {{ instance }}</td>
<td>{{ widget }}</td>
</tr>
{% endfor %}
</table>
<button type='submit' name='submit'>Submit</button>
</form>
Specific to you
It should work if you adjust the form like so:
class ClubForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
cluk_pk = kwargs.pop('club_pk')
super(ClubForm, self).__init__(*args, **kwargs)
self.fields['hikers'].queryset = Club.objects.filter(pk=club_pk)
class Meta:
model = Club
fields = ('hikers',)
widgets = {'hikers': forms.CheckboxSelectMultiple}
Solution 4
Maybe help someone.
template.html
<!-- radio -->
<div class="form-group">
{{ form.field_name.label_tag }}
{% for pk, choice in form.field_name.field.widget.choices %}
<div class="custom-control custom-radio custom-control-inline">
<input id="id_{{form.field_name.name}}_{{ forloop.counter0 }}" name="{{form.field_name.name}}" type="{{form.field_name.field.widget.input_type}}" value="{{pk}}" class="custom-control-input"
{% ifequal form.field_name.data pk.0 %}
checked="checked"
{% endifequal %}/>
<label for="id_{{form.field_name.name}}_{{ forloop.counter0 }}" class="custom-control-label">{{ choice }}</label>
</div>
{% endfor %}
</div>
<!-- checkbox -->
<div class="form-group">
{{ form.field_name.label_tag }}
{% for pk, choice in form.field_name.field.widget.choices %}
<div class="custom-control custom-checkbox custom-control-inline">
<input id="id_{{form.field_name.name}}_{{ forloop.counter0 }}" name="{{form.field_name.name}}" type="{{form.field_name.field.widget.input_type}}" value="{{pk}}" class="custom-control-input"
{% ifequal form.field_name.data pk.0 %}
checked="checked"
{% endifequal %}/>
<label for="id_{{form.field_name.name}}_{{ forloop.counter0 }}" class="custom-control-label">{{ choice }}</label>
</div>
{% endfor %}
</div>
How to custom checkbox and radio in Django using Bootstrap
Solution 5
Another example of a generic solution (template only):
{% for widget in form.field_name %}
<tr>
<th>
<label for="{{widget.id_for_label}}">
<input type="{{widget.data['type']}}" name="{{widget.data['name']}}" value="{{widget.data['value']}}" {% if widget.data['selected'] %}selected{% endif %} {% for k, v in widget.data['attrs'].items() %} {{k}}="{{v}}" {% endfor %}>
</label>
</th>
<td>
{{widget.choice_label}}
</td>
</tr>
{% endfor %}
Explanation:
Basically, you just iterate over form.field_name
and there you get an widget like this:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__html__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'choice_label', 'data', 'id_for_label', 'parent_widget', 'renderer', 'tag', 'template_name'] ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__html__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'choice_label', 'data', 'id_for_label', 'parent_widget', 'renderer', 'tag', 'template_name']
where widget.data
contains all the needed info to build the input elements:
{'name': 'field_name', 'value': 1, 'label': 'Field name 1', 'selected': False, 'index': '0', 'attrs': {'id': 'id_field_name_0'}, 'type': 'checkbox', 'template_name': 'django/forms/widgets/checkbox_option.html'}
Related videos on Youtube
Comments
-
sizeight over 3 years
I have a ModelForm with a multiple choice field. The choices are populated instances of Hikers belonging to a specific Club.
I want to customize the way my form displays, by displaying the choices in a table where the 1st column contains checkboxes, and a few more columns display the details of each hiker. So for example the columns are (checboxes, name, age, favourite hiking trail).
I'm not sure how to approach this. How do I access and display the form field choices with it's associated model instance fields in my template. Anybody know of the Django way to do this?
#models.py class Club(models.Model): title = models.CharField() hikers = models.ManyToManyField(Hikers) class Hiker(models.Model): name = models.CharField() age = models.PositiveIntegerField() favourite_trail = models.CharField() #forms.py class ClubForm(forms.ModelForm): def __init__(self, *args, **kwargs): club_pk = kwargs['club_pk'] del kwargs['club_pk'] super(ClubForm, self).__init__(*args, **kwargs) choices = [(ts.pk, ts.name) for hiker in Club.objects.filter(pk=club_pk)] self.fields['hikers'].choices = choices class Meta: model = Club fields = ('hikers',) widgets = {'hikers': forms.CheckboxSelectMultiple}
-
sizeight over 13 yearsThat's what I did, but the problem is I then only have access to choice id and labels, not any of the Hiker instance fields like age and favourite_trail
-
Bernhard Vallant over 13 yearsThen I'd see two possibilites: Either make a custom form widget, or include the hikers in the context of the page being rendered, so you could access them there!
-
sizeight over 13 yearsThanks insin and Alexey. Really good write up of your solution insin. I have tried this solution and it works, but like drozzy I'm also not too keen to put too much custom HTML in my Python code.
-
anyeone about 8 yearsI love this generic solution, it's exactly what I needed. Thank you very much!
-
Randy31 over 7 yearsWhat does the view look like for the generic solution? Thanks in advance. This is exactly what I've been trying to do for a few days.
-
seddonym over 7 yearsThere's nothing special about the view, just do what you normally do with form handling in views. docs.djangoproject.com/en/1.10/topics/class-based-views/…