Search code examples
djangodjango-viewsdjango-forms

Unable to process the input data from ModelMultipleChoiceField in Django


I've been trying to process the data captured from a simple Django Model Form, and for some reason, it isn't possible to access the group of items selected under ModelMultipleChoiceField. I'm only able to access only one item from the selected items in the form.

The Model:

class Student(models.Model):
    name = models.CharField(max_length=80)
    subjects = models.ManyToManyField(Subject)
    house = models.ForeignKey(House, on_delete=models.CASCADE, related_name="students_of_house")

The Model form:

class AddStudentForm(forms.Form):
    name = forms.CharField(label="Full Name")
    house = ModelChoiceField(queryset=House.objects.all())
    subjects = ModelMultipleChoiceField(queryset=Subject.objects.all())

The view function to process POST and create a Student Object.:

def add_student(request):
    if request.method == "POST":
        name = request.POST["name"]
        house = House.objects.get(id=int(request.POST["house"]))
        subject_ids = request.POST["subjects"]

        new_student = Student(name=name, house=house)
        new_student.save()

        for sub_id in subject_ids:
            new_student.subjects.add(Subject.objects.get(id=int(sub_id))

        new_student.save()
        return HttpResponseRedirect(reverse("administration:students"))

    context = {
        "add_student_form": AddStudentForm(),
    }
    return render(request, "administration/add_student.html", context)

Now, I later tried using form = AddStudentForm(request.POST) and if form.is_valid(): route, which made it work successfully. But I'm having a hard time figuring out the reason why the above method isn't working.

The code which worked:

def add_student(request):
    if request.method == "POST":
        form = AddStudentForm(request.POST)

        if form.is_valid():

            name = form.cleaned_data["name"]
            house = form.cleaned_data["house"]
            subject_ids = form.cleaned_data["subjects"]

            new_student = Student(name=name, house=house)
            new_student.save()

            for sub_id in subject_ids:
                new_student.subjects.add(Subject.objects.get(id=sub_id.id))

            new_student.save()
            return HttpResponseRedirect(reverse("administration:students"))

    context = {
        "add_student_form": AddStudentForm(),
    }
    return render(request, "administration/add_student.html", context)

Why does this work and the view before does not? Any help is much appreciated.

Also tried directly adding subjects instead of using objects.get(), which also didn't work.


Solution

  • In an HttpRequest object, the GET and POST attributes are instances of QueryDict [django-docs] which is a dictionary-like class customized to deal with multiple values for the same key. This is necessary because some HTML form elements, notably <select multiple>, pass multiple values for the same key much like what you are currently facing.

    Django Form handles getting all the values for you by giving you an Iterable containing all the values in this field. But if you will get it yourself, you have to do this:

    subject_ids = request.POST.getlist("subjects", default=[]) #[1]

    [1] You are guaranteed to get a list unless the default value provided isn’t a list.