[Django]認証・ログイン・ログアウト

以前、はてブロにDjangoでのユーザー認証についてまとめました。しかし、あれから少しコードが変わったりしたのでここでまとめます。。これがベストプラクティスかは分かりませんが……。

環境:django2.**

Userモデルのカスタマイズ

すでにstartappなどをしてプロジェクトを立ち上げているとします。

ユーザーモデルをカスタマイズしておきましょう。今回はメアド認証ができるようにしておきます。users/models.pyに書いていきます。

from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin


class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, email, password, **extra_fields):
        if not username:
            raise ValueError('メールアドレスが入力されていません')

        if not email:
            raise ValueError('メールアドレスが入力されていません')

        email = self.normalize_email(email)
        username = self.model.normalize_username(username)

        user = self.model(username=username, email=email, **extra_fields)
        user.set_password(password)
        user.save(self._db)

        return user

    def create_user(self, username, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)

        return self._create_user(username, email, password, **extra_fields)

    def create_superuser(self, username, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(username, email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(max_length=30, unique=True)
    email = models.EmailField(max_length=255, unique=True,)
    name = models.CharField(max_length=30, blank=True,)

    date_joined = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    is_admin = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)

    objects = UserManager()

    USERNAME_FIELD = 'username'
    EMAIL_FIELD = 'email'
    REQUIRED_FIELDS = ['email', 'name']

    def __str__(self):
        return f'@{self.username}'

[プロジェクト]/settings.pyにこのUserモデルを使うことを明記。

AUTH_USER_MODEL = 'users.User'

認証

サインアップ機能を作っていきましょう。

まずはusers/forms.pyから編集。UserCreationFormを用いて作成してます。フォームにはBootstrapを当ててますがそこは別にお好きなようにしていただければいいです。nameusernameemailを入力するようにしています。

from django import forms
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm

from users.models import User


class SignUpForm(UserCreationForm):
    class Meta:
        model = User
        fields = ['username', 'email', 'name']

    def __init__(self, *args, **kwargs):
        super(SignUpForm, self).__init__(*args, **kwargs)

        for field_name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'

次はusers/views.pyを編集。サインアップしたらそのままトップページに行くようにしています。

from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth import authenticate, login

from users.models import User
from users.forms import SignUpForm


class SignUpView(generic.CreateView):
    form_class = SignUpForm
    template_name = 'users/signup.html'
    success_url = reverse_lazy('users:index')

    def form_valid(self, form):
        response = super(SignUpView, self).form_valid(form)
        login(self.request, self.object)

        return response

users/urls.pyに記述しておきましょう。

path('signup/', views.SignUpView.as_view(), name='signup'),

templateを作成しておきます。

<div>
  <form role="form" action="{% url 'users:signup' %}" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <h3>アカウント登録</h3>
    <div>
      <label for="id_username">ユーザー名:</label>
      {{ form.name }}
    </div>
    <div>
      <label for="id_email">メールアドレス:</label>
      {{ form.email }}
    </div>
    <div>
      <label for="id_password1">パスワード:</label>
      {{ form.password1 }}
    </div>
    <div>
      <label for="id_password2">パスワードの確認:</label>
      {{ form.password2 }}
    </div>
    <div>
      <label for="id_username">ユーザーID:</label>
      {{ form.username }}
    <div>
      <button class="btn btn-primary" type="submit">登録</button>
    </div>
  </form>
</div>

とりあえずリダイレクトした後のindex.htmlも作っておきましょう。

users/views.pyはこう。

from django.contrib.auth.mixins import LoginRequiredMixin

class Symphony(LoginRequiredMixin, generic.ListView):
    template_name = 'users/index.html'

index.htmlは簡単に。

<h1>ようこそ</h1>

ログイン

次はログイン機能を作っていきましょう。

まずはusers/forms.pyから。基本的にAuthenticationFormを使います。

class LoginForm(AuthenticationForm):
    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)

        for field_name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'

メールとusernameのどちらでもログインできるようにusers/backends.pyを作成しましょう。

import re

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend

User = get_user_model()


class UsernameOrEmailModelBackend(ModelBackend):
    def authenticate(self, username=None, password=None):
        if re.match(r'[^@]+@[^@]+\.[^@]+', username):
            kwargs = {'email': username}
        else:
            username = username.lstrip('@')
            kwargs = {'username': username}

        try:
            user = User.objects.get(**kwargs)

            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

これをバックエンドに用いることをsettings.pyに明示。

AUTHENTICATION_BACKENDS = [
    'users.backends.UsernameOrEmailModelBackend'
]

次にusers/urls.pyに記述。

from django.urls import path
from django.contrib.auth.views import LoginView, LogoutView

from users.forms import LoginForm

from users import views

app_name = 'users'

urlpatterns = [
    path('signup/', views.SignUpView.as_view(), name='signup'),
    path('login/', LoginView.as_view(form_class=LoginForm, template_name='users/login.html'), name='login'),
]

最後にテンプレートを作りましょう。login.html

<div>
  <form role="form" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <h3>ログイン</h3>
    <div>
      <label for="id_username"><small>ユーザーIDまたはメールアドレス:</small></label>
      {{ form.username }}
    </div>
    <div>
      <label for="id_password"><small>パスワード:</small></label>
      {{ form.password }}
    </div>
    <button class="btn btn-success" type="submit">ログイン</button>
  </form>
</div>

これでログイン機能をおしまい!

ログアウト

最後はログアウト機能をサラーと。

users/urls.pyをこんな風に。

from django.urls import path
from django.contrib.auth.views import LoginView, LogoutView

from users.forms import LoginForm

from users import views

app_name = 'users'

urlpatterns = [
    path('signup/', views.SignUpView.as_view(), name='signup'),
    path('login/', LoginView.as_view(form_class=LoginForm, template_name='users/login.html'), name='login'),
    path('logout/', LogoutView.as_view(), name='logout'),
]

index.htmlに書いときましょう。

<li><a href="{% url 'users:logout' %}">ログアウト</a></li>

以上!