今回のお題
今回は、CreateView
やUpdateView
などの汎用ビューでインスタンスの外部キー由来のフィールドに値をセットする方法をまとめます。
やりたいことの例
以下のようなモデル設計で、Menu
インスタンスの作成時にログイン中のユーザーが所属している店舗を外部キーとして設定したい。
class CustomUser(AbstractUser):
shop = models.Foreignkey("shop.Shop", on_delete=models.PROTECT)
class Menu(models.Model):
name = models.CharField(max_length=20)
shop = models.Foreignkey("shop.Shop", on_delete=models.CASCASE)
class MenuForm(ModelForm):
class Meta:
model = Menu
fields = ["name"]
フォームのフィールドとして指定してしまうと他の店舗を選択できてしまうので、その方法は取りたくない(バリデーションで対応できなくもないが、できれば自動で店舗情報が設定される形にしたい)。
結論
CreateView
とUpdateView
に実装されているform_valid
メソッドをオーバーライドすることで対応可能。
class MenuCreateView(CreateView):
template_name = "menu/create.html"
model = Menu
form_class = MenuForm
def form_valid(self, form):
form.instance.shop = self.request.user.shop
form_valid
メソッドはFormクラスやModelFormクラスのバリデーションがOKだった時に呼ばれてフォームを保存したのちにHttpResponseオブジェクトを返すメソッド。
なのでこのメソッドを上書きし、保存の直前に外部キーのデータがセットされるようにすれば良い。
フォームには外部キーのフィールド自体を用意していないので、店舗情報がなくてもバリデーションに引っかかることはない。
form_validメソッドについて
結論だけでは面白くないので、form_valid
メソッドの解説を一応。
CreateViewとUpdateViewはそれぞれ、
FormMixin > ModelFormMixin > BaseCreate(Update)View > Create(Update)View
というルートで継承を重ねており、その度にform_valid
メソッドに関する機能が追加されている。
具体的には、
- FormMixinでform_validメソッドを定義(この段階ではHttpResponseオブジェクトを返す役割のみ)。
- ModelFormMixinで、form_validメソッドが上書きされ、HttpResponseを返す前にフォーム内容が保存されるようになる。
- ProcessFormView(BaseCreate/Updateviewのもう一つの継承元)のpostメソッド内で、
form.is_valid()=true
の場合にのみform_valid
メソッドが呼び出されるように定義される。
という流れになっている。
class FormMixin(ContextMixin):
def form_valid(self, form):
"""If the form is valid, redirect to the supplied URL."""
return HttpResponseRedirect(self.get_success_url())
class ModelFormMixin(FormMixin, SingleObjectMixin):
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save()
return super().form_valid(form)
class ProcessFormView(View):
"""Render a form on GET and processes it on POST."""
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
# PUT is a valid HTTP verb for creating (with a known URL) or editing an
# object, note that browsers only support POST for now.
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)