【 Docker＋Nginx＋Django＋RDS】WEBアプリができるまで④ログインログアウトをしよう

# 前置き
独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
　①[Djangoのようこそページへたどり着くまで](https://qiita.com/quantity82/items/e8c2392bf982efb55f80)
　②[NginxでDjangoのようこそページへたどり着くまで](https://qiita.com/quantity82/items/88a6348135e25c661feb)
　③[カスタムユーザーを作ってadminにたどり着く](https://qiita.com/quantity82/items/b3cd6ded5bbb5f3726f3)
　④[ログインログアウトをしよう](https://qiita.com/quantity82/items/8e4b7ce2ef56e60ad392)　<--ここです
　⑤[ユーザー登録（サインイン）機能を作ろう](https://qiita.com/quantity82/items/bbfc7e72d4a65c71e24c)
　⑥[ユーザーごとのデータ登録できるようにする〜CRU編](https://qiita.com/quantity82/items/4d019a4ee475208a5a26)
　⑦[ユーザーごとのデータ登録できるようにする〜削除編](https://qiita.com/quantity82/items/c48fee71b5f8eb766dae)
　⑧[画像ファイルのアップロード](https://qiita.com/quantity82/items/9bcbd25902f6878ee325)
　⑨[身長体重を記録する＠一括削除機能つき](https://qiita.com/quantity82/items/c8e58742eeaa8c0fc954)
　⑩[成長曲線グラフを描いてみよう](https://qiita.com/quantity82/items/619f5ed2a6cd45f7f592)
　⑪[本番環境へデプロイ＋色々手直し](https://qiita.com/quantity82/items/6e91b7ee7a2d7e0fab32)

# Goal
ログインログオフ機能を作ろう

# HTML（下準備）
Djangoのtemplateをアプリごとに分けるのが面倒になりそうなので、
テンプレートは一箇所に置くことにする。最終形は以下(細かいものは省略)。

```
.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── templates
│   │   ├── base.html
│   │   └── users
│   │       ├── login.html
│   │       ├── mypage.html
│   │       └── signup.html
│   └── users
│       ├── admin.py
│       ├── apps.py
│       ├── forms.py
│       ├── models.py
│       ├── urls.py
│       └── views.py
├── static
│   └── 略
└── web
    ├── Dockerfile
    └── requirements.txt
```

templateの場所を教えてあげるために、setting.pyを修正する。

```python: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',
            ],
        },
    },
]
```

CSSはbootstrapが手っ取り早くて良い。
bootstrapはdjango用のものがあるので、それを使う。

```requirement.txt
Django==2.2.2
psycopg2==2.8.4
uwsgi==2.0.17
django-bootstrap4==1.1.1 #追加
```

settingはAPPSに追加するのと、Templateの指定にも追加する。

```python:setting.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig',
    'bootstrap4', #追加
]
(略)
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',
            ],
            'builtins':[ #追加
                'bootstrap4.templatetags.bootstrap4',
            ],
        },
    },
]
```

```

```

# HTML（本番）
ベースのHTML。bootstrapでレスポンシブ対応ぽくしている。

```html:base.html
{% load static %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}

<html>
    <head>
        <title>kids Growth</title>
        <link rel="stylesheet" href="{% static 'css/kidsGrowth.css' %}">
        {% block extra_js %}{% endblock %}
 	   </head>
    <body>
        <!-- Navigation -->
        <nav class="navbar navbar-expand-sm navbar-dark bg-dark mt-3 mb-3">
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav4" aria-controls="navbarNav4" aria-expanded="false" aria-label="Toggle navigation">
              <span class="sr-only">メニュー</span>  
              <span class="navbar-toggler-icon"></span>
            </button>
            <a class="navbar-brand" href="">Kids Growth</a>
            <div class="collapse navbar-collapse" id="navbarNav4">
                <ul class="navbar-nav">
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー１</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー２</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー３</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="{% url 'users:mypage'%}">マイページ</a>
                  </li>
                  {% if user.is_authenticated %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'users:logout'%}">ログアウト</a>
                    </li>
                  {% else %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'users:login'%}">ログイン</a>
                    </li>
                  {% endif %}
                </ul>
            </div>
        </nav>    

        <div class="content container">
		  {% block content %}
          {% endblock %}
        </div>
    </body>
</html>
```

マイページ。（ユーザー名が出るだけ）
ログイン後に飛んでくるところとして指定。
{{ user }}はあとでViewから引き渡します。

```html:users/mypage.html
{% extends 'base.html' %}

{% block content %}

<div class="col-md-12 col-lg-5">
    <h2>My Page</h2>

    <br>
    <br>

    <table class = "table">
      <tr>
        <td>ユーザー名</td>
        <td>{{ user }}</td>
      </tr>
    </table>


</div>

{% endblock content %}
```

formという名前でフォームを引き渡して、
bootstrap_formで綺麗に整形するだけ。
ユーザー登録のリンクを用意してますが、その機能は次で。

```html:users/login.html
{% extends 'base.html' %}

{% block content %}

{{ form.media }}

<div class="col-md-12 col-lg-5">
    <h2>Login</h2>
        <form action="" method="POST">
                {{ form.non_field_errors }}
                {% bootstrap_form form %}
                <hr>
                <button type="submit" class="btn btn-success btn-lg btn-block" >ログイン</button>
                {% csrf_token %}
        </form>


        <a href="{% url 'users:signup' %}">ユーザー登録</a>

</div>
{% endblock %}
```

# Form

FormとしてAuthenticationFormを継承することで、
ログイン機能を実装します。これだけ。便利。

```python:users/form.py
class LoginForm(AuthenticationForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

```


# View
ログインとログアウトは専用のViewがあるので継承する。
@login_requiredを指定した関数はログインした状態じゃないと呼ばれない。
ひとまずはログインしているユーザーの名前を取得して、templateに返すだけのお仕事。

ちなみにログインのクラスをloginにすると、このあとSignup関数を作るときに
規定のネーミングとバッティングするのかエラーが外れず、ややカッコ悪いネーミング。

```python:users/view.py
from .forms import LoginForm
from django.shortcuts import render, redirect
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.decorators import login_required

#ログイン
class login_mypage(LoginView):
    form_class = LoginForm
    template_name = 'users/login.html'

#ログアウト
class logout(LogoutView):
    template_name = 'users/login.html'

#マイページ
@login_required
def mypage(request):
    user_name = request.user
    params = {
        'user' : user_name,
    }
    return render(request, 'users/mypage.html',params)
```

# urls
プロジェクトのurlsで、アプリごとのurlsを読み込む。

```python:mysite/urls.py
from django.contrib import admin
from django.urls import path, include

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

```

アプリごとに定義したURLパターンごとにViewを決める。

```python:users/urls.py
app_name = 'users'

urlpatterns = [
    path('', views.mypage, name='mypage'),
    path('mypage/', views.mypage, name='mypage'),
    path('login/', views.login_mypage.as_view(), name='login'),
    path('logout/', views.logout.as_view(), name='logout'),
]
```
# Setting.py
以下を追加する。ログインするときのURLはここだよ〜
ログインしたらここに最初にアクセスしてね〜
ログアウトしたらなんの画面を見せるよ〜
ということを決める。

```python:setting.py
LOGIN_URL = 'users:login'
LOGIN_REDIRECT_URL = 'users:mypage'
LOGOUT_REDIRECT_URL = 'users:login'
```

# 出来上がり
ログイン画面と、ログインした後の画面。

<img width="1440" alt="スクリーンショット 2020-02-11 20.45.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547363/575f17fa-9f0b-c7f4-a12e-5385e4e366fd.png">
<img width="1440" alt="スクリーンショット 2020-02-11 20.45.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547363/91d61490-5f89-b831-3fc6-85a27a28c0c0.png">