Edited at

Djangoでタスク管理アプリをつくりたい! ユーザ認証編


はじめに

前回(環境構築編)で使用したプロジェクトを引き継いで作成する。


開発環境

・Windows10 professional 64bit

・Python 3.7.1

・Django 2.1.2

・MySQL 8.0.12


ユーザ認証


ログイン

・ログインをしトップページを表示する機能を作成する。

 


URL設定

プロジェクトで画面のURLを設定する


urls.py

from django.contrib import admin

from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('accounts.urls')),
]


アプリ内でもURLを設定するために,accounts直下にurls.pyを作成


urls.py

from django.urls import path

from . import views

app_name = 'accounts'

urlpatterns = [
path('', views.Login.as_view(), name='login'),
path('top/', views.Top.as_view(), name='top'),
path('logout/', views.Logout.as_view(), name='logout'),
]


ログインフォームを作成するので,accounts直下にforms.pyを作成


forms.py

from django.contrib.auth.forms import (

AuthenticationForm
)

"""ログインフォーム"""
class LoginForm(AuthenticationForm):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = field.label


表示先を指定するviews.pyを編集


views.py

from django.contrib.auth.mixins import LoginRequiredMixin

from django.contrib.auth.views import (
LoginView, LogoutView
)
from django.views import generic
from django.contrib.auth.decorators import login_required
from .forms import LoginForm

"""ログインページ"""
class Login(LoginView):
template_name = 'login.html'
form_class = LoginForm

""" トップページ """
class Top(LoginRequiredMixin, generic.TemplateView):
template_name = 'top.html'
redirect_field_name = 'redirect_to'

"""ログアウトページ"""
class Logout(LoginRequiredMixin, LogoutView):
template_name = 'logout.html'


LoginRequiredMixinを継承することによって,ログインをしていない場合にログインページにリダイレクトされる

accounts直下にtemplatesフォルダを作成しtemplateの接続先とログインページの設定をsetting.pyで指定する


setting.py

TEMPLATES = [

{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates') # 追記
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

# login setting
LOGIN_ERROR_URL = '/login/'
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '../'


ここまでの設定でプロジェクトから表示するページまでのプログラム上の繋がりが完成した


HTML作成


bootstrapをダウンロード

・見た目を整えるためにbootstrapを使うので,bootstrap4のソースコードをダウンロードする

解凍してその中身をそのままtaskMan/static/bootstrap直下にコピーする

この際static/bootstrapフォルダを作成する


tree

taskMan

|---accounts
|---static
| |---bootstrap
|
|---taskMan
|---manage.py

そして,staticフォルダがどこにあるのかをプロジェクトのsetting.pyに記載する


setting.py

# Static file settings

STATIC_ROOT = os.path.join(BASE_DIR, 'assets')

STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)



ベースHTMLの作成

毎回linkタグやmetaタグなどのヘッダー情報を書くのは大変なので,雛型となるhtmlをtemplatesに作成する


base.html

<!DOCTYPE html>

<html lang='ja'>
<head>
{% load staticfiles %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">

<title>{% block title %}{% endblock %}</title>

<link href="{% static 'bootstrap/dist/css/bootstrap.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap-grid.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap-grid.min.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap-reboot.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap-reboot.min.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/docs/4.0/examples/dashboard/dashboard.css' %}" rel="stylesheet">

<script type="text/javascript" src="{% static 'bootstrap/assets/js/vendor/jquery-slim.min.js' %}"></script>

<script src="{% static 'bootstrap/assets/js/vendor/anchor.min.js' %}"></script>
<script src="{% static 'bootstrap/assets/js/vendor/clipboard.min.js' %}"></script>
<script src="{% static 'bootstrap/assets/js/vendor/holder.min.js' %}"></script>
<script src="{% static 'bootstrap/assets/js/vendor/popper.min.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/bootstrap.bundle.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/bootstrap.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/bootstrap.min.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/alert.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/button.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/carousel.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/collapse.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/dropdown.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/index.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/modal.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/scrollspy.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/tab.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/tooltip.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/util.js' %}"></script>
</head>

<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="/">TaskMan</a>
<div class="form-control form-control-dark w-100">{% block mainT %}{% endblock %}</div>
<div class="form-control form-control-dark w-100" style="text-align: right;">{% block mainL %}{% endblock %}</div>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
{% block auth %}{% endblock %}
</li>
</ul>
</nav>

{% block body %}{% endblock %}

</body>
</html>


トップページ,ログインページ,ログアウトページをtemplatesにそれぞれ作成する


top.html

{% extends "base.html" %}

{% block title %} TaskMan {% endblock %}
{% block mainT %} トップメニュー {% endblock %}
{% block mainL %}UserID : {{ user.get_username }}{% endblock %}
{% block auth %}<a class="nav-link" href="../logout/">ログアウト</a>{% endblock %}
{% block body %}
<h2 style="text-align: center;">Hello World!</h2>
{% endblock %}


login.html

{% extends "base.html" %}

{% block title %} TaskMan ログイン {% endblock %}
{% block mainT %} ログイン {% endblock %}
{% block mainL %}UserID : {{ user.get_username }}{% endblock %}
{% block auth %}{% endblock %}
{% block body %}
<div style='padding-top: 50px;'>
<form action="" method="POST">
<div class="col-md-6 offset-md-3">
<div class="card">
<div class="card-body">
{{ form.non_field_errors }}
{% for field in form %}
{{ field }}
{{ field.errors }}
<hr>
{% endfor %}
<button type="submit" class="btn btn-lg btn-primary btn-block" >ログイン</button>
<input type="hidden" name="next" value="{{ next }}" />
{% csrf_token %}
</div>
</div>
</div>
</form>
</div>
{% endblock %}


lgout.html

{% extends "base.html" %}

{% block title %} TaskMan ログアウト {% endblock %}
{% block mainT %} ログアウト {% endblock %}
{% block mainL %}UserID : {{ user.get_username }}{% endblock %}
{% block auth %}{% endblock %}
{% block body %}
<div style="text-align: center; padding-top: 50px;">
<h2 >お疲れ様でした</h2>
<a class="nav-link" href="../login/">ログイン</a>
</div>
{% endblock %}

HTMLも完成したので,localhostにアクセスするとログイン画面が表示される

image.png

前回作成したアカウントでログインすると

トップページが表示される

image.png

ログアウトをして,トップページに移ろうとするとログインページにリダイレクトする


アカウント登録


URL設定

アカウント登録するためのURLをaccounts/urls.pyに追加


urls.py

urlpatterns = [

path('', views.Top.as_view(), name='top'),
path('login/', views.Login.as_view(), name='login'),
path('logout/', views.Logout.as_view(), name='logout'),
path('registration/', views.Registration.as_view(), name='registration'), # 追加
path('registration/complete', views.RegistrationComp.as_view(), name='registration_complete'), # 追加
]

アカウント登録用のフォームを作成するので,accounts/forms.pyに以下を追加


forms.py

from django import forms

from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import get_user_model

User = get_user_model()

"""アカウント登録フォーム"""
class RegistrationForm(UserCreationForm):

class Meta:
model = User
fields = (
'username', 'email',
'password1', 'password2',
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'


get_user_model()によって,現在アクティブになっているユーザモデルを使用できる

なので,カスタムユーザモデルならカスタムユーザモデルで,そうでないならデフォルトのユーザモデルを使用する

また,Meta情報に`fields'を書き込まないとフォームが使えないので注意する(自分はここでしばらく悩んだ)

Viewに以下を追加する


views.py

from django.conf import settings

from django.contrib.sites.shortcuts import get_current_site
from django.contrib.auth import get_user_model
from .forms import RegistrationForm

User = get_user_model()

"""アカウント登録ページ"""
class Registration(generic.CreateView):
model = User
template_name = 'registration.html'
form_class = RegistrationForm
success_url ='/registration/complete'

"""アカウント登録完了"""
class RegistrationComp(generic.TemplateView):
template_name = 'registration_complete.html'


アカウント登録フォームを表示するページと,登録完了用のページを準備する。


HTML作成

アカウント登録用のregistration.html'と登録完了用のregistration_complete.html'を作成し,アカウント登録画面への入り口をlogin.htmlに作成する


login.html

{% extends "base.html" %}

{% block title %} TaskMan ログイン {% endblock %}
{% block mainT %} ログイン {% endblock %}
{% block mainL %}User : {{ user.get_username }}{% endblock %}
{% block auth %}
{% endblock %}
{% block body %}
<div style='padding-top: 50px;'>
<form action="" method="POST">
<div class="col-md-6 offset-md-3">
<div class="card">
<div class="card-body">
{{ form.non_field_errors }}
{% for field in form %}
{{ field }}
{{ field.errors }}
<hr>
{% endfor %}
<button type="submit" class="btn btn-lg btn-primary btn-block" >ログイン</button>
<input type="hidden" name="next" value="{{ next }}" />
{% csrf_token %}
</div>
</div>
<div class="authElement"> # 追加
<a class="btn btn-outline-secondary" href="/registration/">アカウント登録</a> # 追加
</div> # 追加
</div>
</form>
</div>
{% endblock %}

この時に,見た目を整えるためにCSSを作り適用させる

新しくstatic/css/adjustment.cssを作成する


adjustment.css

.authElement {

margin:20px;
text-align: center;
}

このCSSを適用するために,base.htmlのヘッダに追記する


base.html

<link href="{% static 'css/adjustment.css' %}" rel="stylesheet" type="text/css">


次に登録画面を作成する


registration.html

<div style="padding:20px 200px 0px 200px;">

<form action="" method="POST">
{{ form.non_field_errors }}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label_tag }}</label>
{{ field }}
<span style="color:red">{{ field.errors }}<span>
</div>
{% endfor %}
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-lg">登録</button>
</form>
</div>

そして、登録完了画面を作成


registration_complete.py

    <div style="text-align: center; padding-top: 50px;">

<h2 >登録が完了しました</h2>
<a class="nav-link" href="/login/">ログイン</a>
</div>

登録処理が完成

画面で確認するとこうなる

ログイン画面

image.png

登録画面

image.png

項目を入力し登録をする

image.png

image.png

登録したアカウントでログインできることを確認する

image.png

image.png

アカウント登録が正常にできていることが確認できた


今回のまとめ

今回は導入したかったログイン関係とアカウント登録の作成ができた。しかし,パスワード変更ができないなどまだ課題は残っているので次回はそこら辺を解決していきたい。


シリーズ

Djangoでタスク管理アプリをつくりたい! 環境構築編

・Djangoでタスク管理アプリをつくりたい! ユーザ認証編

Djangoでタスク管理アプリをつくりたい! ユーザ情報編

Djangoでタスク管理アプリをつくりたい! プロジェクト管理編

Djangoでタスク管理アプリをつくりたい! タスク管理編1


追記

(2018/11/12)

@khskさん 誤字の指摘・修正ありがとうございます。