【Django】LoginViewの認証をViewsで実装する【Python】

# 初めに
認証にはDjangoの認証であるLoginViewを使用しますが、urlsではなくviewsで呼び出します。
urlsでのLoginViewの認証方法は多くありますが、viewsでの呼び出す情報が少なかったので備忘録としてまとめます。
自分が必要だと思った部分のみ簡潔にまとめますのでご活用ください。

# 環境
|名称  |バージョン  |
|---|---|
|Python  |3.10.4  |
|Django  |4.0.3 |

# urlsで呼び出す
urlsでの呼び出し方は以下の3行目のようになります。
```php:urls.py
urlpatterns = [
    path('', views.index, name='index'),
    path('login/', LoginView.as_view(template_name='login.html'), name='login'),
    path('logout/', LogoutView.as_view(template_name='logout.html'), name='logout'),
]
```
この場合実装は簡単ですが、直接LoginViewを呼び出し、
認証後は「settings.py」の「LOGIN_REDIRECT_URL」にリダイレクトされます。
自分のアプリのviewsを通らないので、viewsでごちゃごちゃできません。
例えば、
既にログインしているユーザに「app名/login」を直接叩かれた場合に「index」にリダイレクトする、
というような処理を挟む余地がありません。
LoginViewに直接書けば別ですが。

# viewsで呼び出す
ではごちゃごちゃするためにviewsで実装します。
viewsでの呼び出し方は以下のようになります。
(urlsのLoginView呼び出し部分をviewsの関数に向けるのをお忘れなく)
```php:views.py
from django.contrib.auth.views import LoginView

def login(request):
    return LoginView.as_view(template_name='login.html')(request)
```
urlsとの違いはLoginViewの後ろに「(request)」を取るところです。
あとは好きに処理を挟みましょう。
例えば、
```php:views.py
def login(request):
    if request.user.is_active:
        return redirect('test_auth:index')
    return LoginView.as_view(template_name='login.html')(request)
```
これでログインユーザの「app名/login」直叩きに対応できました。
「request.user.is_active」はユーザが認証済みの場合はTrueになります。

# LoginViewで値を渡す
LoginViewの認証はいいけど値も一緒に渡したいときはどうすればよいのでしょうか。
簡潔な解決方法としてDjangoの管理画面と同じ手法を取ることにします。
正道かどうかは分かりません。

まずLoginViewの中身を確認します。
```php:django.contrib.auth.views.py
class LoginView(SuccessURLAllowedHostsMixin, FormView):
    """
    Display the login form and handle the login action.
    """

    form_class = AuthenticationForm
    authentication_form = None
    next_page = None
    redirect_field_name = REDIRECT_FIELD_NAME
    template_name = "registration/login.html"
    redirect_authenticated_user = False
    extra_context = None
...
```

先ほどからLoginViewを呼び出す際に指定していた「template_name」はここの属性だとわかりますね。
値を渡す時は「extra_context」を利用します。

実際にDjangoが管理画面で使用している「extra_context」を出力してみましょう。
```
{'extra_context': {'site_title': 'Django site admin', 'site_header': 'Django administration', 
'site_url': '/', 'has_permission': False, 'available_apps': [], 'is_popup': False, 
'is_nav_sidebar_enabled': True, 'title': 'Log in', 'app_path': '/admin/login/?next=/admin/', 'username': ''}, 
'authentication_form': <class 'django.contrib.admin.forms.AdminAuthenticationForm'>, 
'template_name': 'admin/login.html'}
```

「extra_context」は辞書型で、「title」などの値を持っていることがわかります。
ではどのようにDjangoがフロントで値を受け取っているのかを確認します。
```php:base_site.html
{% extends "admin/base.html" %}

{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}

{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
{% endblock %}

{% block nav-global %}{% endblock %}
```
3行目、extra_contextの値の「title」がありました。
「extra_context」に任意の辞書型オブジェクトを突っ込めば値を渡せることがわかりました。

最後は以下のように好きな辞書を突っ込みましょう。
```php:views.py
def login(request):
    dict = {'topLink': 'hoge@haha.co.jp', 'logout': 'hoge/logout/'}
    if request.user.is_active:
        return redirect('test_auth:index')
    return LoginView.as_view(template_name='login.html', extra_context=dict)(request)
```

# まとめ
LoginViewをviewsで呼び出し、ついでに値渡しを実現できました。
あまり情報がない分正道かどうかの判別がつきませんが、何かご意見ありましたらコメントでお聞かせください。