Django M2M Form

116 Views Asked by At

i try to do checkboxes in form for M2M field, but have this error, have no idea how to fix it. Google didn't help me, i tried.

When i render objects as list, i can select few objects and save it, so problem is not in views.py ,but it doesn't work with checkboxes.

My code:

forms.py

class CheckoutForm(forms.ModelForm):
    class Meta:
        model = Checkout
        fields = ('dishes', 'user')

    def __init__(self, *args, **kwargs):

        super(CheckoutForm, self).__init__(*args, **kwargs)

        self.fields["dishes"].widget = CheckboxSelectMultiple()
        self.fields["dishes"].queryset = Dish.objects.all()

so only way that i can see an form error:

render form fields - send a empty form - put back {{ form.as_p }} - i can see an error "field is required"

page.html

<form class="p-2" action="{% url 'make_order' %}" method="POST" enctype="multipart/form-data">
        {% csrf_token %}
        <select name="user">
            <option value="{{ user.id }}">{{ user }}</option>
        </select>
                <div
          class="row row-cols-1 row-cols-md-5 g-2"
          style="margin-left: -40px"
        >
          {% for dish in dishes %}
          <div class="col">
            <div class="card" style="width: 14rem">
              <img
                class="card-img-top"
                style="width: 220px; height: 240px"
                src="{{ dish.image.url }}"
                alt="Card image cap"
              />
              <div class="card-body">
                <h5 class="card-title">{{ dish.name }}</h5>
                <p class="card-text">Description: {{ dish.description }}</p>
                <p class="card-text">Ingredients: {{ dish.ingredients }} g</p>
                <p class="card-text">Serving size: {{ dish.serving_size }} g</p>
                <p class="card-text">Price: {{ dish.price }} UAH</p>
                <input
                  type="checkbox"
                  class="btn btn-primary"
                  name="dishes"
                  id="{{ dish.id }}"
                  value="{{ dish.id }}"
                />Add to cart
              </div>
            </div>
          </div>
          {% endfor %}
          <input type="submit" value="ADD" class="btn btn-primary" />

      </form>

views.py

class OrderView(LoginRequiredMixin, CreateView):
    model = Checkout
    template_name = "food_order/make_order.html"
    form_class = CheckoutForm
    success_url = "/order/"

    login_url = "/login/"
    raise_exception = True

    def form_valid(self, form):
        print('i am here')
        instance = form.save(commit=False)
        instance.user = self.request.user
        instance.save()
        dishes = form.cleaned_data["dishes"]
        for dish in dishes:
            dish, created = Dish.objects.get_or_create(name = dish)
            dish.save()
            instance.dishes.add(dish)
            instance.save()
            print(instance.dishes)
        print(instance)
        form.save_m2m()
        return super(OrderView, self).form_valid(form)

    def get_context_data(self, **kwargs):
        context = super(OrderView, self).get_context_data(**kwargs)
        context["dishes"] = Dish.objects.all()
        context["orders"] = Checkout.objects.all()
        return context
1

There are 1 best solutions below

9
SamSparx On

I don't think you need to be tinkering with __init__ here when setting the widget. It may be overwriting your values for a modelform.

Try:

class CheckoutForm(forms.ModelForm):
    class Meta:
        model = Checkout
        fields = ('dishes', 'user',)
        widgets = {'dishes': forms.CheckboxSelectMultiple() }

This should work as m2m fields have the ModelMultipleChoiceField by default. If you want to make it all explicit, you can go with:

class CheckoutForm(forms.ModelForm):

    dishes = forms.ModelMultipleChoiceField(
        queryset=Dish.objects.all(),
        widget=forms.CheckboxSelectMultiple,
    )

    class Meta:
        model = Checkout
        fields = ('dishes', 'user',)

If you are formatting the checkboxes by hand

make sure they have name="dishes" (assuming dishes is the name of the field in your Checkout model) and value="<the dish id>"

You may also need to grab the values submitted with request.POST.getList('dishes'), otherwise you will ony get one value submitted