How to prevent django fields from being wrapped in a `div`?
Question:
If I use the below to generate multiple radio buttons:
from django import forms
class MCQCollectionForm(forms.Form):
user_input = forms.ChoiceField(
widget=forms.RadioSelect, label='', choices=enumerate(['option 1', 'option 2'])
)
I get:
<div id="id_user_input">
<div>
<label for="id_user_input_0"><input id="id_user_input_0" name="user_input" required="" type="radio"
value="0">
option 1</label>
</div>
<div>
<label for="id_user_input_1"><input id="id_user_input_1" name="user_input" required="" type="radio"
value="1">
option 2</label>
</div>
</div>
but I want it to be only the below, nothing else:
<label class="user-item">
<input name="user_input" type="radio" ... >
<span>Lorem ipsum dolor</span>
</label>
<label class="user-item">
<input name="user_input" type="radio" ... >
<span>Lorem ipsum dolor</span>
</label>
is there a simple way to do so without having to use JS and manually make the desired changes? Note: the extra attributes id=...
, required
, … are fine, I just want the divs gone and be able to add a class to the label and span
with its contents.
Answers:
You can do it by using a custom widget inherited from forms.RadioSelect
and overriding the method create_option
In your forms.py file add this custom widget class.
class CustomRadioSelect(forms.RadioSelect):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected, index, subindex, attrs)
option["wrapper_attrs"] = {}
if "label_attrs" not in option:
option["label_attrs"] = {}
option["label_attrs"]["class"] = "user-item"
return option
Now, Update your MCQCollectionForm
class with this:
class MCQCollectionForm(forms.Form):
user_input = forms.ChoiceField(
widget=CustomRadioSelect, label='', choices=enumerate(['option 1', 'option 2'])
)
From your views.py
file pass the form through your appropriate view context like: form = MCQCollectionForm() render(.....,{'form':form})
(I believe you already have this in place)
From your template file loop through the form fields like below:
{% for choice in form1.user_input %}
<label class="{{ choice.label.attrs.class }}" for="{{ choice.id_for_label }}">
{{ choice.tag }}
<span>{{ choice.choice_label }}</span>
</label>
{% endfor %}
This will help you to achieve your goal. Below is a screenshot of how it will format in HTML source code.
If I use the below to generate multiple radio buttons:
from django import forms
class MCQCollectionForm(forms.Form):
user_input = forms.ChoiceField(
widget=forms.RadioSelect, label='', choices=enumerate(['option 1', 'option 2'])
)
I get:
<div id="id_user_input">
<div>
<label for="id_user_input_0"><input id="id_user_input_0" name="user_input" required="" type="radio"
value="0">
option 1</label>
</div>
<div>
<label for="id_user_input_1"><input id="id_user_input_1" name="user_input" required="" type="radio"
value="1">
option 2</label>
</div>
</div>
but I want it to be only the below, nothing else:
<label class="user-item">
<input name="user_input" type="radio" ... >
<span>Lorem ipsum dolor</span>
</label>
<label class="user-item">
<input name="user_input" type="radio" ... >
<span>Lorem ipsum dolor</span>
</label>
is there a simple way to do so without having to use JS and manually make the desired changes? Note: the extra attributes id=...
, required
, … are fine, I just want the divs gone and be able to add a class to the label and span
with its contents.
You can do it by using a custom widget inherited from forms.RadioSelect
and overriding the method create_option
In your forms.py file add this custom widget class.
class CustomRadioSelect(forms.RadioSelect):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected, index, subindex, attrs)
option["wrapper_attrs"] = {}
if "label_attrs" not in option:
option["label_attrs"] = {}
option["label_attrs"]["class"] = "user-item"
return option
Now, Update your MCQCollectionForm
class with this:
class MCQCollectionForm(forms.Form):
user_input = forms.ChoiceField(
widget=CustomRadioSelect, label='', choices=enumerate(['option 1', 'option 2'])
)
From your views.py
file pass the form through your appropriate view context like: form = MCQCollectionForm() render(.....,{'form':form})
(I believe you already have this in place)
From your template file loop through the form fields like below:
{% for choice in form1.user_input %}
<label class="{{ choice.label.attrs.class }}" for="{{ choice.id_for_label }}">
{{ choice.tag }}
<span>{{ choice.choice_label }}</span>
</label>
{% endfor %}
This will help you to achieve your goal. Below is a screenshot of how it will format in HTML source code.