Grocery Bag using Django (Part-3) - User Authentication

Introduction

In the previous blog, we had learned about Template Inheritance and how to serve static files. We had fixed our templates also. In this blog, we'll see how we can authenticate the user into our web app.

To keep it simple, we would not be using custom authentication. Instead, we'll be using Django's User model, which will make our task quite easier. Let's get started!

Alerts

Quite commonly in the upcoming sections, we need to display a one-time notification message (also known as “flash message”) to the user after processing a form or some other types of user input. For this, Django provides full support for cookie- and session-based messaging, for both anonymous and authenticated users. The messages framework allows us to temporarily store messages in one request and retrieve them for display in a subsequent request (usually the next one). Every message is tagged with a specific level that determines its priority (e.g., info, warning, or error).

Django template language has an include tag. include tag loads a template and renders it with the current context. This is a way of “including” other templates within a template. The template name can either be a variable or a hard-coded (quoted) string, in either single or double quotes.

Let's create a folder called partials inside the templates folder and within the partials folder, create a alerts.html file and add the following code:

{% if messages %} 
    {% for message in messages %} 
        {% if message.tags == 'error' %}
            <div class="alert alert-danger alert-dismissible fade show" role="alert">
            {{message}}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
            </div>
        {% else %}
            <div class="alert alert-{{message.tags}} alert-dismissible fade show" role="alert">
            {{message}}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
            </div>
        {% endif %}
    {% endfor %} 
{% endif %}

We are using Bootstrap dismissable alerts for the alerts. We are iterating over the messages passed from the view functions and showing them as per their categories. Even if there is only one message, we should still iterate over the messages sequence, because otherwise the message storage will not be cleared for the next request. Note that we are using an if condition to check if the message.tag is error, then we are showing danger alert, else we are directly showing the alerts using the message.tag. This is because, in Bootstrap we have danger class, but Iin Django we have error tag.

Registering New User

The first thing we wish to do is to register a new user. Whenever a user comes to the signup page, he is requested to enter a username and password. If the username is not already in the database, the user is created and redirected to the login page. If there is already a user present with the same username, an error message is flashed.

Since, this is an authentication related task, open the accounts/views.py file and add the following code:

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.models import User

def signup(request):
    if request.user.is_authenticated:
        return redirect('index')
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username and password:
            try:
                User.objects.get(username=username)
                messages.error(request, 'User already exists')
            except User.DoesNotExist:
                User.objects.create_user(username=username, password=password)
                messages.success(request, 'Signup success')
                return redirect('signin')
        messages.error(request, "Username or Password is missing!")
    return render(request, 'signup.html')

In the above script, first we are checking if the user is already authenticated. If it is, we redirect it to the index page. Then we are checking, if the user has entered username and password or not. If not, we flash an error message saying Username or Password is missing!. Then we check if the user is already present in the database. If it is, we again flash an error saying User already exists. Else, the user is created and redirected to the login page.

Now we need to create a URL route for this view function in the accounts/urls.py file:

from django.urls import path

from accounts.views import signup

urlpatterns = [
    path('signup', signup, name='signup'),
]

Now that we have a view function to handle the signup functionality and a URL route is attached to it, we are ready to make changes in the signup form. Open the templates/signup.html, and add the following code:

{% extends "base.html" %}{% load static %} {% block title %} Sign Up {% endblock title %} 

{% block custom_head %}
<link rel="stylesheet" href="{% static 'css/signin.css' %}" />
{% endblock custom_head %} 

{% block content %}
<body class="text-center">
  <main class="form-signin">
    <form method="POST" action="{% url 'signup' %}">
      {% csrf_token %}

      <h1 class="h3 mb-3 fw-normal">Please sign up</h1>
      {% include 'partials/alerts.html' %}
      <div class="form-floating mt-2">
        <input
          type="text"
          class="form-control"
          id="floatingInput"
          name="username"
          placeholder="johndoe"
        />
        <label for="floatingInput">Username</label>
      </div>
      <div class="form-floating mt-2">
        <input
          type="password"
          class="form-control"
          id="floatingPassword"
          name="password"
          placeholder="Password"
        />
        <label for="floatingPassword">Password</label>
      </div>

      <button class="w-100 btn btn-lg btn-primary" type="submit">
        Sign up
      </button>
      <p class="mt-3">
        Already Registered? <a href="{% url 'signin' %}">Sign in now</a>
      </p>
    </form>
  </main>
</body>
{% endblock content %}

The first change we are making is in the form tag. The form method is set to POST and the action URL is the signup route we just created. Also we are adding the URL of the sign in page in the Already Registered? line. We haven't created this route yet.

User Login

Next, we have to create the login functionality. Inside the accounts/views.py file, add the following view function.

from django.contrib.auth import authenticate, login

def signin(request):
    if request.user.is_authenticated:
        return redirect('index')

    redirect_url = request.GET.get('next')
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username and password:
            user = authenticate(username=username, password=password)
            if user:
                login(request, user)
                if redirect_url:
                    return redirect(redirect_url)
                return redirect('index')
            else:
                messages.error(request, 'Incorrect username or password!')
                return redirect(f'/accounts/signin?next={redirect_url}')
        messages.error(request, "Username or Password is missing!")
    return render(request, 'signin.html')

This view function is quite similar to the signup function. The change we are making here is, we are using the authenticate function to check if the user exists in the database or not. If not, we show an error message and redirect the user to the same login page. If the user exists, we login the user. Then we check if there is a next route in the URL. If it's there, we redirect the user to that page, else to the index page. The next parameter tells that what page should the user be redirected to.

Now lets create a URL for this function in the accounts/urls.py file:

from django.urls import path

from accounts.views import signin, signup

urlpatterns = [
    path('signup', signup, name='signup'),
    path('signin', signin, name='signin'),
]

The final change we need to make, is in the signin.html file:

{% extends "base.html" %}{% load static %} {% block title %} Sign In {% endblock title %} 

{% block custom_head %}
<link rel="stylesheet" href="{% static 'css/signin.css' %}" />
{% endblock custom_head %} 

{% block content %}
<body class="text-center">
  <main class="form-signin">
    <form method="POST" action="{% url 'signin' %}{% if request.GET.next %}?next={{request.GET.next}}{% endif %}">
      {% csrf_token %}

      <h1 class="h3 mb-3 fw-normal">Please sign in</h1>
      {% include 'partials/alerts.html' %}
      <div class="form-floating mt-2">
        <input
          type="text"
          class="form-control"
          id="floatingInput"
          name="username"
          placeholder="johndoe"
        />
        <label for="floatingInput">Username</label>
      </div>
      <div class="form-floating mt-2">
        <input
          type="password"
          class="form-control"
          id="floatingPassword"
          name="password"
          placeholder="Password"
        />
        <label for="floatingPassword">Password</label>
      </div>

      <button class="w-100 btn btn-lg btn-primary" type="submit">
        Sign in
      </button>
      <p class="mt-3">New user? <a href="{% url 'signup' %}">Sign up now</a></p>
    </form>
  </main>
</body>
{% endblock content %}

Everything is similar here. But the action URL has a little change. If the request URL has a next parameter, we add that parameter in the action URL.

Logging Out User

The only thing we are left with is to log out the user.

from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required

@login_required
def signout(request):
    logout(request)
    messages.info(request, "Logged out!")
    return redirect('signin')

We have used a @login_required decorator so that only authenticated users can logout. Let's add this view function in the urls.

from django.urls import path

from accounts.views import signin, signout, signup

urlpatterns = [
    path('signup', signup, name='signup'),
    path('signin', signin, name='signin'),
    path('signout', signout, name='signout'),
]

Also add the line in the settings.py file:

LOGIN_URL = '/accounts/signin'

Also, we need to add Logout button in the index page. Open the index.html file and add:

{% extends "base.html" %}{% load static %} {% block title %}View Bag{% endblock title %} 

{% block content %}
<body>
  <div class="container mt-5">
    <!-- top -->
    <div class="row">
      <div class="col-lg-6">
        <h1>View Grocery List</h1>
      </div>
      <div class="col-lg-6 float-right">
        <div class="row">
          <div class="col-lg-6">
            <!-- Date Filtering-->
            <input type="date" class="form-control" />
          </div>
          <div class="col-lg-4">
            <input type="submit" class="btn btn-danger" value="filter" />
          </div>
          <div class="col-lg-2">
            <p class="mt-1"><a href="{% url 'signout' %}">Log Out</a></p>
          </div>
        </div>
      </div>
    </div>
    <!-- // top -->
    <!-- Grocery Cards -->
    <div class="row mt-4">
      <!--- -->
      <!-- Loop This -->
      <div class="col-lg-4">
        <div class="card">
          <div class="card-body">
            <h5 class="card-title">Tomato</h5>
            <h6 class="card-subtitle mb-2 text-muted">2 Pcs.</h6>
            <p class="text-success">BOUGHT</p>
          </div>
        </div>
      </div>
      <!-- // Loop -->
      <div class="col-lg-4">
        <div class="card">
          <div class="card-body">
            <h5 class="card-title">Chicken</h5>
            <h6 class="card-subtitle mb-2 text-muted">2Kgs</h6>
            <p class="text-danger">NOT AVAILABLE</p>
          </div>
        </div>
      </div>

      <div class="col-lg-4">
        <div class="card">
          <div class="card-body">
            <h5 class="card-title">Posto</h5>
            <h6 class="card-subtitle mb-2 text-muted">50gms</h6>
            <p class="text-info">PENDING</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
{% endblock content %}

User Authorisation

We'll make the index page available to the authenticated users only. If the user is not authenticated, it is redirected to the LOGIN_URL.

from django.shortcuts import render
from django.contrib.auth.decorators import login_required

# Create your views here.

@login_required
def index(request):
    return render(request, "index.html")

Database Migrations

Now that we have done everything, we are ready to run the database migrations:

$ python manage.py makemigrations
$ python manage.py migrate

Demo Video

Watch the demo video here:

Conclusion

In this part we have seen how we can authenticate users, and how we can restrict users from accessing certain pages. In the next part, we'll work on the CRUD operations. Stay tuned!

20