iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
😎

Trying out Django User Authentication

に公開
2022/05/04

Overview

I wanted to try out the user authentication features of Django, a full-stack Python web framework, so I created a simple ToDo list and would like to introduce it here.
The prerequisite knowledge for this article is equivalent to the Django tutorial.
Additionally, I referred to the official documentation regarding Django's user authentication features.
The app I created is available in this GitHub repository.

An overview of the app is as follows:

  • Multiple tasks can be registered per user.
  • Viewing, updating, and deleting tasks can only be done by the user who registered them.
  • There are registration, login, and logout pages for user authentication.

The task editing screen is a bit hard to follow as I haven't put much effort into the HTML or CSS, but it looks like the image below.

Django Authentication Features

Creating a User

django.contrib.auth.models.User is Django's user model, and its main attributes are as follows:

  • username
  • password
  • email

Note that username and password are required fields.

To create a user, use the create_user() function as shown below.

from django.contrib.auth.models import User
user = User.objects.create_user('username', 'foo@example.com', 'password')

User Authentication

Django determines whether an HTTP request is authenticated or not.
The request request is provided with a request.user attribute representing the current user.
If the user is not currently logged in, this attribute is set to an instance of AnonymousUser; if they are logged in, it is set to an instance of User.
You can distinguish between the two using is_authenticated as follows:

if request.user.is_authenticated:
    # Do something for authenticated users.
    ...
else:
    # Do something for anonymous users.
    ...

To identify whether the logged-in user is a specific user foo_user, you can do the following:

if request.user == foo_user:
    # Do something for foo_user.
    ...

Login

To log in, use the login() function.
It is assumed that the username and password are retrieved from a POST request.

from django.contrib.auth import authenticate, login

def login_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

Logout

To log out, use the logout() function.

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

Introduction to the ToDo List App

An overview of the ToDo list app is as follows:

  • Multiple tasks can be registered per user.
  • Viewing, updating, and deleting tasks can only be done by the user who registered them.
  • There are registration, login, and logout pages for user authentication.

Referencing the Django User Model from the Task Model

The task model has attributes for the Django user and the task name only.
To reference the Django user model from the task model, use django.conf.settings.AUTH_USER_MODEL as a foreign key as shown below.
Note that the name attribute is made unique because I wanted to test the behavior during setup for reasons unrelated to this article, so please don't worry about it.

todo_list/models.py
from django.conf import settings
from django.db import models


class Task(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    name = models.CharField(max_length=255, unique=True)

    def __str__(self):
        return self.name

User Authentication for Task CRUD

Reading Tasks

We ensure that tasks cannot be viewed by third parties.
Using tasks = Task.objects.filter(user=request.user), only the tasks associated with the logged-in user are extracted and passed to the render() function.

Excerpt from todo_list/views.py
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import render

from .models import Task


def index(request: HttpRequest) -> HttpResponse:
    if not request.user.is_authenticated:
        return HttpResponseRedirect('login/')

    tasks = Task.objects.filter(user=request.user)
    return render(
        request,
        'todo_list/todo_list.html',
        {'username': request.user.username, 'tasks': tasks}
    )

The HTML template file is as follows:

todo_list/templates/todo_list/register.html
<h1>ToDo List of {{ username }}</h1>

{% if tasks %}
    <ul>
    {% for task in tasks %}
        <li>
        <div style="display:inline-flex">
            <form action="/task/update/" method="post">
                {% csrf_token %}
                <input type="hidden" name="id" value="{{ task.id }}">
                <input type="text" name="name" value="{{ task.name }}" required>
                <input type="submit" value="update">
            </form>
            <form action="/task/delete/" method="post">
                {% csrf_token %}
                <input type="hidden" name="id" value="{{ task.id }}">
                <input type="submit" value="delete">
            </form>
        </div>
        </li>
    {% endfor %}
    </ul>
{% else %}
    <p>No task is registered.</p>
{% endif %}

<form action="/task/create/" method="post">
    {% csrf_token %}
    New task:
    <input type="text" name="name" required>
    <input type="submit" value="add">
</form>

<form action="/logout/" method="post">
    {% csrf_token %}
    <input type="submit" value="logout">
</form>

Creating Tasks

Only logged-in users can create tasks.
When a task is created, a POST request is passed to create_task() via an HTML form.
In create_task(), it first uses check_task_request() to reject any requests that are not POST or are from unauthenticated users (returning a 404 response).
It then creates a task based on the name attribute in the POST request and finally redirects back to the task list page.

Excerpt from todo_list/views.py
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect
from .models import Task


def check_task_request(request: HttpRequest) -> None:
    if request.method != 'POST':
        raise Http404('Request method is not POST.')

    user=request.user
    if not user.is_authenticated:
        raise Http404('User is not authenticated.')


def create_task(request: HttpRequest) -> HttpResponse:
    check_task_request(request=request)

    name = request.POST.get('name')
    if name is None:
        Http404('Task name is not set.')

    task = Task(user=request.user, name=name)
    task.save()
    return HttpResponseRedirect('../../')

Updating and Deleting Tasks

Updating and deleting tasks can only be done by the user who created them.
Update and delete operations are passed as POST requests to update_task() and delete_task() respectively through HTML forms.
Just like with task creation, check_task_request() is used first to reject non-POST requests or requests from unauthenticated users (returning a 404 response).
In addition, the task to be updated or deleted is identified based on the id attribute in the POST request.
The if task.user != request.user part verifies that the user associated with the task matches the logged-in user, and returns a 404 response if they do not match.
If everything is correct, the update or deletion is executed, and the user is redirected to the task list page.

Excerpt from todo_list/views.py
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect
from .models import Task


def check_task_request(request: HttpRequest) -> None:
    if request.method != 'POST':
        raise Http404('Request method is not POST.')

    user=request.user
    if not user.is_authenticated:
        raise Http404('User is not authenticated.')


def update_task(request: HttpRequest) -> HttpResponse:
    check_task_request(request=request)

    id_ = request.POST.get('id')
    name = request.POST.get('name')
    if id is None or name is None:
        Http404('Task id or name is not set.')

    task = Task.objects.get(pk=id_)
    if task.user != request.user:
        raise Http404("Another user's task can't be edited.")

    task.name = name
    task.save()
    return HttpResponseRedirect('../../')


def delete_task(request: HttpRequest) -> HttpResponse:
    check_task_request(request=request)

    id_ = request.POST.get('id')
    if id is None:
        Http404('Task id is not set.')

    task = Task.objects.get(pk=id_)
    if task.user != request.user:
        raise Http404("Another user's task can't be edited.")

    task.delete()
    return HttpResponseRedirect('../../')

Conclusion

I have introduced the ToDo list app I created to test Django's user authentication features.

When developing web applications with Python, Flask is another popular choice alongside Django.
With Flask, you need to introduce plugin packages for database integration and authentication, and I chose Django because I found that selection process cumbersome.
After actually using it, I was able to use it without much difficulty.

It has been less than a week since I started using Django, so the code may not be perfect. If you notice anything, I would appreciate your feedback in the comments.

GitHubで編集を提案

Discussion