Django не отображает ошибки от пользовательского валидатора, а вместо этого сбрасывает форму

Я недавно начал изучать Django и не могу решить одну проблему. Я создал свой валидатор для формы, но вместо того, чтобы показать окно ошибки, он просто сбрасывает форму.

Вот код models.py:

from django.db import models
from django.urls import reverse_lazy
from django.core.exceptions import ValidationError

class News(models.Model):
    title = models.CharField(max_length=150, verbose_name='Title')
    content = models.TextField(blank=True, verbose_name='Content')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='Date of publication')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='Update')
    photo = models.ImageField(upload_to='photos/%Y%m/%d/', verbose_name='Photo', blank=True)
    is_published = models.BooleanField(default=True, verbose_name='Is_published ')
    category = models.ForeignKey('Category', on_delete=models.PROTECT, null=True, verbose_name='Category')

    def get_absolute_url(self):
        return reverse_lazy('read', kwargs={'news_id' : self.pk})

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = 'new'
        verbose_name_plural = 'news'
        ordering = ['-created_at', 'title']



class Category(models.Model):
    title = models.CharField(max_length=150, db_index=True, verbose_name='Title of category')

    def get_absolute_url(self):
        return reverse_lazy('category', kwargs={'pk' : self.pk})

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = 'category'
        verbose_name_plural = 'categories'
        ordering = ['-title']

Вот код forms.py:

from django import forms
from .models import News, Category
from django.core.exceptions import ValidationError
import re

class NewsForm(forms.Form):
    title = forms.CharField(max_length=150, min_length=1, label='Title', widget=forms.TextInput(attrs={'class' : 'form-control'}))
    content = forms.CharField(label='Text', required=False, widget=forms.Textarea(attrs={'class' : 'form-control', 'rows' : 15}))
    # photo = forms.ImageField(upload_to='photos/%Y%m/%d/')
    is_published = forms.BooleanField(label="To publish", initial=True)
    category = forms.ModelChoiceField(label='Category', queryset=Category.objects.all(), empty_label='Select a category', widget=forms.Select(attrs={'class' : 'form-control'}))

    def clean_title(self):
        raise ValidationError('Error!')



# class NewsForm(forms.ModelForm):
#     class Meta:
#         model = News
#         fields = ['title', 'content', 'is_published', 'category']
#         widgets = {
#             'title' : forms.TextInput(attrs={'class' : 'form-control'}),
#             'content' : forms.Textarea(attrs={'class' : 'form-control', 'rows' : 15}),
#             'category' : forms.Select(attrs={'class' : 'form-control'})
#         }

А вот код views.py:

from django.shortcuts import render, get_object_or_404, redirect
from .models import News, Category
from .forms import NewsForm


def news(request):
    news = News.objects.all()

    return render(request, 'news/news.html', {'news': news, 'title': 'List of news:'})


def get_category(request, pk):
    news = News.objects.filter(category_id=pk)

    return render(request, 'news/category.html',
                  {'news': news, 'title': f'List of news in the {str(Category.objects.get(pk=pk))} category :'})

def view_news(request, news_id):
    back = request.GET.get('back')
    new = get_object_or_404(News, pk=news_id)

    return render(request, 'news/read_news.html', {'new' : new, 'back' : back})

def add_news(request):
    if request.method == 'POST':
        form = NewsForm(request.POST)
        if form.is_valid():
            new = News.objects.create(**form.cleaned_data)
            # new = form.save()
            return redirect(str(new.get_absolute_url())+'?back=False')

    return render(request, 'news/add_news.html', {'form' : NewsForm() })

Вот код urls.py:

from django.urls import path

from .views import *

urlpatterns = [
    path('news/', news, name='news_all'),
    path('news/category/<int:pk>/', get_category, name='category'),
    path('news/read/<int:news_id>/', view_news, name='read'),
    path('news/add-news/', add_news, name='add'),
]

Код news_tags.py:

from django import template

from ..models import Category

register = template.Library()

@register.simple_tag(name='g_cat')
def get_categories():
    return Category.objects.all()

@register.inclusion_tag('news/tags/news_tag.html', name='news_tag')
def news_tag(news, link=False):
    return {'news' : news, 'link' : link}

Код news_tag.html:

<div class="col-md-9">
    {% for item in news %}
        <div class="card mb-3">
            <div class="card-header">
                {% if link %}
                    Category: <a href="{{ item.category.get_absolute_url }}">{{ item.category.title }}</a>
                {% else %}
                    Category: {{ item.category.title }}
                {% endif %}
            </div>
            <div class="card-body">
                <div>
                    {% if item.photo %}
                        <img src="{{ item.photo.url }}" alt="" class="news-media-photo">
                    {% endif %}
                    <div class="media-body">
                        <h5 class="card-title" style="color: {% cycle 'red' 'green' %}">{{ forloop.revcounter }}. {{ item.title }}</h5>
                        <p class="card-text">{{ item.content|linebreaks|truncatewords:50 }}</p>
                        <a href="{{ item.get_absolute_url }}" class="btn btn-primary">Читать дальше...</a>
                    </div>
                </div>
            </div>
            <div class="card-footer text-muted">
                {{ item.created_at|timesince }} назад...
            </div>
        </div>
    {% empty %}
        <h2>Ooops...</h2>
    {% endfor %}
</div>

Вот код base.html:

<!doctype html>
{% load static %}
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">

    <title>{% block title %}News:{% endblock %}</title>
</head>
<body>

{% block navbar %}
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="/">Navbar</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
    
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item"><a class="nav-link" href="{% url 'news_all' %}">Главная</a></li>
                <li class="nav-item"><a class="nav-link" href="{% url 'add' %}">Add new</a></li>
            </ul>
        </div>
    </nav>
{% endblock %}

<div class="container mt-3">
    <h2>{% block list_title %}News:{% endblock %}</h2>
    <div class="row">
        {% block sidebar %}
            <div class="col-md-3">
                {% load news_tags %}
                <div class="list-group">
                    {% g_cat as g_cat %}
                    <a href="{% url 'news_all' %}" class="list-group-item list-group-item-action">Все</a>
                    {% for i in g_cat %}
                    <a href="{{ i.get_absolute_url }}" class="list-group-item list-group-item-action">
                        {{ i.title }}
                    </a>
                    {% endfor %}
                </div>
            </div>
        {% endblock %}

        {% block content %}
        {% endblock %}
    </div>
</div>

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
        integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
        crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.6/dist/umd/popper.min.js"
        integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut"
        crossorigin="anonymous"></script>
<script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
</body>
</html>

Вот код add_news.html:

{% extends 'base.html' %}

{% block title %}Add new{% endblock %}

{% block list_title %}
<p align="center">{{ new.title }}</p>
{% endblock %}

{% block navbar %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <a class="navbar-brand" href="/">Navbar</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item"><a class="nav-link" href="{% url 'news_all' %}">Home</a></li>
            <li class="nav-item"><a class="nav-link" href="#" onclick="history.back();return false;">Cancel</a></li>
        </ul>
    </div>
</nav>
{% endblock %}

{% block sidebar %}{% endblock %}

{% block content %}
<div class="col-md-12">
    <h1>Add new</h1>
    <form method="post">
        {% csrf_token %}


        {{ form.as_p }}
        {% comment %}
        {{ form.non_field.errors  }}
        {% for i in form %}
        <div class="form-group">
            {{ i.label_tag }}
            {{ i }}
            <div class="invalid-feedback">
                {{ i.errors }}
            </div>
        </div>
        {% endfor %}


        {{ form.non_field.errors  }}
        <div class="form-group">
            <label for="{{ form.title.id_for_label }}">Title</label>
            {{ form.title }}
            <div class="invalid-feedback">
                {{ form.title.errors }}
            </div>
        </div>

        <div class="form-group">
            <label for="{{ form.content.id_for_label }}">Text</label>
            {{ form.content }}
            <div class="invalid-feedback">
                {{ form.content.errors }}
            </div>
        </div>

        <div class="form-group">
            <label for="{{ form.is_published.id_for_label }}">To publish</label>
            {{ form.is_published }}
            <div class="invalid-feedback">
                {{ form.is_published.errors }}
            </div>
        </div>

        <div class="form-group">
            <label for="{{ form.category.id_for_label }}">Category</label>
            {{ form.category }}
            <div class="invalid-feedback">
                {{ form.category.errors }}
            </div>
        </div>
        {% endcomment %}

        <button type="submit" class="btn btn-primary btn-block">Add new</button>
    </form>
</div>
{% endblock %}

Код read_news.html:

{% extends 'base.html' %}

{% block title %}Новость - {{ new.title }}{% endblock %}

{% block list_title %}
    <p align="center">{{ new.title }}</p>
{% endblock %}

{% block navbar %}
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="/">Navbar</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item"><a class="nav-link" href="{% url 'news_all' %}">Главная</a></li>
                {%if back is None %}
                    <li class="nav-item"><a class="nav-link" href="#" onclick="history.back();return false;">Back</a></li>
                {% elif back == False %}
                    <li class="nav-item"><a class="nav-link" href="/news/news/">Back</a></li>
                {% endif %}
            </ul>
        </div>
    </nav>
{% endblock %}

{% block sidebar %}{% endblock %}

{% block content %}
<div class="col-md-12">
    <div class="card mb-3">
        <div class="card-header">
            Category: {{ new.category.title }}
        </div>
        <div class="card-body">
            <div>
                {% if new.photo %}
                    <img src="{{ new.photo.url }}" alt="" height="250" class="news-media-photo">
                {% endif %}
                <div class="media-body">
                    <p class="card-text">{{ new.content|linebreaks }}</p>
                </div>
            </div>
        </div>
        <div class="card-footer text-muted">
            {{ new.created_at|timesince }} ago...
        </div>
    </div>
</div>
{% endblock %}

Вот код news.html:

{% extends 'base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block list_title %}
    {{ title }}
{% endblock %}

{% block content %}
{% load news_tags %}
{% news_tag news True %}
{% endblock %}

Вот код category.html:

{% extends 'base.html' %}

{% block title %}
    {{ title }}
{% endblock %}

{% block list_title %}
    {{ title }}
{% endblock %}

{% block content %}
{% load news_tags %}
{% news_tag news %}
{% endblock %

Для отображения пользовательских сообщений об ошибках, вы должны отключить поведение html5 по умолчанию для обязательного поля, вы можете использовать novalidate в форме.

Вы также можете показывать сообщения об ошибках конкретных полей в цикле.

Попробуйте этот код:

add_news.html

<div class="col-md-12">
    <h1>Add new</h1>
    <form method="POST" novalidate>
        {% csrf_token %}
        {% if form.non_field_errors %}
            {% for error in form.non_field_errors  %}
                {{error|striptags}}
            {% endfor %}
        {% endif %}

        {% for field in form %}
        <div class="form-group">
            {{ field.label_tag }}
            {{field}}
            <div class="invalid-feedback">
                {% for error in field.errors %}
                    {{error}}
                {% endfor %}
            </div>
        </div>
        {% endfor %}
        <button type="submit" class="btn btn-primary btn-block">Add new</button>
    </form>
</div>
Вернуться на верх