2012-09-28 63 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'') 

的多對多第一個是顯示和保存時只剩下選定的值。