Search code examples
pythondjangodjango-formsmany-to-many

Django ManyToMany not saving


I have a ModelMultipleChoiceField that I try to save save as ManyToMany relationships and tried almost every solution on the internet but for some reason I can't get mine to save. I don't get any errors and the rest of the form submits just not the ManyToMany.

    Models.py

    class Game(models.Model):
        players = models.ManyToManyField(Student, blank=True)

        other fields...

    class Student(models.Model):
        user_name = models.CharField(max_length=150, null=False, blank=False)
    Forms.py

    class Game(forms.ModelForm):

        players = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(),                                                   
                                                 queryset=Student.objects.all(), 
                                                 label="Players", required=False)
    Views.py

    def creategame(request):

        if request.method == "POST":
            form = Game(request.POST)
            if form.is_valid():
                form.save()
                return redirect(reverse('management'))

        else:
            ...

I tried multiple solutions like this, but none of them worked.

    Views.py

    def creategame(request):

        if request.method == "POST":
            form = Game(request.POST)
            if form.is_valid():
                new_game = form.save(commit=False)
                new_game.save()
                form.save_m2m()

                return redirect(reverse('management'))

        else:
            ...

Solution

  • Probably the field is not included in the Meta of the form, hence the problem. We can fix this with:

    class GameForm(forms.ModelForm):
        class Meta:
            model = Game
            fields = ['players']
            widgets = {'players': forms.CheckboxSelectMultiple()}
            labels = {'players': 'Players'}

    This should then work with the view:

    def creategame(request):
        if request.method == 'POST':
            form = GameForm(request.POST, request.FILES)
            if form.is_valid():
                form.save()
                return redirect('management')
        else:
            form = GameForm()
        return render(request, 'some-template.html', {'form': form})

    an idea might be to work with a class-based CreateView [Django-doc] however:

    from django.urls import reverse_lazy
    from django.views.generic.edit import CreateView
    
    
    class GameCreateView(CreateView):
        form_class = GameForm
        template_name = 'some-template.html'
        success_url = reverse_lazy('management')

    and register this in the urls.py as:

    path('/game/create', GameCreateView.as_view(), 'name-of-view'),

    Note: Usually a Form or a ModelForm ends with a …Form suffix, to avoid collisions with the name of the model, and to make it clear that we are working with a form. Therefore it might be better to use GameForm instead of Game.


    Note: While most forms do not process media files, it is probably better to pass request.FILES [Django-doc] to the form anyway, such that if you later add an extra media field, all views that use the form will indeed handle the files properly.