🌏

Python、Django ログイン、チュートリアル(パスワード強化版)

に公開

今回は、Django のログイン、チュートリアルを書こうと思います。
パスワードを強化した、ログイン機能のみに、ほぼ特化したチュートリアルを実装します。
ややこしくなるかもですが、なるたけシンプルにわかりやすくをモットーに書こうと思うので、よろしくお願いしまっす!
ちょぃ長めになりますが、いきまっす♪


環境

Windows10、VSCode、Django(v5.1.7)


フォルダ構成

(注:最終、作成・変更したファイルのみです)

ディレクトリ構成
login
  ├ settings.py
  ├ urls.py
  ├ templates(以下、省略)
  └ user
    ├ forms.py
    ├ urls.py
    └ views.py

今回やること

  1. 初期設定
  2. サインアップ・ログイン機能の実装
  3. ログアウト機能の実装
  4. 今回のプロジェクト(コード)

1. 初期設定

通常の初期設定を行い、今回はプロジェクト全体でテンプレート・フォルダを使う仕様にします。
Django デフォルトでは、アプリフォルダの中に templates を作成すれば、設定なしで認識してくれますが、プロジェクト全体でテンプレート・フォルダを使う場合は、ディレクトリの設定が必要です。
その場合、設定したディレクトリが優先されるようです。

まず、プロジェクトを作成します。

コマンド・プロンプト
django-admin startproject login

アプリを作ります。

コマンド・プロンプト
cd login
python manage.py startapp user

初期設定、パスワード強化設定をします。
PASSWORD_HASHERS を追加します。
passwords: https://docs.djangoproject.com/ja/5.2/topics/auth/passwords/
BCrypt を使うので bcrypt パッケージをインストールします。

コマンド・プロンプト
pip install bcrypt
setttings.py
import os
...
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')  # 追加
...
INSTALLED_APPS = [
    ...
    'user',  # 追加
]
...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATE_DIR],  # 追加
...
# 追加
PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.ScryptPasswordHasher",
]

AUTH_PASSWORD_VALIDATORS = [
...
LANGUAGE_CODE = 'ja'  # 変更

TIME_ZONE = 'Asia/Tokyo'  # 変更
...
STATIC_URL = '/static/'  # / スラッシュ追加(参考まで)

マイグレートします。

コマンド・プロンプト
python manage.py migrate

URL パターンを設定していきます。

login/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('user/', include('user.urls')),
]

app_name、name を設定すると、HTML ファイルで、<a href="{% url 'home:index' %}">ホーム</a> みたいな形式でリンクをはったりできます。(リダイレクト時にも使えるので後に実装します)

user/urls.py
from django.urls import path
from . import views

app_name = 'user'

urlpatterns = [
    path('', views.home, name='home'),
]

user/views.py に home 関数を作ります。

views.py
from django.shortcuts import render

def home(request):
    return render(request, 'user/home.html', context={})

login/templates フォルダを作成します。(login はプロジェクト・フォルダ)
templates/user/home.html を作成します。(動作確認用なので、後で内容変更します)

home.html
<h1>ホーム</h1>

動作確認します。

コマンド・プロンプト
python manage.py runserver 8080

http://127.0.0.1:8080/userにアクセス。(http://localhost:8080/userも可)


2. サインアップ・ログイン機能の実装

URL パターンを設定します。

user/urls.py
...
from . import views
...
urlpatterns = [
    ...
    path('signup/', views.signup_func, name='signup'),
    path('login/', views.login_func, name='login'),
...

入力フォーム用のファイルを作ります。
フォーム表示用の調整を行い、clean メソッドで、パスワード認証の強化を実装します。
validate_password: https://docs.djangoproject.com/ja/5.2/topics/auth/passwords/#django.contrib.auth.password_validation.validate_password

user/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError

class SignupForm(forms.ModelForm):
    class Meta():
        model = User
        fields = ('username', 'email', 'password')
        labels = {
            'username': 'ユーザー名',
            'email': 'メールアドレス',
            'password': 'パスワード',
        }
        widgets = {'password': forms.PasswordInput()}
        
    reconfirmation_password = forms.CharField(
        label='パスワード再確認',
        widget=forms.PasswordInput()
    )
    
    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data['password']
        reconfirmation_password = cleaned_data['reconfirmation_password']
        if password != reconfirmation_password:
            self.add_error('password', 'パスワードが一致しません')
        try:
            validate_password(password, self.instance)  # instance: 入力ユーザー情報
        except ValidationError as e:
            self.add_error('password', e)
        return cleaned_data
        
class LoginForm(forms.Form):
    username = forms.CharField(label="ユーザー名")
    password = forms.CharField(label="パスワード", widget=forms.PasswordInput())

HTML に渡すビューを作成します。
表示内容を設定、入力内容を検証し、遷移先を設定します。
render メソッドは、リクエストを受け、遷移先を設定し、遷移先で表示できる変数?を辞書 context で指定します。
render: https://docs.djangoproject.com/ja/5.2/topics/http/shortcuts/#render
redirect メソッドは、遷移先のパス・ファイルを指定する感じです。name を使います。
redirect: https://docs.djangoproject.com/ja/5.2/topics/http/shortcuts/#redirect
また、messages でメッセージを表示するようにしてます。
messages: https://docs.djangoproject.com/ja/5.2/ref/contrib/messages/

user/views.py
from django.contrib import messages
from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect
from .forms import SignupForm, LoginForm
...    
def signup_func(request):
    if request.method == 'POST':
        signup_form = SignupForm(request.POST)
        if signup_form.is_valid():  # True or False
            user = signup_form.save(commit=False)  # commit=False の場合のみインスタンスを返す
            password = signup_form.cleaned_data['password']
            user.set_password(password)  # パスワードのハッシュ化
            user.save()
            messages.success(request, 'サインアップに成功しました')
            login(request, user)
            return redirect('user:index')
        else:
            messages.error(request, 'サインアップに失敗しました')
            return redirect('user:signup')
    else:
        signup_form = SignupForm()
        return render(request, 'user/signup.html', context={'form': signup_form})
        
def login_func(request):
    if request.method == 'POST':
        login_form = LoginForm(request.POST)
        if login_form.is_valid():
            username = login_form.cleaned_data['username']
            password = login_form.cleaned_data['password']
            user = authenticate(username=username, password=password)
            if user:
                login(request, user)
                messages.success(request, 'ログインしました')
                return redirect('user:home')
            else:
                messages.error(request, 'ログインに失敗しました')
                return redirect('user:login')
        else:
            messages.error(request, 'ログインに失敗しました')
            return redirect('user:login')
    else:
        login_form = LoginForm()
        return render(request, 'user/login.html', context={'form': login_form})

表示する HTML ファイルを作成していきます。
Django の便利な Django テンプレート言語(DTL)を、なるたけ使っていきまっす!
DTL: https://docs.djangoproject.com/ja/5.2/ref/templates/language/
DTL では、{{ }} や {% %} といった表示が使われますが、{{ }} は変数・値、{% %} は制御文などといった感じです。
フォーム入力や、メッセージ表示は、使い回しできるので、スニペットとして作成していきます。
templates フォルダ内に、snippets フォルダを作成します。
ここでの form は、views.py で設定した context 辞書のキーになります。
HTML と CSS を使って、少し表示を調整してます。

snippets/form_input.html
<table>
  {% for item in form %}
    <tr>
      <th style="text-align: left;">{{ item.label }}:&nbsp</th>
      <td>{{ item }}</td>
    </tr>
  {% endfor %}
</table>

{% if form.errors %}
  {{ form.errors }}
{% endif %}

スニペットを使って、サインアップページを作成します。
{% csrf_token %} は、クロスサイト・リクエスト・フォージェリ (CSRF) 対策になります。
CSRF 対策: https://docs.djangoproject.com/ja/5.2/ref/csrf/
メッセージ表示用のスニペットも同時に作成します。
views.py で使った messages の表示内容は、messages で取得できます。

message.html
{% if messages %}
  {% for message in messages %}
    {{ message }}
  {% endfor %}
{% endif %}
user/signup.html
{% include 'snippets/message.html' %}
<form method="POST">
  {% csrf_token %}  
  {% include 'snippets/form_input.html' %}
  <br>
  <input type="submit" value="サインアップ">
</form>

つづいて、ログインページを作成します。

login.html
{% include 'snippets/message.html' %}
<form method="POST">
  {% csrf_token %}
  {% include 'snippets/form_input.html' %}
  <br>
  <input type="submit" value="ログイン">
</form>

そして、home.html も書き換えます。
{% if user.is_authenticated %} は、ログイン中かどうかを確認しています。
リンクは、name を使ってはってます。(href="/user/signup" でも可能です)

home.html
{% include 'snippets/message.html' %}
{% if user.is_authenticated %}
  <h1>{{ user.username }}:ホーム</h1>
{% else %}
  サインアップまたはログインしてください<br>
  <a href="{% url 'user:signup' %}">サインアップ</a><br>
  <a href="{% url 'user:login' %}">ログイン</a>
{% endif %}

これで、サインアップとログインができるようになりました。
サーバーを立ち上げて、サインアップできるかを確認します。(ログアウト機能がないので、ログインは後に確認します。コマンド・プロンプトで、Ctrl + C で停止できます)

コマンド・プロンプト
python manage.py runserver 8080

http://127.0.0.1:8080/user/signup/にアクセスし、サインアップします。

サインアップに成功すると、home.html(http://127.0.0.1:8080/user/) に遷移します。


3. ログアウト機能の実装

URL パターンを設定します。

user/urls.py
...
urlpatterns = [
    ...
    path('logout/', views.logout_func, name='logout'),
]

views.py で logout_func を作成します。

user/views.py
...
from django.contrib.auth import authenticate, login, logout
...
def logout_func(request):
    logout(request)
    messages.success(request, 'ログアウトしました')
    return redirect('user:login')

home.html にログアウトのリンクをはります。

home.html
...
{% if user.is_authenticated %}
  <h1>{{ user.username }}:ホーム</h1>
  <a href="{% url 'user:logout' %}">ログアウト</a>
...

動作確認します。
まず、http://127.0.0.1:8080/user/login/にアクセスしてログインします。

http://127.0.0.1:8080/user/に遷移します。

ログアウトすると、http://localhost:8080/user/login/に遷移します。


4. 今回のプロジェクト(コード)

GitHub: https://github.com/Animalyzm/mikoto_project
今回のプロジェクトは、django/login です。


ちょっと、長めになりましたが、以上になります!
ありがとうございましたー♪

Discussion