markwalker_
markwalker_

Reputation: 12869

Django form field data lost when using custom widget

I've written a custom widget for a form field in Django and I'm seeing the kind of HTML output which I want, but the field isn't being returned in the cleaned_data.

The HTML is part of the form in the page, so is there an issue with my widget?

# forms.py

class EntrantDetails(forms.Form):
    TITLE_CHOICES = (
        ('', 'Select:'),
        ('Mr', 'Mr'),
        ('Mrs', 'Mrs'),
        ('Miss', 'Miss'),
        ('Ms', 'Ms'),
        ('Dr', 'Dr'),
    )
    GENDER_CHOICES = (
        ('M', 'male'),
        ('F', 'female'),
    )
    title = forms.CharField(
        max_length=20,
        widget=forms.widgets.Select(choices=TITLE_CHOICES)
    )
    first_name = forms.CharField(max_length=200)
    middle_names = forms.CharField(
        label='Middle name(s)',
        max_length=200, required=False
    )
    last_name = forms.CharField(max_length=200)
    date_of_birth = forms.DateField(
        widget=SelectDateWidget(years=range(2015, 1900, -1))
    )
    gender = forms.CharField(
        max_length=1,
        widget=ButtonSelectWidget(cls='test_class', choices=GENDER_CHOICES)
    )


# widgets.py

class ButtonSelectWidget(Widget):
    """
    Custom widget to display a choice field more like selectable buttons.
    """

    def __init__(self, attrs=None, cls=None, choices=None):
        self.attrs = attrs or {}
        self.cls = cls
        self.choices = choices

    def render(self, name, value, attrs=None, choices=()):
        if not choices:
            choices = self.choices
        output = []
        output.append(u'<div class="controls">')

        for choice in choices:
            label = u'{}'.format(choice[1])
            output.append(u'''
            <div class="radio radio-{label}">
                <input id="user_{name}_{label}" name="user[{name}]" type="radio" value="{choice}">
                <label for="user_{name}_{label}">{title}</label>
            </div>
            '''.format(name=name, choice=choice[0], label=label, title=label.title()))
        output.append(u'</div>')


        return mark_safe(u'\n'.join(output))

Upvotes: 0

Views: 463

Answers (1)

bruno desthuilliers
bruno desthuilliers

Reputation: 77952

The input's name in the markup is wrong, so the form doesn't collect it. Instead of

 <input id="user_{name}_{label}" name="user[{name}]" type="radio" value="{choice}">

you'd need

 <input id="user_{name}_{label}" name="{name}" type="radio" value="{choice}">

Also the standard scheme for controls id in Django forms is id_<name>[_counter]

Now Django already has a RadioSelect widget that gives you the same feature, so you'd be better using it (with your own specific markup in the template) instead of reinventing the (squared) wheel and hard-coding project's specific template in your widget.

Upvotes: 1

Related Questions