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

Posted

技术标签:

【中文标题】自定义/删除 Django 选择框空白选项【英文标题】:Customize/remove Django select box blank option 【发布时间】:2010-10-18 20:43:54 【问题描述】:

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

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

我想知道的是:

    从选择框中删除此自动生成选项的最简洁方法是什么?

    自定义它以使其显示为的最简洁的方法是:

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

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

感谢您的帮助,

杰夫

编辑:我已经这样配置 ForeignKey 字段:

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

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

【问题讨论】:

原来它被认为是 Django 中的一个错误;在这种情况下应该自动删除空选项:code.djangoproject.com/ticket/10792 【参考方案1】:

尚未对此进行测试,但基于阅读 Django 的代码 here 和 here 我相信它应该可以工作:

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

编辑:这是documented,但如果您使用自动生成的 ModelForm,您不一定知道要查找 ModelChoiceField。

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

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

  class Meta:
    model = Thing

【讨论】:

感谢卡尔的帮助。您的示例几乎可以工作,但我必须在最后一步刷新小部件选择列表。看我的回答。哇;错误报告的快速周转! 侧边栏:您知道您的最后一种方法如何适合使用 Meta: 字段来选择要包含在表单中的模型字段吗? 我不确定如果您手动定义表单字段但不将其包含在模型字段的元列表中会发生什么。我确定它会出现,但它会保存在模型上吗?我的猜测是肯定的 - 试试看。 我认为使用 ModelChoiceField 定义的第二种方法更清楚。如果看别人的代码更容易发现:) 第一个解决方案对我来说效果很好,无需重新分配选择。【参考方案2】:

来自文档

不包括空白选项 如果模型字段有 blank=False 并且 显式默认值(默认 最初将选择值 而是)。

所以设置默认就可以了

【讨论】:

查看有问题的编辑。感谢您的帮助。 对 ForeignKey 字段执行此操作可能很危险。它可能的工作是一个有选择的 CharField。问题是 ForeignKey 没有完善的默认值。 是的,这正是我正在寻找的...对于非外键选择字段【参考方案3】:

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

    删除空选项(扩展 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
    

    自定义空选项标签基本相同:

    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 的所有场景,但我并不肯定。我发现当这些字段被初始化时,它们的选择被传递给 Select 小部件(参见 django.forms.fields.ChoiceField._set_choices)。初始化后设置 empty_label 不会刷新 Select 小部件的选项列表。我对 Django 不够熟悉,不知道这是否应该被视为错误。

【讨论】:

可能不是错误;我想empty_label 参数被记录是有原因的,但没有重新设置属性。由于这有点hacky,更好的选择可能是覆盖整个表单字段;查看我编辑的答案。 这很奇怪,但我只是在我的应用程序上测试了这个,我不需要重置字段来删除空标签...【参考方案4】:

你可以在你的模型上使用它:

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

default=None 是答案:D

注意:我在 Django 1.7 上试过这个

【讨论】:

优秀。如this link 所述,具有blank=False 和任何default 值集的模型字段将呈现空白选项。 一个不错的简单解决方案!就复杂性而言,所有其他人都近乎疯狂。 太棒了。比这个问题的〜3个版本的其他〜9种工作答案要好得多。【参考方案5】:

对于 django 1.4,您只需在选择字段上设置“默认”值和“空白=假”

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

【讨论】:

【参考方案6】:

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

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

【讨论】:

models.ForeignKeydjango.db.models.ForeignKey【参考方案7】:

请参阅here,了解有关此问题的完整辩论和解决方法。

【讨论】:

【参考方案8】:

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:] @user3479125 我正在制作BaseForm 来自定义所有表单,所以使用for循环。【参考方案9】:

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

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

对于CharField 等其他字段,您可以将default 设置为None,但这不适用于Django 1.11 中的ForeignKey 字段。

【讨论】:

【参考方案10】:

我今天搞砸了,只是想出了一个 coward hack 漂亮的解决方案:

# 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:不需要投票,无害的猴子补丁总是很方便。

【讨论】:

【参考方案11】:

对于最新版本的 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)`

【讨论】:

【参考方案12】:

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

除非在字段上设置了空白=假以及默认值,否则将使用选择框呈现包含“---------”的标签。要覆盖此行为,请向包含 None 的选项添加一个元组;例如(无,“您的显示字符串”)。或者,您可以在有意义的情况下使用空字符串而不是 None - 例如在 CharField 上。

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

【讨论】:

【参考方案13】:

我找到了解决方案!!

但不适用于 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),
        

【讨论】:

并没有完全解决这个问题,但让我找到了解决我遇到的问题的方法。就我而言,我只是试图更改与模型无关的空值的默认显示。这默认为我想要更改的“--------”。如果是这种情况,那么就这样做:self.fields[field_name].widget.choices[0] = ('', SOME_FIRST_CHOICE_DISPLAY_VALUE)【参考方案14】:

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

我正在使用一个设计,其中选择字段 总是 有一个空白选项,如果需要,它们旁边会有一个星号,并且表单根本不会验证是否他们是空的。也就是说,我只能正确覆盖不是 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]]

【讨论】:

【参考方案15】:

选择是外键并且您想根据某些标准过滤选择时,这会变得更加复杂。在这种情况下,如果您设置 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

【讨论】:

以上是关于自定义/删除 Django 选择框空白选项的主要内容,如果未能解决你的问题,请参考以下文章

php 从选项页面上的自定义字段中删除空白区域

Django Admin 自定义外键选择框

Django:迭代自定义选择字段中的选项

Django - 自定义 ModelMultipleChoiceField 无法根据父模型对选择进行分类

Django Admin,修改/自定义manytomany字段的选择框中的名称

Jquery自定义选择/组合框