Djangoで「会員登録機能 +  ログイン機能」を実装する

# はじめに
本記事では，SNSやショッピングサイトといったサービスを開発する時に必須となる新規会員の登録機能をDjangoで実装する方法を解説します．
また，会員を作成後に必要となるログインフォーム，ログアウト機能についても合わせて解説していきます．
#準備
###開発環境
python 3.6.7
Django 1.11.15

```
sample
├── accounts
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── templates
│   │   ├── create.html
│   │   ├── index.html
│   │   └── login.html
│   ├── tests.py
│   └── views.py
├── login_sample
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── myvenv
└── manage.py

```
上記の開発環境，構成のwebアプリを用いて解説していきます．
ソースコードは[GitHub](https://github.com/knakajima3027/User-login-sample)にて公開しているので，よかったら参考にしてください．
このアプリで用意されているテンプレートは以下の3つです．
1. indexページ (index.html)
2. 会員登録ページ (create.html)
3. ログインページ (login.html)

index.htmlのコードは以下の通りです．
利用者がアカウントを作成して，ログイン状態の場合は，
「ログインしました． こんにちは "ユーザ名"」
と表示するようになっています．
逆に，利用者がログインしていない時は，
「ログイン」，「アカウント作成」のページへのリンクを表示させるようにしています．

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <body>
    {% if user.id  %}
    <p>ログインしました．</p>
    <p>こんにちは，{{ user.username }}さん</p>
    <a href="{% url 'logout' %}">ログアウトする</a>
    {% else %}
    <a href="{% url 'login' %}">ログイン</a>
    <a href="{% url 'create_account' %}">アカウント作成</a>
    {% endif %}
  </body>
</html>
```

また，今回サンプルに用いるwebアプリのページの遷移は以下の図の通りです．
<img width="1280" alt="スクリーンショット 2018-12-07 20.49.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/257347/d9418a64-520c-f5bb-b4de-5133c2003428.png">

#新規会員登録機能
まずは，新規会員登録機能について説明していきます．
この機能を実装するためには，以下の3つのファイルを編集する必要があります．
1. forms.py (会員登録フォームを定義)
2. views.py (会員登録の処理を記述)
3. create.html (会員情報の入力フォームを配置)

forms.pyには以下のコードを追加します．
Userモデルが持つ要素である,"username", "passward1", "passowrd2"の入力欄を，UserCreateFormに追加しています．

```python:forms.py
from django.contrib.auth.forms import UserCreationForm

class UserCreateForm(UserCreationForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        #htmlの表示を変更可能にします
        self.fields['username'].widget.attrs['class'] = 'form-control'
        self.fields['password1'].widget.attrs['class'] = 'form-control'
        self.fields['password2'].widget.attrs['class'] = 'form-control'

    class Meta:
       model = User
       fields = ("username", "password1", "password2",)
```
views.pyには以下のコードを追加します．
先ほど作成したUserCreateFormをフォームとしてレンダリングして，ユーザーからの登録情報(ユーザー名, パスワード)を受け取ります．入力情報に不備が無ければ，新規会員としてデータベースに登録されます．

```python:views.py
from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from django.contrib.auth import login, authenticate
from django.views.generic import CreateView
from . forms import UserCreateForm

#アカウント作成
class Create_account(CreateView):
    def post(self, request, *args, **kwargs):
        form = UserCreateForm(data=request.POST)
        if form.is_valid():
            form.save()
            #フォームから'username'を読み取る
            username = form.cleaned_data.get('username')
            #フォームから'password1'を読み取る
            password = form.cleaned_data.get('password1')
            user = authenticate(username=username, password=password)
            login(request, user)
            return redirect('/')
        return render(request, 'create.html', {'form': form,})

    def get(self, request, *args, **kwargs):
        form = UserCreateForm(request.POST)
        return  render(request, 'create.html', {'form': form,})

create_account = Create_account.as_view()
```
最後に，create.htmlです．
views.pyで，'form'という名前でUserCreateFormがレンダリングされたため，下記のように書くことで，フォームの入力欄を設けることができます．

```html:create.html
<!DOCTYPE html>
<html lang="ja">
  <body>
    <form method="POST" action="{% url 'create_account' %}">
      {% csrf_token %}
      <label>ユーザーID</label>
      {{ form.username }}
      {{ form.username.errors }}
      <label>パスワード</label>
      {{ form.password1 }}
      {{ form.password1.errors }}
      <label>パスワード(確認)</label>
      {{ form.password2 }}
      {{ form.password2.errors }}
      <button type = "submit">登録</button>
    </form>
  </body>
</html>
```

#ログイン機能
続いては，ログイン機能の実装方法です．
ログイン機能を実装する上で変更を加える必要があるファイルは以下の3つです．
1. forms.py (ログインフォームを定義)
2. views.py (ログイン処理を記述)
3. login.html (ログインフォームを配置)

まずは，forms.pyです．
こちらは先ほどのUserCreationFormとほぼ同じです．
AuthenticationFormというDjangoで用意されているフォームを拡張します．

```python3:forms.py
from django.contrib.auth.forms import AuthenticationForm

class LoginForm(AuthenticationForm):
    def __init__(self, *args, **kwargs):
       super().__init__(*args, **kwargs)
       #htmlの表示を変更可能にします
       self.fields['username'].widget.attrs['class'] = 'form-control'
       self.fields['password'].widget.attrs['class'] = 'form-control'
```

続いては，views.pyへ以下のコードを追加します．
先ほどのLoginFormをレンダリングして，ユーザーから"ユーザー名"，"パスワード"を受け取り，一致した場合は，ユーザーをログイン状態にします．

```python:views.py
#ログイン機能
class Account_login(View):
    def post(self, request, *arg, **kwargs):
        form = LoginForm(data=request.POST)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            user = User.objects.get(username=username)
            login(request, user)
            return redirect('/')
        return render(request, 'login.html', {'form': form,})

    def get(self, request, *args, **kwargs):
        form = LoginForm(request.POST)
        return render(request, 'login.html', {'form': form,})

account_login = Account_login.as_view()
```
htmlファイルは以下のようになっています．
"ユーザー名"と"パスワード"を入力する欄を設けています．

```html:login.html
<!DOCTYPE html>
<html lang="ja">
  <body>
    {% if form.errors  %}
    <p>ユーザーID, パスワードが一致しません．</p>
    {% endif %}
    <form method="POST" action="{% url 'login' %}">
      {% csrf_token %}
      <label>ユーザーID</label>
      {{ form.username }}
      {{ form.username.errors }}
      <label>パスワード</label>
      {{ form.password }}
      {{ form.password.errors }}
      <button type = "submit">ログイン</button>
    </form>
  </body>
</html>
```

#ログアウト機能
ログアウト機能の実装は簡単です．
urls.pyに以下のコードを加えます．
`'index.html'`の部分は，ログアウトを実行後に表示したいhtmlファイルを指定します．
今回の場合，ログアウト後はインデックスページに遷移します．

```python:urls.py
from django.contrib.auth.views import logout

urlpatterns = [
  url(r'^logout/$', logout, {'template_name': 'index.html'}, name='logout'), 
]
```