はじめに
このドキュメントは,もともとはラボの新メンバー向けの入門テキストとして作成したもので,はじめてDjangoにふれる人にざっとその全体像をつかんでもらうことを狙っています.Djangoの日本語での参考資料も充実してきたので今さらという気がしないでもないですが,見直しを機にQiitaに移すことにしました.万が一でもどなたかの参考になれば幸いです.
説明のための具体例として,Djangoのオフィシャルチュートリアルにある投票アプリをとりあげています(が,説明上の都合で少しコードを追加,変更しているところもあります).素人の独学がベースで,特に前半は公式チュートリアルをやってみた感想のような記事です(今回は全7回中の6回目).
全体のコードはまとめてGitHubに置きました.
変更履歴
- [2021/04/26] Djangoのバーションを3.2に更新し,それ基づいて内容を微修正しました.
ユーザ登録とログイン・ログアウト
ここまでは,urlさえ指定すれば誰でも閲覧できるページを作成してきました.続いて,ログインしないと閲覧できないページや,ログインしたユーザに応じてチューニングした内容が表示されるページを作成する方法をみていきましょう.
このためには,まずユーザという概念を導入する必要があります.
実は,Djangoにはユーザを扱う枠組みが標準装備されています.データベースを初期化した際に,管理者を登録したのを思い出しましょう.あれがユーザの例です.adminサイトのUsersのページをみると,その管理者のデータが格納されているはずです.
最初にユーザ登録用のページ(signupページ)を作成しましょう.希望するusernameとpasswordを取得するためのフォームを下記のように作成し,forms.pyに書き込みます.
from django.contrib.auth.models import User
class SignUpForm(forms.Form):
username = forms.CharField(widget=forms.TextInput)
enter_password = forms.CharField(widget=forms.PasswordInput)
retype_password = forms.CharField(widget=forms.PasswordInput)
def clean_username(self):
username = self.cleaned_data.get('username')
if User.objects.filter(username=username).exists():
raise forms.ValidationError('The username has been already taken.')
return username
def clean_enter_password(self):
password = self.cleaned_data.get('enter_password')
if len(password) < 5:
raise forms.ValidationError('Password must contain 5 or more characters.')
return password
def clean(self):
super(SignUpForm, self).clean()
password = self.cleaned_data.get('enter_password')
retyped = self.cleaned_data.get('retype_password')
if password and retyped and (password != retyped):
self.add_error('retype_password', 'This does not match with the above.')
def save(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('enter_password')
new_user = User.objects.create_user(username = username)
new_user.set_password(password)
new_user.save()
文字列のfieldが3つ定義されていますが,それらのうち,enter_password
とretype_password
の2つにパスワード入力用のwidget
が指定してあります.これによって,キー入力の内容が画面に表示されないようになります.
また,clean()
の中では,エラーをraise
するのではなく,add_error()
というメソッドを呼んでいます.これによって,エラーを特定のfieldに対応付けることができます.
save()
メソッドをみると,Userクラスのインスタンスの生成方法やpasswordの設定方法が確認できます.
続いて,このフォームを処理するview関数signup()
を作成しましょう.views.pyの末尾に下記の内容を書き足します.
def signup(request):
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
form.save()
return redirect('polls:index')
else:
form = SignUpForm()
context = {'form':form}
return render(request, 'polls/signup.html', context)
これは,view関数でフォームを処理するための典型的なパターンに沿ったものになっています.対応するtemplate(signup.html)は,例えば次のように作成すればいいでしょう.
<h1>Please Sign Up to Polls App.</h1>
<form action="{% url 'polls:signup' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Sign Up">
</form>
<p>or <a href= "{% url 'polls:login' %}">Login</a></p>
最後に,urlルーティングの情報を更新します.まず,polls/urls.pyを下記のように書き換えます.
from django.contrib.auth import views as auth_views
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('signup/', views.signup, name='signup'),
path('login/', auth_views.LoginView.as_view(template_name="polls/login.html"), name='login'),
path('logout/', auth_views.LogoutView.as_view(next_page="polls:index"), name='logout'),
]
ユーザ登録用のsignupページの指定が追加されており,上で作成したview関数signup()
に対応付けられていることがわかります.これで,http://localhost:8000/polls/signup/ にアクセスするとユーザ登録用のページが表示されるはずなので,実際に登録できるか確認してみましょう.
すでに登録済みのユーザに対してはloginのページを用意しましょう.
ユーザ登録と同様にフォームとview関数を自作することもできますが,ここではDjangoに用意されている標準viewを利用してみることにします.また,あわせてlogoutの機能も実装しておきましょう.
上のpolls/urls.pyをよくみると,loginとlogoutのページの指定も追加されていることがわかります.auth_views.LoginView.as_view()
とauth_views.LogoutView.as_view()
で標準view(関数ではなくクラス)を呼び出しています.
まずloginページの方からみていきます.この標準viewを用いると,view関数を自作する必要はなくなります.標準viewに引数としてtemplateを渡していることからわかるように,template(login.html)は用意しなければなりません.下記はその一例です.
<h1>Please Login to Polls App.</h1>
<form action="{% url 'polls:login' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Login">
</form>
<p>or <a href= "{% url 'polls:signup' %}">Sign Up</a></p>
なお,この標準viewのデフォルトのurlはaccounts/login/なので,そのアドレスが指定された場合にpolls/loginにリダイレクトされるように仕組んでおきましょう.また,loginに成功した場合,標準ではaccounts/profile/にリダイレクトされるので,このアドレスはpolls/indexに再リダイレクトすることにしましょう.
これらの設定は,リダイレクトのための標準view(のクラスbase.RedirectView.as_view()
)を用いて,mysite/urls.pyの中に下記のように書くことができます.
from django.contrib import admin
from django.urls import include, path
from django.views.generic import base
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
path('accounts/login/', base.RedirectView.as_view(pattern_name="polls:login")),
path('accounts/profile/', base.RedirectView.as_view(pattern_name="polls:index")),
]
一方,logoutの方は,特にそれ専用のページは用意せず,このアドレスがリクエストされるとlogout処理を行って,それが終わったらindexページにリダイレクトするように指定してあります(この機能の呼び出し方は下で考えることにします).
続いて,detailページを,loginしているユーザしか利用できないように変更してみます.これは,@login_required
というデコレータを利用すれば簡単に実装できます.view関数detail()
を次のように書き換えましょう.
from django.contrib.auth.decorators import login_required
...
@login_required
def detail(request, question_id):
user = request.user
question = get_object_or_404(Question, pk=question_id)
if request.method == 'POST':
form = VoteForm(request.POST, question=question)
if form.is_valid():
form.save()
return redirect('polls:results', question_id=question.id)
else:
form = VoteForm(question=question)
context = {'user':user, 'question':question, 'form':form}
return render(request, "polls/detail.html", context)
detail()
がデコレータ@login_required
で修飾されているのがわかります.
この結果,もしloginしていないユーザがこのページにアクセスしようとした場合は,detailページは表示されず,accounts/login/にリダイレクトされるようになります(accounts/login/はさらにpolls/login/にリダイレクトされるので,上で作成したloginページが表示されます).
また,request.user
(loginしているユーザのUserインスタンス)を取得してuser
というキーでcontext
の中に含めてrender()
を呼んでいることもみてとれます.
せっかくなので,このuser
の情報をtemplateの中で利用してみよう.具体的には,detail.htmlを次のように更新します.
<h1>Hi {{ user|title }}! {{ question.question_text }}</h1>
{{ form.non_field_errors }}
<form action="{% url 'polls:detail' question.id %}" method="post">
{% csrf_token %}
<p>
{{ form.your_choice.label }}<br>
{{ form.your_choice }}
</p>
<p>
{{ form.new_option.label }}<br>
{{ form.new_option }}
</p>
<input type="submit" value="Vote">
</form>
<p>or <a href= "{% url 'polls:logout' %}">Logout</a></p>
最初にloginしているユーザ名を表示するようにしました.title
は,与えられた文字列の先頭だけを大文字にするフィルタです.
また,最後にlogoutを呼ぶリンクも追加してあります.このリンクを踏むとlogoutの処理が走り,indexページにリダイレクトされるはずです.
おわりに
以上で6回目は終了です.最終回に続きます.