DjangoのLoginViewについて理解する

## 初めに
LoginViewは、ユーザー認証とログインプロセスを容易に実装するために設計されたクラスです。

全体コードは[こちら](https://github.com/django/django/blob/main/django/contrib/auth/views.py)

## LoginViewの継承クラス
```RedirectURLMixin```クラスと```FormView```クラスを継承しており、ログイン成功時のリダイレクトロジックとフォームの処理をそれぞれ担当します。

## LoginViewクラス
```python:LoginView
class LoginView(RedirectURLMixin, FormView):

    form_class = AuthenticationForm
    authentication_form = None
    template_name = "registration/login.html"
    redirect_authenticated_user = False
    extra_context = None

    @method_decorator(sensitive_post_parameters())
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        if self.redirect_authenticated_user and self.request.user.is_authenticated:
            redirect_to = self.get_success_url()
            if redirect_to == self.request.path:
                raise ValueError(
                    "Redirection loop for authenticated user detected. Check that "
                    "your LOGIN_REDIRECT_URL doesn't point to a login page."
                )
            return HttpResponseRedirect(redirect_to)
        return super().dispatch(request, *args, **kwargs)

    def get_default_redirect_url(self):
        if self.next_page:
            return resolve_url(self.next_page)
        else:
            return resolve_url(settings.LOGIN_REDIRECT_URL)

    def get_form_class(self):
        return self.authentication_form or self.form_class

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["request"] = self.request
        return kwargs

    def form_valid(self, form):
        auth_login(self.request, form.get_user())
        return HttpResponseRedirect(self.get_success_url())

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        current_site = get_current_site(self.request)
        context.update(
            {
                self.redirect_field_name: self.get_redirect_url(),
                "site": current_site,
                "site_name": current_site.name,
                **(self.extra_context or {}),
            }
        )
        return context
```

| 属性 | 説明 |
| ---- | ---- |
| form_class |  使用されるフォームクラス。デフォルトではAuthenticationFormが使用される。 |
| authentication_form | カスタム認証フォームを指定する場合に使用する |
| template_name | ログインページのテンプレート名を指定する |
| redirect_authenticated_user | 認証済みユーザーがログインページにアクセスした際にリダイレクトするかどうかを制御する |
| extra_context | テンプレートに追加のコンテキストを提供するために使用する |

### dispatchメソッド
基底クラスである```View```クラスのメソッドをオーバーライドしています。
このメソッドはリクエストされたHTTPメソッドに対応するビューメソッドを呼び出します。
詳しい説明は[こちら](https://qiita.com/ryoutancobu/items/c7f446a1f2e8e901fcee)をご覧ください。

このメソッドでは、```redirect_authenticated_user```が```True```であり、ユーザーが既に認証されている場合にリダイレクト処理を行います。
リダイレクト先は```RedirectURLMixin```の```get_success_url```メソッドを使用して取得します。

### get_default_redirect_urlメソッド
このメソッドは、ログインに成功した後のデフォルトのリダイレクトURLを返します。
```next_page```属性が設定されている場合はそれを使用し、そうでない場合は```settings.LOGIN_REDIRECT_URL```を使用してリダイレクト先のURLを決定します。


### get_form_classメソッド
このメソッドは、使用するフォームクラスを返します。
クラスに```authentication_form```属性が設定されていれば、それを使用します。そうでなければ、```form_class```属性に設定されたデフォルトのフォームクラスを使用します。



### get_form_kwargsメソッド
フォームに表示させるデフォルト情報の設定と、複数のフォームを区別させることができます。
ここでは親クラスの同名メソッドをオーバーライドして、```request```オブジェクトを引数に加えています。
詳しくは[こちら](https://qiita.com/ryoutancobu/items/ec7b0395da8b54b5d52a)のget_form_kwargsメソッドで説明しています。

### form_validメソッド
このメソッドは、フォームのバリデーションが成功した後に呼び出されます。
ユーザーをログインさせた後、```get_success_url```メソッドで取得したURLにリダイレクトします。

リダイレクト後に後続の処理は実行されません。


### get_context_data
このメソッドは、テンプレートに渡すコンテキストデータを構築します。
親クラスの同名メソッドを呼び出した後、リダイレクトURL、現在のサイト、サイト名などの追加情報をコンテキストに加えます。

### RedirectURLMixinクラス
このミックスインは、リダイレクト先のURLを取得するための機能を提供しています。
LoginViewクラスで使用しているリダイレクト先のURLの取得は、このクラスをオーバーライドして使用しています。
```python:RedirectURLMixin
class RedirectURLMixin:
    next_page = None
    redirect_field_name = REDIRECT_FIELD_NAME
    success_url_allowed_hosts = set()

    def get_success_url(self):
        return self.get_redirect_url() or self.get_default_redirect_url()

    def get_redirect_url(self):
        """Return the user-originating redirect URL if it's safe."""
        redirect_to = self.request.POST.get(
            self.redirect_field_name, self.request.GET.get(self.redirect_field_name)
        )
        url_is_safe = url_has_allowed_host_and_scheme(
            url=redirect_to,
            allowed_hosts=self.get_success_url_allowed_hosts(),
            require_https=self.request.is_secure(),
        )
        return redirect_to if url_is_safe else ""

    def get_success_url_allowed_hosts(self):
        return {self.request.get_host(), *self.success_url_allowed_hosts}

    def get_default_redirect_url(self):
        """Return the default redirect URL."""
        if self.next_page:
            return resolve_url(self.next_page)
        raise ImproperlyConfigured("No URL to redirect to. Provide a next_page.")
```
| 属性 | 説明 |
| ---- | ---- |
| next_page | デフォルトのリダイレクト先のURLを格納します。get_default_redirect_url メソッドで使用され、このURLが設定されていない場合にはエラーが発生します。 |
| redirect_field_name | リダイレクトのためのフィールド名を指定する属性です。リダイレクト先のURLを含むリクエストのパラメーター名を定義します |
| success_url_allowed_hosts | 安全と見なされるホストのセットを格納します。リダイレクト先のURLがこのセットに含まれるホストによって提供されている場合にのみ、そのURLは安全と見なされます。 |


## FormViewクラス
フォームの表示と処理を行います。
```TemplateResponseMixin```と```BaseFormView```を継承し、テンプレートレスポンスの生成とフォームの基本的な操作を提供します。

```python:FormView
class FormView(TemplateResponseMixin, BaseFormView):
```

## 継承① TemplateResponseMixinクラス
テンプレートを使ってレスポンスを生成する機能を提供します。

```python:TemplateResponseMixin
class TemplateResponseMixin:
    """A mixin that can be used to render a template."""

    template_name = None
    template_engine = None
    response_class = TemplateResponse
    content_type = None

    def render_to_response(self, context, **response_kwargs):
        """
        Return a response, using the `response_class` for this view, with a
        template rendered with the given context.

        Pass response_kwargs to the constructor of the response class.
        """
        response_kwargs.setdefault("content_type", self.content_type)
        return self.response_class(
            request=self.request,
            template=self.get_template_names(),
            context=context,
            using=self.template_engine,
            **response_kwargs,
        )

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. Must return
        a list. May not be called if render_to_response() is overridden.
        """
        if self.template_name is None:
            raise ImproperlyConfigured(
                "TemplateResponseMixin requires either a definition of "
                "'template_name' or an implementation of 'get_template_names()'"
            )
        else:
            return [self.template_name]
```

```render_to_response```メソッドは、指定されたテンプレートとコンテキストデータを使用して、TemplateResponseオブジェクトを生成します。
このオブジェクトを使用して、最終的なHTMLコンテンツをクライアント（ブラウザ）に送り返しています。

詳しくは[こちら](https://qiita.com/ryoutancobu/items/c7f446a1f2e8e901fcee)で説明しています。

## 継承② BaseFormViewクラス
BaseFormViewクラスは```FormMixin```クラスと```ProcessFormView```クラスを継承しています

### FormMixinクラス
フォームの基本的な操作を行い、```FormMixin```と```ProcessFormView```を継承します。

```python:FormMixin
class FormMixin(ContextMixin):
    initial = {}
    form_class = None
    success_url = None
    prefix = None

    def get_initial(self):
        return self.initial.copy()

    def get_prefix(self):
        return self.prefix

    def get_form_class(self):
        return self.form_class

    def get_form(self, form_class=None):
        if form_class is None:
            form_class = self.get_form_class()
        return form_class(**self.get_form_kwargs())

    def get_form_kwargs(self):
        kwargs = {
            "initial": self.get_initial(),
            "prefix": self.get_prefix(),
        }

        if self.request.method in ("POST", "PUT"):
            kwargs.update(
                {
                    "data": self.request.POST,
                    "files": self.request.FILES,
                }
            )
        return kwargs

    def get_success_url(self):
        if not self.success_url:
            raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
        return str(self.success_url)  # success_url may be lazy

    def form_valid(self, form):
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form):
        return self.render_to_response(self.get_context_data(form=form))

    def get_context_data(self, **kwargs):
        if "form" not in kwargs:
            kwargs["form"] = self.get_form()
        return super().get_context_data(**kwargs)
```

このクラスから```LoginView```クラスでオーバーライドしているメソッドは、```get_form_class```、```get_form_kwargs```,```form_valid```,```get_context_data```となっています。

また、```get_form_kwargs```の説明で、```get_initial```と```get_prefix```メソッドの説明もしているので、```get_form```のみ説明します。

### get_form
form_class属性が設定されていない場合、get_form_classメソッドを使って取得します。

### ProcessFormViewクラス
主にフォームの表示と処理を担当する基本的なビュークラスです。
HTTPのGETリクエストに対してフォームをレンダリングし、POSTリクエストでフォームのデータを処理するために使用されます。

```python:ProcessFormView
class ProcessFormView(View):
    
    def get(self, request, *args, **kwargs):
        return self.render_to_response(self.get_context_data())

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def put(self, *args, **kwargs):
        return self.post(*args, **kwargs)
```

```View```クラスを継承していますが、このクラスはリクエストメソッドに応じてそれに対応する名前のメソッドを実行しています。
つまりGETリクエストやPOSTリクエストを受け取れば、上記```ProcessFormView```クラスの対応するメソッドが実行されるということです。

Viewの基底クラスについての説明は[こちら](https://qiita.com/ryoutancobu/items/c7f446a1f2e8e901fcee)