【Django】ログイン・ログアウト機能の実装

#はじめに
ログイン・ログアウト機能を実装したので、その際に調べた内容を備忘録として投稿します。

どなたかの役に立てば幸いです。

記事を作成した後に確認したのですが、結構雑に記載してしまったので、
よくわからない箇所があったら各自調べてください・・・。すみません。。

##実装

プロジェクトの作成からログイン・ログアウト機能の実装まで全て記載していきます。

###プロジェクト作成

login_sample_2というプロジェクトを作成していますが、
別にここの箇所はなんでもいいです。

ついでにcdでディレクトリを移動しておきます。

```
$ django-admin startproject login_sample_2
$ cd login_sample_2/
```

###アプリケーションの作成
今回は`account`というアプリケーションを作成します。
アプリケーション名もなんでも良いです。

```
$ python manage.py startapp account
```

###settings.pyの修正

`settings.py`に必要なもの修正していきます。

先程作成したアプリケーションと、
ログインとログアウトに関わるDjangoの基本設定を行います。

後は言語やタイムゾーンも変更しておきましょう。

```python:settings.py
INSTALLED_APPS = [
    'account',      ### 追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

# LANGUAGE_CODE = 'en-us'    ### 修正前
LANGUAGE_CODE = 'ja'    ### 修正後

# TIME_ZONE = 'UTC'    ### 修正前
TIME_ZONE = 'Asia/Tokyo'    ### 修正後

LOGIN_URL = "account:login"      ### 追加
LOGIN_REDIRECT_URL = "account:home"      ### 追加
LOGOUT_REDIRECT_URL = "account:top"      ### 追加
```

| パラメータ | 説明 |
|:-:|:-:|
| LOGIN_URL  | ログインが必要な画面にログインしていないユーザがアクセスした際に自動的にリダイレクトされるページ  |
| LOGIN_REDIRECT_URL  | ログイン後に自動的にリダイレクトされるページ  |
| LOGOUT_REDIRECT_URL  | ログアウト後に自動的にリダイレクトされるページ  |

先頭についている`account:`は、
urls.pyに記載されている`app_name`の値が`account`の
urlpatternsを参照するように指定しています。


###forms.pyの作成

ログイン画面のフォームを作成します。

今回は、Djangoに標準で用意されているAuthenticationFormというクラスを利用します。
AuthenticationFormクラスには標準でユーザ名とパスワードのフォームが定義されているため、
簡単にユーザ名とパスワードで認証する画面フォームを作成できます。

まず、account配下に`forms.py`というファイルを作成します。

forms.pyの中身を下記のように記述します。

```python: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"
```

###views.pyの修正

`views.py`を以下の通りに修正します。

最初に表示するトップ画面と、ログイン後に表示するホーム画面、
Django標準クラスのLoginViewと、LogoutViewを継承したクラスを作成します。
また、LogoutViewの方には、LoginRequiredMixinも継承します。

LoginRequiredMixinを継承すると、Loginしたユーザのみ閲覧できるようになります。

ログインしていないユーザが閲覧しようとすると、
settigns.pyで定義したLOGIN_URLにリダイレクトします。

また、LogoutViewの`template_name`は、
ログアウト後に表示するページを渡しています。

```python:views.py
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView

from . import forms


class TopView(TemplateView):
    template_name = "account/top.html"

class HomeView(LoginRequiredMixin, TemplateView):
    template_name = "account/home.html"

class LiginView(LoginView):
    """ログインページ"""
    form_class = forms.LoginForm
    template_name = "account/login.html"

class LogoutView(LoginRequiredMixin, LogoutView):
    """ログアウトページ"""
    template_name = "account/login.html"
    


```

###urls.pyの修正

次にurls.pyの修正を行います。

まず先にプロジェクトフォルダ配下のurls.pyです。

```python:login_sample_2/urls.py
from django.contrib import admin
from django.urls import path, include   ### include追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path("", include("account.urls")),  ### 追加
]
```

`path("", include("account.urls"))`を追記することにより、
`http://127.0.0.1:8000/`にアクセスした際に`account.urls`を参照するようになります。

`path("account/", include("account.urls"))`などにすれば、
`http://127.0.0.1:8000/account/`にアクセスした際に参照するようになります。

次に、アプリケーション配下のurls.pyを作成します。
初期状態ではファイルが存在しないので、ファイル作成をしてから記載します。

```python:account/urls.py
from django.conf.urls import url
from django.urls import path

from . import views


app_name="account"
urlpatterns = [
    path("", views.TopView.as_view(), name="top"),
    path("home/", views.HomeView.as_view(), name="home"),
    path("login/", views.LoginView.as_view(), name="login"),
    path("logout/", views.LogoutView.as_view(), name="logout"),
]
```

`path("login/", views.LoginView.as_view(), name="login")`により、
`http://127.0.0.1:8000/login`にアクセスするとログイン画面が表示されるようになります。

###テンプレートファイルの作成

次に表示するテンプレートファイルの作成です。

アプリケーション配下にtemplatesフォルダを作成し、さらにその配下に
アプリケーション名と同じ`account`フォルダを作成します。

accountフォルダ配下に以下の4つのファイルを作成します。
・base.html
・home.html
・top.html
・login.html

構造は下記のようになっていると思います。

```
$ tree
├── account
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── models.py
│   ├── templates
│   │   └── account
│   │       ├── base.html
│   │       ├── home.html
│   │       └── top.html
│   │       └── login.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
```

では各ファイルを修正していきます。

まずはベースとなる`base.html`から。

[Bootstrap4](https://getbootstrap.jp/docs/4.3/getting-started/introduction/)のスタータテンプレートをコピペしています。
Bootstrapを利用すると簡単に綺麗なデザインでWebサイトを組めるので
ぜひ学習してみてください。

`{% block title %}{% endblock %}`と
`{% block content %}{% endblock %}`の箇所は
Django特有の記述方法である、タグ機能です。

これについては後ほど説明します。

```python: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, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>{% block title %}{% endblock %}</title>    ### 修正
  </head>
  <body>

    {% block content %}{% endblock %}    ### 修正
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>
```

これをベースにして他のファイルの作成を行います。

次に`top.html`です。
ここではログイン画面に行くためのリンク記述しておきます。

`{% extends "account/base.html" %}`により、
base.htmlの内容を引き継ぐことができます。

そして、base.htmlにて記載のあった`{% block **** %}{% endblock %}`ですが、
top.htmlでも似たものが登場します。

これは、base.htmlの{% block %}で囲まれている箇所と紐づけているようなイメージです。

```python:account/top.html
{% extends "account/base.html" %}

{% block title %}トップ{% endblock %}

{% block content %}
<div class="container">
    <h1 class="my-5">Top画面</h1>
    <a href="{% url 'account:login' %}" class="btn btn-outline-primary">ログイン</a>
</div>
{% endblock %}
```

base.htmlを1行目の`extendsタグ`で継承していることにより
top.htmlの内容はとても簡単に見えますが、
もし継承していない状態だったら下記のようになります。

```python:account/top.html
<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>トップ</title>
  </head>
  <body>

    <div class="container">
        <h1 class="my-5">Top画面</h1>
        <a href="{% url 'account:login' %}" class="btn btn-outline-primary">ログイン</a>
    </div>
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>
```

titleとbody内は各htmlで異なるので、blockタグで囲んで各ファイルで変更可能にしています。

また、aタグの`{% url 'account:login' %}`ですが、
app_nameがaccountになっているurlpatternsの中からname="login"のものを探しています。

次に`login.html`の内容を記述します。

例のごとく`extends`でbase.htmlを継承。
フォームを作成する際にはformタグで囲んだ中に色々記述します。

`{% csrf_token %}`はアプリの脆弱性対策のために記載します。必須です。

`{% for field in form %}{% endfor %}`内で
色々表示しています。

```python:login.html
{% extends "account/base.html" %}
{% block title %}ログイン{% endblock %}

{% block content %}
<div class="container">
    <h1>ログイン</h1>
    <form action="" method="POST">
        {% csrf_token %}
        {{ form.non_field_errors }}
        {% for field in form %}
            {{ field.label }}
            <br>
            {{ field }}
            <br>
            {{ field.error }}
        {% endfor %}
        <br>
        <button type="submit">ログイン</button>
    </form>
</div>
{% endblock %}
```

ログイン後はsettings.pyで記述した、
LOGIN_REDIRECT_URLにリダイレクトされます。
今回は`account:home`になります。

続いて`home.html`の内容を記述していきます。

`{{ user.username }}`はログインしているユーザの名前を取得しています。

```python:home.py
{% extends "account/base.html" %}
{% block title %}ホーム{% endblock %}

{% block content %}
<div class="container">
    <h1 class="my-5"></h1>
    <p>{{ user.username }}さん、こんにちは。</p>
    <a href="{% url 'account:logout' %}">ログアウト</a>
</div>
{% endblock %}
```

ログアウトは、`{% url 'account:logout' %}`でOKです。

ここまでできたら、コンソールを操作します。
下記の3つを実行してください。

`createsuperuser`では管理者ユーザを作成します。
任意のユーザ名とパスワードを入力してください。

メールアドレスは入力しなくても大丈夫です。

```
$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver
```

サーバを起動したら、`http://127.0.0.1:8000`にアクセスします。

こんな感じの画面が出てきましたかね？
![スクリーンショット 2021-07-28 23.49.49.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/11bb63a5-8d6c-ec42-5692-6b4af670e605.jpeg)

(ちなみに、ログインボタンが少しかっこいいのはBootstrapで設定したからです。
top.htmlの`class="btn btn-outline-primary"`の箇所です。)

ログインボタンを押すと、次の画面にいきました。
URLは`http://127.0.0.1:8000/login`になってますね。
![スクリーンショット 2021-07-28 23.51.22.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/d9b7db1c-5b06-efa0-abd5-223bd5947abd.jpeg)

ここで先程createsuperuserで作成した情報を入力しましょう。

`http://127.0.0.1:8000/home`に遷移しました。
![スクリーンショット 2021-07-28 23.52.32.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/f3a154f2-8ff2-9cdd-c8b6-abaf42781a92.jpeg)

adminという名前でユーザを作成したので、「adminさん、こんにちは」と表示されます。
ログアウトを押すと、top画面に戻ることを確認しましょう。

戻った後に、URLに`http://127.0.0.1:8000/home`と入力して、
ログインしていない状態でホーム画面にアクセスしてみます。

いきなりホーム画面には行けずログイン画面が表示されています！
URLも`http://127.0.0.1:8000/login/?next=/home/`になっています。
![スクリーンショット 2021-07-28 23.54.31.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/772cd6b2-9028-89f1-69e2-5ed4b08a2d9b.jpeg)

こんな感じですかね！

#さいごに

最後までご覧いただきありがとうございました。

記事の作成中にミスに気づいて結構訂正したのですが、
その名残で一部おかしいところあるかもです。。。

もし何か記載ミスがあったらご連絡ください！