在Django中,你可以为查询集添加一个方法吗?

时间:2011-01-02 01:10:48

标签: python django django-queryset

在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()

7 个答案:

答案 0 :(得分:35)

从django 1.7开始,添加了to use a query set as a manager能力:

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')

此外,还添加了添加custom lookups的功能。

答案 1 :(得分:15)

这是一个完整的解决方案,已知在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()方法is now deprecated in Django 1.6;在这种情况下,应该使用get_queryset()

答案 2 :(得分:5)

您需要向最终结果的QuerySet添加方法。因此,您需要创建和使用QuerySet子类,该子类具有您在任何需要此功能的地方定义的方法。

我找到了这个教程,解释了如何操作以及您可能想要的原因:

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

答案 3 :(得分:4)

您可以修改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()在我的实施中无法实现的事实。这可以通过三种方式解决:

  • 在Manager和QuerySet中实现reasonably_complex_filter;
  • 使用Transaction.objects.all().reasonably_complex_filter()时,这是唯一需要的过滤器;
  • 请参阅Marcus Whybrow的答案,了解在QuerySetManager中实施该方法而无需重复代码的解决方案。

这取决于应用程序哪个选项是最需要的,尽管我强烈建议不要使用代码重复(尽管你可以使用全局方法来解决这个问题)。但是,如果您只需要一次这种练习,或者您只打算将复合滤镜与其他滤镜结合使用,那么最后一个选项在开销方面可能成本太高。

答案 4 :(得分:0)

我实际上最后还是采用了另一种方法。事实证明我只需要将filter个调用链接到自定义方法的末尾,所以我修改了我的方法以获取任意关键字参数,并将它们传递给我的filter()调用。相当复杂的查询:

class TransactionManager(models.Manager):

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

似乎可以为我的目的正常工作,并且比继承QuerySet更简单。

答案 5 :(得分:0)

如果同时需要自定义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

答案 6 :(得分:0)

Django 2.0+ 的最简单版本

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

class Transaction(models.Model):
    ...

    objects = models.Manager.from_queryset(TransactionQuerySet)()