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


77

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

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

我想知道的是:

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

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

在寻找解决方案时,我遇到了Django票证4653,给我的印象是其他人也有同样的问题,并且Django的默认行为可能已被修改。这张票已经使用了一年多,所以我希望可以有一种更清洁的方式来完成这些事情。

谢谢你的帮助,

杰夫

编辑:我已经配置了ForeignKey字段为:

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

这确实设置了默认值,因此不再是空/破折号选项,但是不幸的是它似乎无法解决我的任何一个问题。即,空/破折号选项仍出现在列表中。


原来这被认为是Django中的错误;在这种情况下,应自动删除空选项:code.djangoproject.com/ticket/10792
Carl Meyer

Answers:


90

还没有测试过,但是基于在这里这里阅读Django的代码,我相信它应该可以工作:

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

编辑:这已记录在案,尽管如果您正在使用自动生成的ModelForm,则不一定知道要查找ModelChoiceField。

编辑:正如jlpp在他的回答中指出的那样,这还不完整-更改empty_label属性后,您必须将选项重新分配给小部件。由于有点黑,所以可能更容易理解的另一个选项是覆盖整个ModelChoiceField:

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

  class Meta:
    model = Thing

感谢卡尔的帮助。您的示例几乎可以正常工作,但最后一步,我必须刷新小部件选择列表。看我的答案。哇!错误报告的快速周转!
jlpp

补充工具栏:您知道使用Meta:字段选择要包括在表单中的模型字段时的最后一种方法吗?
jlpp

我不确定如果手动定义表单字段但不将其包括在模型字段的“元”列表中会发生什么。我确定它会出现,但是会保存在模型中吗?我的猜测是肯定的-试试看吧。
卡尔·梅耶

我认为使用ModelChoiceField定义的第二种方法更加清晰。如果查看其他人的代码,更容易发现:)
radtek

第一个解决方案对我来说很好,而无需重新分配选择。
ZAD-Man 2014年

38

从文档

如果模型字段具有blank = False和一个明确的默认值(默认情况下将默认选择默认值),则不包括空白选项。

所以设置默认值就可以了


请参阅有问题的编辑。谢谢你的帮助。
jlpp

3
对ForeignKey字段执行此操作可能很危险。它可能是带有选择项的CharField。问题是ForeignKey没有完善的默认值。
amjoconn 2012年

是的,这正是我正在寻找的...对于非ForeignKey选择字段
byoungb

23

以卡尔的回答为指导,并在围绕Django源代码扎根几个小时之后,我认为这是完整的解决方案:

  1. 要删除空选项(扩展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. 定制空选项标签本质上是相同的:

    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
    

我认为这种方法适用于将ModelChoiceFields呈现为HTML的所有情况,但我并不肯定。我发现初始化这些字段时,它们的选择将传递到“选择”小部件(请参阅django.forms.fields.ChoiceField._set_choices)。初始化后设置empty_label不会刷新“选择”小部件的选项列表。我对Django不够熟悉,无法知道是否应该将其视为错误。


可能不是错误;我想这是有原因的,因为记录了empty_label参数,但没有重新设置属性。既然这很容易,那么更好的选择可能是覆盖整个表单字段。看到我编辑的答案。
卡尔·梅耶

2
这很奇怪,但是我只是在我的应用程序上对此进行了测试,因此无需重置字段即可删除空标签……
Paolo Bergantino,2009年

20

您可以在模型上使用它:

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

默认=无答案:D

注意:我在Django 1.7上尝试过


2
优秀。就像在此链接中所述,具有blank=False和任何default值集的模型字段都不会呈现空白选择选项。
emyller 2014年

1
一个不错的简单解决方案!所有其他人在复杂性方面濒临疯狂。
罗伯特·莫斯卡尔

太棒了 这个问题的〜3版本比其他〜9种工作答案好得多。
foob​​arbecue's

8

至于django 1.4,您所要做的就是在选择字段中设置“默认”值和“空白=假”

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


5

您可以在管理员中执行以下操作:

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

4

self.fields['xxx'].empty_value = None如果您的字段类型是TypedChoiceField没有empty_label属性,将无法正常工作。

我们应该做的是删除首选:

1。如果要构建BaseForm自动检测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.只有几种形式,可以使用:

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”不是强制性的,self.fields['xxx'].choices = self.fields['xxx'].choices[1:]如果我们要删除空的选择,我们可以简单地做。但是,如果要替换标题,则需要将“第一个选择”作为元组“重新创建”为第一个空元素,第二个为标题,例如self.fields['xxx'].choices = [('', 'Nothing')] + self.fields['xxx'].choices[1:]
Ivan Borshchov

@ user3479125我正在BaseForm自定义所有表单,因此使用for循环。
秘银2015年

4

对于ForeignKey字段,在模型上将default值设置为''将会删除空白选项。

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

对于其他类似的字段,CharField您可以将设置defaultNone,但这不适用于ForeignKeyDjango 1.11中的字段。


2

我今天在搞怪,只是想出了一个胆小鬼的漂亮解决方案:

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

现在,您可以将默认的ModelChoiceField空标签调整为所需的任何内容。:-)

PS:不需要选票,无害的猴子补丁总是很方便的。


2

对于最新版本的django,第一个答案应该是这样的

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

1

我找到解决方案!!

但不适用于ForeignKey :-)

我也许可以帮你。我查看了Django源代码,发现django.forms.extras.widgets.SelecteDateWidget()是一个名为none_value的属性,该属性等于(0,'-----'),所以我在代码中做了这个

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
不能完全解决问题,但是导致我找到了所遇到问题的解决方案。就我而言,我只是试图将默认显示更改为与模型无关的空值。默认为我要更改的“ --------”。如果是这样,那就去做:self.fields[field_name].widget.choices[0] = ('', SOME_FIRST_CHOICE_DISPLAY_VALUE)
Troy Grosfield 2014年

1

从Django 1.7开始,您可以通过在模型字段定义的选择列表中添加值来为空白值自定义标签。从有关配置字段选择的文档中:

除非在字段上设置了blank = False以及默认值,否则带有“ ---------”的标签将与选择框一起呈现。要覆盖此行为,请将元组添加到包含None的选项中;例如(无,“您的显示字符串”)。或者,可以在有意义的地方使用空字符串代替None(例如在CharField上)。

我查看了不同版本的Django的文档,发现在Django 1.7中添加了该文档。


0

这里有很多很好的答案,但是我仍然对实现不完全满意。我还感到沮丧的是,从不同的来源(外键,选择)中选择小部件会产生不同的行为。

我有一个正在使用的设计,其中选择字段始终有一个空白选项,如果需要它们,它们旁边将有一个星号,并且表单将不验证是否保留为空。也就是说,我只能适当地覆盖非TypedChoiceFields的字段的empty_label 。

这里的结果是什么应该的样子。第一个结果始终是字段的名称-在我的例子中是label

选择小部件

这就是我最终要做的。以下是__init__我的表格中一个覆盖的方法:

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

这将成为在更复杂的选择是外键,如果你想筛选基于一些标准的选择。在这种情况下,如果您设置empty_label,然后重新分配选择(您也可以在此处应用过滤),则空标签将为空白:

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

基本上,下面的第一行init可以通过循环或内联循环应用于表单中的所有字段:

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.