31

複数のマネージャーを連鎖させて、個々のマネージャーの両方の影響を受けるクエリ セットを生成することが可能かどうか (可能な場合はその方法) を考えていました。私が取り組んでいる具体的な例を説明します。

他のモデルに小さな特定の機能を提供するために使用する複数の抽象モデル クラスがあります。これらのモデルのうちの 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 つのインスタンスを持つモデルでテスト ケースを実行しました。SomeModel.objects.all() などの結果セットを取得しようとすると、インスタンス 1 と 3 が取得されます (削除されていない 2 つのインスタンス - すばらしい!)。SomeModel.objects.globals() を試すと、DeleteManager にグローバルがないというエラーが表示されます (これは、モデル宣言がそのようなものであると想定しています: SomeModel(DeleteMixin, GlobalMixin)。順序を逆にすると、そうではありません。エラーは発生しますが、削除されたものは除外されません)。

これと似たような状況に遭遇し、結果に至った人がいるかどうかはわかりませんでした。私の現在の考え方でそれを機能させる方法、または私が求めている機能を提供する再作業のいずれかが非常に高く評価されます. この投稿が少し長くなっていることは承知しています。さらに説明が必要な場合は、喜んで提供します。

編集:

この特定の問題に使用した最終的な解決策を以下に投稿しました。これは、Simon のカスタム QuerySetManager へのリンクに基づいています。

4

4 に答える 4

22

Djangosnippetsでこのスニペットを参照してください:http://djangosnippets.org/snippets/734/

カスタムメソッドをマネージャーに配置する代わりに、クエリセット自体をサブクラス化します。それは非常に簡単で、完璧に機能します。私が抱えていた唯一の問題はモデルの継承です。クエリセットを継承する場合でも、常にモデルのサブクラスでマネージャーを定義する必要があります(サブクラスでは "objects = QuerySetManager()")。QuerySetManagerを使用すると、これはより理にかなっています。

于 2009-05-01T21:04:31.600 に答える
10

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 つは、デフォルトの Manager が最初に宣言されている (デフォルトになっている) ため、delete フィルターは管理インターフェースに影響を与えないことです。管理者が Model.objects の代わりに Model._default_manager を使用するように変更した時期を覚えていませんが、削除されたインスタンスは引き続き管理者に表示されます (削除を取り消す必要がある場合)。

于 2009-05-02T06:00:37.820 に答える
2

これを行うための素敵な工場を構築する方法を考え出すのにしばらく費やしましたが、それに関して多くの問題に直面しています。

私があなたに提案できる最善の方法は、継承を連鎖させることです。それはあまり一般的ではないので、それがどれほど役立つかはわかりませんが、あなたがしなければならないことは次のとおりです。

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)

より一般的なものが必要な場合は、ベースを定義しMixinManagerそれを再定義するのが最善get_query_set()です (これは 1 回だけ実行する必要があると想定しています。それ以外の場合はかなり複雑になります)。次に、フィールドのリストを渡します。 d s 経由で追加したいMixin

次のようになります (まったくテストされていません)。

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

わかりました、これは醜いですが、それはあなたに何をもたらしますか? 基本的に、これは同じソリューションですが、より動的で、少し DRY ですが、読むのはより複雑です。

まず、マネージャーを動的に作成します。

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そして、好きなだけ新しいメソッドを追加できます。マネージャーについても同じです。異なるクエリセットを返す新しい関数を追加し続けるだけです。

それで、これは本当に実用的ですか?そうでないかもしれない。この回答は、Python の柔軟性を (ab) 使用する演習です。私はこれを試したことはありませんが、動的に拡張するクラスの基礎となるプリンシパルのいくつかを使用して、物事に簡単にアクセスできるようにしています。

不明な点があればお知らせください。回答を更新します。

于 2009-05-01T22:26:05.383 に答える
2

検討する価値のあるもう 1 つのオプションは、PassThroughManager です。

https://django-model-utils.readthedocs.org/en/latest/managers.html#passthroughmanager

于 2012-10-01T22:41:21.130 に答える