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

Form field for a foreign key in ModelForm with too many choices for 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 select 样本基于样本的 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说的是如果选择多的话就用别的东西。

Allows the selection of a single model object, suitable for representing a foreign key. Note that the default widget for ModelChoiceField becomes impractical when the number of entries increases. You should avoid using it for more than 100 items.

我想我需要一个自定义表单域,但我不知道该怎么做。

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

此现有代码应从表单中获取一个整数并将其映射回外键,但我不知道要重写什么以填充示例实例中绑定表单的字段。

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

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

Django Admin 有类似的东西。它称为“原始 ID 字段”小部件。您可以通过在注册的 admin.ModelAdmin.

上设置 raw_id_fields = ['field_name'] 来在 admin.py 中配置它来尝试

文档: 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

您可以在管理之外使用它,但这需要向您的应用程序添加一些 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 Admin 中的模型视图也是如此。

The ModelChoiceField documentation says to use something else if the number of choices is large.

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

如果你希望绑定表单的字段是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']

select the sample based on the contents of the sample's alt field

你在这里想要的是一个独立于你从文档中引用的问题。您可以选择在更改或不更改小部件的情况下实施此操作。

如果您希望用户提供 alt 值而不是 Sample 的主键,您可以为 ModelChoiceField 使用 to_field_name 参数(注意这只是合适的在这里是因为您的 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})