4

在 Django 应用程序中,我有一个模型 Bet,其中包含与 Django 的 User 模型的 ManyToMany 关系:

class Bet(models.Model):
    ...
    participants = models.ManyToManyField(User)

用户应该能够使用表格开始新的投注。到目前为止,投注只有两名参与者,其中一名是自己创建投注的用户。这意味着在新投注的表格中,您必须准确选择一位参与者。保存表单数据后,投注创建者被添加为参与者。

我正在为我使用 ModelForm NewBetForm

class NewBetForm(forms.ModelForm):
    class Meta:
        model = Bet
        widgets = {
            'participants': forms.Select()
        }

    def save(self, user):
        ... # save user as participant

请注意参与者字段的重新定义小部件,它确保您只能选择一个参与者。

但是,这给了我一个验证错误:

Enter a list of values.

我不确定这是从哪里来的。如果我查看开发者工具中的 POST 数据,这似乎与我使用默认小部件并仅选择一个参与者完全一样。to_python()但是, ManyToManyField的方法似乎对这些数据有问题。如果我启用 Select 小部件,至少没有创建用户对象。

我知道我可以通过从表单中排除参与者字段并自己定义它来解决这个问题,但是如果仍然可以使用 ModelForm 的容量会更好(毕竟,它只是一个小部件更改)。如果我知道如何,也许我可以以某种方式操纵传递的数据。

谁能告诉我到底是什么问题以及是否有解决问题的好方法?

提前致谢!

编辑

正如评论中所建议的:视图的(相关)代码。

def new_bet(request):
    if request.method == 'POST':
        form = NewBetForm(request.POST)
        if form.is_valid():
            form.save(request.user)
            ... # success message and redirect
    else:
        form = NewBetForm()
    return render(request, 'bets/new.html', {'form': form})
4

6 回答 6

3

在挖掘 Django 代码之后,我可以回答我自己的问题。

问题是 Django 的 ModelForm 将ManyToManyField模型中ModelMultipleChoiceField的 s 映射到表单的 s。这种表单字段期望小部件对象从其value_from_datadict()方法返回一个序列。ModelMultipleChoiceField(即)的默认小部件SelectMultiple覆盖value_from_datadict()以从用户提供的数据中返回一个列表。但是如果我使用Select小部件,则使用超类的默认value_from_datadict()方法,它只是返回一个字符串。ModelMultipleChoiceField根本不喜欢那样,因此验证错误。

对于我能想到的解决方案:

  1. 通过继承或某些类装饰器覆盖value_from_datadict()of 。Select
  2. 通过创建一个新的表单字段并调整其save()方法ModelForm以将其数据保存在 m2m 关系中,手动处理 m2m 字段。

秒的解决方案似乎不那么冗长,所以这就是我将要使用的。

于 2012-11-11T23:15:09.577 回答
3

我并不是要恢复已解决的问题,但我正在研究这样的解决方案,并认为我会分享我的代码来帮助他人。

在 j0ker 的回答中,他列出了两种方法来使其正常工作。我使用了方法 1。我从 SelectMultiple 小部件中借用了“value_from_datadict”方法。

表格.py

from django.utils.datastructures import MultiValueDict, MergeDict

class M2MSelect(forms.Select):
    def value_from_datadict(self, data, files, name):
        if isinstance(data, (MultiValueDict, MergeDict)):
            return data.getlist(name)
        return data.get(name, None)    

class WindowsSubnetForm(forms.ModelForm):
    port_group = forms.ModelMultipleChoiceField(widget=M2MSelect, required=True, queryset=PortGroup.objects.all())
    class Meta:
        model = Subnet
于 2014-01-19T22:36:39.853 回答
1

问题是 ManyToMany 是这种关系的错误数据类型。

从某种意义上说,赌注本身就是多对多的关系。让参与者成为多对多场是没有意义的。你需要的是两个外键,都给用户:一个给创建者,一个给另一个用户(“接受者”?)

于 2012-09-28T14:48:38.697 回答
1

您可以在验证之前(期间)修改提交的值Form.clean_field_name。您可以使用此方法将选择的单个值包装在列表中。

class NewBetForm(forms.ModelForm):
    class Meta:
        model = Bet
        widgets = {
            'participants': forms.Select()
        }

    def save(self, user):
        ... # save user as participant

    def clean_participants(self):
        data = self.cleaned_data['participants']
        return [data]

我实际上只是在猜测 select 提供的值是什么样的,所以这可能需要一些调整,但我认为它会起作用。

这是文档。

于 2012-09-28T17:40:07.093 回答
0

受@Ryan Currah 的启发,我发现这是开箱即用的:

class M2MSelect(forms.SelectMultiple):
    def render(self, name, value, attrs=None, choices=()):
        rendered = super(M2MSelect, self).render(name, value=value, attrs=attrs, choices=choices)
        return rendered.replace(u'multiple="multiple"', u'')

显示多对多中的第一个,保存时只剩下选定的值。

于 2016-05-23T16:46:21.593 回答
0

在@Ryan Currah 的启发下,我找到了一种更简单的方法:您只需覆盖 SelectMultiple 类中的“allow_multiple_selected”属性

class M2MSelect(forms.SelectMultiple):
    allow_multiple_selected = False


class NewBetForm(forms.ModelForm):
    class Meta:
        model = Bet

    participants = forms.ModelMultipleChoiceField(widget=M2MSelect, required=True, queryset=User.objects.all())
于 2021-02-09T06:41:21.913 回答