Search code examples
pythondjangodjango-viewsmany-to-manyrelationship

How to add an object to a manytomany field in django


I have two models (Group and Contact). A Group can have many Contacts and a Contact can be in many Groups.

I can create contacts but I can´t create groups right now.

When I validate form.is_valid() it returns false and the error I'm getting is

{'contacts': [ValidationError(['Enter a list of values.'])]}

What am I missing here?

models.py

class Contact(models.Model):
    # Fields
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=32)
    contacts = models.ManyToManyField(Contact, related_name='groups')

forms.py

class CreateGroupForm(ModelForm):
    class Meta:
        model = Group
        fields = ["name", "contacts"]
        widgets = {
            'name': TextInput(attrs={
                "placeholder" : "",
                "class": "form-control"
            }),
            'contacts': Select(attrs={
                "placeholder" : "",
                "class": "form-control"
            })
        }

class CreateContactForm(ModelForm):
    class Meta:
        model = Contact
        fields = "__all__"
        widgets = {
            'first_name': TextInput(attrs={
                "placeholder" : "Nombre",
                "class": "form-control"
            }),
            'last_name': TextInput(attrs={
                "placeholder" : "Apellido",
                "class": "form-control"
            })
        }

views.py

def create_group(request):
    if request.method == "POST":
        form = CreateGroupForm(request.POST)
        if form.is_valid():
            group = form.save(commit=False)
            group.save()
        else:
            print(form.errors.as_data())
            form = CreateGroupForm()

        return render(request, 'create-group.html', {'form':form})
    elif request.method == "GET":
        form = CreateGroupForm(request.POST or None)
        return render(request, "create-group.html", { 'form' : form})

Solution

  • For a ManyToManyField, it makes not much sense to use a Select widget [Django-doc], since that selects only a single element, a SelectMultiple widget [Django-doc] is constructed to select mulitple objects.

    You should also take a look to the HTML form whether it indeed submits values for the contact field. You can do that by inspecting the browser data.

    Nevertheless, even if you get this working, by using commit=False, the form can not save the many-to-many field. Indeed, in order to save records in a many-to-many field, first the Group object should be saved, since the junction table [wiki] has ForeignKeys to the Contact and Group, and thus these records need to be created first. It is also better not to construct a new Form in case the form is not valid, since then the errors will be reported when you render that form:

    def create_group(request):
        if request.method == 'POST':
            form = CreateGroupForm(request.POST)
            if form.is_valid():
                form.save()  # ← no commit=False
                return redirect('name-of-some-view')
            # do not create a new form.
        else:
            form = CreateGroupForm()
        return render(request, "create-group.html", { 'form' : form})

    Note: In case of a successful POST request, you should make a redirect [Django-doc] to implement the Post/Redirect/Get pattern [wiki]. This avoids that you make the same POST request when the user refreshes the browser.