LoginSignup
0
2

DjangoのCreateViewについて理解する

Last updated at Posted at 2023-11-11

初めに

Django の汎用ビューであるCreateViewでは、主に新しいオブジェクト(通常はデータベースモデルのインスタンス)を簡単に作成し、保存することができます。

全体コードはこちら

CreateViewクラスの継承クラス

CreateViewクラスでは、SingleObjectTemplateResponseMixinクラスとBaseCreateViewクラスを継承しており、HTMLテンプレートを使ったレスポンスのレンダリングと新しいオブジェクトの作成機能を統合しています。

CreateView
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
    template_name_suffix = "_form"

SingleObjectTemplateResponseMixinクラス

このMixinクラスは、TemplateResponseMixinクラスを継承しており、特定のオブジェクトに関連するHTMLテンプレートを使用してレスポンスをレンダリングする機能を提供します。

BaseCreateViewクラス

このクラスは、ProcessFormViewModelFormMixinを継承し、新しいオブジェクトを作成するためのコア機能を提供します。

SingleObjectTemplateResponseMixin

テンプレートレスポンスを生成するためのメソッドを提供します

SingleObjectTemplateResponseMixin
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
    template_name_field = None
    template_name_suffix = "_detail"

    def get_template_names(self):
        try:
            names = super().get_template_names()
        except ImproperlyConfigured:
            names = []

            if self.object and self.template_name_field:
                name = getattr(self.object, self.template_name_field, None)
                if name:
                    names.insert(0, name)

            if isinstance(self.object, models.Model):
                object_meta = self.object._meta
                names.append(
                    "%s/%s%s.html"
                    % (
                        object_meta.app_label,
                        object_meta.model_name,
                        self.template_name_suffix,
                    )
                )
            elif getattr(self, "model", None) is not None and issubclass(
                self.model, models.Model
            ):
                names.append(
                    "%s/%s%s.html"
                    % (
                        self.model._meta.app_label,
                        self.model._meta.model_name,
                        self.template_name_suffix,
                    )
                )

            if not names:
                raise

        return names

get_template_namesメソッド

親のget_template_namesメソッドをオーバーライドしており、テンプレート名を取得する機能を提供します。

まず親のget_template_namesメソッドを実行します。
親のtemplate_nameメソッドでは、template_nameが指定されていない場合エラーになりますが、ここではさらにモデル名やアプリ名を元にテンプレート名を推測します。

親のtemplate_nameメソッドでエラーを検知した場合、まずself.objectself.template_name_fieldが存在する場合、これらの値をもとにgetattr関数を使ってテンプレートネームの取得を試みます。

次にself.objectがモデルインスタンスかどうかをチェックし、self.object._metaでモデルインスタンスのメタデータを取得します。
このメタデータをもとにテンプレートネームを生成します。

最後はビューにmodel属性が設定されており、その属性がモデルクラスであるかどうかをチェックします。そうであればビューに関連付けられたモデルの名前を使用してデフォルトのテンプレート名を生成することができます。

CreateViewクラスでは新規にオブジェクトを作成するので、当然self.objectはNoneとなります。
つまり、template_nameを指定しない場合、フォームにモデルが設定されているか確認して、なければエラーになります。

継承クラス TemplateResponseMixin

TemplateResponseMixinクラスを継承しています。

このクラスではレスポンスを返すrender_to_responseメソッドが実行されますが、ここで使用されるget_template_namesメソッドは先ほどオーバーライドしたメソッドが使用されることになります。

BaseCreateViewクラス

新しいデータベースオブジェクトの作成にを行います。
このクラスは、HTTPのGETリクエストに対してフォームを表示し(getメソッド)、POSTリクエストによって提出されたフォームデータを処理します(postメソッド)。
ModelFormMixinを通じて、使用するフォームクラスや成功時のリダイレクト先URLの設定も行います。

class BaseCreateView(ModelFormMixin, ProcessFormView):

    def get(self, request, *args, **kwargs):
        self.object = None
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = None
        return super().post(request, *args, **kwargs)

getメソッド

HTTPのGETリクエストを処理し、基本的にフォームが初めて表示される際に呼び出されるメソッドになります。(getでformのページをリクエストし、formに入力した値をもとに作成する場合はpostメソッドでリクエストするためです)

self.object = Noneの行は、このビューが新しいオブジェクトの作成を扱っていることを示しています。
既存のオブジェクトを編集する場合とは異なり、新しいインスタンスを作成するためには、object 属性が None である必要があるためです。

最後に親クラスのgetメソッドを呼び出し、フォームをレンダリングしてレスポンスを返しています。

postメソッド

HTTPのPOSTリクエストを処理し、ユーザーがフォームにデータを入力して送信した際に実行されます。
後はgetメソッドと同様になります。

ModelFormMixinとProcessFormViewの相互作用

ModelFormMixinは、ビューに関連付けられたモデルからフォームクラスを自動生成する機能を提供します。一方、ProcessFormViewはフォームの表示と処理の基本フローを担当し、ModelFormMixinと連携して、フォームのデータを効果的にハンドリングします。

BaseCreateViewの継承① ModelFormMixinクラス

モデルに基づいたフォーム(ModelForm)の表示と処理を容易にするためのMixinです。
ビューで使用するModelFormクラスやフォームに渡す値、成功時のURLを決定します。

ModelFormMixin
class ModelFormMixin(FormMixin, SingleObjectMixin):

    fields = None

    def get_form_class(self):
        if self.fields is not None and self.form_class:
            raise ImproperlyConfigured(
                "Specifying both 'fields' and 'form_class' is not permitted."
            )
        if self.form_class:
            return self.form_class
        else:
            if self.model is not None:
                model = self.model
            elif getattr(self, "object", None) is not None:
                model = self.object.__class__
            else:
                model = self.get_queryset().model

            if self.fields is None:
                raise ImproperlyConfigured(
                    "Using ModelFormMixin (base class of %s) without "
                    "the 'fields' attribute is prohibited." % self.__class__.__name__
                )

            return model_forms.modelform_factory(model, fields=self.fields)

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        if hasattr(self, "object"):
            kwargs.update({"instance": self.object})
        return kwargs

    def get_success_url(self):
        if self.success_url:
            url = self.success_url.format(**self.object.__dict__)
        else:
            try:
                url = self.object.get_absolute_url()
            except AttributeError:
                raise ImproperlyConfigured(
                    "No URL to redirect to.  Either provide a url or define"
                    " a get_absolute_url method on the Model."
                )
        return url

get_form_classメソッド

ビューで使用するModelFormクラスを返します。

このメソッドは、form_class属性が設定されていればそれを使用し、設定されていなければ、ビューに関連付けられたモデルからフォームクラスを動的に生成します。

もしビューがfieldsform_classの両方を同時に設定している場合、ImproperlyConfigured エラーが発生し、form_classのみ設定されていればform_classを返します。

もしform_classが未設定の場合は、self.modelまたはビューが現在操作しているオブジェクト(self.object)、それもない場合はget_querysetメソッドを呼び出してクエリセットを取得し、そのクエリセットからモデルクラスを取得します。
上記で取得したmodelfields属性でmodelform_factory関数を使用して関連モデルに基づくフォームクラスを自動的に生成します。
なので、form_classが未定義でfieldsが指定されていなければエラーとなります。

CreateViewは新規作成であり、self.objectはNoneのため、ここでobjectが定義されることはありません。

get_form_kwargsメソッド

フォームに表示させるデフォルトの情報の設定と、複数のフォームを区別させることができます。
親(FormMixinクラス)のget_form_kwargsでは、デフォルトの情報の設定をget_initialメソッドで、複数のフォーム区別をget_prefixメソッドで行っています。

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_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
prefixの使用例

#同じfieldを持つ二つのform
class RegistrationForm(forms.Form): #登録フォーム
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)

class LoginForm(forms.Form): #ログインフォーム
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)


#ビューの定義
class MyView(FormView):
    template_name = 'my_template.html'

    def get_context_data(self, **kwargs):
        context = super(MyView, self).get_context_data(**kwargs)
        context['registration_form'] = RegistrationForm(prefix='register')
        context['login_form'] = LoginForm(prefix='login')
        return context

#テンプレートでの使用
<form method="post">
    {{ registration_form.as_p }}
    <button type="submit">Register</button>
</form>

<form method="post">
    {{ login_form.as_p }}
    <button type="submit">Login</button>
</form>

上記prefixの例では、登録フォームのフィールドにはregister-usernameregister-passwordという名前が付けられ、ログインフォームのフィールドにはlogin-usernamelogin-passwordという名前が付けられます。
これにより、同じページ上でusernameという名前のフィールドが衝突することなく、サーバー側で正確にどのフォームのデータかを識別することができます。

initialの使用例
class ProfileUpdateView(FormView):
    template_name = 'profile_update.html'
    form_class = ProfileForm

    def get_initial(self):
        # 初期データを取得するロジック
        initial = super(ProfileUpdateView, self).get_initial()
        initial['username'] = self.request.user.username
        initial['email'] = self.request.user.email
        # その他の初期データを辞書に追加する
        return initial

ユーザーがフォームを表示すると、username と email フィールドには既に値が入力されています。
get_initialは通常、GETリクエスト時にフォームを表示する際に使用されます。
POSTリクエストやフォームのバリデーション後には、通常、ユーザーが入力したデータが優先され、get_initial で設定した値は無視されます。

今回だと、、ビューにobject属性(現在のモデルインスタンス)があれば、このオブジェクトをフォームのinstance引数として追加します。
つまりobjectが存在すれば、デフォルトの値を指定します。

prefixとinitialの使用例は、フォームのカスタマイズとデータの事前入力の方法を示しています。これらはCreateViewやその他のフォームビューでのフォームの柔軟な管理に役立ちます。

get_success_urlメソッド

フォームが有効に処理された後にユーザーをリダイレクトするURLを返します。

success_url属性にURLを指定することで、urlを返します。
もしsuccess_url属性がない場合、self.object(作成または更新されたオブジェクト)の get_absolute_url メソッドを呼び出し、その戻り値をリダイレクトURLとして使用します。
ただ、self.objectget_absolute_urlメソッドが定義されていない場合エラーになります。

継承クラス FormMixinクラス

このクラスはフォームの初期化、処理、成功時の動作などを管理します。

form_validメソッドは、フォームが有効なデータ(バリデーションを通過したデータ)を持っている場合に呼び出されます。
通常、このメソッドはフォームのデータを保存し、成功時のURLへリダイレクトする処理を行います。

form_invalidメソッド
フォームが無効なデータ(バリデーションエラーを含むデータ)を持っている場合に呼び出されます。
通常、フォームとエラーメッセージを含むレスポンスを再表示する処理を行います。

BaseCreateViewの継承② ProcessFormViewクラス

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

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の基底クラスについての説明はこちら

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2