Warning: count(): Parameter must be an array or an object that implements Countable in /home/xs638785/agile-software.site/public_html/wp-content/plugins/rich-table-of-content/functions.php on line 490
認証の仕組み
セッション認証(Cookie認証)を利用したログインは
- ユーザー名・パスワードと事前に登録されたユーザーテーブルのユーザー名・パスワードで本人確認を行う
- 本人確認後、ログイン情報をセッションに保存しキーとなるセッションIDを発行する
- このセッションIDはレスポンスでブラウザに返され、ブラウザ側の「Cookie」に保存されます。
- 再びログインする際にセッションIDが送信されることになるため本人確認を行う必要がなくなります。
GitHubとの連携
https://www.agile-software.site/2021/04/24/vs-code%e3%81%ae%e8%a8%ad%e5%ae%9a/仮想環境作成・実行
python3 -m venv myvenv
source myvenv/bin/activate
requirements.txt作成
Django~=3.1.4
Pillow~=8.1.0
django-widget-tweaks~=1.4.8
stripe~=2.27.0
Pillow~=8.1.0
パッケージのインストール
(myvenv) ~$ pip3 install -r requirements.txt
すでにあるプロジェクトをコピーしている場合は以下の工程を省略できます。
プロジェクト作成
(myvenv) ~$ django-admin startproject mysite .
アプリケーション作成
(myvenv) ~$ python3 manage.py startapp app
(myvenv) ~$ python3 manage.py startapp accounts
settings.py
- django.contrib.admin:管理(admin)サイト
- django.contrib.auth:認証システム
- django.contrib.contenttypes:コンテンツタイプフレームワーク
- django.contrib.sessions:セッションフレームワーク
- django.contrib.messages:メッセージフレームワーク
- django.contrib.staticfiles:静的ファイルの管理フレームワーク
- widget_tweaks:
- app:作成したappアプリケーションを登録
- accounts:作成したaccountsアプリケーションを登録
- django.contrib.sites:下記解説あり
- allauth:
- allauth.account:
- allauth.socialaccount:ソーシャルログイン機能を実装
- Djangoに付属するsitesフレームワークを導入すると、1つのウェブサイトに1つのSiteデータが割り当てられます。これとOneToOneで紐づくSiteDetailのようなモデルを作ることで、そのウェブサイト全体の設定を表すモデルが作れるらしいです。
- django-allauthはsitesフレームワークの利用が必要
- SITE_ID = 1 :sitesフレームワーク用のサイトID
- LOGIN_REDIRECT_URL = ‘/’ :ログイン時のリダイレクト先
- ACCOUNT_LOGOUT_REDIRECT_URL = ‘/’ :ログアウト時のリダイレクト先
- ACCOUNT_EMAIL_VERIFICATION = ‘none’ :ユーザ登録時に確認メールを送信するか(none=送信しない, mandatory=送信する)
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'widget_tweaks', # 追加
'app', # 追加
'widget_tweaks', # 追加
'app', # 追加
'accounts', # 追加
'django.contrib.sites', # 追加
'allauth', # 追加
'allauth.account', # 追加
'allauth.socialaccount' # 追加
]
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
SITE_ID = 1
LOGIN_REDIRECT_URL = '/'
ACCOUNT_LOGOUT_REDIRECT_URL = '/'
ACCOUNT_EMAIL_VERIFICATION = 'none'
プロジェクトURL
アプリケーションのURLであるapp.urlsとaccounts.urlsを追加します。
urlpatternsにpath(‘accounts/’,include(‘django.contrib.auth.urls’))を追加すると認証周りのすべてのURLパターンが設定されます。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('app.urls')),
path('accounts/', include('accounts.urls')),
]
アプリケーションURL(app)
from django.urls import path
from app import views
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
]
views(app)
from django.views.generic import TemplateView
class IndexView(TemplateView):
template_name = "app/index.html"
base.html(app)
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<title>カスタム認証チュートリアル</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="/">ホーム</a>
</li>
</ul>
</div>
</nav>
<main>
<div class="container">
{% block content %}
{% endblock %}
</div>
</main>
<footer class="py-2 bg-dark">
<p class="m-0 text-center text-white">Copyright © Django Startup 2020</p>
</footer>
{% block extra_js %}
{% endblock %}
</body>
</html>
index.html(app)
{% extends "app/base.html" %}
{% block content %}
<div class="my-5">
<h1>Hello World!!</h1>
</div>
{% endblock %}
CSS(app)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #F1F1F1;
display: flex;
flex-flow: column;
min-height: 100vh;
}
main {
flex: 1;
}
アプリケーションURL(accounts)
from django.urls import path
from accounts import views
urlpatterns = [
path('profile/', views.ProfileView.as_view(), name='profile'),
]
流れ
- 127.0.0.1:8000をrequest
- urls.pyがProfileviewを呼び出す
- Profileviewの中でreturn renderが実行され、profile.htmlが画面に表示される
データベースの削除
view(accounts)
ログイン・ログアウトのViewはDjango側がdjango.contrib.auth
に用意してくれているので、それらをimport
して使います。
モデルで作成したCustomUser
クラスからログイン中のユーザー情報を取得
user_data = CustomUser.objects.get(id=request.user.id)
from django.views import View
from accounts.models import CustomUser
from django.shortcuts import render, redirect
class ProfileView(View):
def get(self, request, *args, **kwargs):
user_data = CustomUser.objects.get(id=request.user.id)
return render(request, 'accounts/profile.html', {
'user_data': user_data,
})
profile.html(accounts)
{% extends "app/base.html" %}
{% block content %}
<div class="card card-profile my-5 mx-auto">
<div class="card-body">
<h5 class="card-title text-center">プロフィール</h5>
<table class="profile_table mb-4">
<tbody>
<tr>
<th class="header">名前</th>
<td class="data">xxx</td>
</tr>
<tr>
<th class="header">メールアドレス</th>
<td class="data">xxx</td>
</tr>
<tr>
<th class="header">所属</th>
<td class="data">xxx</td>
</tr>
<tr>
<th class="header">入会日</th>
<td class="data">xxx</td>
</tr>
</tbody>
</table>
<div class="button mx-auto">
<a class="btn btn-lg btn-warning btn-block" href="">編集する</a>
</div>
</div>
</div>
{% endblock %}
カスタムユーザー作成
django-allauthは以下の機能を備えています。
- ユーザログオン機能
- ユーザログアウト機能
- パスワード変更
- パスワード再設定
- ユーザ登録
Djangoで会員登録機能を使う場合はUserモデルを使います。
しかし、実はUser
モデルはprojectにつき1つしか使えません。
settings.py
で設定するAUTH_USER_MODEL
の部分を以下の例ではaccountsアプリケーションに対して設定しています。
機能 | URLパターン | ビュー/フォーム |
---|---|---|
ログイン | /accounts/login/ | LoginView |
ログアウト | /accounts/ logout/ | LogoutView |
パスワード変更 | /accounts/ password_change/ | PasswordChangeView |
パスワード変更完了 | /accounts/ password_change/done | PasswordChangeDoneView |
パスワード再設定メール送信 | /accounts/ password_reset/ | PasswordResetView |
パスワード再設定メール送信完了 | /accounts/ password_reset/done/ | PasswordResetDoneView |
パスワード再設定 | /accounts/ reset/<uidb64>/<token>/ | PasswordResetConfirmView |
パスワード再設定完了 | /accounts/ reset/done/ | PasswordResetCompleteView |
AUTH_USER_MODEL = 'accounts.CustomUser'
DjangoのデフォルトのUserモデルでは、ユーザー名を表すusername
というフィールドがあります。しかし、Webアプリによってはユーザー名としてメールアドレスを利用することも多いため、今回はusernameフィールドをなくし、emailフィールドをメインに扱うモデルを作成します。
2つの方法があります。
django.contrib.auth.base_user.AbstractBaseUser
を継承するdjango.contrib.auth.models.AbstractUser
を継承する
AbstractBaseUser
を継承する
AbstractUser
はデフォルトのUserモデルが持っている機能を全て持っており、デフォルトUserに少し手を入れたい場合に向いています。よくやるのは、email
フィールドを必須にしてみたり、電話番号や住所といったフィールドの追加です。
AbstractUser
を継承する
AbstractBaseUser
は最低限な機能だけを持っています。AbstractUser
が持っているフィールドの幾つかを消したり、大きな変更が必要な場合は、こちらの継承を使います。
UserManager
create_userはユーザーの新規作成時に呼び出されるメソッド。
create_superuserは管理者用のユーザーを作成するときに使われるメソッド。
class UserManager(UserManager):
def _create_user(self, email, password, **extra_fields):
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, 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(email, password, **extra_fields)
CustomUser
AbstractBaseUser
クラスを継承し、新たにCustomUser
クラスを作成します。
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField('メールアドレス', unique=True)
first_name = models.CharField(('姓'), max_length=30)
last_name = models.CharField(('名'), max_length=30)
department = models.CharField(('所属'), max_length=30, blank=True)
created = models.DateTimeField(('入会日'), default=timezone.now)
is_staff = models.BooleanField(
('staff status'),
default=False,
help_text=('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
('active'),
default=True,
help_text=(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
objects = UserManager()
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
verbose_name = ('user')
verbose_name_plural = ('users')
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
from django.db import models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import UserManager, PermissionsMixin
from django.utils import timezone
管理画面
from django.contrib import admin
from .models import CustomUser
admin.site.register(CustomUser)
AUTH_USER_MODEL = 'accounts.CustomUser'
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
サインアップ
accountsからviewsをインポートします。
urlpatternsにおいてユーザーからsignupのリクエストが来た時SignupViewを表示させます。
path('signup/', views.SignupView.as_view(), name='account_signup'),
from django.urls import path
from accounts import views
urlpatterns = [
path('signup/', views.SignupView.as_view(), name='account_signup'),
path('login/', views.LoginView.as_view(), name='account_login'),
path('logout/', views.LogoutView.as_view(), name='account_logout'),
path('profile/', views.ProfileView.as_view(), name='profile'),
path('profile/edit/', views.ProfileEditView.as_view(), name='profile_edit'),
]
サインアップのフォームを作成
メールアドレスとパスワードは、allauthですでに設定してあるので必要ありません。
ここでは名前のみ追加しておきます。
サインアップボタンが押された時にsave関数が実行されるようにします。self.cleaned_data
でフォームに記載された内容を取得し、save
関数でデータベースに保存します。
def save(self, request):
user = super(SignupUserForm, self).save(request)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
return user
from django import forms
from allauth.account.forms import SignupForm # 追加
class SignupUserForm(SignupForm):
first_name = forms.CharField(max_length=30, label='姓')
last_name = forms.CharField(max_length=30, label='名')
def save(self, request):
user = super(SignupUserForm, self).save(request)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
return user
views
forms.pyで作成したSignupUserFormをfrom_class変数に設定することでオリジナルのフォームを使用することができます。テンプレートはaccounts/signup.htmlです。
from django.views import View
from accounts.models import CustomUser
from accounts.forms import ProfileForm, SignupUserForm # 追加
from django.shortcuts import render, redirect
from allauth.account import views
class SignupView(views.SignupView):
template_name = 'accounts/signup.html'
form_class = SignupUserForm
テンプレート
signup.html
base.htmlの動的な部分にsignup.htmlを使用します。
{% extends "app/base.html" %}
{% load widget_tweaks %}
{% block content %}
<div class="card card-auth my-5 mx-auto">
<div class="card-body">
<h5 class="card-title text-center">サインアップ</h5>
<form method="post" class="form-auth">
{% csrf_token %}
<div class="form-label-group form_wrap form_wrap-2">
{% render_field form.first_name class="form-control" placeholder="姓" %}
{% render_field form.last_name class="form-control" placeholder="名" %}
</div>
<div class="form-label-group">
{% render_field form.email class="form-control" placeholder="メールアドレス" %}
</div>
<div class="form-label-group">
{% render_field form.password1 class="form-control" placeholder="パスワード" %}
</div>
<div class="form-label-group">
{% render_field form.password2 class="form-control" placeholder="パスワード(確認)" %}
</div>
<div class="button mx-auto">
<button class="btn btn-lg btn-primary btn-block mx-auto" type="submit">サインアップ</button>
</div>
</form>
</div>
</div>
{% endblock %}
base.html
ナビゲーションにサインアップを追加し、ログアウト時に表示するようにします。