Welcome to part 7 of the web development with Python and Django tutorial series, where we'll be covering messaging and continuing to cover handling for users within our web app by popping up messages to the user, changing the navbar depending on if they are logged in or not, as well as showing the use for includes
in Django templating.
The Django web frameworks comes with a messaging system that allows us to store messages that we can check for on each page load. If there are some messages, we can display them to the user.
For these messages, we could show them however we see fit. With materialize.css, there are things called toasts
, which use javascript to popup a little snippet of a message to your user. For more information: Materialize Toasts.
In our case right now, we'd like to be able to quickly inform the user of errors with their form, or alert them to the success of logging in. This is the sort of thing that Django messaging is for, and we can use the toasts to display the messages if they happen to exist.
To begin, let's add the cases for messages. Inside our mysite/main/views.py
, make the following import:
from django.contrib import messages
Next, after a user has been created, we can do something like:
messages.success(request, f"New account created: {username}")
Then, for our errors, we could handle them with:
for msg in form.error_messages: messages.error(request, f"{msg}: {form.error_messages[msg]}")
Full register function:
def register(request): if request.method == "POST": form = UserCreationForm(request.POST) if form.is_valid(): user = form.save() username = form.cleaned_data.get('username') messages.success(request, f"New account created: {username}") login(request, user) return redirect("main:homepage") else: for msg in form.error_messages: messages.error(request, f"{msg}: {form.error_messages[msg]}") return render(request = request, template_name = "main/register.html", context={"form":form}) form = UserCreationForm return render(request = request, template_name = "main/register.html", context={"form":form})
Once that's done, we're ready to show the messages. We don't need to pass messages through the context, though that's exactly what we'd do if there wasn't something already in place like Django's messaging! Instead, we can just go straight to our templates to handle for them.
We could modify the register.html
page to handle for messages, but messages are something we'd like to use all over our website, not just on this specific page, so instead we'll handle this on the header.html
page.
Just before the container div that contains our block content, let's add the following:
{% if messages %} {% for message in messages %} <script>M.toast({html: "{{message}}", classes: 'blue rounded', displayLength:2000});</script> {% endfor %} {% endif %}
Now, create another user, but this time purposefully mess up the password fields. You should now see the error message pop up. Go ahead and register a new user legitimately, and you should see a message about creating a new account successfully!
Next, we could get a bit more fancy with this by setting custom messages depending on the message tag itself.
{% if messages %} {% for message in messages %} {% if message.tags == 'success'%} <script>M.toast({html: "{{message}}", classes: 'green rounded', displayLength:2000});</script> {% elif message.tags == 'info'%} <script>M.toast({html: "{{message}}", classes: 'blue rounded', displayLength:2000});</script> {% elif message.tags == 'warning'%} <script>M.toast({html: "{{message}}", classes: 'orange rounded', displayLength:10000});</script> {% elif message.tags == 'error'%} <script>M.toast({html: "{{message}}", classes: 'red rounded', displayLength:10000});</script> {% endif %} {% endfor %} {% endif %}
Next, we should dynamically handle for the user being logged in. Once someone has registered and logged in, we shouldnt show the register and login buttons. Instead maybe an account and logout button.
Looking above the messages part of the header.html
file, let's look specifically at the ul
tags for the navbar:
<ul id="nav-mobile" class="right hide-on-med-and-down"> <li><a href="/">Home</a></li> <li><a href="https://discord.gg/sentdex">Community</a></li> <li><a href="/login">Login</a></li> <li><a href="/register">Register</a></li> </ul>
Here, we can do a check to see if the user is authenticated. If they are, then instead we should show a logout button and maybe a user account. If not, then we show login/register.
<li><a href="/">Home</a></li> <li><a href="https://discord.gg/sentdex">Community</a></li> {% if user.is_authenticated %} <li><a href="/account">{{user.username|title}}</a></li> <li><a href="/logout">Logout</a></li> {% else %} <li><a href="/login">Login</a></li> <li><a href="/register">Register</a></li> {% endif %}
At this point, we've got some cool functionality for our header, but, in all honesty, things are starting to get messy for this header page. As we continue to add functionality like this, it's only going to become more and more confusing and time consuming to add things or change them. This is probably a good time to incorporate includes
. These includes
are similar to extends
, where we can just place one line of code to call in an include
, which may contain many more lines of code. Let's see an example. First, let's change our navbar list items to be an include.
Inside of our mysite/main/template/main
directory, let's make another directory called includes
. Includes are just snippets of HTML usually, so I would like to organize them into their own directory.
Inside the new directory, let's create a new HTML file called navbaritems.html
. Edit that, and let's copy and paste in the list items and the logic for the navbar.
mysite/main/templates/main/includes/navbaritems.html
<li><a href="/">Home</a></li> <li><a href="https://discord.gg/sentdex">Community</a></li> {% if user.is_authenticated %} <li><a href="/account">{{user.username|title}}</a></li> <li><a href="/logout">Logout</a></li> {% else %} <li><a href="/login">Login</a></li> <li><a href="/register">Register</a></li> {% endif %}
Then, we can head over to our header.html
file in the main templates dir and replace the list items there with:
{% include 'main/includes/navbaritems.html' %}
We can now do the same thing with messages. Again, it's useful for us to know "oh okay, the nav options are here" ...but otherwise we probably wont be modifying those very often. Same with messages. We'd like to see where the message code is, but there's really no need for us to have it cluttering up that top level HTML file.
Now, inside that includes
directory, make another file called messaging.html
and put the following in there:
{% if messages %} {% for message in messages %} {% if message.tags == 'success'%} <script>M.toast({html: "{{message}}", classes: 'green rounded', displayLength:2000});</script> {% elif message.tags == 'info'%} <script>M.toast({html: "{{message}}", classes: 'blue rounded', displayLength:2000});</script> {% elif message.tags == 'warning'%} <script>M.toast({html: "{{message}}", classes: 'orange rounded', displayLength:10000});</script> {% elif message.tags == 'error'%} <script>M.toast({html: "{{message}}", classes: 'red rounded', displayLength:10000});</script> {% endif %} {% endfor %} {% endif %}
Then remove those lines from the main template and replace with:
{% include 'main/includes/messaging.html' %}
Now our header.html
is just:
<head> {% load static %} <!-- Prism CSS --> <link href="{% static "tinymce/css/prism.css" %}" rel="stylesheet"> <!-- Compiled and minified CSS --> <link rel="stylesheet" href="{% static "main/css/materialize.css" %}"> <!-- Compiled and minified JavaScript --> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> </head> <body> <nav> <div class="nav-wrapper" <a href="/" class="brand-logo">Tutorials</a> <ul id="nav-mobile" class="right hide-on-med-and-down"> {% include 'main/includes/navbaritems.html' %} </ul> </div> </nav> {% include 'main/includes/messaging.html' %} <div class="container"> {% block content %} {% endblock %} </div> </body> <!-- Prism JS --> <script src="{% static "tinymce/js/prism.js" %}"></script>
Now the next thing we need to handle for is allowing a user to actually login or logout. Right now, the only way to log in is by registering, and there's no great way to log out! Let's fix both in the next tutorial.