web-dev-qa-db-ja.com

DRYを壊さずにカスタムQuerySetおよびManagerを使用しますか?

DRYを壊すことなく、カスタムQuerySetとカスタムManagerの両方を実装する方法を見つけようとしています。これは私がこれまでに持っているものです:

class MyInquiryManager(models.Manager):
    def for_user(self, user):
        return self.get_query_set().filter(
                    Q(assigned_to_user=user) |
                    Q(assigned_to_group__in=user.groups.all())
                )

class Inquiry(models.Model):   
    ts = models.DateTimeField(auto_now_add=True)
    status = models.ForeignKey(InquiryStatus)
    assigned_to_user = models.ForeignKey(User, blank=True, null=True)
    assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
    objects = MyInquiryManager()

これは私がこのようなことをするまでうまくいきます:

inquiries = Inquiry.objects.filter(status=some_status)
my_inquiry_count = inquiries.for_user(request.user).count()

QuerySetにはManagerと同じメソッドがないため、これはすべてを即座に破壊します。カスタムQuerySetクラスを作成してMyInquiryManagerに実装しようとしましたが、すべてのメソッド定義を複製してしまいます。

私は this snippet も機能することを発見しましたが、for_userに追加の引数を渡す必要があるため、get_query_setの再定義に大きく依存しているため、機能しなくなります。

QuerySetManagerサブクラスの両方ですべてのメソッドを再定義せずにこれを行う方法はありますか?

52
Jack M.

Djangoが変更されました! 2009年に作成されたこの回答のコードを使用する前に、残りの回答とDjangoドキュメントを確認してください。より適切な解決策がある場合。


これを実装した方法は、実際のget_active_for_accountをカスタムQuerySetのメソッドとして追加することです。次に、それをマネージャーから機能させるために、__getattr__をトラップしてそれに応じて返すだけです。

このパターンを再利用可能にするために、Managerビットを別のモデルマネージャーに抽出しました。

custom_queryset/models.py

from Django.db import models
from Django.db.models.query import QuerySet

class CustomQuerySetManager(models.Manager):
    """A re-usable Manager to access a custom QuerySet"""
    def __getattr__(self, attr, *args):
        try:
            return getattr(self.__class__, attr, *args)
        except AttributeError:
            # don't delegate internal methods to the queryset
            if attr.startswith('__') and attr.endswith('__'):
                raise
            return getattr(self.get_query_set(), attr, *args)

    def get_query_set(self):
        return self.model.QuerySet(self.model, using=self._db)

それができたら、モデルでQuerySetをカスタム内部クラスとして定義し、マネージャーをカスタムマネージャーに設定するだけです。

your_app/models.py

from custom_queryset.models import CustomQuerySetManager
from Django.db.models.query import QuerySet

class Inquiry(models.Model):
    objects = CustomQuerySetManager()

    class QuerySet(QuerySet):
        def active_for_account(self, account, *args, **kwargs):
            return self.filter(account=account, deleted=False, *args, **kwargs)

このパターンでは、次のいずれでも機能します。

>>> Inquiry.objects.active_for_account(user)
>>> Inquiry.objects.all().active_for_account(user)
>>> Inquiry.objects.filter(first_name='John').active_for_account(user)

UPDをカスタムユーザー(AbstractUser)で使用している場合は、変更する必要があります
から

class CustomQuerySetManager(models.Manager):

from Django.contrib.auth.models import UserManager

class CustomQuerySetManager(UserManager):
    ***
50
T. Stone

Django 1.7は、クエリセットとモデルマネージャを組み合わせて作成する新しくてシンプルな方法をリリースしました:

class InquiryQuerySet(models.QuerySet):
    def for_user(self):
        return self.filter(
            Q(assigned_to_user=user) |
            Q(assigned_to_group__in=user.groups.all())
        )

class Inquiry(models.Model):
    objects = InqueryQuerySet.as_manager()

詳細は QuerySetメソッドを使用したManagerの作成 を参照してください。

32
iMom0

ミックスインを使用して、マネージャーとクエリセットのメソッドを提供できます。次の手法を参照してください。

http://hunterford.me/Django-custom-model-manager-chaining/

これにより、__getattr__()アプローチの使用も回避されます。

from Django.db.models.query import QuerySet

class PostMixin(object):
    def by_author(self, user):
        return self.filter(user=user)

    def published(self):
        return self.filter(published__lte=datetime.now())

class PostQuerySet(QuerySet, PostMixin):
    pass

class PostManager(models.Manager, PostMixin):
    def get_query_set(self):
        return PostQuerySet(self.model, using=self._db)
10
vdboor

T.ストーンのアプローチを少し改良したバージョン:

def objects_extra(mixin_class):
    class MixinManager(models.Manager, mixin_class):
        class MixinQuerySet(QuerySet, mixin_class):
            pass

        def get_query_set(self):
            return self.MixinQuerySet(self.model, using=self._db)

    return MixinManager()

クラスデコレータを使用すると、次のように簡単に使用できます。

class SomeModel(models.Model):
    ...
    @objects_extra
    class objects:
        def filter_by_something_complex(self, whatever parameters):
            return self.extra(...)
        ...

更新:非標準のManagerおよびQuerySet基本クラスのサポート。 g。 @objects_extra(Django.contrib.gis.db.models.GeoManager、Django.contrib.gis.db.models.query.GeoQuerySet):

def objects_extra(Manager=Django.db.models.Manager, QuerySet=Django.db.models.query.QuerySet):
    def oe_inner(Mixin, Manager=Django.db.models.Manager, QuerySet=Django.db.models.query.QuerySet):
        class MixinManager(Manager, Mixin):
            class MixinQuerySet(QuerySet, Mixin):
                pass

            def get_query_set(self):
                return self.MixinQuerySet(self.model, using=self._db)

        return MixinManager()

    if issubclass(Manager, Django.db.models.Manager):
        return lambda Mixin: oe_inner(Mixin, Manager, QuerySet)
    else:
        return oe_inner(Mixin=Manager)
3
Roman Odaisky

これで、マネージャで from_queryset() メソッドを使用して、ベースクエリセットを変更できます。

これにより、クエリセットメソッドとマネージャーメソッドを一度だけ定義できます。

ドキュメントから

高度な使用法では、カスタムマネージャーとカスタムクエリセットの両方が必要になる場合があります。カスタムQuerySetメソッドのコピーを使用して、基本Managerのサブクラスを返すManager.from_queryset()を呼び出すことで、これを行うことができます。

class InqueryQueryset(models.Queryset):
    def custom_method(self):
        """ available on all default querysets"""

class BaseMyInquiryManager(models.Manager):
    def for_user(self, user):
        return self.get_query_set().filter(
                    Q(assigned_to_user=user) |
                    Q(assigned_to_group__in=user.groups.all())
                )

MyInquiryManager = BaseInquiryManager.from_queryset(InquiryQueryset)

class Inquiry(models.Model):   
    ts = models.DateTimeField(auto_now_add=True)
    status = models.ForeignKey(InquiryStatus)
    assigned_to_user = models.ForeignKey(User, blank=True, null=True)
    assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
    objects = MyInquiryManager()
0
maazza