web-dev-qa-db-de.com

Anpassen/Entfernen der Option "Django-Auswahlfeld leer"

Ich verwende Django 1.0.2. Ich habe eine ModelForm geschrieben, die von einem Model unterstützt wird. Dieses Modell hat einen ForeignKey, bei dem leer = 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 am oberen Rand der Liste eine Option erstellt, die keinen Wert enthält und als Reihe von Strichen angezeigt wird:

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

Was ich gerne wissen würde:

  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 als angezeigt wird:

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

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

Danke für jede Hilfe,

Jeff

Bearbeiten: Ich habe das Feld ForeignKey als solches konfiguriert: 

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

Dies setzt den Standard so, dass es nicht mehr die leere/Bindestrich-Option ist, aber leider scheint keine meiner Fragen zu klären. Das heißt, die leere Option steht weiterhin in der Liste.

70
jlpp

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

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: Dies ist dokumentiert , obwohl Sie nicht unbedingt wissen müssen, ob Sie ModelChoiceField suchen müssen, wenn Sie mit einer automatisch generierten ModelForm arbeiten.

EDIT: Wie jlpp in seiner Antwort feststellt, ist dies nicht vollständig - Sie müssen die Auswahlmöglichkeiten den Widgets zuweisen, nachdem Sie das empty_label-Attribut geändert haben. Da dies ein bisschen hässlich ist, überschreibt die andere Option, die möglicherweise leichter zu verstehen ist, das gesamte ModelChoiceField:

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

  class Meta:
    model = Thing
84
Carl Meyer

aus den docs

Die leere Auswahl wird nicht berücksichtigt wenn das Modellfeld leer ist = False und ein expliziter Standardwert (der Standardwert wird anfangs ausgewählt).

stellen Sie also den Standard ein und Sie sind in Ordnung

34
zalew

Mit Carls Antwort als Leitfaden und nachdem ich die Django-Quelle für ein paar Stunden verwurzelt hatte, denke ich, dass dies die komplette Lösung ist:

  1. So entfernen Sie die leere Option (erweitern Sie das Beispiel von 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. Um das leere Optionsetikett anzupassen, 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 dargestellt werden, aber ich bin nicht positiv. Ich habe festgestellt, dass bei der Initialisierung dieser Felder ihre Auswahlmöglichkeiten an das Auswahl-Widget übergeben werden (siehe Django.forms.fields.ChoiceField._set_choices). Durch das Festlegen der empty_label nach der Initialisierung wird die Auswahlliste des Auswahl-Widget nicht aktualisiert. Ich bin mit Django nicht vertraut genug, um zu wissen, ob dies als Fehler betrachtet werden sollte.

22
jlpp

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

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

default = Keine ist die Antwort: D

HINWEIS: Ich habe dies an Django 1.7 versucht

18
Lu.nemec

Für Django 1.4 müssen Sie lediglich den Wert "default" und "blank = False" im Auswahlfeld einstellen

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

sie können dies in admin tun:

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

Siehe here für die vollständige Debatte und Methoden zur Lösung dieses Problems.

5

self.fields['xxx'].empty_value = None würde nicht funktionieren Wenn Ihr Feldtyp TypedChoiceField ist, haben Sie keine empty_label-Eigenschaft.

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

1 Wenn Sie eine BaseForm automatische Erkennung TypedChoiceField erstellen möchten

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:]
4
Mithril

Ich habe mich heute damit herumgespielt und habe gerade ein feigling Hack clevere Lösung:

# 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 die standardmäßige ModelChoiceField leere Beschriftung beliebig anpassen. :-)

PS: Keine Notwendigkeit für Downvotes, nicht schädliche Affen-Patches sind immer praktisch.

2
emyller

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)`
2
lwj0012

Wenn Sie für ein ForeignKey-Feld den default-Wert im Modell auf '' setzen, wird die leere Option entfernt.

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

Für andere Felder wie CharField können Sie default auf None setzen. Dies funktioniert jedoch nicht für ForeignKey-Felder in Django 1.11.

2
YPCrumble

Ich finde LÖSUNG !!

Nicht aber für ForeignKey :-)

Vielleicht kann ich Ihnen helfen ... Ich habe im Django-Quellcode nachgesehen und herausgefunden, dass in Django.forms.extras.widgets.SelecteDateWidget () eine Eigenschaft mit dem Namen none_value gleich (0, '-----') ist habe 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),
        }
1
user3002411

Dies wird komplizierter, wenn die Auswahlmöglichkeiten Fremdschlüssel sind und wenn Sie die Auswahl nach bestimmten Kriterien filtern möchten . Wenn Sie in diesem Fall den empty_label setzen und dann die Optionen erneut zuweisen (Sie können auch hier eine Filterung durchführen), ist das leere Etikett leer:

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 init für alle Felder im Formular mit einer Schleife oder einer 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
0
Ibo

Hier gibt es viele großartige Antworten, aber mit den Implementierungen bin ich noch nicht ganz zufrieden. Ich bin auch etwas frustriert, dass ausgewählte Widgets aus verschiedenen Quellen (Fremdschlüssel, Auswahlmöglichkeiten) unterschiedliche Verhaltensweisen ergeben. 

Ich habe ein Design, mit dem ich arbeite, in dem die Auswahlfelder always eine leere Option haben. Wenn sie erforderlich sind, wird ein Stern daneben angezeigt, und das Formular wird einfach nicht bestätigt, wenn es leer bleibt. Das heißt, ich kann die empty_label nur für Felder richtig überschreiben, die nicht TypedChoiceFields sind.

So sieht das Ergebnis sollte aus. Das erste Ergebnis ist immer der Name des Feldes - in meinem Fall der label.

 select widget

Hier ist, was ich am Ende getan habe. 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]]
0
Jamie Counsell

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

Wenn nicht leer = False im Feld zusammen mit einem Standardwert gesetzt ist, wird eine Beschriftung, die "---------" enthält, mit der Auswahlbox gerendert. Um dieses Verhalten zu überschreiben, fügen Sie einen Tupel zu einer Auswahl hinzu, die None enthält. z.B. (Keine, "Ihr String für Anzeige"). Alternativ können Sie auch eine leere Zeichenfolge anstelle von None verwenden, wenn dies sinnvoll ist - beispielsweise in einem CharField.

Ich habe in der Dokumentation nach verschiedenen Versionen von Django gesucht und festgestellt, dass dies hinzugefügt in Django 1.7 war.

0
Rebecca Koeser