具有反向多对多字段的 ModelForm

Posted

技术标签:

【中文标题】具有反向多对多字段的 ModelForm【英文标题】:ModelForm with a reverse ManytoMany field 【发布时间】:2011-10-24 11:48:11 【问题描述】:

我无法让 ModelMultipleChoiceField 显示模型实例的初始值。我找不到任何关于该领域的文档,而且我一直在阅读的示例太混乱了。 Django: ModelMultipleChoiceField doesn't select initial choices 似乎很相似,但那里给出的解决方案对模型实例来说不是动态的。

这是我的情况(每个数据库用户都连接到一个或多个项目):

models.py

from django.contrib.auth.models import User
class Project(Model):
    users = ManyToManyField(User, related_name='projects', blank=True)

forms.py

from django.contrib.admin.widgets import FilteredSelectMultiple
class AssignProjectForm(ModelForm):
    class Meta:
        model = User
        fields = ('projects',)

    projects = ModelMultipleChoiceField(
        queryset=Project.objects.all(),
        required=False,
        widget=FilteredSelectMultiple('projects', False),
    )

views.py

def assign(request):
    if request.method == 'POST':
        form = AssignProjectForm(request.POST, instance=request.user)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/index/')
    else:
        form = AssignProjectForm(instance=request.user)

    return render_to_response('assign.html', 'form': form)

它返回的表单没有选择实例的链接项目(它看起来像:Django multi-select widget?)。此外,它不会使用保存表单时所做的任何选择来更新用户。

编辑:设法使用此处的方法解决此问题:http://code-blasphemies.blogspot.com/2009/04/dynamically-created-modelmultiplechoice.html

【问题讨论】:

【参考方案1】:

这是一个比旧的更好的解决方案,它真的不起作用。

您必须在创建表单时从数据库中加载现有的相关值,并在保存表单时将它们保存回来。我在相关名称(经理)上使用set() 方法,它为您完成所有工作:删除不再选择的现有关系,并添加已被选中的新关系。所以你不必做任何循环或检查。

class AssignProjectForm(ModelForm):

    def __init__(self, *args, **kwargs):
        super(AssignProjectForm, self).__init__(*args, **kwargs)

        # Here we fetch your currently related projects into the field,     
        # so that they will display in the form.
        self.fields['projects'].initial = self.instance.projects.all(
            ).values_list('id', flat=True)

    def save(self, *args, **kwargs):
        instance = super(AssignProjectForm, self).save(*args, **kwargs)

        # Here we save the modified project selection back into the database
        instance.projects.set(self.cleaned_data['projects'])

        return instance

除了简单之外,如果您在 m2m 关系上使用 Django 信号(例如 post_save 等),使用 set() 方法还有另一个优势:如果您一次添加和删除一个条目循环,您将获得每个对象的信号。但是,如果您使用set() 在一次操作中执行此操作,您将只获得一个带有对象列表的信号。如果您的信号处理程序中的代码完成了大量工作,那么这很重要。

【讨论】:

这在您更新实例时非常有效,但如果您正在创建实例呢?我收到错误needs to have a value for field "id" before this many-to-many relationship can be used. 它应该可以工作,但你必须先保存实例,然后再调用反向关系上的 set()。【参考方案2】:

ModelForm 不会自动用于反向关系。

save() 上什么都没有发生,因为 ModelForm 只知道如何处理自己的字段 - projects 不是 User 模型上的字段,它只是表单上的一个字段。

你必须告诉你的表单如何使用你的这个新字段来保存自己。

def save(self, *args, **kwargs):
    for project in self.cleaned_data.get('projects'):
        project.users.add(self.instance)
    return super(AssignProjectForm, self).save(*args, **kwargs)

【讨论】:

这就是为什么它不能用反向关系中的值正确初始化的原因吗?有没有办法让它正确初始化? 另外,有没有办法设置 self.instance.users.projects = self.cleaned_data.get('projects') ?这种方式只会添加对象,如果对象被删除则不会删除。 我猜目前的解决方案可以添加,但不能删除条目。这是支持删除条目的版本:***.com/a/21480139/633961

以上是关于具有反向多对多字段的 ModelForm的主要内容,如果未能解决你的问题,请参考以下文章

Django ORM 查询更新反向多对多字段

在 Django 中为多对多字段冲突反向访问器和查询?

Django ModelForm - 多对多嵌套选择

使 ModelForm 与 Django 中的中间模型的多对多关系工作的步骤是啥?

如何保存具有直通关系的多对多字段

具有相同 ID 键字段的多对多