首页 > 解决方案 > 如何在 Django 的管理员中设置关系字段的初始值?

问题描述

概括

我们想在 Django 管理员的基本“添加”或“更改”页面上设置代表不同类型模型关系的选择框的初始选择。如果用户单击“保存”按钮之一,则初始选择应保存到数据库中。

我们特别想通过initial在表单字段(for ModelAdminor TabularInline)上设置值来做到这一点。

问题是:

对于不同类型的关系表单字段Field.initial分配给的正确“值”是什么?

背景

假设我们有一个简单Model的具有一个或多个 关系字段,即OneToOneField,ForeignKeyManyToManyField。默认情况下,后者有一个隐式 through模型,但它也可以使用显式 through模型,如docs中所述。显式through模型是具有至少两个ForeignKey字段的模型。

由选择框表示的关系字段自动出现在标准的“添加”或“更改”表单上,ModelAdmin除了显式模型。那个需要内联,例如如文档中所述。ManyToManyField throughTabularInline

请注意,隐式 ManyToManyFieldModelMultipleChoiceField(admin) 表单上的 a 相关联,而其他关系字段与 a 相关联ModelChoiceField。后者包括ManyToManyFieldwith显式 模型,through因为它实际上由ForeignKey.TabularInline

目标

现在考虑这个模型的基本 Django ModelAdmin“添加”(或“更改”)页面,包括任何显式 through模型的内联。下面描述了一个基于 Django 的 Pizza-Topping 的示例。

我们要实现两个目标:

  1. 为一个或多个关系字段设置初始选择
  2. 如果我们单击“保存”按钮之一,则必须将初始选择保存到数据库中(假设我们没有手动更改选择)

请注意,第二个似乎微不足道,但显然不是(见下文)。

django 比萨管理示例

方法

据我所知,有几种方法可以实现这一点:

无论哪种方法对于某个用例来说是“最好的”,这个问题都是关于最后一种方法:设置Field.initial.

我们通过扩展ModelAdmin.formfield_for_dbfield(或TabularInline.formfield_for_dbfield)来做到这一点,如下所示:

def formfield_for_dbfield(self, db_field, request, **kwargs):
    if db_field.name == 'some_relationship_field_name':
        kwargs['initial'] = value
    return super().formfield_for_dbfield(db_field, request, **kwargs)

value例中的 可以是obj.id, obj, [obj.id, ...], 或[obj, ...], 其中obj是一个Model实例。

问题

根据关系字段的类型,不同类型的value可能会或可能不会起作用。

对于ManyToManyFieldwith 隐式through模型,我们只能分配一个对象列表,这样很容易。

对于其他关系,在某些情况下,初始选择框选择与分配的值匹配,但不会保存(例如,因为Field.has_changed返回False)。在其他情况下,初始选择框值被保存,但与分配的值不匹配。

问题

所以,显而易见的问题是:

对于管理表单上的不同类型的关系字段,value分配给的正确方法是什么?Field.initial

有关的

尽管花了很多时间搜索,但我在文档、SO 和其他任何地方都找不到明确的答案。还试图通过源代码来解决这个问题,但事实证明这非常棘手。

一些类似的问题:

标签: pythondjangoforeign-keysdjango-adminrelationship

解决方案


下表显示了使用 Django 2.1 的专用最小示例的一些实验结果。根据表格,看来:

  • objobj.id只为OneToOneField和工作ForeignKey
  • [obj.id]适用于由 a 代表的所有关系ModelChoiceField
  • [obj]仅适用于ManyToManyField由 a 表示的隐式ModelMultipleChoiceField

但是,我不能说每种情况下的预期方法是什么。例如,为ForeignKey字段分配列表没有多大意义。然而,有趣的是,即使ManyToManyFieldwith 显式through模型具有与 a 的内联ModelChoiceFielda ForeignKey,我们也不能只分配objor obj.id:它必须在列表中。

任何评论都非常感谢这里。

表 1 的关键字:

  • 1:选择框初始选择对应的值为Field.initial
  • 2:选择框初始选择被保存,点击“保存”后(注意:如果1失败,则选择框初始选择是第一个可用选项)
  • T:(TypeError对象不可迭代)
  • A : AttributeError('int' 对象没有属性 'pk')

请注意,我们想要的行为1 & 2

Field.initial表一:不同关系字段赋值结果

|                                                       |  value assigned to Field.initial  |
|-----------------|----------|--------------------------|-------|--------|-------|----------|
| model field     | through  | associated form field    | obj   | obj.id | [obj] | [obj.id] |
|-----------------|----------|--------------------------|-------|--------|-------|----------|
| OneToOneField   | n/a      | ModelChoiceField         | 1 & 2 | 1 & 2  | 2     | 1 & 2    |
| ForeignKey      | n/a      | ModelChoiceField         | 1 & 2 | 1 & 2  | 2     | 1 & 2    |
| ManyToManyField | explicit | ModelChoiceField         | 1     | 1      | 2     | 1 & 2    |
| ManyToManyField | implicit | ModelMultipleChoiceField | T     | T      | 1 & 2 | A        |

推荐阅读