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
の再定義に大きく依存しているため、機能しなくなります。
QuerySet
とManager
サブクラスの両方ですべてのメソッドを再定義せずにこれを行う方法はありますか?
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):
***
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の作成 を参照してください。
ミックスインを使用して、マネージャーとクエリセットのメソッドを提供できます。次の手法を参照してください。
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)
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)
これで、マネージャで 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()