Anpassen / Entfernen der leeren Option für das Django-Auswahlfeld

77

Ich benutze Django 1.0.2. Ich habe eine ModelForm geschrieben, die von einem Model unterstützt wird. Dieses Modell hat einen ForeignKey, wobei blank = False ist. Wenn Django HTML für dieses Formular generiert, wird ein Auswahlfeld mit einer Option für jede Zeile in der Tabelle erstellt, auf die der ForeignKey verweist. Außerdem wird oben in der Liste eine Option erstellt, die keinen Wert hat und als eine Reihe von Strichen angezeigt wird:

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

Was ich gerne wissen würde ist:

  1. Was ist der sauberste Weg, um diese automatisch generierte Option aus dem Auswahlfeld zu entfernen?
  2. Was ist der sauberste Weg, um es so anzupassen, dass es wie folgt angezeigt wird:

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

Auf der Suche nach einer Lösung stieß ich auf das Django-Ticket 4653, das den Eindruck erweckte, dass andere die gleiche Frage hatten und dass das Standardverhalten von Django möglicherweise geändert wurde. Dieses Ticket ist über ein Jahr alt und ich hatte gehofft, dass es einen saubereren Weg gibt, um diese Dinge zu erreichen.

Vielen Dank für jede Hilfe,

Jeff

Bearbeiten: Ich habe das ForeignKey-Feld als solches konfiguriert:

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

Dies setzt die Standardeinstellung so, dass es nicht mehr die Option "Leer / Bindestriche" ist, aber leider scheint es keine meiner Fragen zu lösen. Das heißt, die Option leer / Bindestriche wird weiterhin in der Liste angezeigt.

jlpp
quelle
Es stellt sich heraus, dass es in Django als Fehler angesehen wird. sollte die leere Option in diesem Fall automatisch entfernen: code.djangoproject.com/ticket/10792
Carl Meyer

Antworten:

90

Ich habe das noch nicht getestet, aber basierend auf dem Lesen von Djangos Code hier und hier glaube ich, dass es funktionieren sollte:

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

BEARBEITEN : Dies ist dokumentiert , obwohl Sie nicht unbedingt nach ModelChoiceField suchen müssen, wenn Sie mit einem automatisch generierten ModelForm arbeiten.

EDIT : Wie jlpp Notizen in seiner Antwort nicht abgeschlossen ist - Sie müssen neu ordnen die Auswahl der Widgets nach dem empty_label Attribut ändern. Da dies etwas hackig ist, überschreibt die andere Option, die möglicherweise leichter zu verstehen ist, nur das gesamte ModelChoiceField:

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

  class Meta:
    model = Thing
Carl Meyer
quelle
Danke für die Hilfe Carl. Ihr Beispiel hat fast funktioniert, aber ich musste die Widget-Auswahlliste als letzten Schritt aktualisieren. Siehe meine Antwort. Und Wow; Schnelle Abwicklung des Fehlerberichts!
jlpp
Seitenleiste: Wissen Sie, wie Ihr letzter Ansatz zur Verwendung von Meta: -Feldern passt, um auszuwählen, welche Modellfelder in das Formular aufgenommen werden sollen?
jlpp
Ich bin nicht sicher, was passieren würde, wenn Sie ein Formularfeld manuell definieren, es aber nicht in die Meta-Liste der Modellfelder aufnehmen. Ich bin sicher, es würde erscheinen, aber würde es auf dem Modell gespeichert werden? Meine Vermutung ist ja - probieren Sie es aus und sehen Sie.
Carl Meyer
Ich denke, der zweite Weg, dies mit der ModelChoiceField-Definition zu tun, ist klarer. Leichter zu erkennen, wenn man sich einen anderen Code ansieht :)
Radtek
Die erste Lösung funktioniert für mich einwandfrei, ohne dass Sie die Auswahlmöglichkeiten neu zuweisen müssen.
ZAD-Man
38

aus den Dokumenten

Die leere Auswahl wird nicht berücksichtigt, wenn das Modellfeld leer = Falsch und einen expliziten Standardwert enthält (stattdessen wird zunächst der Standardwert ausgewählt).

Stellen Sie also die Standardeinstellung ein und Sie sind in Ordnung

zalew
quelle
Siehe Bearbeiten in Frage. Danke für Ihre Hilfe.
jlpp
3
Dies für ein ForeignKey-Feld zu tun, kann gefährlich sein. Es könnte funktionieren, wenn es sich um ein CharField mit Auswahlmöglichkeiten handelt. Das Problem ist, dass ForeignKey keine gut etablierten Standardeinstellungen hat.
Amjoconn
Ja, das war genau das, wonach ich gesucht habe ... für ein Nicht-ForeignKey-Auswahlfeld
byoungb
23

Mit Carls Antwort als Leitfaden und nachdem ich ein paar Stunden in der Django-Quelle herumgewühlt habe, denke ich, dass dies die vollständige Lösung ist:

  1. So entfernen Sie die leere Option (Erweiterung von Carls Beispiel):

    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. Das Anpassen der leeren Optionsbezeichnung ist im Wesentlichen dasselbe:

    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
    

Ich denke, dieser Ansatz gilt für alle Szenarien, in denen ModelChoiceFields als HTML gerendert werden, aber ich bin nicht positiv. Ich habe festgestellt, dass beim Initialisieren dieser Felder ihre Auswahl an das Select-Widget übergeben wird (siehe django.forms.fields.ChoiceField._set_choices). Durch das Festlegen des leeren Labels nach der Initialisierung wird die Auswahlliste des Widgets "Auswählen" nicht aktualisiert. Ich bin mit Django nicht vertraut genug, um zu wissen, ob dies als Fehler angesehen werden sollte.

jlpp
quelle
Wahrscheinlich kein Fehler; Ich denke, es gibt einen Grund, warum das Argument empty_label dokumentiert ist, aber nicht das Zurücksetzen des Attributs. Da dies etwas hackig ist, besteht die bessere Option darin, einfach das gesamte Formularfeld zu überschreiben. siehe meine bearbeitete Antwort.
Carl Meyer
2
Das ist seltsam, aber ich habe es gerade in meiner Anwendung getestet und musste die Felder nicht zurücksetzen, um das leere Etikett zu entfernen ...
Paolo Bergantino
20

Sie können dies für Ihr Modell verwenden:

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

Standard = Keine ist die Antwort: D.

HINWEIS: Ich habe dies auf Django 1.7 versucht

Lu.nemec
quelle
2
Ausgezeichnet. Wie unter diesem Link angegeben , wird in einem Modellfeld mit blank=Falseund mit einem festgelegten defaultWert keine leere Auswahloption angezeigt.
Emyller
1
Eine schöne einfache Lösung! Alle anderen sind in Bezug auf Komplexität fast verrückt.
Robert Moskal
Fantastisch. So viel besser als die anderen ~ 9 funktionierenden Antworten auf die ~ 3 Versionen dieser Frage.
Foobarbecue
8

Für den Django 1.4 müssen Sie lediglich den "Standard" -Wert und "blank = False" im Auswahlfeld festlegen

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

Sehen Sie hier für die gesamte Debatte über und Methoden zur Lösung dieses Problems.

Thomas B. Higgins
quelle
5

Sie können dies in admin tun:

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

self.fields['xxx'].empty_value = Nonewürde nicht funktionieren Wenn Sie einen Feldtyp TypedChoiceFieldhaben, der keine empty_labelEigenschaft hat.

Was wir tun sollten, ist die erste Wahl zu entfernen:

1. Wenn Sie eine BaseFormautomatische Erkennung erstellen möchtenTypedChoiceField

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.nur ein paar Formulare können Sie verwenden:

class AddClientForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(AddClientForm, self).__init__(*args, **kwargs)
        self.fields['xxx'].choices = self.fields['xxx'].choices[1:]
Mithril
quelle
Es ist ein echtes Problem, danke für die Erklärung. Aber anscheinend ist "für" nicht obligatorisch, wir können es einfach tun, self.fields['xxx'].choices = self.fields['xxx'].choices[1:]wenn wir leere Auswahl entfernen möchten. Aber wenn Sie den Titel ersetzen möchten, müssen Sie die erste Wahl als Tupel mit dem ersten leeren Element und dem Titel im zweiten "neu erstellen", z. B.self.fields['xxx'].choices = [('', 'Nothing')] + self.fields['xxx'].choices[1:]
Ivan Borshchov
@ user3479125 Ich mache ein BaseForm, um alle Formulare anzupassen, also mit for-Schleife.
Mithril
4

Wenn Sie für ein ForeignKeyFeld den defaultWert für ''das Modell festlegen, wird die leere Option entfernt.

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

Für andere Felder wie CharFieldSie das einstellen könnten defaultzu None, aber dies nicht funktioniert für ForeignKeyFelder in Django 1.11.

YPCrumble
quelle
2

Ich habe heute damit rumgespielt und mir gerade eine feige Hack- Lösung ausgedacht:

# 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,)

Jetzt können Sie das ModelChoiceFieldleere Standardetikett nach Belieben anpassen. :-)

PS: Keine Abstimmungen erforderlich, nicht schädliche Affenpflaster sind immer griffbereit.

Emyller
quelle
2

Für die neueste Version von Django sollte die erste Antwort so sein

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)`
lwj0012
quelle
1

Ich finde LÖSUNG !!

Aber nicht für ForeignKey :-)

Vielleicht kann ich Dir helfen. Ich habe im Django-Quellcode nachgesehen und festgestellt, dass in django.forms.extras.widgets.SelecteDateWidget () eine Eigenschaft namens none_value ist, die gleich (0, '-----') ist, also habe ich dies in meinem Code getan

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),
        }
3002411
quelle
1
Behandelt die Frage nicht genau, führte mich aber zur Lösung des Problems, das ich hatte. In meinem Fall habe ich nur versucht, die Standardanzeige für einen Nullwert zu ändern, der nicht an ein Modell gebunden ist. Dies ist standardmäßig "--------", was ich ändern wollte. Wenn das der Fall ist, dann tun Sie einfach:self.fields[field_name].widget.choices[0] = ('', SOME_FIRST_CHOICE_DISPLAY_VALUE)
Troy Grosfield
1

Seit Django 1.7 können Sie die Beschriftung für den leeren Wert anpassen, indem Sie Ihrer Auswahlliste in Ihrer Modellfelddefinition einen Wert hinzufügen. Aus der Dokumentation zur Konfiguration der Feldauswahl :

Wenn im Feld nicht leer = Falsch zusammen mit einer Standardeinstellung festgelegt ist, wird eine Beschriftung mit "---------" mit dem Auswahlfeld gerendert. Um dieses Verhalten zu überschreiben, fügen Sie Auswahlmöglichkeiten mit None ein Tupel hinzu. zB (Keine, 'Ihre Zeichenfolge für die Anzeige'). Alternativ können Sie eine leere Zeichenfolge anstelle von Keine verwenden, wenn dies sinnvoll ist - beispielsweise auf einem CharField.

Ich habe die Dokumentation für verschiedene Versionen von Django überprüft und festgestellt, dass dies in Django 1.7 hinzugefügt wurde .

Rebecca Koeser
quelle
0

Hier gibt es viele gute Antworten, aber ich bin immer noch nicht ganz zufrieden mit den Implementierungen. Ich bin auch ein bisschen frustriert darüber, dass ausgewählte Widgets aus verschiedenen Quellen (Fremdschlüssel, Auswahlmöglichkeiten) zu unterschiedlichen Verhaltensweisen führen.

Ich arbeite an einem Design, bei dem ausgewählte Felder immer eine leere Option haben. Wenn sie benötigt werden, wird neben ihnen ein Stern angezeigt, und das Formular wird einfach nicht überprüft, wenn sie leer bleiben. Das heißt, ich kann das empty_label nur für Felder, die nicht TypedChoiceFields sind, richtig überschreiben .

Hier ist , was das Ergebnis sollte aussehen. Das erste Ergebnis ist immer der Name des Feldes - in meinem Fall das label.

Widget auswählen

Folgendes habe ich getan. Das Folgende ist eine überschriebene __init__Methode meines Formulars:

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]]
Jamie Counsell
quelle
0

Dies wird komplizierter, wenn die Auswahl Fremdschlüssel sind und wenn Sie die Auswahl anhand einiger Kriterien filtern möchten . In diesem Fall ist empty_labeldas leere Etikett leer, wenn Sie die Auswahl festlegen und dann die Auswahlmöglichkeiten neu zuweisen (Sie können auch hier die Filterung anwenden):

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

Grundsätzlich kann die erste Zeile unter initfür alle Felder im Formular mit einer Schleife oder Inline-Schleife angewendet werden:

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
Ibo
quelle