Python
Django

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でタスク管理アプリをつくりたい! ユーザ情報編

追記

(2018/11/12)
@khskさん 誤字の指摘・修正ありがとうございます。