Django設計 マネージャーを使って開発・保守しやすいコードを書く方法

マネージャーはDjangoのドキュメントでは”モデル層”の”モデルの高度な話題”に分類されていますが、非常に便利なので、できるだけ早く使い慣れたい機能です。

今回はマネージャーを使って開発・保守しやすいコードを書く方法を解説します。

今回はDjangoでよく使われるUserクラスやUserManagerクラスを使います。

マネージャーとは?

マネージャーについては公式サイトに次の様に書かれています。

マネージャ (Manager) とは、Django のモデルに対するデータベースクエリの操作を提供するインターフェイスです。Django アプリケーション内の1つのモデルに対して、Managerは最低でも1つ存在します。

Django公式サイト

マネージャーのカスタマイズ

django.db.modelsにあるManagerクラスを継承すれば、カスタマイズしたManagerを作成できます。モデル内でインスタンス化すれば、特定のモデル用のManagerとして利用できます。

例えば、django.contrib.auth.base_userにはBaseUserManagerというカスタムモデルが定義されています。

このBaseUserManagerを継承して作成したUserManagerクラスのインスタンスが、AbstractUserクラスのマネージャーとして定義されています。

AbstractUserクラスはdjango.contrib.auth.modelsに定義されています。

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    # メンバ変数宣言 略

    objects = UserManager()

    # 以下略

Managerをカスタマイズする方法は次の2つあります。

・マネージャーに新しいメソッドを追加する方法

・マネージャーが初めに返すQuerySetを修正する方法

次からはUserManagerを使ってこれらのカスタマイズ方法を紹介します。

マネージャーに新しいメソッドを追加する方法

DjangoのUserManagerを継承して機能を追加したオリジナルのUserManagerを作成します。

from django.contrib.auth.models import UserManager as BaseUserManager
from django.contrib.auth.models import AbstractUser

class UserManager(BaseUserManager):

    def active(self):
        """アクティブユーザーを取得"""
        return self.filter(is_active=True)


class User(AbstractUser):
    objects = UserManager()

UserManagerにアクティブユーザーを取得するactiveメソッドを定義しました。

これにより、以下の2パターンのコードでアクティブユーザーを取得できるようになります。

# activeメソッドを使わない方法 (1)
active_users = User.objects.filter(is_active=True)

# activeメソッドを使う方法 (2)
active_users = User.objects.active()

(1)の方法と(2)の方法の比較

(1)の方法はアクティブユーザーの条件を直接書いていますが、(2)の方法はその条件を書く必要がありません。

アクティブユーザーを取得するコードは複数箇所に書かれるかも知れません。アクティブユーザーの条件が絶対変わらないのであれば問題ないですが、仕様変更が起きた時(1)と(2)では変更のしやすさが全然違います。

例えば、ユーザーに年齢(age)の属性を追加し、アクティブユーザーの定義に”年齢が20歳以上”という条件が加わったとします。

(1)の方法で実装していた場合、アクティブユーザーを取得しているコードを全て探して以下の様に書き換える必要が出るでしょう。

active_users = User.objects.filter(is_active=True, age_gte=20)

このコードを使い回している数に比例して変更箇所が増えます。変更を忘れてしまった場合はバグの原因にもなります。

テストコードも複数箇所書き直さないといけないかもしれません。

(2)の方法で実装していた場合は、UserManagerのactiveメソッドを書き換えるだけで済みます。変更箇所は1つのみです。

def active(self):
    '''アクティブユーザーを取得''''
    return self.filter(is_active=True, age_gte=20)

再度、アクティブユーザーの定義が変わっても容易に対処できます。

今回の例は簡単ですが、実際は取得条件が複雑な場合やモデルが増える度に取得条件を書いたコードが増加します。

どちらの方法が適切か?

私の経験では、Managerを使わず(1)の様に直接条件を書いた方が早く実装(確認)できるので、時間に追われている場合や「このメソッド以外で使う機会はないだろう」と安易に考えていた場合は(1)の方法で書いていまいがちでした。

しかし、(1)の即効性のある書き方より、

「最初から(2)の方法で書いておけば修正が簡単だったな」

と思ったことの方が多かったです。

とは言え、一概にデータベースクエリの操作を全てマネージャーに書けば良いというわけではありません。状況に応じてどこに書くのが適切か考える必要はあります。

マネージャーが初めに返すQuerySetを修正する方法

マネージャーのベースクエリセット(マネージャーがテーブルから取得する元となるデータ)は全てのオブジェクトを返します。

例えば次のコードは全てのユーザーオブジェクトを返します。

User.objects.all()

Manager.get_queryset()メソッドをオーバーライドすることで、マネージャーのベースクエリセットをオーバーライドできます。

get_querysetメソッドの戻り値はプロパティを指定したQuerySetにする必要があります。

例えばユーザーモデルに次の3つのマネージャーを設定してみます。

  • UserManager:全ユーザーを返す
  • ActiveUserManager:有効なユーザーを返す
  • StaffManager:スタッフ権限を持つユーザーを返す
from django.db import models
from django.contrib.auth.models import UserManager


class ActiveUserManager(models.Manager):
    """アクティブユーザ専用マネージャー"""
    def get_queryset(self):
        return super().get_queryset().filter(is_active=True)


class StaffManager(models.Manager):
    """スタッフ専用マネージャー"""
    def get_queryset(self):
        return super().get_queryset().filter(is_staff=True)


class User(AbstractUser):
    objects = UserManager()
    staffs = StaffManager()
    actives = ActiveUserManager()

これらのマネージャーは次の様に利用できます。

all_users = User.objects.all()
active_users = User.actives.all()
staffs = User.staffs.all()

「マネージャーに新しいメソッドを追加する方法」でも「マネージャーが初めに返すQuerySetを修正する方法」でもアクティブユーザーを絞り込む事ができます。

マネージャーに新しいメソッドを追加する方法”か”マネージャーが初めに返すQuerySetを修正する方法”をどう使い分けるか?

「マネージャーに新しいメソッドを追加する方法」の方は用途が広く、利用頻度は高いです。

# 同じマネージャーにメソッドを追加すると以下の様なコードが増える
User.objects.hoge1()
User.objects.hoge2()
・・・

上のコードの様に全てのユーザーに対してhoge1メソッドやhoge2メソッドを実行できますが、規模が大きくなると管理が難しくなるかもしれません。

そんな時、

「アクティブユーザー向けのメソッドと非アクティブユーザー向けのメソッドに分けられるな」

とか、

「スタッフ向けのメソッドと非スタッフ向けのメソッドに分けられるな」

の様な特徴があれば、「マネージャーが初めに返すQuerySetを修正する」方法を使って、新たにマネージャークラスを作成し、コードを分離した方が凝集度が上がり管理しやすくなるかもしれません。

「マネージャーが初めに返すQuerySetを修正する方法」は、例えばアクティブユーザーのみが利用できる機能が複数ある時はUser.actives.foo1(), User.actives.foo2()・・・の様に書けるので、可読性が上がると思います。

しかし、一つのモデルクラスに、複数のマネージャーを定義することは避けた方が無難だと思います。

「あると便利かも知れないと思ってマネージャーを作ったが全然使っていなかった」

となりやすいと思います。

個人的には「マネージャーに新しいメソッドを追加する方法」の方が便利で「マネージャーが初めに返すQuerySetを修正する方法」は忘れやすいです。

使い分けの基準

私なりの使い分けの基準は以下の通りです。

  1. 要件を見比べて、あらかじめベースクエリセットを定義した方が良さそうな場合は、「マネージャーが初めに返すQuerySetを修正する方法」を使う。
  2. 1に当てはまらない場合は「マネージャーに新しいメソッドを追加する方法」を使う。
  3. 「マネージャーに新しいメソッドを追加する方法」でコードが肥大化し、リファクタリングしたい場合は「マネージャーが初めに返すQuerySetを修正する方法」が使えないか考え直す。

まとめ

マネージャーには「マネージャーに新しいメソッドを追加する方法」と「マネージャーが初めに返すQuerySetを修正する方法」があります。

マネージャーに新しいメソッドを追加する方法」は利用価値が高いです。この方法を使うと、開発や保守しやすいコーディングができることもわかりました。

「マネージャーが初めに返すQuerySetを修正する方法」は利用シーンが少なく、忘れやすいですが上手く使えばコードの凝集度を上げて、コードを管理しやすくなる利点があります。

2つの方法のメリット・デメリットを把握してより設計レベルの高いコーディングを目指しましょう。

スポンサーリンク
スポンサーリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です