Django message not showing up in template
I’m using Django 5.2.4, and my login_user view sets an error message with messages.error when authentication fails, but it doesn’t appear in the template (login page) after redirecting.
App urls:
from django.urls import path
from django.shortcuts import redirect
from . import views
# urlpatterns of the app checks the function names defined in views
urlpatterns = [
path('', lambda request: redirect('login', permanent=True)),
path("login/", views.login_user, name="login"),
]
Project urls:
from django.contrib import admin
from django.shortcuts import redirect
from django.urls import path, include
urlpatterns = [
path("", lambda request: redirect('loginapp/', permanent=True)),
path('admin/', admin.site.urls),
path("loginapp/", include("loginapp.urls")),
path("loginapp/", include('django.contrib.auth.urls')),
]
template html :
<html lang="en">
<body>
<div class="box">
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<h2>Welcome to Trevo</h2>
<h3>Sign in now!</h3>
<form method="post", action="{% url 'login' %}"> <!-- Django uses to verify that the form submission is coming from your site and not from a malicious third party. -->
{% csrf_token %}
<label for="Username">Username</label><br>
<input type="text" id="username" name="username"><br>
<br>
<label for="Password">Password</label><br>
<input type="password" id="password" name="password"><br>
<br>
<input type="submit" id="submit" name="submit" value="Sign in"><br>
</form>
</div>
<style>
Views.py :
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
import logging
# Create your views here.
def login_user(request):
logging.debug(f"Request method: {request.method}")
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
logging.debug(f"POST DATA: {request.POST}")
user = authenticate(request, username=username, password=password) # built-in contrib.auth
if user is not None:
login(request, user)
logging.debug("Code 0: User authenticated, redirecting to home")
return redirect('home')
else:
messages.error(request, "Invalid username or password. Please try again.")
logging.debug("Code 1: Invalid credentials, setting error message")
logging.debug(f"Messages: {list(messages.get_messages(request))}")
return redirect('login')
else:
logging.debug("Code 2: Rendering login page")
return render(request, "registration/login.html", {})
settings.py:
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-#kik2_dtfxgrdgg7&t9t&s*(#hv(hp8h@#0np3j=huwa-55rob'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'loginapp',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'loginpage.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'loginpage.wsgi.application'
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
MESSAGE_LEVEL = 10
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
The logs are the following when arriving on the page and trying some random credentials.
[20/Jul/2025 17:55:51] "GET /loginapp/login/ HTTP/1.1" 200 1756
[20/Jul/2025 17:55:55] "POST /loginapp/login/ HTTP/1.1" 302 0
[20/Jul/2025 17:55:55] "GET /loginapp/login/ HTTP/1.1" 200 1756
As @furas says in their comment: "I'm not sure but maybe messages.get_messages(request)
automatically removes messages and this way messages are used only once - you don't see the same messages on next pages. You may have to remove this part."
Your debugging has side-effects, and these side-effects result in the messages no longer been delivered the second time. Django's messages framework aims to make it more convenient to deliver messages, by adding the messages to the session, and eventually deliver these once, and in order for the session, when you call .get_messages(…)
[Django-doc] with a request
for the same session.
path('', lambda request: redirect('login', permanent=True)),
Please don't make the redirect permanent: it means that people that visit the home page, will always be redirected to the login, even if they have already been logged in. By making it permanent, most browsers will no longer even fetch the redirect: if you visit home, the browser will know this the second time, and send you straight to the login page. If you later change your mind, it can take a lot of time before the browsers adapt to this.
You can use a RedirectView
[Django-doc] to do this:
from django.views.generic.base import RedirectView
# …
urlpatterns = [
path('', RedirectView.as_view(pattern_name='login')),
# …
]
this is also more safe than a lambda expression: if there are parameters in the URL for example, then these will have to be put in parameters of the lambda function, so it is less robust to do this.
I think the problem is in your views.py file where you're consuming the messages before they can be displayed in the template.
Exactly in this:
logging.debug(f"Messages: {list(messages.get_messages(request))}")
The messages.get_messages(request) function consumes the messages from the message storage. When you call it for debugging purposes, it removes the messages from the storage, so they're no longer available when the template tries to display them after the redirect.
So, you can fix that simply using something like this:
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
import logging
def login_user(request):
logging.debug(f"Request method: {request.method}")
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
logging.debug(f"POST DATA: {request.POST}")
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
logging.debug("Code 0: User authenticated, redirecting to home")
return redirect('home')
else:
messages.error(request, "Invalid username or password. Please try again.")
logging.debug("Code 1: Invalid credentials, setting error message")
return redirect('login')
else:
logging.debug("Code 2: Rendering login page")
return render(request, "registration/login.html", {})```