概要
クラスベースビュー(class-based view)で会員登録機能を作成したのでまとめた。
会員登録機能は、Djangoがもともと用意しているCreateViewクラスを継承して作成する。
CreateViewをどのように使用するのか、また処理イメージについて紹介する。
前提
以下の記事の続きとなる。
概要 クラスベースビュー(class-based view)でログイン機能を作成したのでまとめた。 ログイン機能は、Djangoがもともと用意しているLoginViewクラスを使用する。 LoginViewの場合、ListViewのよ[…]
会員登録機能処理イメージ
今回実装する会員登録機能の画面イメージは以下となる。
入力チェックやエラーメッセージはCreateViewを継承したクラスを呼び出せば自動で用意してくれる機能になる。
画面
会員登録画面
会員登録失敗
ログイン成功
タスク一覧画面に遷移する。
概要 クラスベースビュー(class-based view)で一覧検索機能を作成したのでまとめた。 一覧検索機能は、Djangoがもともと用意しているListViewクラスを継承してを作成する。 前提 Djang[…]
会員登録機能の実装
大事な概念
入力フォームを作成してサーバーへ送信する画面の場合、以下の概念をもっておかないとDjangoで実装イメージができないと感じた。
①views.pyから入力画面を表示する際に、フォームクラスをコンテキスト(画面表示用の辞書データ)につめて渡している
②登録画面等の入力フィールドは、フォームクラスが保持しているフィールドの名前と紐づく
③登録リクエストを送ると、パラメータはフォームクラスの中に格納されてサーバーへ送られる
④views.pyはフォームクラス内のバリデーション(入力チェック)処理を呼ぶ
⑤入力チェックエラーの場合、フォームクラス内にエラー情報が設定される
⑥views.pyはエラー情報を保持したフォームクラスをコンテキストにつめて画面に返却する
⑦入力チェックでエラーがない場合、views.pyはフォームクラスからパラメータを取得して対象のテーブルに登録する
上記の理解を妨げるのが、Djangoはいろいろと見えないところで自動処理を実行するため。
フォームの入力チェックなどはカスタマイズしない限り、モデル定義から自動で作成される模様。
また、今回の会員登録処理のフォームクラスも、もともと用意されているものを使用するため、中身が見えない。
そのため、実装する際には見えないところも含めて上記の流れでDjangoは処理を行っているということを意識する必要がある。
サーバー側
ルーティング
accounts/urls.py
from django.urls import path
from . import views
from django.contrib.auth.views import LoginView
urlpatterns = [
path("login/", LoginView.as_view(), name="login"),
path("signup/", views.SignupView.as_view(), name="signup"),
]
上記により、サーバーを起動して「http://localhost:8000/accounts/signup/」にアクセスすると、viewsファイルに定義しているSignupViewクラス内の処理を呼ぶ。
会員登録ビュー
accounts/views.py
from django.views.generic import CreateView
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.urls import reverse_lazy
class SignupView(CreateView):
"""会員登録"""
model = User
form_class = UserCreationForm
template_name = "registration/signup.html"
success_url = reverse_lazy("top")
登録処理を作成するには、クラスベースビューのCreateViewを継承して実装する。
modelには、最終的に更新するテーブルオブジェクトを設定する。
フォームクラスで行われる入力チェックに問題がなければ、Userオブジェクト(テーブル)で会員情報を新規登録する。
form_classには、画面とパラメータのやりとりを行うためのフォームクラスを設定する。
画面から会員登録リクエストがくると、自動生成された会員登録まわりの入力チェックを行う。
今回はもともと会員登録機能用にDjangoが用意しているUserCreationFormを使用している。
template_nameには、会員登録画面のテンプレート(HTML)パスを設定する。
success_urlには、会員登録完了後のリダイレクトパスを設定する。
reverse_lazy(“top”)という記述は、urls.pyに定義された(今回はtodoapps/urls.py)ビューの名前からURL情報を取得する。
画面側
会員登録画面
accounts/templates/registration/signup.html
(略)
<form method="post" class="p-4 m-4 bg-light border border-success rounded form-group">
{% csrf_token %}
<input type="text" name="username" class="form-control my-4" placeholder="ユーザー名" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
{% for error in form.username.errors %}
<div class="mbn-2 small text-danger d-block text-start">{{ error }}</div>
{% endfor %}
<input type="password" name="password1" class="form-control mt-4" placeholder="パスワード" value="{% if form.password1.value %}{{ form.password1.value }}{% endif %}">
{% for error in form.password1.errors %}
<div class="mbn-2 small text-danger d-block text-start">{{ error }}</div>
{% endfor %}
<input type="password" name="password2" class="form-control mt-4" placeholder="パスワード確認用" value="{% if form.password2.value %}{{ form.password2.value }}{% endif %}">
{% for error in form.password2.errors %}
<div class="mbn-2 small text-danger d-block text-start">{{ error }}</div>
{% endfor %}
<small class="mb-2 d-block text-start">パスワードは8文字以上で設定してください。</small>
<button type="submit" class="btn btn-success m-2">アカウント作成</button>
</form>
(略)
<input type=”password” name=”password1″ (略)>
<input type=”password” name=”password2″ (略)>
UserCreationFormにより使用できるフォーム部品は「username」と「password1」と「password2」という変数になる。
上記の変数を定義することで、会員登録リクエストをサーバーへ送った際にUserCreationFormの中にパラメータがセットされるイメージ。
valueの中に「form.フィールド名.value」とすることで、エラーのときでもフォーム入力内容を保持できる。
formのフィールドのvalueが存在するときという条件文をつけないと、Noneが表示されてしまうので注意。
<div class=”text-danger”>{{ error }}</div>
{% endfor %}
UserCreationFormクラスで入力チェックエラーになった場合、対象のフィールドの「form.フィールド名.errors」にエラーメッセージリストが格納される。
以上でシンプルな会員登録機能を実装できる。
補足
会員登録後にログイン処理
会員登録後にログインした状態でタスク一覧画面を表示させる場合の方法について紹介する。
会員登録ビューを以下のように修正する。
accounts/views.py
from django.contrib import auth
from django.views.generic import CreateView
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.urls import reverse_lazy
from django.http import HttpResponseRedirect
class SignupView(CreateView):
"""会員登録"""
model = User
form_class = UserCreationForm
template_name = "registration/signup.html"
def form_valid(self, form):
# 会員情報をUserテーブルに新規登録
user = form.save()
# 登録した内容を元にログインする
auth.login(self.request, user)
# 登録完了後、タスク一覧画面にリダイレクトする
return HttpResponseRedirect(reverse_lazy("top"))
会員登録リクエストを送ると、UserCreationFormが呼ばれて入力チェック処理を行う。
エラーがある場合、エラー情報を保持したUserCreationFormを会員登録画面に返却する。
エラーがない場合、form_validメソッドを呼び出し、以下の処理を行う。
・UserCreationFormに保持している入力値をUserテーブルに新規登録する
・新規登録したUser情報をもとに、ログイン処理を行う。
・最後に、topビューにリダイレクトする。
アクセス制御
タスク一覧表示(top)ビューの機能は、ログインした状態でないと利用できないよう制限する。
todoapps/views.py
from django.views.generic import ListView
from . import models
from django.contrib.auth.mixins import LoginRequiredMixin
class TodoTopView(LoginRequiredMixin, ListView):
"""TODOリスト一覧表示"""
model = models.Todo
template_name = "todo/top.html"
context_object_name = "tasks"
def get_queryset(self):
return models.Todo.objects.filter(status=0).order_by("due_date")
settings.py
LOGIN_URL = "login"
LOGIN_URL定数にビュー(もしくはURL)を指定することで、ログインしていない状態でURLにアクセスした際のリダイレクト先を指定できる。
上記はログイン画面にリダイレクトするよう設定している。
アクセス制御の挙動
ログインしていない状態でtopビューリクエスト(http://localhost:8000/tasks/)をブラウザから送ると、ログイン画面に遷移する。
入力チェックのカスタマイズ
入力チェック機能はモデルのフィールド定義に合わせて、フォームクラスが自動で用意する。
フォームクラスの入力チェック内容を増やしたい又は、メッセージ内容を変更したい場合について紹介する。
カスタマムフォームの作成
UserCreationFormを継承して、独自の入力チェック仕様を作成する。
新たにforms.pyを作成して、会員登録フォームクラスを定義する。
accounts/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.core.validators import MinLengthValidator
class SignupForm(UserCreationForm):
"""会員登録フォーム"""
# 独自入力チェック定義-------------
# ユーザー名
username = forms.CharField(
max_length=100,
error_messages={
"required": "ユーザー名は必須です。",
"max_length": "ユーザー名は最大100文字までです"
}
)
# パスワード
password1 = forms.CharField(
validators=[MinLengthValidator(8, message="パスワードは最低8文字以上必要です。")],
error_messages={
"required": "パスワードは必須です。"
}
)
# パスワード確認用
password2 = forms.CharField(
validators=[MinLengthValidator(8, message="パスワード(確認用)は最低8文字以上必要です。")],
error_messages={
"required": "パスワード(確認用)は必須です。"
}
)
class Meta:
model = User
fields = ("username", "password1", "password2")
username = forms.CharField(
max_length=100,
error_messages={
"required": "ユーザー名は必須です。",
"max_length": "ユーザー名は最大100文字までです"
}
)
max_lengthを定義して、最大100文字までに変更している。
※もともとデフォルトのUserテーブルの仕様だと150文字が最大になっている。
error_messagesにエラーとなるキーとメッセージを独自に定義している。
必須チェックの場合、必ずrequiredキーにエラーメッセージが設定される模様。
パスワードの入力フィールドについても、ユーザー名と同様の記載で必須チェックエラーメッセージを変更している。
class Meta:
model = User
fields = ("username", "password1", "password2")
お決まりの書き方として覚えた方がいい箇所。
modelにテーブルオブジェクトを定義することで、テーブルカラムにあわせた入力チェックを自動生成してくれる。(入力フィールドも自動生成されている)
fieldsには画面に表示させる入力フィールドを定義する。
会員登録ビュー
会員登録ビューで使用するフォームを、カスタマイズしたフォームに変更する。
accounts/views.py
from django.contrib import auth
from django.views.generic import CreateView
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.urls import reverse_lazy
from django.http import HttpResponseRedirect
from . import forms
class SignupView(CreateView):
"""会員登録"""
model = User
form_class = forms.SignupForm
template_name = "registration/signup.html"
def form_valid(self, form):
# 会員情報をUserテーブルに新規登録
user = form.save()
# 登録した内容を元にログインする
auth.login(self.request, user)
# 登録完了後、タスク一覧画面にリダイレクトする
return HttpResponseRedirect(reverse_lazy("top"))
もともと用意されているUserCreationFormを使用していたが、UserCreationFormを継承してカスタマイズしたSignupFormに変更する。
結果
もともと「このフィールドは必須です。」というエラーメッセージだったが、変更された。
入力フィールドの自動生成
画面に渡しているフォームクラスを利用することで、画面入力フィールドを自動生成できる。
accounts/templates/registration/signup.html
<form method="post" class="p-4 m-4 bg-light border border-success rounded form-group">
{% csrf_token %}
{{ form.as_p }}
<small class="mb-2 d-block text-start">パスワードは8文字以上で設定してください。</small>
<button type="submit" class="btn btn-success m-2">アカウント作成</button>
</form>
{{ form_as_p }} という記載方法は、フォームの内容をpタグで囲んで表示する記述になる。
細かいレイアウトのスタイリングがやりづらい模様。
結果
フィールド名はモデル作成時に定義可能。
定義しないと物理項目名がフィールド名になる模様。