2012-09-28 32 views
2

在Django应用程序,我在其中包含的Django用户模型中的多对多关系的模型投注:Django的ModelForms:显示多对多字段单选

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数据,则看起来与使用默认小部件并仅选择一个参与者完全相同。但是,似乎ManyToManyField的to_python()方法在这些数据中存在问题。如果启用Select小部件,至少不会创建User对象。

我知道我可以通过从表单中排除参与者字段并自己定义它来解决此问题,但如果仍然可以使用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}) 

回答

2

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

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

为了我能想到的解决方案:

  1. 重写或者通过继承或一些装饰类的Selectvalue_from_datadict()
  2. 通过创建一个新表单域并调整ModelFormsave()方法手动处理m2m域,以将其数据保存在m2m关系中。

秒解决方案似乎不太详细,所以这就是我将与。

1

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

从某种意义上说,赌注本身是多对多的关系。让参与者成为一个多元领域是没有意义的。您需要的是两个ForeignKeys,两个给用户:一个用于创建者,一个用于其他用户('acceptor'?)

+0

M2M关系背后的想法是,将有可能将功能扩展到以后两人以上的参与者。所以我认为,m2m是适当的。 – j0ker

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] 

实际上,我只是猜测什么价值通过选择貌似proivded,所以这可能需要一些调整,但我认为它会奏效。

Here are the docs.

+0

我仍然尝试过。它不起作用,因为'clean_participants()'永远不会被调用。该表单似乎无法创建Python用户对象,因此它之前已经失败。如果我尝试重写'clean()'方法,'self.cleaned_data ['participants']'没有设置。 – j0ker

+0

'ModelForm.save'不带任何参数。我复制了你的保存方法,但除非你做的是非标准的东西,这个签名应该是'save(self)'。 – dokkaebi

+0

'save(self,user)'不是重写,而是调用'super(...)。save()'的新方法,并且将传递的用户保存为参与者(加上更多内容)。 – j0ker

1

我不是故意重振一个已解决的问题,但我正在研究这样的解决方案,并认为我会分享我的代码来帮助其他人。

在j0ker的回答中,他列出了两种方法来使其工作。我使用了方法1.其中我从SelectMultiple小部件中借用了'value_from_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 
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'') 

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