web-dev-qa-db-fra.com

Personnaliser/supprimer l'option vierge de la case de sélection Django

J'utilise Django 1.0.2. J'ai écrit un ModelForm soutenu par un Model. Ce modèle a une ForeignKey où blank = False. Lorsque Django génère du code HTML pour ce formulaire, il crée une boîte de sélection avec une option pour chaque ligne de la table référencée par la clé étrangère. Il crée également une option en haut de la liste qui n’a aucune valeur et s’affiche sous la forme d’une série de tirets:

<option value="">---------</option>

Ce que j'aimerais savoir, c'est:

  1. Quel est le moyen le plus propre de supprimer cette option générée automatiquement de la zone de sélection? 
  2. Quel est le moyen le plus propre de le personnaliser pour qu’il se présente comme suit:

    <option value="">Select Item</option>
    

En cherchant une solution, je suis tombé sur billet Django 4653 , ce qui m'a donné l'impression que d'autres personnes avaient la même question et que le comportement par défaut de Django avait peut-être été modifié. Ce billet a plus d'un an alors j'espérais qu'il pourrait y avoir un moyen plus propre d'accomplir ces choses.

Merci pour toute aide,

Jeff

Edit: j'ai configuré le champ ForeignKey en tant que tel: 

verb = models.ForeignKey(Verb, blank=False, default=get_default_verb)

Ceci définit la valeur par défaut de sorte que ce ne soit plus l'option vide/tirets, mais malheureusement, cela ne semble résoudre aucune de mes questions. C'est-à-dire que l'option vide/tirets apparaît toujours dans la liste.

70
jlpp

Je n'ai pas testé cela, mais basé sur la lecture du code de Django ici et ici Je crois que cela devrait fonctionner:

class ThingForm(models.ModelForm):
  class Meta:
    model = Thing

  def __init__(self, *args, **kwargs):
    super(ThingForm, self).__init__(*args, **kwargs)
    self.fields['verb'].empty_label = None

EDIT: Ceci est documenté , bien que vous ne sachiez pas nécessairement que ModelChoiceField est utilisé si vous utilisez un ModelForm généré automatiquement.

EDIT: Comme jlpp le note dans sa réponse, cela n'est pas complet - vous devez réaffecter les choix aux widgets après avoir modifié l'attribut empty_label. Comme c'est un peu compliqué, l'autre option, qui pourrait être plus facile à comprendre, consiste simplement à remplacer tout le ModelChoiceField:

class ThingForm(models.ModelForm):
  verb = ModelChoiceField(Verb.objects.all(), empty_label=None)

  class Meta:
    model = Thing
84
Carl Meyer

des docs

Le choix en blanc ne sera pas inclus si le champ du modèle a la valeur vide = False et une valeur par défaut explicite (la valeur par défaut sera initialement sélectionnée ).

alors définissez la valeur par défaut et vous êtes ok

34
zalew

Avec la réponse de Carl comme guide et après avoir exploré la source Django pendant quelques heures, je pense que c'est la solution complète:

  1. Pour supprimer l'option vide (extension de l'exemple de Carl):

    class ThingForm(models.ModelForm):
      class Meta:
        model = Thing
    
      def __init__(self, *args, **kwargs):
        super(ThingForm, self).__init__(*args, **kwargs)
        self.fields['verb'].empty_label = None
        # following line needed to refresh widget copy of choice list
        self.fields['verb'].widget.choices =
          self.fields['verb'].choices
    
  2. Pour personnaliser l’étiquette d’option vide est essentiellement la même:

    class ThingForm(models.ModelForm):
      class Meta:
        model = Thing
    
      def __init__(self, *args, **kwargs):
        super(ThingForm, self).__init__(*args, **kwargs)
        self.fields['verb'].empty_label = "Select a Verb"
        # following line needed to refresh widget copy of choice list
        self.fields['verb'].widget.choices =
          self.fields['verb'].choices
    

Je pense que cette approche s'applique à tous les scénarios où ModelChoiceFields sont rendus au format HTML, mais je ne suis pas positif. J'ai constaté que lorsque ces champs sont initialisés, leurs choix sont transmis au widget Sélectionner (voir Django.forms.fields.ChoiceField._set_choices). La définition de empty_label après l'initialisation n'actualise pas la liste de choix du widget Sélectionner. Je ne connais pas suffisamment Django pour savoir si cela doit être considéré comme un bug.

22
jlpp

Vous pouvez utiliser ceci sur votre modèle:

class MyModel(models.Model):
    name = CharField('fieldname', max_length=10, default=None)

default = None est la réponse: D

NOTE: j'ai essayé ceci sur Django 1.7

18
Lu.nemec

Pour ce qui est de Django 1.4, il vous suffit de définir la valeur "par défaut" et "blanc = Faux" dans le champ choix

class MyModel(models.Model):
    CHOICES = (
        (0, 'A'), 
        (1, 'B'),
    )
    choice_field = models.IntegerField(choices=CHOICES, blank=False, default=0)
8
ykhrustalev

vous pouvez le faire dans admin:

formfield_overrides = {
    models.ForeignKey: {'empty_label': None},
}
5
holmes86

Voir ici pour le débat complet et les méthodes de résolution de ce problème.

5
Thomas B. Higgins

self.fields['xxx'].empty_value = None ne fonctionnerait pas si votre type de champ est TypedChoiceField qui n’a pas la propriété empty_label.

Ce que nous devrions faire est de supprimer le premier choix:

1 . Si vous voulez créer une BaseForm auto detect TypedChoiceField

class BaseForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(BaseForm, self).__init__(*args, **kwargs)

        for field_name in self.fields:
            field = self.fields.get(field_name)
            if field and isinstance(field , forms.TypedChoiceField):
                field.choices = field.choices[1:]
            # code to process other Field
            # ....

class AddClientForm(BaseForm):
     pass

2. seulement quelques formulaires, vous pouvez utiliser:

class AddClientForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(AddClientForm, self).__init__(*args, **kwargs)
        self.fields['xxx'].choices = self.fields['xxx'].choices[1:]
4
Mithril

Je déconnais avec cela aujourd'hui et viens de venir avec un lâche bidouille solution astucieuse:

# Cowardly handle ModelChoiceField empty label
# we all hate that '-----' thing
class ModelChoiceField_init_hack(object):
    @property
    def empty_label(self):
        return self._empty_label

    @empty_label.setter
    def empty_label(self, value):
        self._empty_label = value
        if value and value.startswith('-'):
            self._empty_label = 'Select an option'
ModelChoiceField.__bases__ += (ModelChoiceField_init_hack,)

Maintenant, vous pouvez modifier l’étiquette par défaut ModelChoiceField par tout ce que vous voulez. :-)

PS: Pas besoin de faire l’objet d’un abaissement de voix, les patchs de singe non nocifs sont toujours pratiques.

2
emyller

Pour la dernière version de Django .__, la première réponse devrait être comme ceci

class ThingForm(models.ModelForm):
class Meta:
 model = Thing

  def __init__(self, *args, **kwargs):
    self.base_fields['cargo'].empty_label = None
    super(ThingForm, self).__init__(*args, **kwargs)`
2
lwj0012

Pour un champ ForeignKey, définir la valeur default sur '' sur le modèle supprimera l'option vide.

verb = models.ForeignKey(Verb, on_delete=models.CASCADE, default='')

Pour d'autres champs comme CharField, vous pouvez définir default sur None, mais cela ne fonctionne pas pour les champs ForeignKey dans Django 1.11.

2
YPCrumble

Je trouve la solution !!

Mais pas pour ForeignKey :-)

Peut-être que je peux vous aider… .J'ai consulté le code source de Django et découvert que Django.forms.extras.widgets.SelecteDateWidget () est une propriété appelée none_value qui est égale à (0, '-----'), alors a fait dans mon code cette

class StudentForm(ModelForm):
    class Meta:
        this_year = int(datetime.datetime.today().strftime('%Y')) 
        birth_years = []
        years = []

        for year in range(this_year - 2, this_year + 3 ):
            years.append(year)
        for year in range(this_year - 60, this_year+2):
            birth_years.append(year)

        model = Student
        exclude = ['user', 'fullname']
        date_widget = SelectDateWidget(years=years)

        date_widget.__setattr__('none_value', (0, 'THERE WAS THAT "-----" NO THERES THIS:-)'))
        widgets = {
            'beginning': date_widget,
            'birth': SelectDateWidget(years=birth_years),
        }
1
user3002411

Cela devient plus compliqué lorsque les choix sont des clés étrangères et si vous souhaitez filtrer les choix en fonction de certains critères. Dans ce cas, si vous définissez le empty_label puis réaffectez les choix (vous pouvez également appliquer la filtration ici), l’étiquette vide sera vide:

class ThingForm(models.ModelForm):

    class Meta:
    model = Thing

    def __init__(self, *args, **kwargs):
        super(ThingForm, self).__init__(*args, **kwargs)
        self.fields['verb'].empty_label = None
        self.fields['verb'].queryset=Verb.objects.all()

bbasiquement, la première ligne sous init peut être appliquée à tous les champs du formulaire avec une boucle ou une boucle en ligne:

def __init__(self,user, *args, **kwargs):
    super(NewTicket, self).__init__(*args, **kwargs)
    for f in self.fields:
       self.fields[f].empty_label = None # or "Please Select" etc
0
Ibo

Il y a beaucoup de bonnes réponses ici, mais je ne suis toujours pas entièrement satisfait des implémentations. Je suis aussi un peu frustré que de sélectionner des widgets de différentes sources (clés étrangères, choix) produisent des comportements différents. 

Je travaille sur un design dans lequel les champs sélectionnés always ont une option vide, et s’ils sont obligatoires, ils auront une étoile à côté et le formulaire ne sera tout simplement pas validé s’ils sont laissés vides. Cela dit, je ne peux remplacer correctement le empty_label que pour les champs qui ne sont pas TypedChoiceFields.

Voici à quoi ressemble le résultat devrait. Le premier résultat est toujours le nom du champ - dans mon cas, le label.

 select widget

Voici ce que j'ai fini par faire. Voici une méthode __init__ remplacée de mon formulaire:

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    for _, field in self.fields.items():
        if hasattr(field, 'empty_label'):
            field.empty_label = field.label
        if isinstance(field, forms.TypedChoiceField):
            field.choices = [('', field.label)] + [choice for choice in field.choices if choice[0]]
0
Jamie Counsell

Depuis Django 1.7, vous pouvez personnaliser le libellé de la valeur vide en ajoutant une valeur à votre liste de choix dans la définition de votre champ de modèle. De la documentation sur la configuration choix des champs :

Sauf si blank = False est défini sur le champ avec une valeur par défaut, une étiquette contenant "---------" sera rendue avec la zone de sélection. Pour remplacer ce comportement, ajoutez un tuple aux choix contenant aucun; par exemple. (Aucun, 'Votre chaîne à afficher'). Alternativement, vous pouvez utiliser une chaîne vide au lieu de None si cela convient, comme dans un CharField.

J'ai consulté la documentation de différentes versions de Django et constaté qu'il s'agissait de ajouté dans Django 1.7 .

0
Rebecca Koeser