Django Manager Chaining
-
03-07-2019 - |
質問
複数のマネージャーを連鎖させて、両方の個々のマネージャーの影響を受けるクエリセットを生成することが可能かどうか(そして可能であれば、どのように)疑問に思っていました。私が取り組んでいる特定の例を説明します:
複数の抽象モデルクラスを使用して、他のモデルに小さな特定の機能を提供します。これらのモデルの2つは、DeleteMixinとGlobalMixinです。
DeleteMixinは次のように定義されています:
class DeleteMixin(models.Model):
deleted = models.BooleanField(default=False)
objects = DeleteManager()
class Meta:
abstract = True
def delete(self):
self.deleted = True
self.save()
基本的に、実際にオブジェクトを削除する代わりに、疑似削除(削除済みフラグ)を提供します。
GlobalMixinは次のように定義されています:
class GlobalMixin(models.Model):
is_global = models.BooleanField(default=True)
objects = GlobalManager()
class Meta:
abstract = True
任意のオブジェクトをグローバルオブジェクトまたはプライベートオブジェクト(パブリック/プライベートブログ投稿など)として定義できます。
これらの両方には、返されるクエリセットに影響する独自のマネージャーがあります。私のDeleteManagerは、削除済みフラグがFalseに設定された結果のみを返すようにクエリセットをフィルタリングし、GlobalManagerはグローバルとしてマークされた結果のみを返すようにクエリセットをフィルタリングします。両方の宣言は次のとおりです。
class DeleteManager(models.Manager):
def get_query_set(self):
return super(DeleteManager, self).get_query_set().filter(deleted=False)
class GlobalManager(models.Manager):
def globals(self):
return self.get_query_set().filter(is_global=1)
望ましい機能は、これらの抽象的なモデルの両方をモデルで拡張し、削除されていないグローバルな結果のみを返す機能を付与することです。 4つのインスタンスを持つモデルでテストケースを実行しました。1つはグローバルで削除されず、1つはグローバルで削除され、1つは非グローバルで削除されず、1つは非グローバルで削除されました。 SomeModel.objects.all()のような結果セットを取得しようとすると、インスタンス1と3が取得されます(2つの削除されていないもの-すばらしい!)。 SomeModel.objects.globals()を試すと、DeleteManagerにグローバルがないというエラーが表示されます(これは、モデル宣言がSomeModel(DeleteMixin、GlobalMixin)であると仮定しています。順序を逆にすると、 tエラーが表示されますが、削除されたものは除外されません)。 GlobalMixinを変更して、GlobalManagerをオブジェクトではなくグローバルにアタッチする場合(新しいコマンドはSomeModel.globals.globals()になります)、インスタンス1および2(2つのグローバル)を取得しますが、意図した結果はインスタンスのみを取得することです1(グローバルな、削除されていないもの)。
これに似た状況に誰かが陥り、結果に至ったかどうかはわかりませんでした。それを私の現在の考え方で機能させる方法か、私が望んでいる機能を提供する再作業のいずれかが非常に高く評価されるでしょう。私はこの投稿が少々長引くことを知っています。さらに説明が必要な場合は、喜んで提供します。
編集:
この特定の問題に使用した最終的な解決策を以下に掲載しました。 SimonのカスタムQuerySetManagerへのリンクに基づいています。
解決
Djangosnippetsの次のスニペットを参照してください: http://djangosnippets.org/snippets/734/
カスタムメソッドをマネージャーに配置する代わりに、クエリセット自体をサブクラス化します。それは非常に簡単で完璧に動作します。私が抱えていた唯一の問題はモデルの継承に関するもので、クエリセットを継承する場合でも、モデルサブクラスでマネージャーを定義する必要があります(サブクラスでは" objects = QuerySetManager()"のみ)。 QuerySetManagerを使用すると、これはより意味があります。
他のヒント
ScottがリンクしたSimonによるカスタムQuerySetManagerを使用して、私の問題に対する具体的な解決策を示します。
from django.db import models
from django.contrib import admin
from django.db.models.query import QuerySet
from django.core.exceptions import FieldError
class MixinManager(models.Manager):
def get_query_set(self):
try:
return self.model.MixinQuerySet(self.model).filter(deleted=False)
except FieldError:
return self.model.MixinQuerySet(self.model)
class BaseMixin(models.Model):
admin = models.Manager()
objects = MixinManager()
class MixinQuerySet(QuerySet):
def globals(self):
try:
return self.filter(is_global=True)
except FieldError:
return self.all()
class Meta:
abstract = True
class DeleteMixin(BaseMixin):
deleted = models.BooleanField(default=False)
class Meta:
abstract = True
def delete(self):
self.deleted = True
self.save()
class GlobalMixin(BaseMixin):
is_global = models.BooleanField(default=True)
class Meta:
abstract = True
クエリセットに機能を追加したい将来のmixinは、BaseMixinを拡張する(または階層内のどこかに配置する)必要があります。クエリセットをフィルター処理しようとするたびに、そのフィールドが実際に存在しない場合(つまり、そのmixinを拡張しない場合)に備えて、try-catchでラップしました。グローバルフィルターはglobals()を使用して呼び出されますが、削除フィルターは自動的に呼び出されます(何かが削除された場合、表示したくありません)。このシステムを使用すると、次のタイプのコマンドが可能になります。
TemporaryModel.objects.all() # If extending DeleteMixin, no deleted instances are returned
TemporaryModel.objects.all().globals() # Filter out the private instances (non-global)
TemporaryModel.objects.filter(...) # Ditto about excluding deleteds
注意すべきことの1つは、デフォルトのマネージャーが最初に宣言されている(デフォルトにする)ため、削除フィルターが管理インターフェースに影響を与えないことです。 Model.objectsの代わりにModel._default_managerを使用するようにadminを変更したことは覚えていませんが、削除されたインスタンスは引き続きadminに表示されます(削除を取り消す必要がある場合)。
これを行うための素晴らしいファクトリーを構築する方法を考え出そうとしてしばらく費やしましたが、それに関して多くの問題に直面しています。
あなたに提案できる最善の方法は、継承を連鎖することです。あまり一般的ではないので、どれほど便利かはわかりませんが、あなたがしなければならないのは次のとおりです:
class GlobalMixin(DeleteMixin):
is_global = models.BooleanField(default=True)
objects = GlobalManager()
class Meta:
abstract = True
class GlobalManager(DeleteManager):
def globals(self):
return self.get_query_set().filter(is_global=1)
より一般的なものが必要な場合、私が思いつくのは、 get_query_set()
Mixin および Manager
を定義することです>(これは1回だけ行いたいと想定しています。それ以外の場合は非常に複雑になります)そして、追加するフィールドのリストを Mixin
sで渡します。
次のようになります(まったくテストされていません):
class DeleteMixin(models.Model):
deleted = models.BooleanField(default=False)
class Meta:
abstract = True
def create_mixin(base_mixin, **kwargs):
class wrapper(base_mixin):
class Meta:
abstract = True
for k in kwargs.keys():
setattr(wrapper, k, kwargs[k])
return wrapper
class DeleteManager(models.Manager):
def get_query_set(self):
return super(DeleteManager, self).get_query_set().filter(deleted=False)
def create_manager(base_manager, **kwargs):
class wrapper(base_manager):
pass
for k in kwargs.keys():
setattr(wrapper, k, kwargs[k])
return wrapper
わかりました、これは見苦しいですが、何が得られますか?本質的には、同じソリューションですが、はるかに動的で、少し乾燥していますが、読むのはより複雑です。
まず、マネージャーを動的に作成します:
def globals(inst):
return inst.get_query_set().filter(is_global=1)
GlobalDeleteManager = create_manager(DeleteManager, globals=globals)
これにより、 DeleteManager
のサブクラスであり、 globals
というメソッドを持つ新しいマネージャーが作成されます。
次に、ミックスインモデルを作成します:
GlobalDeleteMixin = create_mixin(DeleteMixin,
is_global=models.BooleanField(default=False),
objects = GlobalDeleteManager())
私が言ったように、それはいです。ただし、 globals()
を再定義する必要はありません。別のタイプのマネージャーに globals()
を持たせたい場合は、異なるベースで create_manager
を再度呼び出すだけです。そして、好きなだけ新しいメソッドを追加できます。マネージャーについても同じです。異なるクエリセットを返す新しい関数を追加し続けるだけです。
それで、これは本当に実用的ですか?そうでないかもしれない。この答えは、(ab)Pythonの柔軟性を使用するための演習です。アクセスを簡単にするために、動的に拡張するクラスの基礎となるプリンシパルの一部を使用していますが、これを使用しようとはしていません。
不明な点がある場合はお知らせください。回答を更新します。
検討する価値があるもう1つのオプションはPassThroughManagerです:
https://django-model-utils.readthedocs。 org / en / latest / managers.html#passthroughmanager