Hello and welcome to this Python 3.7 and Django 2.2 tutorial series, my name is Henry Mbugua and I will be taking you through the various aspect and new answer of how to create a custom dashboard, redirect users to this custom dashboard after register and to login user. In our previous tutorial, we learned how to use bootstrap and register new users and this lesson continues from where we left.

The best and the quickest way to get you started with creating a custom dashboard is by checking what Bootstrap offers as an example of a dashboard template. We are going to visit bootstrap examples in the documentation. We are going to implement a simple dashboard that has side navbar and a content area.

Creating Dashboard View

Now let’s add a new view to person_portfolio/views.py file, make sure that this file has the following code:

from django.shortcuts import render from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import login, authenticate from django.http import HttpResponse def sign_up(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): form.save() username = form.cleaned_data.get('username') password = form.cleaned_data.get('password1') user = authenticate(username=username, password=password) login(request, user) return HttpResponse('A new user has been successfully registered!') else: form = UserCreationForm() return render(request, 'register.html', {'form': form}) def user_dashboard(request): return render(request, 'dashboard.html')

Let’s understand the code we have added:

Line 22 – we have created a function called user_dashboard which takes one parameter named request.

Line 23 – we render a template called dashoard.html which we are yet to create.

The next step is to create a URL mapping to this function, open person_portfolio/urls.py file and make sure it has the following code:

from django.urls import path from . import views urlpatterns = [ path('', views.sign_up, name='user_sign'), path('dashboard', views.user_dashboard, name='user_dashboard'), ]

Line 7 – we have defined a URL pattern that maps to our user_dashboard function. Before we can test this code, we need to create a template called dashboard.html, which should be in the templates folder. Create a dashboard.html template and make sure it has the following code:

{% extends 'base.html' %} {% block content %} {# top navbar #} <nav class="navbar navbar-light fixed-top bg-danger flex-md-nowrap p-0 shadow"> <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="#" style="color: white">HLAB</a> <ul class="navbar-nav px-3"> <li class="nav-item text-nowrap"> <a class="nav-link" href="#" style="color: white">Sign out</a> </li> </ul> </nav> {# top navbar #} <div class="container-fluid"> <div class="row"> {# Side navbar #} <nav class="col-md-2 d-none d-md-block bg-light sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" href="#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg> Dashboard <span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg> User profiles </a> </li> <li class="nav-item"> <a class="nav-link" href="#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg> Notifications </a> </li> </ul> <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted"> <span>User Management</span> <a class="d-flex align-items-center text-muted" href="#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg> </a> </h6> <ul class="nav flex-column mb-2"> <li class="nav-item"> <a class="nav-link" href="#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg> Admins </a> </li> <li class="nav-item"> <a class="nav-link" href="#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg> Login Audit </a> </li> </ul> </div> </nav> {# end of sidenavbar #} <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"><div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;"><div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;"><div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div></div><div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;"><div style="position:absolute;width:200%;height:200%;left:0; top:0"></div></div></div> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="h2">Dashboard</h1> </div> </main> </div> </div> {% endblock %}

Let’s understand the code in this file:

Line 1 – we extend the base template.

Line 5 to 12 – we create a top navbar.

Line 21 to 65 – we create a side navbar for our dashboard.

Line 69 to 75 – we create a section where the content of the page will be displayed.

The next step is to create styles.css in the static/css folder and make sure it has the following code:

NB: Don’t forget to include styles.css file in base.html file.

body { font-size: .875rem; } .feather { width: 16px; height: 16px; vertical-align: text-bottom; } /* * Sidebar */ .sidebar { position: fixed; top: 0; bottom: 0; left: 0; z-index: 100; /* Behind the navbar */ padding: 48px 0 0; /* Height of navbar */ box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); } .sidebar-sticky { position: relative; top: 0; height: calc(100vh - 48px); padding-top: .5rem; overflow-x: hidden; overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ } @supports ((position: -webkit-sticky) or (position: sticky)) { .sidebar-sticky { position: -webkit-sticky; position: sticky; } } .sidebar .nav-link { font-weight: 500; color: #333; } .sidebar .nav-link .feather { margin-right: 4px; color: #999; } .sidebar .nav-link.active { color: #007bff; } .sidebar .nav-link:hover .feather, .sidebar .nav-link.active .feather { color: inherit; } .sidebar-heading { font-size: .75rem; text-transform: uppercase; } /* * Content */ [role="main"] { padding-top: 133px; /* Space for fixed navbar */ } @media (min-width: 768px) { [role="main"] { padding-top: 48px; /* Space for fixed navbar */ } } /* * Navbar */ .navbar-brand { padding-top: .75rem; padding-bottom: .75rem; font-size: 1rem; background-color: rgba(0, 0, 0, .25); box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25); } .navbar .form-control { padding: .75rem 1rem; border-width: 0; border-radius: 0; } .form-control-dark { color: #fff; background-color: rgba(255, 255, 255, .1); border-color: rgba(255, 255, 255, .1); } .form-control-dark:focus { border-color: transparent; box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); }

Learn more about bootstrap classes and how to use them. Make sure that your development server is running and navigate to http://127.0.0.1:8000/dashboard you should have the following screenshot.

How to protect our Dashboard from an Unauthenticated User

Django authentication system offers a convenient way to limit access to pages, in this tutorial we are going to use the login_required decorator. Learn more about the Django login_required decorator. Now open person_portfolio/views.py and make sure it has the following code.

from django.shortcuts import render from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import login, authenticate from django.http import HttpResponse from django.contrib.auth.decorators import login_required def sign_up(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): form.save() username = form.cleaned_data.get('username') password = form.cleaned_data.get('password1') user = authenticate(username=username, password=password) login(request, user) return HttpResponse('A new user has been successfully registered!') else: form = UserCreationForm() return render(request, 'register.html', {'form': form}) @login_required() def user_dashboard(request): return render(request, 'dashboard.html')

Let’s understand the code:

Line 5 – we import the login decorator from Django Auth decorators.

Line 23 – we wrap our dashboard view with the login_decorator.

How the Login Decorator Works

If the user isn’t logged in, redirect to settings.LOGIN_URL , passing the current absolute path in the query string. Example: / accounts/login/?next=/dashboard

, passing the current absolute path in the query string. Example: If the user is logged in, execute the view normally. The view code is free to assume the user is logged in.

Now if you visit http://127.0.0.1:8000/dashboard you should get the following screenshot:

Django is trying to take you to the login page which we do not have at the moment. Our dashboard view requires users to login before we can allow them to view the dashboard.

How to Login User in Django

We are going to write a custom sign in view, which we will use to login user. Open person_portfolio/views.py and make sure it has the following code.

from django.shortcuts import render, redirect from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import login, authenticate from django.http import HttpResponse from django.contrib.auth.decorators import login_required def sign_up(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): form.save() username = form.cleaned_data.get('username') password = form.cleaned_data.get('password1') user = authenticate(username=username, password=password) login(request, user) return redirect('user_dashboard') else: form = UserCreationForm() return render(request, 'register.html', {'form': form}) @login_required() def user_dashboard(request): return render(request, 'dashboard.html') def sign_in(request): msg = [] if request.method == 'POST': username = request.POST['username'] password = request.POST['password'] user = authenticate(username=username, password=password) if user is not None: if user.is_active: login(request, user) return redirect('user_dashboard') else: msg.append('You account has been deactivated!') else: msg.append('Invalid Login credentials, try again!') return render(request, 'login.html', {'errors': msg})

Let’s understand the code:

Line 1 – we have imported the redirect method.

Line 17 – we redirect the user to the dashboard URL after sign up.

Line 28 – we create a function called sign_in.

Line 29 – we create a variable called msg which is an empty list.

Line 30 – we check whether the request method is a post.

Line 31 and 32 – we get the username and password from the request respectively.

Line 33 – we create a variable called user and we authenticate a user by passing username and password to the authenticate function.

Line 35 – we check the user exists in our database or not.

Line 36 – we check the user account whether it’s active.

Line 37 – we log in the user at this point.

Line 38 – we redirect the user to the user dashboard URL.

Line 40 – if the user account is not active, we send message to the user telling them their account has been deactivated.

Line 42 – we tell the user their login credentials do not match.

Line 43 – we display the login form and pass the msg variable.

The next step is to create the login URL, open person_portfolio/urls.py file and make sure it has the following code:

from django.urls import path from . import views urlpatterns = [ path('', views.sign_up, name='user_sign'), path('dashboard', views.user_dashboard, name='user_dashboard'), path('login', views.sign_in, name='login_user'), ]

Line 8 – we map our URL to the sign in function. The next step is to create a login.html file in the templates folder, make sure the login template has the following code.

{% extends 'base.html' %} {% block content %} <div class="container"> <div class="row"> <br> <br> </div> <div class="row"> <div class="col-md-6 offset-3"> {% if form.errors %} {% for field in form %} {% for error in field.errors %} <div class="alert alert-danger"> <strong>{{ error|escape }}</strong> </div> {% endfor %} {% endfor %} {% for error in form.non_field_errors %} <div class="alert alert-danger"> <strong>{{ error|escape }}</strong> </div> {% endfor %} {% endif %} <form method="post"> {% csrf_token %} <div class="form-group"> <label for="exampleInputEmail1">Username</label> <input type="text" class="form-control" name="username" aria-describedby="emailHelp" placeholder="Enter username" required> </div> <div class="form-group"> <label for="exampleInputPassword1">Password</label> <input type="password" class="form-control" name="password" id="exampleInputPassword1" placeholder="Password" required> </div> <button type="submit" class="btn btn-primary">Login</button> </form> </div> </div> </div> {% endblock %}

Let’s understand the code:

Line 11 to 24 – we check whether the form has errors and display them to the users.

Line 25 to 36 – we create our login form using bootstrap.

The last thing we need to do is to set our login URL in our settings.py file. Open webbasedauth/settings.py file and make sure it has the following code:

""" Django settings for webbasedauth project. Generated by 'django-admin startproject' using Django 2.2.1. For more information on this file, see https://docs.djangoproject.com/en/2.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.2/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '5d9n&(z$lj1ibvttm$*3$+e^9afmoc$arai-u!*%7c$o)(!3r#' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'person_portfolio.apps.PersonPortfolioConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] 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 = 'webbasedauth.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'webbasedauth.wsgi.application' # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/2.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', }, ] # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATIC_URL = '/static/' from django.urls import reverse_lazy LOGIN_URL = reverse_lazy('login_user')

Online 124, we import the reverse lazy method from Django urls. Online 125, we define our login URL and pass the name of our login URL. Make sure your development server is running and try to visit http://127.0.0.1:8000/dashboard you should see the following screen.

Phew! That was a lot of work but the results are encouraging. Try to login and see what happens.

Goal Achieved in this Lesson

Learned how to use the login_required decorator.

Learned how to set our login URL in the settings.py file.

Learned how to create our custom dashboard.

Learned how to create a login function.

With that, we conclude this lesson. Get the code of this tutorial series. See you in lesson 4.