Django Validation Error raised but not displayed for shopping cart app

I am running Django 2.2 and have written a simple shopping cart. I wish to validate two fields at the same time in such a way that both cannot be empty at the same time. In my forms.py,

from django import forms

class CartAddProductForm(forms.Form):
    cc_handle = forms.CharField(required=False, label='CC Handle', empty_value='')
    lc_handle = forms.CharField(required=False, label='LC Handle', empty_value='')

    def clean(self):
        cleaned_data = super().clean()
        if cleaned_data.get('cc_handle') == '' and cleaned_data.get('lc_handle') == '':
            print("issue detected")
            raise forms.ValidationError('Either cc or lc handle is required.')
        return cleaned_data

This is following the official Django docs on cleaning and validating fields that depend on each other. The print() statement above lets me know that the issue has been detected, i.e. both fields are empty. Running the Django server, I see that the issue was indeed detected but no validation error message was displayed on top of the originating page. The originating page is the product page that contains the product and a link to add the product to the shopping cart. Normally the validation error message is displayed at the top of the page.

According to the docs, the validation is done when is_valid() is called. So I put a diagnostic print of my views.py

from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from shop.models import Product
from .cart import Cart
from .forms import CartAddProductForm

@require_POST
def cart_add(request, product_id):
    cart = Cart(request)
    product = get_object_or_404(Product, id=product_id)
    form = CartAddProductForm(request.POST)
    if form.is_valid():
        cd = form.cleaned_data
        cart.add(product=product,
                cc_handle=cd['cc_handle'],
                lc_handle=cd['lc_handle'])
    else:
        print('invalid form')
    return redirect('cart:cart_detail')

And indeed the words 'invalid form' popped up. The code then takes me to the shopping cart. Instead, what I want is to be at the product page and show the validation error informing the reader that both fields cannot be empty. Is there a simple way of doing it?

For required=True fields in the forms, if I leave it blank, there will be a message popping up saying that I need to fill it in. So I want to do something similar except the validation requires that both fields cannot be empty.

This is different from this Stackoverflow answer because that is a registration form. You can redirect it to the same form whereas for this case, the CartAddProductForm is embedded in all the products page on the site. If possible, I want the validation to occur at the same stage as the field with required=True option.

The product/detail.html template looks like the following.

{% extends "shop/base.html" %}
{% load static %}

{% block title %}
{{ product.name }}
{% endblock %}

{% block content %}
<div class="product-detail">
    <img src="{% if product.image %}{{ product.image.url }}{% else %}{% static "img/no_image.png" %}{% endif %}">
    <h1>{{ product.name }}</h1>
    <h2><a href="{{ product.category.get_absolute_url }}">{{ product.category }}</a></h2>
    <p class="price">${{ product.price }}</p>
    <form action="{% url "cart:cart_add" product.id %}" method="post">
    {{ cart_product_form }}
    {% csrf_token %}
    <input type="submit" value="Add to cart">
    </form>
    {{ product.description|linebreaks }}
</div>
{% endblock %}

2 answers

  • answered 2019-05-21 07:31 bruno desthuilliers

    Your view is inconditionnally redirecting to cart_details so no surprise you don't see the validation errors - you'd have to render the invalid form for this. You should only redirect when the post succeeded.

  • answered 2019-05-21 09:10 shaik moeed

    Adding this line in form template has cleared your issue.

    {{ cart_product_form.non_field_errors }}

    product/detail.html:

    {% extends "shop/base.html" %}
    {% load static %}
    
    {% block title %}
    {{ product.name }}
    {% endblock %}
    
    {% block content %}
    <div class="product-detail">
        <img src="{% if product.image %}{{ product.image.url }}{% else %}{% static "img/no_image.png" %}{% endif %}">
        <h1>{{ product.name }}</h1>
        <h2><a href="{{ product.category.get_absolute_url }}">{{ product.category }}</a></h2>
        <p class="price">${{ product.price }}</p>
        <form action="{% url "cart:cart_add" product.id %}" method="post">
        {{ cart_product_form }}
        {% csrf_token %}
        {{ cart_product_form.non_field_errors }} // This line will raise validation errors
        <input type="submit" value="Add to cart">
        </form>
        {{ product.description|linebreaks }}
    </div>
    {% endblock %}
    

    Doc:(Copied from official documentation)

    Note that any errors raised by your Form.clean() override will not be associated with any field in particular. They go into a special “field” (called all), which you can access via the non_field_errors() method if you need to. If you want to attach errors to a specific field in the form, you need to call add_error().