Django ModelForms:将多个字段显示为单选
在Django应用程序中,我有一个模型赌注,其中包含与Django用户模型的许多关系:Django ModelForms:将多个字段显示为单选,django,django-forms,Django,Django Forms,在Django应用程序中,我有一个模型赌注,其中包含与Django用户模型的许多关系: class Bet(models.Model): ... participants = models.ManyToManyField(User) 用户应该能够使用表单开始新的赌注。到目前为止,赌注只有两个参与者,其中一个是自己创建赌注的用户。这意味着在新赌注的形式中,您必须选择一名参与者。保存表单数据时,下注创建者将添加为参与者 我正在为我的NewBetForm使用模型表单: class N
class Bet(models.Model):
...
participants = models.ManyToManyField(User)
用户应该能够使用表单开始新的赌注。到目前为止,赌注只有两个参与者,其中一个是自己创建赌注的用户。这意味着在新赌注的形式中,您必须选择一名参与者。保存表单数据时,下注创建者将添加为参与者
我正在为我的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数据,它似乎与我使用默认小部件并只选择一个参与者完全相同。然而,ManyToManyField的to_python()
方法似乎对这些数据有问题。如果启用Select小部件,至少不会创建任何用户对象
我知道我可以通过将参与者字段从表单中排除并自己定义来解决这个问题,但是如果模型表单的容量仍然可以使用的话(毕竟,这只是一个小部件的更改),那就更好了。如果我知道如何操作,也许我可以以某种方式操作传递的数据
有谁能告诉我问题到底是什么,有没有好办法解决
提前谢谢
编辑
正如评论中所建议的:视图的(相关)代码
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})
问题在于,对于这种关系,ManyToMany是错误的数据类型
从某种意义上说,赌注本身就是多对多的关系。将参与者作为一个多领域是没有意义的。您需要的是两个外键,都是给用户的:一个给创建者,一个给另一个用户(“接受者”)您可以在
表单的验证之前(在验证期间)修改提交的值。clean\u field\u name
。可以使用此方法将select的单个值包装到列表中
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提供的值是什么样子,所以这可能需要一些调整,但我认为它会起作用
在深入挖掘Django代码之后,我可以回答我自己的问题 问题是Django的模型表单将模型中的
ManyToManyField
s映射到表单的modelmultipechoicefield
s。这种表单字段要求widget对象从其值\u from\u datadict()
方法返回一个序列。modelmultipleechoicefield
(即SelectMultiple
)的默认窗口小部件覆盖了value\u from\u datadict()
,以从用户提供的数据返回列表。但是如果我使用Select
小部件,将使用超类的默认value\u from\u datadict()
方法,它只返回一个字符串modelmultipechoicefield
根本不喜欢这样,因此出现了验证错误
对于我能想到的解决方案:
选择的的\u datadict()
中的值
ModelForm
的save()
方法手动处理m2m字段,以将其数据保存在m2m关系中秒解决方案似乎没有那么冗长,所以这就是我要做的。我并不想重提一个已解决的问题,但我正在研究这样一个解决方案,并认为我会分享我的代码来帮助其他人 在j0ker的回答中,他列出了两种方法来实现这一点。我使用方法1。在其中,我从SelectMultiple小部件借用了“value\u from\u datadict”方法 forms.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
受@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'')
将显示多对多中的第一个,保存时只剩下选定的值。我从@Ryan Currah的灵感中找到了一种更简单的方法: 您只需覆盖SelectMultiple类中的“allow\u multiple\u selected”属性
class M2M选择(表单。选择多个):
允许\u多个\u选择=False
类NewBetForm(forms.ModelForm):
类元:
模型=赌注
参与者=forms.modelmultipechoicefield(widget=M2MSelect,required=True,queryset=User.objects.all())
m2m关系背后的理念是,可以在以后将功能扩展到两个以上参与者的下注。所以我认为m2m是合适的。我仍然尝试过。它不起作用,因为从未调用clean\u participants()
。表单似乎无法创建Python用户对象,因此它以前已经失败了。如果我尝试重写clean()
方法,则不会设置self.cleaned_data['participants']
。ModelForm.save
不接受任何参数。我复制了你的save方法,但是除非你做了一些非标准的事情,否则签名应该是save(self)
save(self,user)
不是重写,而是一个调用super(…).save()
的新方法,然后将传递的用户保存为参与者(以及其他内容)。我添加了查看代码。至于堆栈跟踪,我不知道如何获得它,因为Django正在内部处理表单验证错误。MergeDict
在某个时候从Django中删除。为什么您同时检查了MultiValueDict
和MergeDict
,而不是简单地检查MultiValueDict
?