web-dev-qa-db-fra.com

ChoiceField n'affiche pas d'étiquette vide lors de l'utilisation d'un tuple

Ce que j'essaye de faire

Je vais garder des données sur les compétitions dans ma base de données. Je veux pouvoir rechercher les compétitions selon certains critères - type de compétition en particulier.

A propos des types de compétition

Les types de compétition sont conservés dans un tuple. Un exemple légèrement raccourci:

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

Celles-ci sont utilisées dans le modèle comme suit (encore une fois - il s'agit d'une version abrégée/simplifiée du modèle):

class Competition(models.Model):
    name = models.CharField(max_length=256)
    type = models.IntegerField(choices=COMPETITION_TYPE_CHOICES) 

Le formulaire de recherche

Je ne veux pas que les champs soient obligatoires dans le formulaire de recherche, le formulaire est donc défini comme suit:

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

Le problème

J'aimerais que le widget sélectionné dans ChoiceField affiche une étiquette vide, mais je n'en ai pas. Toute aide à ce sujet serait très appréciée :)

40
Monika Sulik

J'ai trouvé une solution qui fonctionne comme je le souhaite sans violer le principe DRY. Pas très propre, mais il faudra que je suppose.

Selon la documentation choix ne doit pas nécessairement être un tuple:

Enfin, notez que les choix peuvent être n’importe lequel objet itérable - pas nécessairement un liste ou tuple. Cela vous permet de construire choix dynamiquement. Mais si vous trouvez vous-même le piratage des choix pour être dynamique, vous êtes probablement mieux lotis en utilisant une table de base de données appropriée avec un Clé étrangère. choice est destiné à données statiques qui ne changent pas beaucoup, si jamais.

Donc, la solution que je vais pour le moment est la suivante:

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

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

Et alors:

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

Le modèle reste le même.

36
Monika Sulik

J'ai essayé les solutions de Monika et d'Evgeniy sans succès, mais Monika a un bon point en ce sens que les choix ne doivent pas nécessairement être des tuples. Par conséquent, la solution la plus simple (et la plus sèche) consiste simplement à faire ce que Django fait déjà dans le champ Modèle. Ajoutez simplement le choix vide et les n-uplets ensemble après les avoir convertis en une liste:

from Django.db.models.fields import BLANK_CHOICE_DASH

...

type = forms.ChoiceField(choices=BLANK_CHOICE_DASH + list(COMPETITION_TYPE_CHOICES), required=False)
27
Scott

Un meilleur choix consiste à mettre à jour les choix de champs sous la forme 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, ('','---------' ) )
9
Evgeniy

Selon la documentation:

Soit un élément itérable (par exemple, une liste ou un nuplet) de 2 tuples à utiliser comme choix pour ce champ, ou un objet appelable qui renvoie un tel élément itérable ( https://docs.djangoproject.com/en/dev/ref/forms/fields/ )

Donc, vous pouvez simple:

sample_field = forms.ChoiceField(choices=(('', '---'),) + Model.YOUR_CHOICES)
7
Edson Dota

Juste un petit changement dans la réponse d'Evgeniy qui vérifie si l'alternative vide n'est pas déjà ajoutée.

Sans la vérification (au moins lors de l'exécution du serveur d'exécution intégré), une étiquette vide supplémentaire est ajoutée pour chaque rechargement de page.

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, ('','---------' ) )
6
sigurdga

Essayez d’ajouter blank=True aux champs du modèle (en supposant que ce soit le comportement souhaité), puis de remplacer le formulaire par un ModelForm et de supprimer les définitions de champ. Notez que les champs pour lesquels vous définissez blank=True ne seront pas obligatoires lors de la validation ou de l'enregistrement du modèle. Encore une fois, ce n'est peut-être pas ce que vous voulez, mais si c'est le cas, cela permettra à Django de prendre en charge automatiquement certaines tâches.

Sinon changez simplement votre COMPETITION_TYPE_CHOICES en:

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

Pourquoi n'utilisez-vous pas ModelForm si vous avez déjà une classe de modèle?

Meilleure 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)

Vous pouvez définir blank = False pour supprimer empty_label de la liste

0
2xS

Un peu tard pour la fête ..

Pourquoi ne pas modifier les choix du tout et simplement le manipuler avec un 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

Alors appelez ça:

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

Voici ce avec quoi vous vous retrouvez:

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>
0
Javier Buzzi