0
0

More than 1 year has passed since last update.

【Django】ClassBasedViewで共有変数と初期値を設定する

Last updated at Posted at 2022-10-30

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']で初期値を設定すると、タプルで返ってくるので注意。

あと、 FormMixinDetailViewを組み合わせる場合は、formとviewを別にして、別で合体させるのが良いらしい。

0
0
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
0