views:

102

answers:

1

Hi,

I'm trying to understand why I can't specify choices to a form field's widget if I'm overriding a ModelForm's field in Django. It works if I give the choices to the field but not the widget. My understanding is/was that if you give choices to a field it'll be passed onto the widget for rendering. I know I can get this to work with any of the first three snippets below but I just wanted to fully understand why this one way doesn't work.

This is my ModelForm code, thanks!

from django import forms
from models import Guest

    class RSVPForm(forms.ModelForm):

        class Meta:
            model = Guest

        def __init__(self, *args, **kwargs):

            """
            Override a form's field to change the widget
            """

            super(RSVPForm, self).__init__(*args, **kwargs)

                # This works
                self.fields['attending_ceremony'].required = True
                self.fields['attending_ceremony'].widget=forms.RadioSelect(choices=Guest.CHOICES)

                # This works
                self.fields['attending_ceremony'].required = True
                self.fields['attending_ceremony'].widget=forms.RadioSelect()
                self.fields['attending_ceremony'].choices=Guest.CHOICES

                # This works
                self.fields['attending_ceremony'] = forms.TypedChoiceField(
                    required=True,
                    widget=forms.RadioSelect,
                    choices=Guest.CHOICES
                )

                # This doesn't - the choices aren't set (it's an empty list)
                self.fields['attending_ceremony'] = forms.TypedChoiceField(
                    required=True,
                    widget=forms.RadioSelect(choices=Guest.CHOICES)
                )
+1  A: 

I think the best way of explaining is to walk through the code for ChoiceField, the superclass of TypeChoiceField.

class ChoiceField(Field):
    widget = Select
    default_error_messages = {
        'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
    }

    def __init__(self, choices=(), required=True, widget=None, label=None,
                 initial=None, help_text=None, *args, **kwargs):
        super(ChoiceField, self).__init__(required=required, widget=widget, label=label,
                                        initial=initial, help_text=help_text, *args, **kwargs)
        self.choices = choices

    def _get_choices(self):
        return self._choices

    def _set_choices(self, value):
        # Setting choices also sets the choices on the widget.
        # choices can be any iterable, but we call list() on it because
        # it will be consumed more than once.
        self._choices = self.widget.choices = list(value)

    choices = property(_get_choices, _set_choices)

With your example,

     self.fields['attending_ceremony'] = forms.TypedChoiceField(
                required=True,
                widget=forms.RadioSelect(choices=Guest.CHOICES)
            )
  1. The widget is initialised, with choices=guest.Choices
  2. super(ChoiceField, self).__init__ sets self.widget=widget. The widget's choices are still set.
  3. self.choices=choices sets the choices for the field and the widget to the default (), because it wasn't specified (see _set_choices above).

Hope that makes sense. Looking at the code also explains why your other examples work. Either the choices are set for the widget and the choice field at the same time, or widget's choices are set after the choice field has been initialised.

Alasdair
Oh right, I'm with you. It gets overwritten because choices for the field is not specified. Thanks!
Tim Fletcher