In a Django app, I'm having a model Bet which contains a ManyToMany relation with the User model of Django:
class Bet(models.Model):
...
participants = models.ManyToManyField(User)
User should be able to start new bets using a form. Until now, bets have exactly two participants, one of which is the user who creates the bet himself. That means in the form for the new bet you have to chose exactly one participant. The bet creator is added as participant upon saving of the form data.
I'm using a ModelForm for my NewBetForm
:
class NewBetForm(forms.ModelForm):
class Meta:
model = Bet
widgets = {
'participants': forms.Select()
}
def save(self, user):
... # save user as participant
Notice the redefined widget for the participants field which makes sure you can only choose one participant.
However, this gives me a validation error:
Enter a list of values.
I'm not really sure where this comes from. If I look at the POST data in the developer tools, it seems to be exactly the same as if I use the default widget and choose only one participant. However, it seems like the to_python()
method of the ManyToManyField has its problems with this data. At least there is no User object created if I enable the Select widget.
I know I could work around this problem by excluding the participants field from the form and define it myself but it would be a lot nicer if the ModelForm's capacities could still be used (after all, it's only a widget change). Maybe I could manipulate the passed data in some way if I knew how.
Can anyone tell me what the problem is exactly and if there is a good way to solve it?
Thanks in advance!
Edit
As suggested in the comments: the (relevant) code of the view.
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})
After digging in the Django code, I can answer my own question.
The problem is that Django's ModelForm maps ManyToManyField
s in the model to ModelMultipleChoiceField
s of the form. This kind of form field expects the widget object to return a sequence from its value_from_datadict()
method. The default widget for ModelMultipleChoiceField
(which is SelectMultiple
) overrides value_from_datadict()
to return a list from the user supplied data. But if I use the Select
widget, the default value_from_datadict()
method of the superclass is used, which simply returns a string. ModelMultipleChoiceField
doesn't like that at all, hence the validation error.
To solutions I could think of:
value_from_datadict()
of Select
either via inheritance or some class decorator.save()
method of the ModelForm
to save its data in the m2m relation.The seconds solution seems to be less verbose, so that's what I will be going with.
I don't mean to revive a resolved question but I was working a solution like this and thought I would share my code to help others.
In j0ker's answer he lists two methods to get this to work. I used method 1. In which I borrowed the 'value_from_datadict' method from the SelectMultiple widget.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With