ModelForm 中外键的表单字段,ModelChoiceField 有太多选择?

问题描述

我想在 ModelForm 中使用一个简单的外键关系,但没有 ModelChoiceField。

class Sample(models.Model):
    alt = IntegerField(db_index=True,unique=True)

class Assignment(models.Model):
    sample = models.ForeignKey(Sample,on_delete=models.CASCADE)

我想让 AssignmentForm 根据样本的 alt 字段的内容选择样本。使用 ModelChoiceField 会是这样:

class SampleSelect(ModelChoiceField):
    def label_from_instance(self,obj):
        return obj.alt

class AssignmentForm(ModelForm):
    sample = SampleSelect(queryset=Sample.objects.all())
    class Meta:
        model = Assignment
        fields = ['sample']

ModelChoiceField documentation 表示如果选择的数量很大,请使用其他内容

允许选择单个模型对象,适合表示外键。请注意,当条目数量增加时,ModelChoiceField 的认小部件变得不切实际。您应该避免将它用于超过 100 个项目。

我想我需要一个自定义表单域,但我不知道如何做到这一点。

class SampleBAltField(IntegerField):
    def clean(self,value):
        try:
            return Sample.objects.get(alt=value)
        except Sample.DoesNotExist:
            raise ValidationError(f'Sample with alt {value} does not exist')

此现有代码应从表单中获取一个整数并将其映射回外键,但我无法弄清楚要覆盖哪些内容以填充来自 Sample 实例的绑定表单的字段。

ModelForm 中的 FormFields 有没有比较简单的方法可以解决这个问题,还是我需要从头开始编写 Form?

解决方法

ModelChoiceField 文档说,如果选择的数量很大,请使用其他东西。

文档建议使用不同的小部件(否则用户将不得不从包含过多项目的下拉列表中进行选择),但您不一定需要完全不同的字段。

如果你希望绑定表单的字段是 Sample 的 n 个实例,那么 ModelChoiceField 仍然是合适的。

为避免文档中预期的问题,您只需更改该字段的小部件即可。您可能需要确切地决定那是什么。一个简单的选择是使用 NumberInput 小部件,用户只需在其中输入一个整数作为外键。

from django.forms.widgets import NumberInput

class AssignmentForm(ModelForm):
    sample = ModelChoiceField(queryset=Sample.objects.all(),widget=NumberInput)
    class Meta:
        model = Assignment
        fields = ['sample']

根据样本的alt字段内容选择样本

您在这里想要的是与您从文档中引用的内容不同的问题。您可以选择在更改或不更改小部件的情况下实现此功能。

如果您希望用户提供 alt 值而不是示例的主键,您可以使用 to_field_name 参数作为 ModelChoiceField(注意这仅适用于此处,因为您的 alt 字段是唯一的)

class AssignmentForm(ModelForm):
    sample = ModelChoiceField(
        queryset=Sample.objects.all(),widget=NumberInput,help_text="Enter the alt of the sample",to_field_name='alt'
    )

    class Meta:
        model = Assignment
        fields = ["sample"]

为了在呈现绑定表单时正确呈现初始值,您可以在实例化绑定表单时提供 initial 关键字参数:

form = AssignmentForm(instance=inst,initial={'sample': inst.sample.alt})

或者,您可以覆盖表单的 __init__ 方法以在表单实例化时自动执行此操作:

class AssignmentForm(ModelForm):
    ...
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        if self.instance.pk:
            self.initial.update({'sample': self.instance.sample.alt})

,

您要寻找的是自定义小部件,而不是字段本身。

Django Admin 有类似的东西。它被称为“原始 ID 字段”小部件。您可以通过在注册的 admin.py 上设置 raw_id_fields = ['field_name']admin.ModelAdmin 中进行配置来尝试。

文档: https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.raw_id_fields

实现: https://github.com/django/django/blob/ca9872905559026af82000e46cde6f7dedc897b6/django/contrib/admin/widgets.py#L120

您可以在 admin 之外使用它,但这需要向您的应用程序添加一些 Django Admin 的 JS。

或者,如果你需要更简单的东西,你可以复制这个想法。

,

如果您没有在其他实例中使用示例模型,其中示例通过模板中的 id 或名称显示,您可以只向模型添加一个 str 方法,以允许它在自定义庄园中显示.

class Sample(models.Model):
    alt = IntegerField(db_index=True,unique=True)

    def __str__(self):
        return f"{self.alt}"

现在,每当您调用模型实例时,它都会显示在 alt 字段中。这也适用于 Django 管理中的模型视图。