【Django】ユーザ登録機能の実装

#はじめに
Djangoでのユーザ登録あたりの処理を実装する機会があったので、
備忘録として連携しようと思います。

まだまだ初学者なので、間違っている箇所はご指摘お願いします。

最低限の知識はある前提で記載していきますので、
わからない箇所はご自身でお調べください！

##前準備
ユーザ登録機能を実装する前に、
プロジェクトの作成やその他もろもろの記述を行なっていきます。

###仮想環境の作成

まずはじめに、仮想環境で作業を行うので仮想環境の作成を行います。
仮想環境の作成はターミナルで行います。

仮想環境を作成する場所は特に指定はないのでご自由に。

pythonのバージョン確認

```
$ python -V
Python 2.7.10

$ python3 -V
Python 3.9.4
```

仮想環境の作成

`env`の箇所は好きな名前で大丈夫です。

```
$ python -m venv env
```

Activate

```
$ source env/bin/activate
```

実行後にターミナルの先頭に`(env)`とついていたらOKです。

pythonのバージョンを確認して3以上になっていることを確認できたらOKです。

```
$ python -V
Python 3.9.4
```

###プロジェクトとアプリの作成

次にDjangoプロジェクトやアプリの作成、設定などを行います。

まずはプロジェクト作成とフォルダ移動
`create_user_project`の箇所は、お好きなプロジェクト名にしてください。

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

アプリの作成
`myapp`の箇所は、お好きなプロジェクト名にしてください。

```
$ python manage.py startapp myapp
```

一旦djangoを起動してみます。
下記のコマンドを実行すると赤字が出ますが気にしない。

```
$ python manage.py runserver
```

ブラウザを開いて、`http://127.0.0.1:8000/`にアクセスします。

下のような画面が出ればOKです。

![スクリーンショット 2021-07-23 16.21.56.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/f232946f-15c0-4fce-abd0-f15cad8bbba3.jpeg)

確認後、ターミナルで`control` + `C`を押して停止します。
(Windowsの方は別のコマンドです。)

停止後、ターミナルの操作を続けます。

マイグレーションの実行
マイグレーションはDjangoのモデルをデータベースに反映させるコマンドみたいなものです。

下記のようにOKがたくさん出てきます。

```
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
```

管理者権限を持つユーザの作成
`email`は、入力しなくてもOKです。

ユーザ名、パスワードは各自好きなものにしてください。

```
$ python manage.py createsuperuser
Username (leave blank to use 'ryosuke'): admin
Email address: 
Password: 
Password (again): 
Superuser created successfully.
```

ここからは各種ファイルの設定変更を行います。

ちなみに現在のフォルダ構成は下記の状態になっているはずです。
プロジェクト配下には、2つのファイルと2つのフォルダが存在します。

主に`create_user_project`配下のファイルと、
`myapp`配下のファイルをいじっていきます。

```
$ tree
.
├── create_user_project
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-39.pyc
│   │   ├── settings.cpython-39.pyc
│   │   ├── urls.cpython-39.pyc
│   │   └── wsgi.cpython-39.pyc
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
├── manage.py
└── myapp
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py
```

次にmyappフォルダ配下に必要になるファイルなどを先に作っておきます。
作成後は下記のような構造になっています。

一応記載しておくと下記の通り作成しました。
myappフォルダ配下に、`forms.pyファイル`、`urls.pyファイル`、`templatesフォルダ`
templatesフォルダ配下に、`myappフォルダ` ※アプリ名と同じフォルダを作成すること
myappフォルダ配下に、`home.htmlファイル`、`index.htmlファイル`、
`signup.htmlファイル`、`base.html`ファイルを作成します。

```
$ tree
.
├── create_user_project
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-39.pyc
│   │   ├── settings.cpython-39.pyc
│   │   ├── urls.cpython-39.pyc
│   │   └── wsgi.cpython-39.pyc
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
├── manage.py
└── myapp
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-39.pyc
    │   ├── admin.cpython-39.pyc
    │   ├── apps.cpython-39.pyc
    │   └── models.cpython-39.pyc
    ├── admin.py
    ├── apps.py
    ├── forms.py   ### 追加
    ├── migrations
    │   ├── __init__.py
    │   └── __pycache__
    │       └── __init__.cpython-39.pyc
    ├── models.py
    ├── templates   ### 追加
    │   └── myapp   ### 追加
    │       ├── base.html   ### 追加
    │       ├── home.html   ### 追加
    │       ├── index.html   ### 追加
    │       └── signup.html   ### 追加
    ├── tests.py
    ├── urls.py   ### 追加
    └── views.py
```

ここからはファイルの修正などを行なっていきます。
まずは、プロジェクトフォルダ配下の`settings.py`の修正

```python:settings.py
# インストール アプリ
INSTALLED_APPS = [
    'myapp',    ### 追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

# 言語
LANGUAGE_CODE = 'ja'   ### en-us から ja に変更

# タイムゾーン
TIME_ZONE = 'Asia/Tokyo'    ### UTC から Asia/Tokyo　に変更
```
この段階で、`python manage.py runserver`を実行しブラウザを確認すると
日本語表記になって表示されているはずです。

気になる方はご確認ください。

次にプロジェクトフォルダ配下の`urls.py`の修正を行います。
`http://127.0.0.1/`にアクセスしたら`myapp.urls`を参照してねというイメージです。

`''`の中身を`'myapp/'`にした場合は、
`http://127.0.0.1/myapp/`にアクセスしたら
`myapp.urls`を参照してねというイメージです。

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

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

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

IndexViewを`urls.py`で呼び出した際には、
`template_name`に記載されているファイルが表示されます。

```python:myapp/views.py
from django.shortcuts import render
from django.views.generic import TemplateView


class IndexView(TemplateView):
    template_name = "myapp/index.html"
```

myappフォルダ配下のurls.pyを修正します。
とりあえず下記の通りに記載します。また後で追記します。

`top/`にアクセスした時はIndexViewを返します。
IndexViewには上で記載した通り`index.html`が紐づいています。

```python:myapp/urls.py
from django.urls import path

from . import views


app_name = "myapp"
urlpatterns = [
    path('top/', views.IndexView.as_view(), name='index'),
]
```

今のままだとブラウザに何も表示されないため、
`base.html`ファイルと、`index.html`ファイルを修正します。

まずは、`base.html`から。
ベースとなるようなhtmlを記載します。

[ブートストラップ](https://getbootstrap.jp/docs/4.3/getting-started/introduction/)のスターターテンプレートの内容をコピーして貼り付けます。

`<title>タグ`と、`<body>タグ`の中身を一部修正します。

```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>
    <div class="container">                  #### 追加
        {% block content %}{% endblock %}    #### 追加
    </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>
```

次に、`index.html`を記述します。

このファイルでは、base.htmlを読み込んで一部修正するようにします。

```python:index.html
{% extends "myapp/base.html" %}     ### 先程の base.html 読み込み
{% block title %}トップページ{% endblock %}   ### titleブロックの中身書き込み

{% block content %}   ### contentブロックの中身書き込み
<h1>トップページ</h1>
<p><a href="">ユーザ作成</a></p>
{% endblock %}
```

ここまで記載したら一度起動し、ブラウザから
`http://127.0.0.1/top/`にアクセスします。

```
$ python manage.py runserver
```

こんな感じだと思います。
![スクリーンショット 2021-07-23 17.25.46.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/e91d4a70-3cd6-ed8b-cefb-a0fd297a7847.jpeg)

ここまででトップページまでは作成できたので、
次にユーザ登録系の処理を記述していきます。

##ユーザ登録機能の実装

`forms.py`の修正を行います。

```python:myapp/forms.py
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class SignUpForm(UserCreationForm):
    class Meta:
        model = User
        fields = ["username", "email", "password1", "password2"]
```

`views.py`にユーザ登録のビューを定義します。
先程まで記載のなかった下記を追加します。

SignUpViewクラスの、form_valid関数は、
ユーザ登録フォームに記載された内容に問題がない時に実行される関数です。

success_urlでは、ユーザ登録成功後に遷移するパスを指定します。

```python:views.py
from django.http.response import HttpResponseRedirect
from django.views.generic import TemplateView, CreateView
from django.contrib.auth import login
from django.urls import reverse_lazy

from .forms import SignUpForm


class HomeView(TemplateView):
    template_name = "myapp/home.html"

class SignUpView(CreateView):
    form_class = SignUpForm
    template_name = "myapp/signup.html"
    success_url = reverse_lazy("myapp:home")

    def form_valid(self, form):
        user = form.save()
        login(self.request, user)
        self.object = user
        return HttpResponseRedirect(self.get_success_url())
```

myapp配下の`urls.py`にパスを追加します。

```python:myapp/urls.py
urlpatterns = [
    path('top/', views.IndexView.as_view(), name='index'),
    path('signup/', views.SignUpView.as_view(), name="signup"),
    path('home/', views.HomeView.as_view(), name="home"),
]
```

`home.html`と、`signup.html`を記述していきます。

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

{% block content %}
<h1>ホーム</h1>
<p>{{ user.username }}さんようこそ。</p>
{% endblock %}
```

```python:signup.html
{% extends "myapp/base.html" %}
{% block title %}ユーザ登録{% endblock %}

{% block content %}
<div class="row">
    <div class="col-sm-6">
        <br>
        <h1>ユーザ登録</h1>
        <form method="POST">
            {% csrf_token %}
            {{ form.non_field_errors }}
            {% for field in form %}
                {{ field.label }}
                {{ field }}
                {{ field.errors }}
                <br>
            {% endfor %}
            <button type="submit">登録</button>
        </form>
    </div>
</div>
{% endblock %}
```

最後に、index.htmlを修正します。
ユーザ作成ボタンを押したら、SignUpの画面に遷移するように紐づけます。

```python:index.html
<p><a href="{% url 'myapp:signup' %}">ユーザ作成</a></p>   ### 修正
```

ここまで記載したら一度起動し、ブラウザから
`http://127.0.0.1/top/`にアクセスします。

```
$ python manage.py runserver
```

ユーザ登録画面は、こんな感じだと思います。
![スクリーンショット 2021-07-23 17.56.28.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/68a6e0f8-b61a-bbe3-3bc7-325ce26246e5.jpeg)

適当に`test`というユーザを作成し登録ボタンを押したところ、
画面遷移し下の画面が表示されました。
![スクリーンショット 2021-07-23 17.57.05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/971b9fb9-cf75-b285-1c23-8f7690e432c8.jpeg)

実際に登録されているのか確認するために、
`http://127.0.0.1:8000/admin/`にアクセスします。

ユーザとパスワードは、
`python manage.py createsuperuser`で作成したものになります。

ユーザをタップすると、testユーザが追加されていることが確認できます。
![スクリーンショット 2021-07-23 17.59.26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/f4462876-78be-e90f-5c35-aade8e0cc40e.jpeg)

以上でユーザ登録機能の実装が完了になります。

#さいごに

長くなってしまったので、詳細な説明は省いてしまいました。
それぞれのコードやコマンドにどのような意味があるのかは調べればすぐに出てくると思いますので、
各自で調べていただけると幸いです。

次は、気が向いたらログインやログアウトといった機能の実装をしてみようと思います。