【Django】新規登録後、自動でログインさせたい

# はじめに
　Webサイトを作成する際の基本的な機能として新規登録とログインがある。新規登録完了後、再度ログイン画面で必要項目を入力する作業を省略したいと思ったので方法を調べた。

## 前提
　プロジェクト構成は以下の通り。設定ディレクトリをconfig,アプリケーションディレクトリはappとaccountsに二つを作成している。

```
.
├── accounts
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── config
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── app
│   ├── __init__.py
│   ├── apps.py
│   ├── forms.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── static
│   └── css   
│       └── style.css
└── templates
    ├── base.html
    ├── registration
    │   ├── base.html
    │   ├── logged_out.html
    │   ├── login.html
    │   └── signup.html
    └── app
        └── index.html
```

　設定ディレクトリとその中で指定されたdjango.contrib.auth内のURLconf、及びアプリケーションディレクトリのURlconfを以下に示す。
###### 設定ファイルのURLconf
```python:config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('app.urls')),
    path('accounts/', include('django.contrib.auth.urls')),  # Djangoがあらかじめ提供しているurls.pyへ
    path('accounts/', include('accounts.urls')),  # 自分が作成したurls.pyへ
]
```
```python:django/contrib/auth/urls.py
from django.contrib.auth import views
from django.urls import path

urlpatterns = [
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.LogoutView.as_view(), name='logout'),

    path('password_change/', views.PasswordChangeView.as_view(), name='password_change'),
    path('password_change/done/', views.PasswordChangeDoneView.as_view(), name='password_change_done'),

    path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
    path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]

```
###### アプリケーションのURLconf
```python:app/urls.py
from django.urls import path
from . import views

app_name = 'app'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]
```
```python:accounts/urls.py
from django.urls import path
from . import views

app_name = 'accounts'
urlpatterns = [
    path('signup/', views.SignUpView.as_view(), name='signup'),
]

```
# 本題
## 新規登録完了 → ログイン画面で入力 → ログイン完了
　まず、実装前の状態を確認する。

```python:accounts/views.py
from django.urls import reverse_lazy
from django.views import generic

from .forms import UserCreateForm


class SignUpView(generic.CreateView):
    form_class = UserCreateForm
    template_name = 'registration/signup.html'
    success_url = reverse_lazy('login')
```
* CreateViewを継承したSignUpViewを作成する。
* form_class = UserCreateFormで扱うフォームを指定する。今回はapp/forms.pyで作成したUserCreateFormを`form_class`として指定する。
* `template_name = 'registration/signup.html'`で新規登録画面のhtmlファイルを指定する。
* `success_url = reverse_lazy('login')`で新規登録が完了した後どこのページにいくのかをreverse_lazyを用いて指定する。この場合は'login'なので新規登録が完了するとDjangoがあらかじめ用意してくれているログインのページ(registration/login.html)に飛ぶ。
* `template_name = 'registration/signup.html'`で新規登録画面のhtmlファイルを指定する。(※ログインページのhtmlはあらかじめ用意されているが新規登録画面は用意されていないので自分で作る必要がある。)

## 新規登録完了 → ログイン完了
　新規登録が完了した後、ログイン画面を介さずにログインを完了するためにはacccouts/views.pyを少し変更するだけでいい。

```python:accounts/views.py
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth import login, authenticate    #　追加

from .forms import UserCreateForm


class SignUpView(generic.CreateView):
    form_class = UserCreateForm
    success_url = reverse_lazy('app:index')   # 変更
    template_name = 'registration/signup.html'

    # 以下追加
    def form_valid(self, form):
        response = super().form_valid(form)
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return response
```
　行う作業は`success_url`の指定とform_validメソッドのオーバーライドの２点。

* `success_url = reverse_lazy('app:index')`で登録完了後に遷移する画面を 'login' から 'app:index' つまりトップページに変更をする。
* form_validメソッドをオーバーライドするコードを追加する。
* `response = super().form_valid(form)`で親のform_valid()で返された値を取得する。
* `username = form.cleaned_data.get('username')`で新規登録のuserneme欄で入力された値をusernameに代入する。`form.cleaned_data`は入力検証を通過したデータを示す。
* `password = form.cleaned_data.get('password1')`も同様です。
* `user = authenticate(username=username, password=password)`で、あるユーザーとパスワードに対する認証を行う。`authenticate()`は引数として、usernameとpasswordをとり、ユーザー名に対してパスワードが有効だった場合にUserオブジェクトを返す。無効だった場合はNoneを返す。
* `login(self.request, user)`でユーザーを自動でログインさせる。`login()`は引数としてHttpRequestオブジェクトとUserオブジェクトをとる。
* 最後にresponseを返す。