A complete Django entry guide (two)

A complete Django entry guide (two)

the third part

Introduction

       In this tutorial, we are going to dive deep into two fundamental concepts: URLs and Forms. In the process, we are going to explore many other concepts like creating reusable templates and installing third-party libraries. We are also going to write plenty of unit tests.

3.1.urls and views

Proceeding with the development of our application, now we have to implement a new page to list all the topics that belong to a given  Board . Just to recap, below you can see the wireframe we drew in the previous tutorial:

We will start by editing the  urls.py  inside the  myproject  folder:

myproject/urls.py

# mysite/urls.py

from django.conf.urls import url
from django.contrib import admin

from boards import views


urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', views.home,name='home'),
    url(r'^boards/(?P<pk>\d+)$', views.board_topics,name='board_topics'),
]
This time let's take a moment and analyze the urlpatterns and url.

The URL dispatcher and URLconf (URL configuration) are fundamental parts of a Django application. In the beginning, it can look confusing; I remember having a hard time when I first started developing with Django.

In fact, right now the Django Developers are working on a proposal to make simplified routing syntax. But for now, as per the version 1.11, that's what we have. So let's try to understand how it works.

A project can have many urls.py distributed among the apps. But Django needs a url.py to use as a starting point. This special urls.py is called root URLconf. It's defined in the settings.py file.

myproject/settings.py
      ROOT_URLCONF ='myproject.urls'

it already comes configured, so you don't need to change anything here.

When Django receives a request, it starts searching for a match in the project's URLconf. It starts with the first entry of the urlpatterns variable, and test the requested URL against each url entry.

If Django finds a match, it will pass the request to the view function, which is the second parameter of the url. The order in the urlpatterns matters, because Django will stop searching as soon as it finds a match. Now, if Django doesn 't find a match in the URLconf, it will raise a 404 exception, which is the error code for Page Not Found.

This is the anatomy of the url function:

   def url(regex, view, kwargs=None, name=None):
    # ...

regex: A regular expression for matching URL patterns in strings. Note that these regular expressions do not search GET or POST parameters. In a request to http://127.0.0.1:8000/boards/?page=2 only/boards/will be processed.
view: A view function used to process the user request for a matched URL. It also accepts the return of the django.conf.urls.include function, which is used to reference an external urls.py file. You can, for example, use it to define a set of app specific URLs, and include it in the root URLconf using a prefix. We will explore more on this concept later on.
kwargs: Arbitrary keyword arguments that's passed to the target view. It is normally used to do some simple customization on reusable views. We don't use it very often.
name: A unique identifier for a given URL. This is a very important feature. Always remember to name your URLs. With this, you can change a specific URL in the whole project by just changing the regex. So it's important to never hard code URLs in the views or templates, and always refer to the URLs by its name.

 Now let's create the view function  board_topics:

boards/views.py

from django.shortcuts import render,get_object_or_404

def board_topics(request,pk):
    board = get_object_or_404(Board,pk=pk)
    return render(request,'topics.html',{'board':board})

Create new topics.html, the code is as follows:

{#templates/topics.html#}

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ board.name }}</title>
    <link rel="stylesheet" href="{% static'css/bootstrap.min.css' %}">
</head>
<body>
    <div class="container">
        <ol class="breadcrumb my-4">
            <li class="breadcrumb-item">Boards</li>
            <li class="breadcrumb-item active">{{ board.name }}</li>
        </ol>
    </div>

</body>
</html>

Visit and see

Te next step now is to create the navigation links in the screens. The homepage should have a link to take the visitor to the topics page of a given  Board . Similarly, the topics page should have a link back to the homepage.

3.2. Improve home and topics page

 Edit the  home.html  template:

templates/home.html

 <td>
   <a href="{% url'board_topics' board.pk %}">{{ board.name }}</a>
   <small class="text-muted d-block">{{ board.description }}
 </td>

Now we can check how it looks in the web browser:

Update the board topics template:

templates/topics.html

 <ol class="breadcrumb my-4">
     <li class="breadcrumb-item"><a href="{% url'home' %}">Boards</a></li>
     <li class="breadcrumb-item active">{{ board.name }}</li>
 </ol>

3.3.List of Useful URL Patterns

 A list of the most commonly used URL patterns.

3.4. Create a motherboard

In this section we are going to refactor our HTML templates, creating a  master page  and only adding the unique part for each template.

Create a new file named  base.html  in the  templates  folder:

templates/base.htm

{#templates/base.html#}

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Django Boards{% endblock %}</title>
    <link rel="stylesheet" href="{% static'css/bootstrap.min.css' %}">
</head>
<body>
    <div class="container">
        <ol class="breadcrumb my-4">
            {% block breadcrumb %}

            {% endblock %}
        </ol>
        {% block content %}
        {% endblock %}
    </div>
</body>
</html>

templates/home.html

{#templates/home.html#}

{% extends'base.html' %}

{% block breadcrumb %}
    <li class="breadcrumb-item active">Boards</li>
{% endblock %}

{% block content %}


<table class="table">
    <thead class="thead-dark">
    <tr>
        <th>Boards</th>
        <th>Posts</th>
        <th>Topics</th>
        <th>Last post</th>
    </tr>

    </thead>

    <tbody>
    {% for board in boards %}
        <tr>
            <td>
                <a href="{% url'board_topics' board.pk %}">{{ board.name }}</a>
                <small class="text-muted d-block">{{ board.description }}
            </td>
            <td class="align-middle">0</td>
            <td class="align-middle">0</td>
            <td class="align-middle">0</td>
        </tr>
    {% endfor %}
    </tbody>
</table>
{% endblock %}

templates/topics.html

{#templates/topics.html#}

{% extends'base.html' %}

{% block title %}{{ board.name }}-{{ block.super }}{% endblock %}

{% block breadcrumb %}
    <li class="breadcrumb-item"><a href="{% url'home' %}">Boards</a></li>
    <li class="breadcrumb-item active">{{ board.name }}</li>
{% endblock %}

{% block content %}

{% endblock %}

In the  topics.html  template, we are changing the  {% block title %} default value. Notice that we can reuse the default value of the block by calling  {{ block.super }}. So here we are playing with the website title, which we defined in the  base.html  as “Django Boards ." So for the "Python" board page, the title will be "Python-Django Boards," for the "Random" board the title will be "Random-Django Boards."

Now that we have the  base.html  template, we can easily add a top bar with a menu:

Add top bar

templates/base.html

   <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url'home' %}">Django</a>
        </div>
    </nav>
{#templates/base.html#}

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Django Boards{% endblock %}</title>
    <link rel="stylesheet" href="{% static'css/bootstrap.min.css' %}">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url'home' %}">Django</a>
        </div>
    </nav>


    <div class="container">
        <ol class="breadcrumb my-4">
            {% block breadcrumb %}

            {% endblock %}
        </ol>
        {% block content %}
        {% endblock %}
    </div>
</body>
</html>

In base.html add the font template:

<head>
    <meta charset="UTF-8">
    <title>{% block title %}Django Boards{% endblock %}</title>
    <link rel="stylesheet" href="{% static'css/bootstrap.min.css' %}">
    <link href="https://fonts.googleapis.com/css?family=Peralta" rel="stylesheet">
    <link rel="stylesheet" href="{% static'css/app.css' %}">
</head>

Create a new CSS file named app.css in the static/css folder :

 static/css/app.css

.navbar-brand {
  font-family:'Peralta', cursive;
}

 Modify templates/topics.html

{#templates/topics.html#}

{% extends'base.html' %}

{% block title %}
    {{ board.name }}-{{ block.super }}
{% endblock %}

{% block breadcrumb %}
    <li class="breadcrumb-item"><a href="{% url'home' %}">Boards</a></li>
    <li class="breadcrumb-item active">{{ board.name }}</li>
{% endblock %}

{% block content %}
    <div class="mb-4">
        <a href="{% url'new_topic' board.pk %}" class="btn btn-primary">New topic</a>
    </div>
    <table class="table">
        <thead class="thead-inverse">
        <tr>
            <th>Topic</th>
            <th>Starter</th>
            <th>Replies</th>
            <th>Views</th>
            <th>Last Update</th>
        </tr>
        </thead>
        <tbody>
        {% for topic in board.topics.all %}
            <tr>
                <td>{{ topic.subject }}</td>
                <td>{{ topic.starter.username }}</td>
                <td>0</td>
                <td>0</td>
                <td>{{ topic.last_updated }}</td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}

Refresh, you can see that there is an option to add topic

3.5.Forms

Anyway, let's start by implementing the form below:

First thing, let's create a new URL route named  new_topic :

myproject/urls.py

urlpatterns = [
   url(r'^boards/(?P<pk>\d+)/new/$', views.new_topic,name='new_topic'),
]

Now let's create the  new_topic  view function:

boards/views.py

def new_topic(request,pk):
    board = get_object_or_404(Board,pk=pk)
    return render(request,'new_topic.html',{'board':board})

Now we just need a template named  new_topic.html  to see some code working:

templates/new_topic.html

{#templates/new_topic.html#}

{% extends'base.html' %}

{% block title %}Start a New Topic{% endblock %}


{% block breadcrumb %}
    <li class="breadcrumb-item"><a href="{% url'home' %}">Board</a></li>
    <li class="breadcrumb-item"><a href="{% url'board_topics' board.pk %}">{{ board.name }}</a></li>
    <li class="breadcrumb-item active">New Topic</li>
{% endblock %}

{% block content %}

{% endblock %}

Open the URL  http://127.0.0.1:8000/boards/2/new/ . The result, for now, is the following page:

Create a form

Let's create a new file named  forms.py  inside the  boards ' folder:

boards/forms.py

# boards/forms.py

from django import forms
from .models import Topic

class NewTopicForm(forms.ModelForm):
    message = forms.CharField(
        widget=forms.Textarea(attrs = {'row':5,'placeholder':'what is on your mind?'}), #Add attributes, number of rows and placeholders
        max_length=4000,
        help_text='The max length of the text is 4000' #Help information
    )

    class Meta:
        model = Topic                              
        fields = ['subject','message'] #Displayed fields

views.py

#views.py

from django.shortcuts import render,get_object_or_404,redirect
from django.contrib.auth.models import User
from .models import Board,Topic,Post
from .forms import NewTopicForm


#theme
def new_topic(request,pk):
    board = get_object_or_404(Board,pk=pk)
    #Get the currently logged-in user
    user = User.objects.first()
    if request.method =='POST':
        #InstanceA form instance
        form = NewTopicForm(request.POST)
        if form.is_valid():
            topic = form.save(commit=False)
            topic.board = board
            topic.starter = user
            topic.save()
            post = Post.objects.create(
                message = form.cleaned_data.get('message'),
                topic = topic,
                created_by = user
            )
            return redirect('board_topics',pk=board.pk) #Redirect to the created topic page

    else:
        form = NewTopicForm()
    return render(request,'new_topic.html',{'board':board,'form':form})

Templates/new_topic.html

{#templates/new_topic.html#}

{% extends'base.html' %}

{% block title %}Start a New Topic{% endblock %}


{% block breadcrumb %}
    <li class="breadcrumb-item"><a href="{% url'home' %}">Board</a></li>
    <li class="breadcrumb-item"><a href="{% url'board_topics' board.pk %}">{{ board.name }}</a></li>
    <li class="breadcrumb-item active">New Topic</li>
{% endblock %}

{% block content %}
    <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-success">Post</button>
    </form>
{% endblock %}

 Visit address: http://127.0.0.1:8000/boards/1/new/

Render the form with Bootstrap

When working with Bootstrap or any other Front-End library, I like to use a Django package called django-widget-tweaks . It gives us more control over the rendering process, keeping the defaults and just adding extra customizations on top of it.

Let's start off by installing it:

pip install django-widget-tweaks

Add to apps

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'boards',
    'widget_tweaks',
]

Modify templates/new_topic.html

{% extends'base.html' %}

{% load widget_tweaks %}

{% block title %}Start a New Topic{% endblock %}

{% block breadcrumb %}
    <li class="breadcrumb-item"><a href="{% url'home' %}">Boards</a></li>
    <li class="breadcrumb-item"><a href="{% url'board_topics' board.pk %}">{{ board.name }}</a></li>
    <li class="breadcrumb-item active">New topic</li>
{% endblock %}

{% block content %}
    <form method="post" novalidate>
        {% csrf_token %}

        {% for field in form %}
            <div class="form-group">
                {{ field.label_tag }}

                {% if form.is_bound %}
                    {% if field.errors %}

                        {% render_field field class="form-control is-invalid" %}
                        {% for error in field.errors %}
                            <div class="invalid-feedback">
                                {{ error }}
                            </div>
                        {% endfor %}

                    {% else %}
                        {% render_field field class="form-control is-valid" %}
                    {% endif %}
                {% else %}
                    {% render_field field class="form-control" %}
                {% endif %}

                {% if field.help_text %}
                    <small class="form-text text-muted">
                        {{ field.help_text }}
                    </small>
                {% endif %}
            </div>
        {% endfor %}

        <button type="submit" class="btn btn-success">Post</button>
    </form>
{% endblock %}

 In the templates folder, create a new folder called includes

In the includes folder, create a file named form.html :

{#templates/includes/form.html#}

{% load widget_tweaks %}

{% for field in form %}
  <div class="form-group">
    {{ field.label_tag }}

    {% if form.is_bound %}
      {% if field.errors %}
        {% render_field field class="form-control is-invalid" %}
        {% for error in field.errors %}
          <div class="invalid-feedback">
            {{ error }}
          </div>
        {% endfor %}
      {% else %}
        {% render_field field class="form-control is-valid" %}
      {% endif %}
    {% else %}
      {% render_field field class="form-control" %}
    {% endif %}

    {% if field.help_text %}
      <small class="form-text text-muted">
        {{ field.help_text }}
      </small>
    {% endif %}
  </div>
{% endfor %}

To change our new_topic.html template:

{#templates/new_topic.html#}

{% extends'base.html' %}

{% block title %}Start a New Topic{% endblock %}

{% block breadcrumb %}
  <li class="breadcrumb-item"><a href="{% url'home' %}">Boards</a></li>
  <li class="breadcrumb-item"><a href="{% url'board_topics' board.pk %}">{{ board.name }}</a></li>
  <li class="breadcrumb-item active">New topic</li>
{% endblock %}

{% block content %}
  <form method="post" novalidate>
    {% csrf_token %}
    {% include'includes/form.html' %}
    <button type="submit" class="btn btn-success">Post</button>
  </form>
{% endblock %}

 the fourth part

 Introduction

This tutorial is going to be all about Django's authentication system. We are going to implement the whole thing: registration, login, logout, password reset, and password change.

You are also going to get a brief introduction on how to protect some views from non-authorized users and how to access the information of the logged in user.

Wireframes

We have to update the wireframes of the application. 1. we are going to add new options for the top menu. If the user is not authenticated, we should have two buttons: sign up and log in.

If the user is authenticated, we should display their name and a drop-down menu with three options: My Account, Change Password and Logout.

On the login page, we need a form with a username and password , a button with the main operation (login) and two alternate paths: the registration page and the password reset page.

On the registration page, we should have a form with four fields: username , email address , password, and  password confirmation . The user should also be able to access the login page.

 On the password reset page, we will have a form with only an email address .

Then, after clicking on a special token link, the user will be redirected to a page where they can set a new password:

default setting

 To manage all this information, we can break it down in a different app. In the project root, in the same page where the  manage.py  script is, run the following command to start a new app:

django-admin startapp accounts

Next step, include the  accounts  app to the  INSTALLED_APPS in the  settings.py  file:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts',
    'boards',
    'widget_tweaks',
]

4.1. Registration

(1) add registration in myproject/urls.py

from accounts import views as accounts_views

urlpatterns = [
    url(r'^signup/$', accounts_views.signup,name='signup'),
]   

(2) views.py

from django.shortcuts import render
from django.contrib.auth.forms import UserCreationForm


def signup(request):
    form = UserCreationForm()
    return render(request,'signup.html',{'form':form})

(3) Modify base.html

Add {% block stylesheet %}{% endblock %} and{% block body %}{% endblock body %}

{#templates/base.html#}

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Django Boards{% endblock %}</title>
    <link rel="stylesheet" href="{% static'css/bootstrap.min.css' %}">
    <link href="https://fonts.googleapis.com/css?family=Peralta" rel="stylesheet">
    <link rel="stylesheet" href="{% static'css/app.css' %}">
    {% block stylesheet %}{% endblock %}
</head>
<body>
{% block body %}

    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url'home' %}">Django Boards</a>
        </div>
    </nav>


    <div class="container">
        <ol class="breadcrumb my-4">
            {% block breadcrumb %}

            {% endblock %}
        </ol>
        {% block content %}
        {% endblock %}
    </div>
{% endblock %}
</body>
</html>

(4) Create signup.html, the code is as follows

{#templates/signup.html#}

{% extends'base.html' %}

{% block body %}
    <div class="container">
        <h2>Sign up</h2>
        <form method="post" novalidate>
            {% csrf_token %}
            {% include'includes/form.html' %}
            <button type="submit" class="btn btn-primary">Create an account</button>
        </form>
    </div>
{% endblock %}

(5) Modify form.html

Add save

{{ field.help_text|safe }}
{#templates/includes/form.html#}

{% load widget_tweaks %}

{% for field in form %}
  <div class="form-group">
    {{ field.label_tag }}

    {% if form.is_bound %}
      {% if field.errors %}
        {% render_field field class="form-control is-invalid" %}
        {% for error in field.errors %}
          <div class="invalid-feedback">
            {{ error }}
          </div>
        {% endfor %}
      {% else %}
        {% render_field field class="form-control is-valid" %}
      {% endif %}
    {% else %}
      {% render_field field class="form-control" %}
    {% endif %}

    {% if field.help_text %}
      <small class="form-text text-muted">
        {{ field.help_text|safe }}
      </small>
    {% endif %}
  </div>
{% endfor %}

Visit: http://127.0.0.1:8000/signup/

(6) The business logic is implemented in the registration view:

def signup(request):
    if request.method =='POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request,user)
            return redirect('home')

    else:
        form = UserCreationForm()
    return render(request,'signup.html',{'form':form})

Then register a user "test1" to see if it jumps to the home page

(7) Reference to verified users in the template

How do we know if it works? Then, we can edit the base.html template to add the user’s name on the top bar

{#templates/base.html#}

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Django Boards{% endblock %}</title>
    <link rel="stylesheet" href="{% static'css/bootstrap.min.css' %}">
    <link href="https://fonts.googleapis.com/css?family=Peralta" rel="stylesheet">
    <link rel="stylesheet" href="{% static'css/app.css' %}">
    {% block stylesheet %}{% endblock %}
</head>
<body>
{% block body %}

    <nav class="navbar navbar-expand-sm navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url'home' %}">Django Boards</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mainMenu"
                    aria-controls="mainMenu" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="mainMenu">
                <ul class="navbar-nav ml-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="#">{{ user.username }}</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>


    <div class="container">
        <ol class="breadcrumb my-4">
            {% block breadcrumb %}

            {% endblock %}
        </ol>
        {% block content %}
        {% endblock %}
    </div>
{% endblock %}
</body>
</html>

Mainly used {{ user.username }} to display the current login user name

(8) Add email field

UserCreationForm will not provide an email field. But we can extend it.

Create a file named forms.py in the accounts folder:

accounts/forms.py

# accounts/forms.py

from django.contrib.auth.forms import UserCreationForm
from django import forms
from django.contrib.auth.models import User

class SignUpForm(UserCreationForm):
    email = forms.EmailField(max_length=254,required=True)

    class Meta:
        model = User
        fields = ('username','email','password1','password2')

Modify views.py again

def signup(request):
    if request.method =='POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request,user)
            return redirect('home')

    else:
        form = SignUpForm()
    return render(request,'signup.html',{'form':form})

You can see that the email field has been added

(9) Improve registration template

Find a background image as the background of the account page

Create an image folder under the static folder

Create a new CSS file named accounts.css in static/css .

 The css/accounts.css code is as follows:

body {
  background-image: url(../image/background_login.jpg);
}

.logo {
  font-family:'Peralta', cursive;
}

.logo a {
  color: rgba(0,0,0,.9);
}

.logo a:hover,
.logo a:active {
  text-decoration: none;
}

Reference the new css in templates/signup.html and add Bootstrap4 components

{#templates/signup.html#}

{% extends'base.html' %}
{% load staticfiles %}

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

{% block body %}
  <div class="container">
    <h1 class="text-center logo my-4">
      <a href="{% url'home' %}">Django Boards</a>
    </h1>
    <div class="row justify-content-center">
      <div class="col-lg-8 col-md-10 col-sm-12">
        <div class="card">
          <div class="card-body">
            <h3 class="card-title">Sign up</h3>
            <form method="post" novalidate>
              {% csrf_token %}
              {% include'includes/form.html' %}
              <button type="submit" class="btn btn-primary btn-block">Create an account</button>
            </form>
          </div>
          <div class="card-footer text-muted text-center">
            Already have an account? <a href="#">Log in</a>
          </div>
        </div>
      </div>
    </div>
  </div>
{% endblock %}

Register now page

4.2. Logout

(1) Add logout routing

from django.contrib.auth import views as auth_views


url(r'^logout/$', auth_views.LogoutView.as_view(),name='logout'),

We imported the view from Django's contrib module . We renamed it to auth_views to avoid conflicts with boards.views . Note that this view is a bit different:LogoutView.as_view() . This is a Django class-based view. So far, we have only implemented classes as Python functions. Class-based views provide a more flexible way to extend and reuse views. We will discuss more of this topic later.

Open the settings.py file and add the LOGOUT_REDIRECT_URLvariables to the bottom of the file:

myproject/settings.py

LOGOUT_REDIRECT_URL ='home'

Here we pass the name of the URL pattern we want to redirect the user after logging out.

 (2) Add user display menu

First download: 

Create a js folder under the static folder, and put the above three files under the js folder

 Modify base.html, add Bootstrap4 drop-down menu

<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
  <div class="container">
    <a class="navbar-brand" href="{% url'home' %}">Django Boards</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mainMenu" aria-controls="mainMenu" aria-expanded="false" aria-label="Toggle navigation ">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="mainMenu">
      {% if user.is_authenticated %}
        <ul class="navbar-nav ml-auto">
          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" id="userMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
              {{ user.username }}
            </a>
            <div class="dropdown-menu dropdown-menu-right" aria-labelledby="userMenu">
              <a class="dropdown-item" href="#">My account</a>
              <a class="dropdown-item" href="#">Change password</a>
              <div class="dropdown-divider"></div>
              <a class="dropdown-item" href="{% url'logout' %}">Log out</a>
            </div>
          </li>
        </ul>
      {% else %}
        <form class="form-inline ml-auto">
          <a href="#" class="btn btn-outline-secondary">Log in</a>
          <a href="{% url'signup' %}" class="btn btn-primary ml-2">Sign up</a>
        </form>
      {% endif %}
    </div>
  </div>
</nav>

 When logging in:

Click Log out, log out the currently logged-in user, and jump to the home page

When not logged in, login and sign up are displayed

 4.3. Login

(1) Add login route

url(r'^login/$', auth_views.LoginView.as_view(template_name='login.html'), name='login'),

settings add

LOGIN_REDIRECT_URL ='home'

This configuration tells Django where to redirect the user after a successful login.

 Modify the login url in base.html

<a href="{% url'login' %}" class="btn btn-outline-secondary">Log in</a>

(2) Create login.html

{% extends'base.html' %}

{% load static %}

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

{% block body %}
  <div class="container">
    <h1 class="text-center logo my-4">
      <a href="{% url'home' %}">Django Boards</a>
    </h1>
    <div class="row justify-content-center">
      <div class="col-lg-4 col-md-6 col-sm-8">
        <div class="card">
          <div class="card-body">
            <h3 class="card-title">Log in</h3>
            <form method="post" novalidate>
              {% csrf_token %}
              {% include'includes/form.html' %}
              <button type="submit" class="btn btn-primary btn-block">Log in</button>
            </form>
          </div>
          <div class="card-footer text-muted text-center">
            New to Django Boards? <a href="{% url'signup' %}">Sign up</a>
          </div>
        </div>
        <div class="text-center py-2">
          <small>
            <a href="#" class="text-muted">Forgot your password?</a>
          </small>
        </div>
      </div>
    </div>
  </div>
{% endblock %}

 (3)base_accounts.html

Create a master, let login.html and signup.html inherit

base_accounts.html

{% extends'base.html' %}

{% load static %}

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

{% block body %}
  <div class="container">
    <h1 class="text-center logo my-4">
      <a href="{% url'home' %}">Django Boards</a>
    </h1>
    {% block content %}
    {% endblock %}
  </div>
{% endblock %}

 Modify login.html

{% extends'base_accounts.html' %}

{% block title %}Log in to Django Boards{% endblock %}

{% block content %}
  <div class="row justify-content-center">
    <div class="col-lg-4 col-md-6 col-sm-8">
      <div class="card">
        <div class="card-body">
          <h3 class="card-title">Log in</h3>
          <form method="post" novalidate>
            {% csrf_token %}
            {% include'includes/form.html' %}
            <button type="submit" class="btn btn-primary btn-block">Log in</button>
          </form>
        </div>
        <div class="card-footer text-muted text-center">
          New to Django Boards? <a href="{% url'signup' %}">Sign up</a>
        </div>
      </div>
      <div class="text-center py-2">
        <small>
          <a href="#" class="text-muted">Forgot your password?</a>
        </small>
      </div>
    </div>
  </div>
{% endblock %}

 Modify signup.html

{% extends'base_accounts.html' %}

{% block title %}Sign up to Django Boards{% endblock %}

{% block content %}
  <div class="row justify-content-center">
    <div class="col-lg-8 col-md-10 col-sm-12">
      <div class="card">
        <div class="card-body">
          <h3 class="card-title">Sign up</h3>
          <form method="post" novalidate>
            {% csrf_token %}
            {% include'includes/form.html' %}
            <button type="submit" class="btn btn-primary btn-block">Create an account</button>
          </form>
        </div>
        <div class="card-footer text-muted text-center">
          Already have an account? <a href="{% url'login' %}">Log in</a>
        </div>
      </div>
    </div>
  </div>
{% endblock %}
Reference: https://cloud.tencent.com/developer/article/1091550 A complete guide to getting started with Django (2)-Cloud + Community-Tencent Cloud