初めに
Django の汎用ビューであるCreateView
では、主に新しいオブジェクト(通常はデータベースモデルのインスタンス)を簡単に作成し、保存することができます。
全体コードはこちら
CreateViewクラスの継承クラス
CreateViewクラスでは、SingleObjectTemplateResponseMixin
クラスとBaseCreateView
クラスを継承しており、HTMLテンプレートを使ったレスポンスのレンダリングと新しいオブジェクトの作成機能を統合しています。
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
template_name_suffix = "_form"
SingleObjectTemplateResponseMixinクラス
このMixinクラスは、TemplateResponseMixin
クラスを継承しており、特定のオブジェクトに関連するHTMLテンプレートを使用してレスポンスをレンダリングする機能を提供します。
BaseCreateViewクラス
このクラスは、ProcessFormView
とModelFormMixin
を継承し、新しいオブジェクトを作成するためのコア機能を提供します。
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.object
とself.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を決定します。
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
属性が設定されていればそれを使用し、設定されていなければ、ビューに関連付けられたモデルからフォームクラスを動的に生成します。
もしビューがfields
とform_class
の両方を同時に設定している場合、ImproperlyConfigured エラーが発生し、form_class
のみ設定されていればform_classを返します。
もしform_class
が未設定の場合は、self.model
またはビューが現在操作しているオブジェクト(self.object
)、それもない場合はget_queryset
メソッドを呼び出してクエリセットを取得し、そのクエリセットからモデルクラスを取得します。
上記で取得したmodel
とfields
属性でmodelform_factory
関数を使用して関連モデルに基づくフォームクラスを自動的に生成します。
なので、form_class
が未定義でfields
が指定されていなければエラーとなります。
CreateViewは新規作成であり、self.objectはNoneのため、ここでobjectが定義されることはありません。
get_form_kwargsメソッド
フォームに表示させるデフォルトの情報の設定と、複数のフォームを区別させることができます。
親(FormMixinクラス)のget_form_kwargsでは、デフォルトの情報の設定をget_initial
メソッドで、複数のフォーム区別をget_prefix
メソッドで行っています。
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
#同じ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-username
、register-password
という名前が付けられ、ログインフォームのフィールドにはlogin-username
、login-password
という名前が付けられます。
これにより、同じページ上でusernameという名前のフィールドが衝突することなく、サーバー側で正確にどのフォームのデータかを識別することができます。
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.object
にget_absolute_url
メソッドが定義されていない場合エラーになります。
継承クラス FormMixinクラス
このクラスはフォームの初期化、処理、成功時の動作などを管理します。
form_valid
メソッドは、フォームが有効なデータ(バリデーションを通過したデータ)を持っている場合に呼び出されます。
通常、このメソッドはフォームのデータを保存し、成功時のURLへリダイレクトする処理を行います。
form_invalid
メソッド
フォームが無効なデータ(バリデーションエラーを含むデータ)を持っている場合に呼び出されます。
通常、フォームとエラーメッセージを含むレスポンスを再表示する処理を行います。
BaseCreateViewの継承② ProcessFormViewクラス
主にフォームの表示と処理を担当する基本的なビュークラスです。
HTTPのGETリクエストに対してフォームをレンダリングし、POSTリクエストでフォームのデータを処理するために使用されます。
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の基底クラスについての説明はこちら