Add authentication features.
Tags: django, beginners, codenewbie, tutorial
Published: 2022-05-08 20:45:40

![Django and Netflix](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4qht1jfclf5qdz799mo.png)

Hello, welcome to the second part of this tutorial-course in which you'll learn and understand the basics of Django by building a clone of the popular website [Netflix](https://www.netflix.com/).
_________________________________________


We are going to add authentication features. Basically, we will allow users to login and register. 
Let's get started.

## Add website templates
For this project, we won't write our own Html or CSS files. Instead, we will use the work done by Carlos Avila available here: [Netflix Clone](https://codepen.io/cb2307/pen/XYxyeY). Thank to him.
If you open that codepen you'll see an HTML code and some CSS.

![Netflix Clone by Carlos Avila](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhc5uw67jkt1dprcgsmk.png)

### Create templates folder
Django recommends putting our Html files or templates inside a directory called `templates` at the root of the project. Then inside that directory, we can create a folder for each app of the project to separate each app template. This is possible because of the line: `'BACKEND': 'django.template.backends.django.DjangoTemplates',` in the setting file.
In addition, modify line 58 of the settings file like this:
`'DIRS': [os.path.join(BASE_DIR, 'templates')],`, to tell Django that he should look for templates inside a folder named `templates`.
- create the `templates` folder at the root of the project:
`mkir templates`.
- create a folder named `netflix` for our netflix app inside it:
    - `cd templates`
    - `mkir netflix`
- create an index.html file inside it: 
    - `cd netflix`
    - `touch index.html`. I made a lighter version of the codepen html available here [https://github.com/fayomihorace/django-netflix-clone/blob/main/templates/netflix/full_index_light.html](https://github.com/fayomihorace/django-netflix-clone/blob/main/templates/netflix/full_index_light.html).
Click to open the link on a browser, copy the html and paste it into your `index.html` file.

Good. Our homepage is created. Now we need to access it from the browser. And for that we need a `route`.

### Django URLs
Remember in part1 we saw that the `urls.py` file contains all the routes of our project.
We only have one route to access the admin page.
Now we will add a route for the home page with this line:
Modify it like this:
```python
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static

from netflix.views import index_view # Add this line

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index_view, name='home'), # Add this line
]
```
We should have an error because the module `index_view` imported by the line `from netflix.views import index_view` doesn't yet exist.
It's the view that will be executed when someone will call the path named `home` we have just added at line 
`path('', index_view, name='home')`. That means we should be able to access the homepage with our website host URL. Locally it's just `http://localhost:8000`.

So we need to create `index_view` that will render the homepage to requesters.

- open the `netflix/views.py` file and add this code:

```python
def index_view(request, path):
    """Home page view."""
    return render(request, 'index.html')
``` 
There we go.

- serve the project: 'python manage.py runserver'
- Open your browser, and go to `http://localhost:8000`
You should see something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3zloeimhq1cwf4pyoal7.png)

That means we can access our homepage. But the render is not the right because we didn't yet add the styles.

That is what we will do now.

### Static files
In Django, CSS, js, and images needed to render the website are called `statics` that's why Django recommends placing them in a folder called `statics`.
Let's go:
- First, create a directory called `static` at the root of the project. Django will look for static files there similar to how Django finds templates inside `templates`: `mkdir static`.
- create a folder for `netflix` app similarly to templates: 
    - `cd static`
    - `mkdir netflix`
- create a file called `style.css` inside it: `touch style.css` and copy the CSS part of the codepen into it.

We had already specified the statics files URL with line:
`STATIC_URL = 'static/'`.

That means we should access them like this:
`localhost:8000/static/style.css`

- Now adds this `{% load static %}` at the beginning of the `index.html` file. That adds the `static` tag that could be used to embed links from static files.
And then use that `static` tag to import our style.css.
- Adds this line `<link rel="stylesheet" href="{% static 'netflix/style.css' %}">` into the `<head>` tag of the index.html file.
- Refresh the home page.

There we go.

### Registration and login templates
First, we will add a `registration` and `login` buttons to the homepage.
- In the index.html file replace `<a href="#">Account</a>` by `<a href="/register">Register</a><a href="/login">Login</a>`
The clicks on `register` button should redirect the user to the route `register` which will render the registration page and in the same way clicking on the `login` button will redirect the user to route `login` that should render the `login` page.

- Add the `register_view` and `login_view` inside the `netflix/views.py` file:
```python
def register_view(request):
    """Registration view."""
    return render(request, 'netflix/register.html')

def login_view(request):
    """Login view."""
    return render(request, 'netflix/login.html')
```

- Modify `urls.py` file to add the registration and login routes:
```python
...
from netflix.views import register_view # Add this line
from netflix.views import login_view # Add this line

urlpatterns = [
    ...
    path('register', register_view, name='register'), # Add this line
    path('login', login_view, name='login'), # Add this line
]
```

- create `register.html` and `login.html` files inside `templates/netlfix/`.
We'll created a registration HTML inspired by the index file. 
    - For `registration.html` file, You just have to copy and past the following html code:
```html
{% load static %}
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Netflix</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script defer src="https://use.fontawesome.com/releases/v5.1.0/js/all.js"  crossorigin="anonymous"></script>
  <script src="main.js"></script>
  <link rel="stylesheet" href="{% static 'netflix/style.css' %}">
</head>
<body>
  <div class="wrapper">

    <!-- HEADER -->
    <header>
      <div class="netflixLogo">
        <a id="logo" href="/"><img src="https://github.com/carlosavilae/Netflix-Clone/blob/master/img/logo.PNG?raw=true" alt="Logo Image"></a>
      </div>      
      <nav class="main-nav">                
        <a href="/">Home</a>  
      </nav>
      <nav class="sub-nav">
        <a href="/login">Login</a>
      </nav>
    </header>
    <section class="main-container" >
      <div class="location" id="home">
          <div class="box">
          </div>
      </div>
    </section>
  </div>
</body>
</html>
```

- For `login.html` file, You just have to copy and past the following html code:
```html
{% load static %}
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Netflix</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script defer src="https://use.fontawesome.com/releases/v5.1.0/js/all.js"  crossorigin="anonymous"></script>
  <script src="main.js"></script>
  <link rel="stylesheet" href="{% static 'netflix/style.css' %}">
</head>
<body>
  <div class="wrapper">

    <!-- HEADER -->
    <header>
      <div class="netflixLogo">
        <a id="logo" href="/"><img src="https://github.com/carlosavilae/Netflix-Clone/blob/master/img/logo.PNG?raw=true" alt="Logo Image"></a>
      </div>      
      <nav class="main-nav">                
        <a href="/">Home</a>  
      </nav>
      <nav class="sub-nav">
        <a href="/register">Register</a>
      </nav>
    </header>
    <section class="main-container" >
      <div class="location" id="home">
          <div class="box">
          </div>
      </div>
    </section>
  </div>
</body>
</html>
```

### Django Forms: Add registration and login forms
Django allows us to create and manage forms easily.
Let's create a file `netlfix/forms.py` to put forms related to Netflix app:
- inside `netlfix` folder create `forms.py` file: `touch forms.py`
- Add this code to it:
```python
from django import forms
from django.contrib.auth.models import User


class RegisterForm(forms.Form):
    """Registration form class."""

    firstname = forms.CharField(label="First name")
    lastname = forms.CharField(label="Last name")
    email = forms.EmailField(label="Email Address")
    password = forms.CharField(label="Password", widget=forms.PasswordInput)
    password_conf = forms.CharField(label="Password confirmation", widget=forms.PasswordInput)


class LoginForm(forms.Form):
    """Login form class."""

    email = forms.EmailField(label="Email Address")
    password = forms.CharField(label="Password", widget=forms.PasswordInput)
```

- Modify the `register_view` like this:
```python
from .forms import RegisterForm
...
...
def register_view(request):
    """Registration view."""
    register_form = RegisterForm()
    return render(request, 'netflix/register.html', locals())
```
Note that the argument `locals()` passed to `render()` is to send all local variables of the view (register_view) to the HTML template (here `register.html`). That means we can access our form in `register.html` as a variable named `register_form`. That feature is customizable.

- In `register.html`, modify the `<div class="box">` tag like this:
```html
<div class="box">
    <form id="registerForm" action="/register" method="POST">
        {% csrf_token %}
        {{ register_form.as_p }}
        <button type="submit">Register</button>
    </form>
</div>
```
Important note: You will notice the syntax `{{ register_form.as_p }}` and `{% csrf_token %}`. Remember I said, the `locals()` arguments send all the registration view local variables to the template the variable `register_form` is sent to the HTML template (`register.html`). Then to access those locals in `register.html` we just need to wrap the variable name with `{{ }}` as it's done for `{{ register_form.as_p }}`. Now for `{% csrf_token %}` it's a little bit different as it's not rendered as a variable but as a `Tag`. If you want to know more, read this [part of the documentation](https://docs.djangoproject.com/en/4.0/topics/templates/#tags). And  `{% csrf_token %}` allows us to set the `csrf_token` value of user session to prevent [CSRF attacks](https://portswigger.net/web-security/csrf/tokens#:~:text=CSRF).


Refresh the register page.
Django will automatically generate the HTML code of the form. Isn't it magic ^_^ ? And we can customize it, but not in this course.
It should look like this now:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bfwrhix392n8bzdxdqb8.png)

- Modify the `login_view` like this:
```python
...
from .forms import LoginForm  # add this line
...
...
def login_view(request):
    """Login view."""
    login_form = LoginForm()
    return render(request, 'netflix/login.html', locals())
```
- In `login.html`, modify the `<div class="box">` tag like this:
```html
<div class="box">
    <form id="loginForm" action="/login" method="POST">
        {% csrf_token %}
        {{ login_form.as_p }}
        <button type="submit">Login</button>
    </form>
</div>
```
Refresh the login page.
It should look like this now:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lpdlf5eiq417i7ylmpfo.png)


### Handle the forms submission.
#### Registration form
Now, if a user fills in the form and submits it, he should be able to register.

If you look at the registration form HTML code, you'll notice that the form `action` HTML property value is the `/register` (the registration route in our `url.py`).
We will modify the `register_view` to handle the form submission. (But it's up to you to move the post inside another view).
- Modify the `register_view` like this:
```python
from django.http import HttpResponseRedirect
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password
...
...
def register_view(request):
    """Registration view."""
    if request.method == 'GET':
        # executed to render the registration page
        register_form = RegisterForm()
        return render(request, 'netflix/register.html', locals())
    else:
        # executed on registration form submission
        register_form = RegisterForm(request.POST)
        if register_form.is_valid():
            User.objects.create(
                first_name=request.POST.get('firstname'),
                last_name=request.POST.get('lastname'),
                email=request.POST.get('email'),
                username=request.POST.get('email'),
                password=make_password(request.POST.get('password'))
            )
            return HttpResponseRedirect('/login')
        return render(request, 'netflix/register.html', locals())
```
Now our `register_view` handle both the rendering of the registration page under condition `if request.method == 'GET':` and also the submission of the registration form in the `else` condition. And as I said it's up to you to handle them with two different views.
Let explains what we are doing in the `else`:
- `form = RegisterForm(request.POST)` is the submission of the POST request
- `if form.is_valid():` allows us to handle errors. We will come on it at the end of this part when we do the form validations. Basically, that line checks if our form is filled with good values.
- If it's the case, `User.objects.create(` is executed to create the User model instance.
- And `return HttpResponseRedirect('/login')` redirect the user to `login.page`
- If the form is not valid, then the `else:` condition is executed instead
- We then redirect the user to the register page and Django will automatically display validation errors to the user, so he could fix them and submit the form again. Again isn't that magic ^_^.

#### Registration Form validation
We might have some constraints for our data. 
- password mininum length is 6 and maxinum length is 20: inside `forms.py`, modify line `password = forms.CharField(label="Password", widget=forms.PasswordInput)` into `password = forms.CharField(label="Password", widget=forms.PasswordInput(render_value=True), min_length=6, max_length=20)`. (You'll notice we have added `render_value=True`, it's to tells django to keep the value of the password when the form is returned back with errors).

- the password confirmation should match the password and the email shouldn't exist in the database. To handle those two validation constraints, we will override the default behavior of a Django Form class method `clean`. So modify the `RegistrationForm` like this:

```python
...
class RegisterForm(forms.Form):
    """Registration form class."""

    ....

    # Add this methods
    def clean(self):
        """Check if the form is validated."""
        # call the default `clean` method to perform 
        # default validation of the form (max_lenght and min_length defined for password for instance)
        super(RegisterForm, self).clean()

        # extract user input data
        email = self.cleaned_data.get('email')
        password = self.cleaned_data.get('password')
        password_conf = self.cleaned_data.get('password_conf')

        # Check if the password match the password confirmation
        if password != password_conf:
            self._errors['password_conf'] = self.error_class([
                "wrong confirmation"
            ])

        # Check if the email used doen't already exist
        if User.objects.filter(username=email).exists():
            self._errors['email'] = self.error_class(['Email already exist'])

        # return any errors if found
        return self.cleaned_data
```
The method is documented enough to explain what it does.
And there we go.

Note: For password validators, you can use the settings `AUTH_PASSWORD_VALIDATORS`, use default Django validators or create your own ones. But in this tutorial, we will use instead Django form. Let me know in the comments if you want a tutorial on how to build custom password validators.

Try to register a user with wrong input and then with good input.
After you're done. Login to `django admin` with the superuser, and go to `http://localhost:8000/admin/auth/user/`. You'll see the new user added. You can also test in django shell:
```bash
from django.contrib.auth.models import User
User.objects.all()
```. You should see the new user.

#### Login form
Now, a registered user should be able to login.

If you look at the login form HTML code, you'll notice that the form `action` HTML property value is the `/login` (the login route in our `url.py`).
We will modify the `login_view` to handle the form submission.
- Modify the `login_view` like this:
```python
...
from django.contrib.auth import authenticate, login  # Add this line

from .forms import LoginForm # Add this line
...
...
def login_view(request):
    """Login view."""
    if request.method == 'GET':
        # executed to render the login page
        login_form = LoginForm()
        return render(request, 'netflix/login.html', locals())
    else:
        # get user credentials input
        username = request.POST['email']
        password = request.POST['password']
        # If the email provided by user exists and match the
        # password he provided, then we authenticate him.
        user = authenticate(username=username, password=password)
        if user is not None:
            # if the credentials are good, we login the user
            login(request, user)
            # then we redirect him to home page
            return HttpResponseRedirect('/')
        # if the credentials are wrong, we redirect him to login and let him know
        return render(
            request,
            'netflix/login.html',
            {
                'wrong_credentials': True,
                'login_form': LoginForm(request.POST)
            }
        )
```
The method is documented enough.
- Modify the `login.html` file from this:
```html
<form id="loginForm" action="/login" method="POST">
    {% csrf_token %}
    {{ login_form.as_p }}
    <button type="submit">Login</button>
</form>
```
into this:
```html
<form id="loginForm" action="/login" method="POST">
    {% csrf_token %}
    {% if wrong_credentials %}
        <p style="margin-top: 60px">Invalid credentials.</p>
    {% endif %}
    {{ login_form.as_p }}
    <button type="submit">Login</button>
</form>
```
Note that we have just added:
```html
{% if wrong_credentials %}
    <p style="margin-top: 60px">Invalid credentials.</p>
{% endif %}
```
Important note: That is called `template conditional rendering`. Django allows us to render some HTML according to some conditions. So here we display the message `Invalid credentials` if the view returns the variable `wrong_credentials` with the value `True`. 

- Now when the user is authenticated and redirected to home page, we will render his username and a logout button instead of `login` and `register` buttons. Modify the line
```html
<a href="/register">Register</a><a href="/login">Login</a> 
``` of `index.html` file into theses ones:
```html
{% if request.user.is_authenticated %}
  {{ request.user }}
  <a href="/logout"><i class="fas fa-power-off sub-nav-logo"></i>Logout</a>
{% else %}
  <a href="/register">Register</a><a href="/login">Login</a>
{% endif %}
```
Note: We used again conditional rendering here to achieve our goal with `{% if request.user.is_authenticated %}`. But you might be asking that `we didn't send that variable (request) to the HTML template`. And you're right. We are able to access the variable `request` because it's a global (context) variable automatically sent by Django to allow us to get useful information about the current request. So if `{{ request.user.is_authenticated }}` is `True`, user is authenticated, and `request.user` is the User model instance of the authenticated user. And we can then display the user email using `request.user.username` or directly by `request.user`. But if instead `{{ request.user.is_authenticated }}` is `False`, we display the registration and login buttons.

If you login with the right credentials, you should now be able to see the authenticated user email and a log out button like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wudo3w1cajx4mcc5udaj.png)


### Handle Logout
An authenticated user should be able to logout.
We already have a logout button on index.html:
```html
<a href="/logout"><i class="fas fa-power-off sub-nav-logo"></i>Logout</a>
```
Note that the link point to `logout` route that doesn't yet exist.
Let's add it.
- At the bottom of the `views.py` file, add the logout_view:
```python
...
...
def logout_view(request):
    """Logout view."""
    # logout the request
    logout(request)
    # redirect user to home page
    return HttpResponseRedirect('/')
```
- Add the logout view into `urls.py` file:

```python
...
...
from netflix.views import logout_view # Add this line

urlpatterns = [
    ...
    ...
    path('logout', logout_view, name='logout'), # Add this line
]
```

### Congratulations.
We are at the end of this second part.


# Summary
In this tutorial you've learned
- how to manage Django template
- how to render variables inside the template
- how to conditionally render HTML inside the template
- how to manage Django static files
- how to create Django forms and handle validation
- how to access and use `request` variable inside Django template
- how to handle basic authentication in Django (registration, login, and logout).


NB: The full source code is available here:  [https://github.com/fayomihorace/django-netflix-clone](https://github.com/fayomihorace/django-netflix-clone)

If you face any blocker or error while following this tutorial, please drop a comment, I'll reply to help you as soon as possible. Excited to have you in part 3 of this tutorial-course.