What is the \"djangoy\" way to approach this problem:
In my form class, I have a forms.ChoiceField whose widget is a forms.RadioSelect widget, one of whose choices n
Anton's answer worked, and was a decent answer for a while there - but unfortunately it became unmaintainable. So, taking a cue from a diff attached to django ticket #9230, I just monkey patched django.forms.forms.BoundField
from django import forms
def MonkeyPatchDjangoFormsBoundField():
def prepare_widget_render(self, widget=None, attrs=None, only_initial=False):
"""
Prepare the data needed for the widget rendering.
"""
if not widget:
widget = self.field.widget
attrs = attrs or {}
auto_id = self.auto_id
if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
if not only_initial:
attrs['id'] = auto_id
else:
attrs['id'] = self.html_initial_id
if not only_initial:
name = self.html_name
else:
name = self.html_initial_name
return widget, name, attrs
def as_widget(self, widget=None, attrs=None, only_initial=False):
"""
Renders the field by rendering the passed widget, adding any HTML
attributes passed as attrs. If no widget is specified, then the
field's default widget will be used.
"""
widget, name, attrs = self.prepare_widget_render(widget, attrs, only_initial)
return widget.render(name, self.value(), attrs=attrs)
def __iter__(self):
"""
Check if current widget has a renderer and iterate renderer.
"""
widget, name, attrs = self.prepare_widget_render()
if not hasattr(widget, 'get_renderer'):
raise Exception, "Can not iterate over widget '%s'" % widget.__class__.__name__
renderer = widget.get_renderer(name, self.value(), attrs=attrs)
for entry in renderer:
yield entry
def __getitem__(self,idx):
"""
Tries to use current widget's renderer, and then check attribute.
"""
widget, name, attrs = self.prepare_widget_render()
try:
renderer = widget.get_renderer(name, self.value(), attrs=attrs)
return renderer[idx]
except Exception:
return getattr(self,idx)
forms.forms.BoundField.prepare_widget_render = prepare_widget_render
forms.forms.BoundField.as_widget = as_widget
forms.forms.BoundField.__iter__ = __iter__
forms.forms.BoundField.__getitem__ = __getitem__
This allowed me to be able to access the radio inputs directly, by using {{ form.field.0.tag }}
, or through iteration - {% for radio in form.field %} {{ radio.tag }} {% endfor %}
. Much easier to take care of!
I would do this by subclassing RadioFieldRenderer and attaching it to a custom widget:
# forms.py
from django import forms
from django.forms.widgets import RadioSelect, RadioFieldRenderer
from django.template.loader import render_to_string
from myapp.models import RatherComplicatedModel
class MyRadioFieldRenderer(RadioFieldRenderer):
def render(self):
return render_to_string(
'my_radio_widget.html',
{'field': self})
class MyRadioSelect(RadioSelect):
renderer = MyRadioFieldRenderer
class RatherComplicatedForm(forms.ModelForm):
RADIO_CHOICES = (
('none', "No Textbox"),
('one', "One Textbox: "),
)
rad = forms.ChoiceField(widget=MyRadioSelect(),choices=RADIO_CHOICES)
class Meta:
model = RatherComplicatedModel
Then the template:
#my_radio_widget.html
<ul>
{% for choice in field %}
<li>
<label for="id_{{ field.name }}_{{ forloop.counter0 }}">
<input type="radio"
name="{{ field.name }}"
value="{{ choice.choice_value }}"
id="id_{{ field.name }}_{{ forloop.counter0 }}"
{% if field.value == choice.choice_value %}
checked='checked'
{% endif %}/>
{{ choice.choice_label }}
</label>
</li>
{% endfor %}
</ul>
If I understand your problem correctly, you can access choices tuple in template:
<ul>
{# Assuming {{ field }} here is {{ form.rad }} #}
{% for choice in field.field.choices %}
<li>
<label for="id_{{ field.html_name }}_{{ forloop.counter0 }}">
<input type="radio"
id="id_{{ field.html_name }}_{{ forloop.counter0 }}"
value="{{ choice.0 }}"
name="{{ field.html_name }}" />
{{ choice.1 }}
{% if choice.0 == 'one' %}
{# Necessary field here #}
{{ form.bar }}
{% else %}
No Textbox
{% endif %}
</label>
</li>
{% endfor %}
</ul>
Choices should be in the Model:
class RatherComplicatedModel(models.Model):
BAR_CHOICES = (
(0, "No Textbox"),
(1, "One Textbox: "),
)
#some other stuff...
bar = models.IntegerField(blank=True, null=True, choices=BAR_CHOICES)
Then just:
class RatherComplicatedForm(forms.ModelForm):
#various and sundry code...
bar = forms.ChoiceField(widget=forms.RadioSelect(),
choices=RatherComplicatedModel.BAR_CHOICES)
class Meta:
model = RatherComplicatedModel