ClassBasedViewで共有変数を設定して、重複クエリをなくすのが目的です。
条件
- id、pk、slugを指定しない
- ClassBasedViewを使う
- DetailView
- FormMixin
- post機能
- 重複したクエリを発生させない
前置き
これを利用するシーンとしては、プロフィール編集機能を持たせたマイページのようなものを想定している。
要は、UpdateViewとDetailViewを一緒にしたようなものだ。
ちなみに、def(関数ベースビュー)
を使えば簡単に実装できる笑
が、可読性を上げるために?あえてClassBasedViewで実装した。
この手の記事が英文でも無かったので、記録として残しておく。
悩んだ問題点
初期値を設定する必要があったので、get_initial()
メソッドでデータベースから初期値を設定するが、get_context_data()
メソッドでも同様のクエリを発生させなければいけなかったので、どうしてもクエリが重複してしまった。
def
であれば、一度変数に代入し使い回せるのだが、ClassBaseViewだとそうもいかない。
ClassBasedViewで共有して使える変数を設定することはできないのだろうか?
setup()で設定できた
DetailView
には、setup
メソッドがある。
このsetup
メソッドのおかげで、self.
をつけさえすれば、request、args、kwargsがどのメソッドでも使える様になっている。
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
if hasattr(self, "get") and not hasattr(self, "head"):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
ようはここにself.HOGE
をオーバーライドすればどのメソッドでも変数として使える。
サンプルとして適当にクラスを作ってみる。
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
class SampleView(LoginRequiredMixin, FormMixin, DetailView):
model = User
form_class = UserEditForm
template_name = 'myapp/mypage.html'
success_url = reverse_lazy('myapp:mypage')
# 共有の変数を設定
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
# self.hoge_listが使い回せるようになる。
if request.user.is_authenticated:
self.hoge_list = request.user.m2m.all()
# id等を指定しないのでget_objectメソッドを使う
def get_object(self):
return self.request.user
# フォームにデータベースからの初期値の設定
def get_initial(self):
initial = super().get_initial()
ctx = {
'username': self.request.user.username,
# 上で追加したオブジェクト
'hoges': self.hoge_list
}
initial.update(ctx)
return initial
# post処理
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = UserEditForm(request.POST, request.FILES, instance=request.user)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
# 成功時の処理
def form_valid(self, form):
form.save()
messages.info(self.request, '更新しました')
return super().form_valid(form)
# contextの作成
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
hoge_list = self.hoge_list
...
ctx = {
'user': user, 'hoge_list': hoge_list
}
context.update(ctx)
return context
setup
メソッドに、self.HOGE
を設定しておくと、クエリの重複を防ぐことができるのでなかなか便利。
ちなみにget_object()
で使いまわしてみたが、クエリが通常の倍近くになった(当たり前か笑)
ClassBaseViewは便利ではあるが、構造を理解してないと予期しないエラーを吐きそうで怖い。
複雑な処理をする場合は、defの方が安心感があるのはご愛嬌。
おまけ
-
super()
を最初で呼び出すか? - return後に
super()
を呼び出すか? - 親コードを持ってきて改変する
-
hoge = super().method()
で代入するか? -
initial['hoge']
の形か? -
ctx = {'hoge': hoge}
の形か?
initial['hoge']で初期値を設定すると、タプルで返ってくるので注意。
あと、 FormMixin
とDetailView
を組み合わせる場合は、formとviewを別にして、別で合体させるのが良いらしい。