web-dev-qa-db-ja.com

Djangoで、クエリセットにメソッドを追加できますか?

Djangoでは、モデルクラスがある場合、たとえば.

from Django.db import models

class Transaction(models.Model):
    ...

次に、モデルにメソッドを追加したい場合は、たとえば適度に複雑なフィルター、カスタムモデルマネージャーを追加できます。

class TransactionManager(models.Manager):

    def reasonably_complex_filter(self):
        return self.get_query_set().filter(...)


class Transaction(models.Model):
    objects = TransactionManager()

そして、私はできる:

>>> Transaction.objects.reasonably_complex_filter()

モデルからクエリセットの最後にチェーンできるカスタムメソッドを追加する方法はありますか?

つまり、私がこれを行うことができるような方法でカスタムメソッドを追加します:

>>> Transaction.objects.filter(...).reasonably_complex_filter()
30
Paul D. Waite

最終的に最終的になるQuerySetにメソッドを追加する必要があります。したがって、この機能が必要な場所で定義するメソッドを持つQuerySetサブクラスを作成して使用する必要があります。

私はそれを行う方法とあなたがしたいかもしれない理由を説明するこのチュートリアルを見つけました:

http://adam.gomaa.us/blog/2009/feb/16/subclassing-Django-querysets/index.html

5
Marcus Whybrow

Django 1.7、機能 クエリセットをマネージャーとして使用する が追加されました:

class PersonQuerySet(models.QuerySet):
    def authors(self):
        return self.filter(role='A')

    def editors(self):
        return self.filter(role='E')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=(('A', _('Author')),
                                                   ('E', _('Editor'))))
    people = PersonQuerySet.as_manager()

結果は次のとおりです。

Person.people.authors(last_name='Dahl')

さらに、 カスタムルックアップ を追加する機能も追加されました。

36
Burhan Khalid

これは、Django 1.3、 Zach Smith とBenの好意により)で機能することが知られている完全なソリューションです。

_class Entry(models.Model):
    objects = EntryManager() # don't forget this

    is_public = models.BooleanField()
    owner = models.ForeignKey(User)


class EntryManager(models.Manager):
    '''Use this class to define methods just on Entry.objects.'''
    def get_query_set(self):
        return EntryQuerySet(self.model)

    def __getattr__(self, name, *args):
        if name.startswith("_"): 
            raise AttributeError
        return getattr(self.get_query_set(), name, *args) 

    def get_stats(self):
        '''A sample custom Manager method.'''
        return { 'public_count': self.get_query_set().public().count() }


class EntryQuerySet(models.query.QuerySet):
    '''Use this class to define methods on queryset itself.'''
    def public(self):
        return self.filter(is_public=True)

    def by(self, owner):
        return self.filter(owner=owner)


stats = Entry.objects.get_stats()    
my_entries = Entry.objects.by(request.user).public()
_

注:get_query_set()メソッド Django 1.6 で非推奨になりました=;この場合、代わりにget_queryset()を使用する必要があります。

15
Dan Abramov

get_query_set()メソッドを変更して、必要なメソッドを追加し、カスタムQuerySetを返すことができます。あなたの場合、あなたは以下を使用するでしょう:

_class TransactionManager(models.Manager):
    def get_query_set(self):
        return TransactionQuerySet(self.model)

class TransactionQuerySet(models.query.QuerySet):
    def reasonably_complex_filter(self):
        return self.filter(...)
_

TransactionQuerySetをTransactionモデルまたは関連するManagerにサブクラス化する例を見てきましたが、それは完全にあなた次第です。

編集objectsが最初にTransactionManagerを参照しているため、実装ではTransaction.objects.reasonably_complex_filter()が不可能であるという事実を見落としているようです。これは、次の3つの方法で修正できます。

  • ManagerとQuerySetの両方に_reasonably_complex_filter_を実装します。
  • 必要なフィルターがそれだけの場合は、Transaction.objects.all().reasonably_complex_filter()を使用します。
  • コードを重複させずにQuerySetManagerの両方にメソッドを実装するソリューションについては、MarcusWhybrowの回答を参照してください。

どのオプションが最も望ましいかはアプリケーションによって異なりますが、コードの重複は強くお勧めします(ただし、グローバルな方法を使用してこれを克服することもできます)。ただし、この種の練習が1回だけ必要な場合、または複雑なフィルターを別のフィルターと組み合わせて使用​​する場合は、最後のオプションはオーバーヘッドの点でコストがかかりすぎる可能性があります。

4
ralphje

カスタムManagerメソッドとカスタムQuerySetメソッドの両方が必要な場合は、from_querysetを使用できます。

class BaseManager(models.Manager):
    def manager_only_method(self):
        return

class CustomQuerySet(models.QuerySet):
    def manager_and_queryset_method(self):
        return

class MyModel(models.Model):
    objects = BaseManager.from_queryset(CustomQuerySet)()

https://docs.djangoproject.com/en/2.1/topics/db/managers/#from-queryset

0
Stian Jensen

私は実際に別の方法で行くことになった。カスタムメソッドの最後にfilter呼び出しをチェーンするだけでよいことがわかったので、任意のキーワード引数を取り、それらをfilter()呼び出しに渡すようにメソッドを修正しました。私のかなり複雑なクエリの終わり:

class TransactionManager(models.Manager):

    def reasonably_complex_filter(self, **kwargs):
        return self.get_query_set().filter(...).filter(**kwargs)

私の目的には問題なく機能しているようで、QuerySetをサブクラス化するよりも少し簡単です。

0
Paul D. Waite