[ACCEPTED]-ChoiceField doesn't display an empty label when using a tuple-django-forms

Accepted answer
Score: 36

I've found a solution that works the way 13 I want it to without violating the DRY principle. Not 12 very clean, but it'll have to do I suppose.

According 11 to the documentation choices don't have to be a tuple:

Finally, note 10 that choices can be any iterable object 9 -- not necessarily a list or tuple. This 8 lets you construct choices dynamically. But 7 if you find yourself hacking choices to 6 be dynamic, you're probably better off using 5 a proper database table with a ForeignKey. choices 4 is meant for static data that doesn't 3 change much, if ever.

So the solution I'm 2 going with for the moment is:

COMPETITION_TYPE_CHOICES = [
     (1, 'Olympic Games'),
     (2, 'ISU Championships'),
     (3, 'Grand Prix Series'),
]

COMP_TYPE_CHOICES_AND_EMPTY = [('','All')] + COMPETITION_TYPE_CHOICES

And then:

class CompetitionSearchForm(forms.Form):
    name = forms.CharField(required=False)
    type = forms.ChoiceField(choices=COMP_TYPE_CHOICES_AND_EMPTY, required=False)

The 1 model stays the same as it was.

Score: 32

I tried both Monika's and Evgeniy's solutions 7 with no success, but Monika has a good point 6 in that the choices do not need to be tuples. Therefore, the 5 easiest (and DRYest) solution is to simply 4 do what Django does already in the Model 3 Field. Simply add the blank choice and the 2 tuples together after converting them to 1 a list:

from django.db.models.fields import BLANK_CHOICE_DASH

...

type = forms.ChoiceField(choices=BLANK_CHOICE_DASH + list(COMPETITION_TYPE_CHOICES), required=False)
Score: 10

Better choice is to update field choices 1 in form init method

COMPETITION_TYPE_CHOICES = (
    (1, 'Olympic Games'),
    (2, 'ISU Championships'),
    (3, 'Grand Prix Series'),
)


class CompetitionSearchForm(forms.Form):
    name = forms.CharField(required=False)
    type = forms.ChoiceField(choices=COMPETITION_TYPE_CHOICES,required=False)

    def __init__(self, *args, **kwargs):
        super(CompetitionSearchForm, self).__init__(*args, **kwargs)
        self.fields['type'].choices.insert(0, ('','---------' ) )
Score: 8

According to the documentation:

Either an 4 iterable (e.g., a list or tuple) of 2-tuples 3 to use as choices for this field, or a callable 2 that returns such an iterable. (https://docs.djangoproject.com/en/dev/ref/forms/fields/)

So, you 1 can simple:

sample_field = forms.ChoiceField(choices=(('', '---'),) + Model.YOUR_CHOICES)
Score: 7

Try adding blank=True to the model fields (assuming 8 that's the behavior you want), then changing 7 the form to a ModelForm and removing the 6 field definitions. Note that any fields 5 for which you set blank=True won't be required when 4 validating or saving the model. Again, this may not 3 be what you want but if it is it'll allow 2 Django to take care of a few things automatically.

Otherwise 1 just change your COMPETITION_TYPE_CHOICES to:

COMPETITION_TYPE_CHOICES = (
    ('', '---------'),
    ('1', 'Olympic Games'),
    ('2', 'ISU Championships'),
    ('3', 'Grand Prix Series'),
)
Score: 6

Just a small change to Evgeniy's answer that checks if the 4 blank alternative is not already added.

Without 3 the check (at least when running the builtin 2 runserver) one extra empty label is added 1 for each page reload.

COMPETITION_TYPE_CHOICES = (
    (1, 'Olympic Games'),
    (2, 'ISU Championships'),
    (3, 'Grand Prix Series'),
)

class CompetitionSearchForm(forms.Form):
    name = forms.CharField(required=False)
    type = forms.ChoiceField(choices=COMPETITION_TYPE_CHOICES,required=False)

    def __init__(self, *args, **kwargs):
        super(CompetitionSearchForm, self).__init__(*args, **kwargs)
        if not self.fields['type'].choices[0][0] == '':
            self.fields['type'].choices.insert(0, ('','---------' ) )
Score: 0

Why don't you use ModelForm if you are already 2 have model class?

Best solution:

forms.py

class CompetitionSearchForm(ModelForm):

    class Meta:
        model = Competition

models.py

class Competition(models.Model):
    name = models.CharField(max_length=256)
    type = models.IntegerField(choices=COMPETITION_TYPE_CHOICES, default=COMPETITION_TYPE_CHOICES[0][0], blank=True)

You can set 1 blank=False to remove empty_label from list

Score: 0

A little late to the party..

How about not modifying the choices at all 2 and just handling it with a widget?

from django.db.models import BLANK_CHOICE_DASH

class EmptySelect(Select):
    empty_value = BLANK_CHOICE_DASH[0]
    empty_label = BLANK_CHOICE_DASH[1]

    @property
    def choices(self):
        yield (self.empty_value, self.empty_label,)
        for choice in self._choices:
            yield choice

    @choices.setter
    def choices(self, val):
        self._choices = val

Then 1 just call it:

class CompetitionSearchForm(forms.Form):
    name = forms.CharField(required=False)
    type = forms.ChoiceField(choices=COMPETITION_TYPE_CHOICES,required=False, widget=EmptySelect)

This is what you end up with:

print(CompetitionSearchForm().as_p())
<p>
    <label for="id_name">Name:</label>
    <input id="id_name" name="name" type="text" />
</p>
<p>
    <label for="id_type">Type:</label>
    <select id="id_type" name="type">
        <option value="" selected="selected">------</option>
        <option value="1">Olympic Games</option>
        <option value="2">ISU Championships</option>
        <option value="3">Grand Prix Series</option>
    </select>
</p>

More Related questions