DjangoとVueでカンバンアプリケーションを作る(2)

※はじめからは[こちら](https://qiita.com/denzow/items/046f3c8b9bd8d3378eb4#_reference-7b3e4a58510793f8a9c7)


第2回目ではログイン処理を作っていきます。Djangoは実はデフォルトでログイン周りの機能が実装されています。`http://localhost:3000/admin`で表示される管理画面用と同じものが利用できます。

しかし、せっかくなのでデザイン等を少しいじりたいと思います。

### 初期ユーザの作成

ログイン周りを作っていく上では、ユーザがあると便利なので先に作ってしまいます。ユーザ名は何でもいいですが、とりあえず`test`としてパスワードも`test`としています。

```
$ docker-compose exec service python manage.py createsuperuser
WARNING: The DJANGO_ENV variable is not set. Defaulting to a blank string.
Username (leave blank to use 'root'): test  ★入力
Email address:
Password:  ★見えないが、testと入力
Password (again): ★見えないが、testと入力
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y ★y
Superuser created successfully.
```

ユーザ名と、2度のパスワード入力をしてそれ以外はEnterでSKIPすると、パスワードが短いが良いか？と聞かれるのでyを入力してユーザの作成が完了します。

念の為以下にアクセスしtest/testでログインできるかは確認しておきましょう。

http://localhost:3000/admin/

<img width="844" alt="Site_administration___Django_site_admin_と_Franz.png" src="https://qiita-image-store.s3.amazonaws.com/0/44722/5bb54062-34c3-419f-db32-835945d45ba4.png">

管理画面に入れるようになっているはずです。

### ホーム画面のログイン必須化

今は`http://localhost:3000`にログインせずにアクセスできるようになっていますが、こちらをログイン必須にしましょう。

Djangoでは`login_required`という関数が用意されており、これを利用すると簡単に特定URLへのアクセスをログイン必須にすることができます。

また、認証周りのロジックに割り当てるURLも指定しておきます。

```diff:application/views/urls.py
@@ -1,13 +1,15 @@
 from django.conf import settings
 from django.conf.urls.static import static
+from django.contrib.auth.decorators import login_required
 from django.contrib import admin
-from django.urls import path, re_path
+from django.urls import path, re_path, include
 from django.views.generic import TemplateView

 urlpatterns = [
+    path('accounts/', include('django.contrib.auth.urls')),
     path('admin/', admin.site.urls),
 ]
 urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

 # app routing to vue-router
-urlpatterns += [re_path('.*', TemplateView.as_view(template_name='index.html'))]
+urlpatterns += [re_path('.*', login_required(TemplateView.as_view(template_name='index.html')))]
```

もし、先程の管理画面にログインしたままであれば一旦右上のLOGOUTをクリックしてログアウトした上で`http://localhost:3000/`にアクセスしてみましょう。

<img width="932" alt="TemplateDoesNotExist_at__accounts_login_.png" src="https://qiita-image-store.s3.amazonaws.com/0/44722/5abd277f-83e5-7742-255a-db39f43c0cd2.png">

`registration/login.html`というファイルが見つからないというエラーになります。これはログイン画面にリダイレクトしたものの、そこで使用するテンプレートがないため表示できなかったことを意味しています。では、ないと言われたファイルを作りましょう。

### templateの作成

以下のファイルを作成します。

```html:application/templates/registration/login.html
{% extends "base.html" %}

{% block title %}LOGIN{% endblock %}

{% block body %}

  <div class="container py-5">
    <div class="row">
      <div class="col-md-12">
        <h2 class="text-center mb-4">The KANBAN</h2>
        <div class="row">
          <div class="col-md-6 mx-auto">

            <div class="card rounded-0">
              <div class="card-header">
                <h3 class="mb-0">Login</h3>
              </div>
              <div class="card-body">

                {% if form.errors %}
                   <div class="alert alert-danger" role="alert">Your username and password didn't match. Please try again.</div>
                {% endif %}

                {% if next %}
                    {% if user.is_authenticated %}
                    <p>Your account doesn't have access to this page. To proceed,
                    please login with an account that has access.</p>
                    {% endif %}
                {% endif %}

                <p>Please login to see this page. (or <a href="/accounts/signup/">signup</a>)</p>
                <form class="form" role="form" autocomplete="off" id="formLogin" method="post" action="{% url 'login' %}">
                  {% csrf_token %}
                  <div class="form-group">
                    <label for="uname1">Username</label>
                    <input type="text" class="form-control form-control-lg rounded-0" name="username" id="username"
                           required="">
                    <div class="invalid-feedback">Oops, you missed this one.</div>
                  </div>
                  <div class="form-group">
                    <label>Password</label>
                    <input type="password" class="form-control form-control-lg rounded-0" id="password"
                           name="password"
                           required=""
                           autocomplete="new-password">
                    <div class="invalid-feedback">Enter your password too!</div>
                  </div>
                  <button type="submit" class="btn btn-success btn-lg float-right" id="btnLogin">Login</button>
                </form>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
{% endblock %}
```

結構込み入ったHTMLですが、おしゃれなログイン画面をBootstrap4で作ろうと思った結果やむなしでした。ここは申し訳ないですがコピペでおねがいします。

また、ログイン周りの設定を少し入れておきます。


```diff:application/settings/base.py
@@ -127,3 +127,8 @@ STATIC_ROOT = os.path.join(APP_ROOT_PATH, 'static')

 # HTTPの起点を指定
 ROOT_URLCONF = 'application.views.urls'
+
+# login related
+LOGIN_REDIRECT_URL = '/'
+LOGIN_URL = '/accounts/login/'
+LOGOUT_REDIRECT_URL = '/'
```

`LOGIN_REDIRECT_URL`はログイン完了後にデフォルトでどのページに飛ばすかの指定で、ここでは`/`に飛ばすようにしています。また、`LOGOUT_REDIRECT_URL`は逆にログアウト後にどこに飛ばすかです。

これらの変更をしてから再度`http://localhost:3000`にアクセスすると、以下のようなログイン画面が出るはずです。

<img width="831" alt="LOGIN.png" src="https://qiita-image-store.s3.amazonaws.com/0/44722/db853f47-3d18-6c46-bba7-0b85a8e0717d.png">

ここからtest/testでログインすればちゃんとログイン後の画面がでるようになります。

### ユーザ作成の実装

さて、ちゃんとログインできるようになりましたがユーザを作成する画面がないのでtest以外のユーザがいません。これを実装していきましょう。

まずはデザインからつくっていきます。ほとんどLoginと同じものを作ります。

```html:application/templates/registration/signup.html
{% extends "base.html" %}

{% block title %}SIGNUP{% endblock %}

{% block body %}

  <div class="container py-5">
    <div class="row">
      <div class="col-md-12">
        <h2 class="text-center mb-4">SignUp Form</h2>
        <div class="row">
          <div class="col-md-6 mx-auto">

            <div class="card rounded-0">
              <div class="card-header">
                <h3 class="mb-0">SignUp</h3>
              </div>
              <div class="card-body">

                {% if form.errors %}
                  <div class="alert alert-danger" role="alert">{{ form.errors }}</div>
                {% endif %}

                {% if next %}
                    {% if user.is_authenticated %}
                    <p>Your account doesn't have access to this page. To proceed,
                    please login with an account that has access.</p>
                    {% endif %}
                {% endif %}

                <p>Please signup to see this page. (or <a href="/accounts/login/">login</a>)</p>
                <form class="form" role="form" autocomplete="off" id="formLogin" method="post" action="{% url 'signup' %}">
                  {% csrf_token %}
                  <div class="form-group">
                    <label for="uname1">Username</label>
                    <input type="text" class="form-control form-control-lg rounded-0" name="username" id="username"
                           required="">
                    <div class="invalid-feedback">Oops, you missed this one.</div>
                  </div>

                 <div class="form-group">
                    <label>Password</label>
                    <input type="password" class="form-control form-control-lg rounded-0" id="password1"
                           name="password1"
                           required=""
                           autocomplete="new-password">
                    <div class="invalid-feedback">Enter your password too!</div>
                  </div>

                 <div class="form-group">
                    <label>Re Password</label>
                    <input type="password" class="form-control form-control-lg rounded-0" id="password2"
                           name="password2"
                           required=""
                           autocomplete="new-password">
                    <div class="invalid-feedback">Enter your password too!</div>
                  </div>
                  <button type="submit" class="btn btn-info btn-lg float-right" id="btnLogin">SignUp</button>
                </form>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
{% endblock %}
```


ユーザ作成の機能は標準では用意されていないのでViewを自分で実装します。`application/views/accounts/signup.py`としてファイルとして保存します。

```python:application/views/accounts/signup.py
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.views import generic


class SignUpView(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('login')
    template_name = 'registration/signup.html'

```

`generic.CreateView`はDjangoの機能で、あるモデル(データ)の作成を簡単にしてくれる関数です。`form_class = UserCreationForm`で指定しているようにユーザの作成フォームを`registration/signup.html`で表示してくれます、

最後はこのViewとURLのマッピングを追加します。

```diff:application/views/urls.py
 from django.urls import path, re_path, include
 from django.views.generic import TemplateView

+from .accounts import signup as signup_view
+
+
 urlpatterns = [
+    path('accounts/signup/', signup_view.SignUpView.as_view(), name='signup'),
     path('accounts/', include('django.contrib.auth.urls')),
     path('admin/', admin.site.urls),
 ]
```

これで、`http://localhost:3000/accounts/signup/`にアクセスすると以下のような画面が出ます。

<img width="786" alt="SIGNUP.png" src="https://qiita-image-store.s3.amazonaws.com/0/44722/822b51e2-1abf-38f5-be78-d8bdedec4fb7.png">

これでユーザ作成画面は完了です。

ついでに、ログイン画面側にもsignupへのリンクを貼っておきましょう。

```diff:application/templates/registration/login.html
               </div>
               <div class="card-body">

-                <p>Please login to see this page.</p>
+                <p>Please login to see this page. (or <a href="/accounts/signup/">signup</a>)</p>
                 <form class="form" role="form" autocomplete="off" id="formLogin" method="post" action="{% url 'login' %}">
                   {% csrf_token %}
                   <div class="form-group">
```

これでログイン画面にsignupへのリンクがはられました。


<img width="727" alt="LOGIN.png" src="https://qiita-image-store.s3.amazonaws.com/0/44722/87ac49b8-2df5-f3f8-8d4b-fc0e00cd15cd.png">

ログイン周りはこれで完了です。


### 次回

https://qiita.com/denzow/items/f13717d81eaa2e72086e