stripe7
stripe7

Reputation: 311

Django - How to iterate and inspect each choice for a ModelChoiceField in the template code

I have a form (edited for brevity) as follows:

class InteractionForm(forms.Form):
    def __init__(self, *args, **kwargs):

        # Each object within this queryset is a model object of type InteractionChoice
        choices_qs = interaction.interactionchoice_set.all()

        self.fields['choices'] = forms.ModelChoiceField(
                                    widget=forms.RadioSelect(),
                                    queryset=choices_qs,

The InteractionChoice model looks like:

class InteractionChoice(models.Model):
    interaction = models.ForeignKey(Interaction)
    name = models.CharField(max_length=255)
    is_answer = models.BooleanField(default=False)

An instance of InteractionForm is passed from a view to a template and rendered via:

{{ form.choices }}

My question is whether there is a way to iterate over each choice in my template and access one of its properties -- specifically, the is_answer property defined in InteractionChoice. The purpose being to customize how a choice is displayed if it is indeed the answer. More specifically, if is_answer is True, I'd possibly change the class attribute on the <label> for that choice.

Perhaps, I'm approaching this problem from the wrong direction. If anyone has pointers for alternative ideas, I'd be happy to hear them.

Thanks in advance.

Update 1: Thinking about this more after @rczajka's response, I don't believe I can achieve what I'm hoping to do in the template code. Instead, if the purpose is to modify the tag's class attribute, I should perhaps be looking to subclass and override certain methods in forms.widgets.RadioInput, forms.widgets.RadioFieldRenderer, and forms.widgets.RadioSelect. I'll dig into this more.

Upvotes: 3

Views: 4096

Answers (4)

richy_irad
richy_irad

Reputation: 81

As of Django 3.2, you can iterate over the choices in templates. The RadioSelect widget docs state that you can loop over RadioSelect generated markup

For more granular control over the generated markup, you can loop over the radio buttons in the template.

Here's an example of how you can do it:

<ul>
 {% for choice in form.choices %}
   <li>{{ choice }}</li>
 {% endfor %}
</ul>

Upvotes: 0

Stephen Paulger
Stephen Paulger

Reputation: 5343

I found a similar problem but solved it in a different way.

How to get ModelChoiceField instances in the template

Iterating over the field's queryset property.

Upvotes: 1

stripe7
stripe7

Reputation: 311

I came up with one solution that addresses this problem. It's hackish, to say the least, but it's the only approach I've thought of that works thus far without a lot of back-end changes to my existing design.

My approach stemmed from this article on subclassing `RadioFieldRenderer' and 'RadioSelect'.

In the __unicode__ method for an InteractionChoice model, I return:

return self.name + "_" + str(self.is_answer)

which is the value used for a radio button's label (amongst other things). I then subclassed forms.widgets.RadioInput, forms.widgets.RadioFieldRenderer, and forms.widgets.RadioSelect.

For the custom RadioInput class, I overrode its __unicode__ method to include logic to append a class string – whose value is ultimately dictated by the string returned from the unicode method in InteractionChoice – to the <label> tag string it returns.

For the custom RadioFieldRenderer class, I overrode __iter__ and __getitem__ to use the custom RadioInput class.

For the custom RadioSelect class, I overrode the renderer property to use my custom radio field renderer.

This is obviously far from an ideal solution. Hopefully, a better one will arise.

Upvotes: 1

rczajka
rczajka

Reputation: 1840

You should subclass ModelChoiceField and override label_from_instance. It says so here: https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield:

The unicode method of the model will be called to generate string representations of the objects for use in the field's choices; to provide customized representations, subclass ModelChoiceField and override label_from_instance. This method will receive a model object, and should return a string suitable for representing it.

Upvotes: 0

Related Questions