自定义/删除 Django 选择框空白选项

我使用的是 Django 1.0.2。我已经编写了一个由 Model 支持的 ModelForm。这个模型有一个 ForeignKey,其中 black = False。当 Django 为该表单生成 HTML 时,它会为 ForeignKey 引用的表中的每一行创建一个带有一个选项的选择框。它还在列表的顶部创建一个没有值的选项,并显示为一系列破折号:

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

我想知道的是:

  1. 从选择框中删除这个自动生成选项的最干净的方法是什么?
  2. 什么是最干净的方式来定制它,使它显示为:

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

In searching for a solution I came across Django ticket 4653 which gave me the impression that others had the same question and that the default behavior of Django may have been modified. This ticket is over a year old so I was hoping there might be a cleaner way to accomplish these things.

Thanks for any help,

Jeff

Edit: I've configured the ForeignKey field as such:

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

这确实设置了默认值,所以它不再是空/破折号选项,但不幸的是,它似乎并没有解决我的任何一个问题。也就是说,空/破折号选项仍然出现在列表中。

62180 次浏览

from the docs

The blank choice will not be included if the model field has blank=False and an explicit default value (the default value will be initially selected instead).

so set the default and you're ok

Haven't tested this, but based on reading Django's code here and here I believe it should work:

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

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

EDIT: This is documented, though you wouldn't necessarily know to look for ModelChoiceField if you're working with an auto-generated ModelForm.

EDIT: As jlpp notes in his answer, this isn't complete - you have to re-assign the choices to the widgets after changing the empty_label attribute. Since that's a bit hacky, the other option that might be easier to understand is just overriding the entire ModelChoiceField:

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


class Meta:
model = Thing

With Carl's answer as a guide and after rooting around the Django source for a couple hours I think this is the complete solution:

  1. To remove the empty option (extending Carl's example):

    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. To customize the empty option label is essentially the same:

    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
    

I think this approach applies to all scenarios where ModelChoiceFields are rendered as HTML but I'm not positive. I found that when these fields are initialized, their choices are passed to the Select widget (see django.forms.fields.ChoiceField._set_choices). Setting the empty_label after initialization does not refresh the Select widget's list of choices. I'm not familiar enough with Django to know if this should be considered a bug.

See here for the complete debate on and methods for resolution of this issue.

As for the django 1.4 all you need is to set the "default" value and "blank=False" on the choices field

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

you can do this in admin:

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

I was messing around with this today and just came up with a coward hack nifty solution:

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

Now you can tweak the default ModelChoiceField empty label to anything you'd like. :-)

PS: No need for downvotes, non-harmful monkey patches are always handy.

I find SOLUTION!!

But not for ForeignKey :-)

Maybe I can help you. I looked in Django source code and discovered that in django.forms.extras.widgets.SelecteDateWidget() is a property called none_value that equals (0, '-----') so I did in my code this

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

You can use this on your model:

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

default=None is the answer :D

NOTE: I tried this on Django 1.7

For the latest version of django the first answer should be like this

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

self.fields['xxx'].empty_value = None would not work If you field type is TypedChoiceField which do not have empty_label property.

What we should do is to remove first choice:

1 . If you want to build a 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.only a few form, you can use:

class AddClientForm(forms.ModelForm):


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

For a ForeignKey field, setting the default value to '' on the model will remove the blank option.

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

For other fields like CharField you could set the default to None, but this does not work for ForeignKey fields in Django 1.11.

There are lots of great answers here, but I'm still not entirely satisfied with the implementations. I'm also a bit frustrated that select widgets from different sources (foreign keys, choices) yield different behaviours.

I have a design I'm working with where select fields always have a blank option, and if they're required they will have a star next to them and the form will simply not validate if they're left empty. That said, I can only properly override the empty_label for fields that are not TypedChoiceFields.

Here's what the result should look like. The first result is always the name of the field - in my case, the label.

select widget

Here's what I ended up doing. The following is an overridden __init__ method of my form:

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]]

Since Django 1.7, you can customize the label for the blank value by adding a value to your choices list in your model field definition. From the documentation on configuring field choices:

Unless blank=False is set on the field along with a default then a label containing "---------" will be rendered with the select box. To override this behavior, add a tuple to choices containing None; e.g. (None, 'Your String For Display'). Alternatively, you can use an empty string instead of None where this makes sense - such as on a CharField.

I checked the documentation for different versions of Django and found that this was added in Django 1.7.

This becomes more complicated when the choices are foreign keys and if you want to filter the choices based on some criteria. In such case if you set the empty_label and then re-assign the choices (you can apply filtration here too) the empty label will be blank:

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

bbasically, the first line under init can be applied for all of the fields in the form with a loop or inline loop:

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