Django

Django 簡単なアプリを作って基礎を覚える⑩ ログイン機能を実装しよう!

CRUDをすべてやったので、次は、システムでは必須のログイン機能について実装しましょう。

Djnagoには、ユーザー管理が標準で備わっています。ただし、標準タイプとAbstractBaseUserと2つあり、本格的なシステムを作成するときには、拡張性が高いカスタムユーザーで作成することが、公式サイトでも推奨されています。

運用開始後のAbstractBaseUserへの切替は、めっちゃ大変なのでしないこと💦

推奨されているAbstractBaseUserで作成しないと、あとでUserモデルからCustomuserに変えることは、めっちゃ大変みたいなのでやめておいてください。

今回は、データベースを一度、削除してからmaigreをやり直して実装していきます。

Djangoのユーザー認証機能は、最低限の機能がそろっている

Djangoの標準機能であるユーザー認証機能については、

  • ログイン
  • ログアウト
  • パスワード変更
  • メールを使ってのパスワード変更

ユーザー登録は、機能としてはありませんので、管理画面で登録するか機能を作成する必要があります。これは、作るシステムによって異なると思います。

カスタムユーザー情報を設定する

まずは、accountsというアプリを作成しましょう❕

python manage.py startapp accounts

accounts/models.pyに追記

from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.core.mail import send_mail
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from django.contrib.auth.models import PermissionsMixin, UserManager

from django.contrib.auth.validators import UnicodeUsernameValidator


class CustomUser(AbstractBaseUser, PermissionsMixin):
    username_validator = UnicodeUsernameValidator()
    username = models.CharField(
        _('username'),
        max_length=7,
        unique=True,
        help_text=_('username),
        validators=[username_validator],
        error_messages={
            'unique': _("すでに登録されているユーザーです"),
        },
    )
    first_name = models.CharField(_('姓'), max_length=30, blank=True)
    last_name = models.CharField(_('名'), max_length=150, blank=True)
    email = models.EmailField(_('Eメール'), blank=True)
    is_staff = models.BooleanField(
        _('管理者'),
        default=False,
        help_text=_('管理サイトにログインできるかどうか'),
    )
    is_active = models.BooleanField(
        _('退社済'),
        default=True,
        help_text=_(
            '無効フラグ'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        # abstract = True

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def get_full_name(self):
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)
Django AbstractBaseUserのでデータベースが作成され管理画面をみてみる
ここからは、管理画面に表示するための設定をします。

先ほど作ったaccountsのadminに追記します。


from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser


admin.site.register(CustomUser, UserAdmin)

testproject/settings.py に追記

LOGIN_REDIRECT_URL='/task/list/'
AUTH_USER_MODEL = 'accounts.CustomUser'

task/models.py の修正

Userを参照していたのをCustomuserを参照するように変更します。

from django.db import models
from accounts.models import CustomUser

class TaskModel(models.Model):
    title = models.CharField(verbose_name='タイトル', max_length=100,  null=True)
    memo = models.TextField(verbose_name='内容', blank=True, null=True)
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, verbose_name='担当者',blank=True, null=True)
    plants_startdate = models.DateField(verbose_name='開始予定日', blank=True, null=True)
    plants_enddate = models.DateField(verbose_name='終了予定日', blank=True, null=True)

    def __str__(self):
        return self.title

これで、準備ができたのでmaigreをしていきます。

python manage.py makemigrations
python manage.py migrate

スーパーユーザー作成
python manage.py createsuperuser

これで、スーパーユーザー作成までできました。

ここでDjangoの標準で備わっているログイン機能について確認したいと思います。

Djangoのエラーが面で表示もしてくれます。

Using the URLconf defined in testproject.urls, Django tried these URL patterns, in this order:

  1. admin/
  2. accounts/ login/ [name='login']
  3. accounts/ logout/ [name='logout']
  4. accounts/ password_change/ [name='password_change']
  5. accounts/ password_change/done/ [name='password_change_done']
  6. accounts/ password_reset/ [name='password_reset']
  7. accounts/ password_reset/done/ [name='password_reset_done']
  8. accounts/ reset/<uidb64>/<token>/ [name='password_reset_confirm']
  9. accounts/ reset/done/ [name='password_reset_complete']

これを、表にしてみましょう❕

機能URLパステンプレート名
ログイン/accounts/login/login.html
ログアウト/accounts/logout/logged_out.html
パスワード変更/accounts/password_change/password_change_form.html
パスワード変更終了/accounts/password_change/done/password_change_done.html
パスワード再設定メール送信/accounts/password_reset/password_reset_form.html
パスワード再設定メール送信終了/accounts/password_reset/done/password_reset_done.html
パスワード再設定/accounts/reset/<uidb64>/<token>/password_reset_confirm.html
パスワード再設定終了/accounts/reset/done/password_reset_complete.html

わかりやすくなりましたね!標準でこれだけの機能をもっているなんて本当に便利ですよね!

指示役のurls.py にaccountsに指示を出せるようにする

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('django.contrib.auth.urls')), #追加
    path('task/', include('task.urls')),
]

次は、今までならviewsファイルに、与えられた命令の処理を書いていましたが、ここは標準機能が行ってくれます。

なので、次は、テンプレートを作ります。

フォルダの名称は決められています。templatesの直下に、registrationを作成し、そのなかにlogin.htmlを作成します。

ログイン用のBaseファイルと、ログイン時に使うloginファイルを作成します。

base.html にtemplates直下にあるbase.htmlをコピーしてregistrationのbase.htmlに貼り付ける

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">

    <title>
    {% block title %}
    {% endblock title %}
    </title>
  </head>
  <body>
     {% block header %}
    {% endblock header %}

    {% block content %}
    {% endblock content %}

    <!-- Optional JavaScript; choose one of the two! -->

    <!-- Option 1: Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>

    <!-- Option 2: Separate Popper and Bootstrap JS -->
    <!--
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js" integrity="sha384-q2kxQ16AaE6UbzuKqyBE9/u/KzioAlnx2maXQHiDX9d4/zp8Ok3f+M7DPm+Ib6IU" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.min.js" integrity="sha384-pQQkAEnwaBkjpqZ8RU1fF1AKtTcHJwFl3pblpTlHXybJjHpMYo79HY3hIi4NKxyj" crossorigin="anonymous"></script>
    -->
  </body>
</html>

login.html を作成

{% extends 'templates/registration/base.html' %} #新しく使ったbaseファイルを読み込みする


{% block titele %}ログイン{% endblock %}

{% block content %}
<div>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-primary">ログイン</button>
    </form>
</div>
{% endblock %}

これで、ログイン画面が実装できています。

ランサーバーをして確認してみましょう!

http://127.0.0.1:8000/accounts/login/ のURLをひらく

これで、ログインなんとできるようになっています。

間違ったパスワードを入れてみましょう

次は、ログインを成功させてみましょう!

settingsファイルで、ログイン後の画面は、taskのlistを指定していますので、リストが表示されるはずです。

表示されましたね❕

ログアウト機能を作成

つぎは、ログアウト機能を実装しましょう。

ナビゲーションバーを実装する

ログアウトをするためのリンクを作成しますが、ここではリアルな場合を想定して作っていこうと思います。BootstrapのドキュメントにNavbarがあるので、templateの直下にあるbase.htmlに貼り付けます。

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">

    <title>
    {% block title %}
    {% endblock title %}
    </title>
  </head>
  <body>
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Navbar</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="#">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Link</a>
        </li>
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            Dropdown
          </a>
          <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
            <li><a class="dropdown-item" href="#">Action</a></li>
            <li><a class="dropdown-item" href="#">Another action</a></li>
            <li><hr class="dropdown-divider"></li>
            <li><a class="dropdown-item" href="#">Something else here</a></li>
          </ul>
        </li>
        <li class="nav-item">
    <!--ここをログアウトように変更-->
          <a class="nav-link" href="{% url 'logout' %}" tabindex="-1" aria-disabled="true">ログアウト</a>
        </li>
      </ul>
      <form class="d-flex">
        <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
        <button class="btn btn-outline-success" type="submit">Search</button>
      </form>
    </div>
  </div>
</nav>
    {% block header %}
    {% endblock header %}

    {% block content %}
    {% endblock content %}

    <!-- Optional JavaScript; choose one of the two! -->

    <!-- Option 1: Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>

    <!-- Option 2: Separate Popper and Bootstrap JS -->
    <!--
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js" integrity="sha384-q2kxQ16AaE6UbzuKqyBE9/u/KzioAlnx2maXQHiDX9d4/zp8Ok3f+M7DPm+Ib6IU" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.min.js" integrity="sha384-pQQkAEnwaBkjpqZ8RU1fF1AKtTcHJwFl3pblpTlHXybJjHpMYo79HY3hIi4NKxyj" crossorigin="anonymous"></script>
    -->
  </body>
</html>

これで、再度ログインしてみましょう!

bootstrap^navbar

素晴らしいですね!たったこれだけで作成できちゃいます。しかもレスポンシブル対応です❕

ログアウトをクリックしてみましょう❕

ログアウト用のテンプレが読み込みされていますね❕これでログアウトが完了しています。

ナビゲーションバーにログイン者の名前を表示する

管理画面から、管理者の氏名を登録しておきます。

ナビゲーションバーのログアウトの後にログインユーザー名を表示する

ナビゲーションバーにログインしているユーザーのフルネームをゲットして表示させるようにします。

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">

    <title>
    {% block title %}
    {% endblock title %}
    </title>
  </head>
  <body>
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Navbar</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="#">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Link</a>
        </li>
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            Dropdown
          </a>
          <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
            <li><a class="dropdown-item" href="#">Action</a></li>
            <li><a class="dropdown-item" href="#">Another action</a></li>
            <li><hr class="dropdown-divider"></li>
            <li><a class="dropdown-item" href="#">Something else here</a></li>
          </ul>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="{% url 'logout' %}" tabindex="-1" aria-disabled="true">ログアウト</a>
        </li>
        <li class="nav-item">
          <a class="nav-link disabled" tabindex="-1" aria-disabled="true">{{ user.get_full_name }}</a>
        </li>
      </ul>
      <form class="d-flex">
        <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
        <button class="btn btn-outline-success" type="submit">Search</button>
      </form>
    </div>
  </div>
</nav>
    {% block header %}
    {% endblock header %}

    {% block content %}
    {% endblock content %}

    <!-- Optional JavaScript; choose one of the two! -->

    <!-- Option 1: Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>

    <!-- Option 2: Separate Popper and Bootstrap JS -->
    <!--
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js" integrity="sha384-q2kxQ16AaE6UbzuKqyBE9/u/KzioAlnx2maXQHiDX9d4/zp8Ok3f+M7DPm+Ib6IU" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.min.js" integrity="sha384-pQQkAEnwaBkjpqZ8RU1fF1AKtTcHJwFl3pblpTlHXybJjHpMYo79HY3hIi4NKxyj" crossorigin="anonymous"></script>
    -->
  </body>
</html>

ログインしてみましょう❕

ちゃんと、名前が表示されましたね

これで、ログインできているかどうかがわかるようになりますね😀

Djangoログインを一括設定
Djangoログイン必須を一括でセットする方法

もくじ1 Djangoで ログイン必須を一括セットして、 @login_requiredをたくさん書かなくていい方法1.1 GlobalLoginRequiredMiddlewareをインストールする ...

続きを見る

-Django

© 2021 ごろう@縁紡ぐ