9.4 DjangoとReactでの実装
Djangoは「9.2. ログインとセッション(認証と認可)」で説明した、ログイン認証やセッションの管理を行う機能を備えています。ここでは、Djangoのこれらの機能を使って、ログインしたユーザーのみがアプリケーションサービスを利用できるようにする方法を解説します。
9.4.1 認証のためのユーザーモデル
まずDjangoの認証機能を使うためのユーザーモデルが必要になりますので、django.contrib.auth.models.AbstractUserを継承したクラスを定義します。
基底クラスとなるAbstractUserは、元々以下のフィールドを持っています。
- username:ユーザ名(ログインに使用する)
- first_name : 名
- last_name:姓
- email :emailアドレス
- is_staff:スタッフかどうかのフラグ
- is_active:アクティブかどうかのフラグ
- date_joined:登録された日時
- password (AbstractBaseUserからの継承)
- groups:ユーザーが所属するグループ
- user_permissions:パーミッション
また、パスワードを文字列からハッシュ化して格納したり、emailアドレスのフォーマットをチェックしたりするするためのメソッドなどを備えています。
ユーザーモデルでは、これにアプリケーションで必要な独自のフィールドを追加設定します。特に追加するものがなければdjango.contrib.auth.models.Userを使うのが良いでしょう。
ここではgender(性別)のフィールドを追加してみます。さらに、基底クラスのフィールドで不要なものにはNoneを入れておきます。
#user_models.py
from django.db import models
from django.contrib.auth.models import AbstractUser,Group, Permission
class Gender(models.IntegerChoices):
MAN = 1
WOMAN = 2
class Users(AbstractUser):
first_name = None
last_name = None
gender = models.IntegerField(choices=Gender)
9.4.2 ユーザーの登録機能の追加
次にユーザーを登録する機能を作っておきます。(djangoのアドミニストレーション画面から登録することもできますが、ここでは登録機能のビューを作ってみます。)
ここではSignupView という名前のビューと、これが使うテンプレートとフォームを定義します。
フォーム
SignUpFormに、定義したユーザーモデルに対応するフォームを設定します。
#forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from jfootball_record.model_definition.users_models import Users
class SignUpForm(UserCreationForm):
GENDER_CHOICE={
1:"男性",
2:"女性"
}
gender=forms.ChoiceField(label="性別", choices=GENDER_CHOICE)
class Meta:
model = Users
fields = ["username", "email", "password1", "password2", "gender"]
Metaクラスを使って、対応するユーザーモデルと、モデルにあるフィールドのうち実際に入力をしてもらうものを設定します。また、新たに追加したgenderフィールドについてはフォームに設定を追加しておきます。(AbstractUserを使うとパスワード入力用のフィールドpassword1と確認用のpassword2が必要になりますので、これは定義しておきます)
テンプレート
ViewをGETで呼び出した時に表示される。以下の画面のテンプレートsignup.htmlを定義します。
<!DOCTYPE html>
<html lang="ja">
<header> <title>Test 登録 page</title></header>
<body>
<div>Test 登録</div>
<form method="post">
{% csrf_token %}
{{ form }}
<button type="submit">登録</button>
</form>
</body>
</html>
テンプレートファイルは、以下のようにsettings.pyで定義した'TEMPLATES'の'DIRS'に記載したフォルダの下に配置します。
#settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'], # templatesフォルダの下にテンプレートファイルを配置するように設定
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
ビュー
最後にSignupViewを定義します。
# sign_up_view.py
from django.contrib.auth import login, authenticate
from django.views.generic import View
from django.shortcuts import render, redirect
from django.conf import settings
from django.db import transaction
from django.contrib.auth import login as auth_login
from jfootball_record.forms import SignUpForm
class SignupView(View):
def get(self, request, *args, **kwargs):
#print(f"get called :{request}")
context = {'form': SignUpForm()}
return render(request, 'signup.html', context)
def post(self, request, *args, **kwargs):
print(request);
form = SignUpForm(request.POST)
if not form.is_valid():
return render(request, 'signup.html', {'form': form})
user = self.register_user(form)
auth_login(request, user)
return redirect(settings.LOGIN_REDIRECT_URL)
@transaction.atomic
def register_user(self, form):
user = form.save(commit=False)
user.set_password(form.cleaned_data['password1'])
user.save()
print(user);
return user
- getメソッドには、使用するフォーム(SiguUpForm)とテンプレート(signup.html)を指定します。
- postメソッドでは、requestから入力されたフォームを取り出し、register_userでユーザーモデルインスタンスを作り保存し、ついでにauth_login()でログインを行っています。
9.4.3 ログイン機能の追加
django.contrib.auth.viewsが提供しているデフォルトのLoginViewを使います。
ユーザー名とパスワード文字列を入力することにより、認証を行い、バックエンド側でセッション情報を生成。cookieにセッションIDを入れて返送します。このビューがGETで呼ばれた時にログイン情報入力画面を表示するテンプレートをtemplatesフォルダの下の'registration/login.html'に置きます。
registration/login.html
<!DOCTYPE html>
<html lang="ja">
<header> <title>Test Login page</title></header>
<body>
<div>Test ログイン</div>
<form method="post">
{% csrf_token %}
{{ form }}
<button type="submit">ログイン</button>
</form>
</body>
</html>
テンプレートの構造は先ほどのsignup.htmlと同じです。
6.4.4 ログアウト機能の追加
django.contrib.auth.viewsで提供されているLoginViewを使います。LogoutViewが呼ばれると、djangoは保持していたセッション情報を廃棄します。こちらは特にテンプレートを用意する必要はありません。
6.4.5 システム設定
まず、backend/urls.pyにユーザー登録、ログイン、ログアウトのためのURLを設定します。
from django.urls import include, path
from django.contrib.auth.views import LoginView, LogoutView
from jfootball_record.views.sign_up_view import SignupView
urlpatterns = [
path('sign_up/', SignupView.as_view()), # <-追加
path('login/', LoginView.as_view(), name='logout'), # <-追加
path('logout/', LogoutView.as_view(), name='logout'), # <-追加
path('api/v1/', include('jfootball_record.urls')),
]
次にbackend/settings.pyに以下の情報を追加して、Djangoによる認証、ログイン、ログアウトの動作を設定します。
# ロケールを日本にしておくと、日本語のフォームが使えるようになります。
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
#認証に使うユーザーモデルを定義
AUTH_USER_MODEL = "jfootball_record.Users"
#ログイン後に遷移するURL
LOGIN_REDIRECT_URL = '/my_app/'
ログアウト後に遷移するURL
LOGOUT_REDIRECT_URL ='/login/'
6.4.6 ログインしたユーザーのみにdjangoのサービスを許可する
ここまででログイン認証とセッション管理はできるようになりました。ここからはログインしているユーザーのみに対してサービスが有効になるようにアプリケーションプログラムを設定していきます。
ログインしているユーザーにのみサービスを有効にする方法は2通りあります。
Viewクラス単位での設定
genericビューのクラスのauthentication_classesとpermission_classesを設定します。
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
class SampleView(generics.ListCreateAPIView):
authentication_classes = (SessionAuthentication,)
permission_classes = (IsAuthenticated, )
これにより、Viewクラスのメソッド全体に対し、セッション認証ができている場合にのみアクセスが許可されるようになります。
関数単位での設定
関数単位でデコレーターを使って許可設定を行う方法です。デコレータとは関数やクラスの前後に特定の処理を追加できる機能です。
from django.contrib.auth.decorators import login_required
@login_required
def post(request):
関数の定義の前に、login_requiredデコレーターを設定しておくと、この関数post()はセッション認証ができている場合にのみアクセスを許可されます。
6.4.7 ログインしたユーザーのみにReactアプリへのアクセスを許可する
6.4.6の設定でdjangoのREST APIへのアクセス許可は設定することができましたが、これだけだと、staticディレクトリに置かれているReactを使ったフロントエンドアプリケーションのファイルはそのままアクセスできてしまいます。djangoのアクセス制限の機能を使うために、少しトリックを使います。
Djangoのビュー関数index_view()を定義し、これの中で「テンプレート」としてREACTアプリケーションの呼び出しを含むindex.htmlを表示します。index_view()に対しlogin_requiredデコレーターを指定することで、ログインしているユーザーのみに対してのみアプリケーションの表示がされるようになります。
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
# reactの画面を呼ぶindex.htmlを表示するビュー
@login_required
def index_view(request):
return render(request, 'index.html')
このプログラムからindex.htmlを正しく読み込むために、REACTでビルドされたファイルのうち、index.htmlをsettings.pyのTEMPLATE のDIRで定義したディレクトリ(ここではtemplates)に移動しておきます。
これまではREACTアプリケーション関連のファイルへのルーティングは、staticディレクトリをmy_appと紐づけることで行なっていましたが、これをdjangoのurlpatternsで紐づけるようにします。
まず、settings.pyのSTATIC_URLをmy_appとは違う名称に変更します。
#settings.py
STATIC_URL = 'statics/'
次にurls.pyのurlpatternsにエントリーを追加します。
from django.urls import include, path, re_path
from django.conf import settings
from django.shortcuts import render
from django.conf.urls.static import static
from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles.views import serve
from django.contrib.auth.views import LoginView, LogoutView
from jfootball_record.views.sign_up_view import SignupView
@login_required(login_url="/login")
def index_view(request, *args, **kwargs):
return render(request, 'index.html')
urlpatterns = [
path('my_app/', index_view, name='index'), # <-追加
re_path(r'^my_app/(?P<path>.*?\.[^/]+)$', serve), # <-追加
path('sign_up/', SignupView.as_view()),
path('login/', LoginView.as_view(), name='logout'),
path('logout/', LogoutView.as_view(), name='logout'),
path('api/v1/', include('jfootball_record.urls')),
]
ちなみにurlpatternsの2つ目のエントリーにあるserveというviewはstaticディレクトリにリダイレクトを行う機能を持っています。