【问题标题】:Form field for a foreign key in ModelForm with too many choices for ModelChoiceField?ModelForm 中外键的表单字段,ModelChoiceField 的选择太多?
【发布时间】:2021-07-16 22:32:13
【问题描述】:

我想在 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?

【问题讨论】:

  • 看到这个grid of packages,选择适合你的。 django-autocomplete-light 是一个非常受欢迎的选项。
  • 我不完全确定这个问题在这里是否非常清楚。您希望用户选择或呈现什么?您是否只想让用户输入他们知道的整数而不是从下拉列表中选择?
  • @sytech 是的,我希望用户输入一个整数,由于不值得描述的原因,他们会很熟悉。

标签: python django django-forms


【解决方案1】:

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 值而不是 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})

【讨论】:

    【解决方案2】:

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

    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

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

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

    【讨论】:

      【解决方案3】:

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

      class Sample(models.Model):
          alt = IntegerField(db_index=True, unique=True)
      
          def __str__(self):
              return f"{self.alt}"
      

      现在,无论何时调用模型实例,它都会由 alt 字段显示。 Django Admin 中的模型视图也是如此。

      【讨论】:

        猜你喜欢
        • 2011-07-13
        • 2017-01-28
        • 2015-09-24
        • 1970-01-01
        • 2012-08-12
        • 1970-01-01
        • 2015-11-12
        • 1970-01-01
        • 2019-11-10
        相关资源
        最近更新 更多