【问题标题】:In Django, can you add a method to querysets?在 Django 中,您可以向查询集添加方法吗?
【发布时间】:2011-06-02 08:59:40
【问题描述】:

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

【问题讨论】:

    标签: python django django-queryset


    【解决方案1】:

    您需要将方法添加到您最终得到的QuerySet。因此,您需要创建并使用一个 QuerySet 子类,该子类具有您定义的方法,无论您想要此功能。

    我找到了这个教程,它解释了如何做以及你可能想要这样做的原因:

    https://web.archive.org/web/20160329131857/http://adam.gomaa.us/blog/2009/feb/16/subclassing-django-querysets/index.html

    【讨论】:

    • 对,明白了。你知道那篇博文中建议的方法是否可靠吗? cmets 认为__getattr__ 方法存在一些问题。
    • 这篇文章很老了,但重点是您需要以某种方式将您的方法附加到QuerySet 对象。
    • 有一个newer article,我觉得它更容易理解。请注意,在 cmets 中有一个修复,没有它的代码应该会在 1.2 中中断。
    • 链接失效
    • 两个链接都失效了。这就是为什么答案不应该只是博客链接的原因。博客消失了。
    【解决方案2】:

    您可以修改get_query_set() 方法以返回自定义查询集,添加您需要的方法。在您的情况下,您将使用:

    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()
    • 有关在QuerySetManager 中实现该方法且无需重复代码的解决方案,请参阅 Marcus Whybrow 的回答。

    这取决于应用程序最需要的选项,尽管我强烈建议不要重复代码(尽管您可以使用全局方法来克服这个问题)。但是,如果您只需要这种练习一次,或者如果您只打算将复杂过滤器与另一个过滤器结合使用,则最后一个选项在开销方面可能过于昂贵。

    【讨论】:

      【解决方案3】:

      我实际上最终选择了另一种方法。原来我只需要将filter 调用链接到我的自定义方法的末尾,因此我修改了我的方法以采用任意关键字参数,并在我相当复杂的查询结束时将它们传递给filter() 调用:

      class TransactionManager(models.Manager):
      
          def reasonably_complex_filter(self, **kwargs):
              return self.get_query_set().filter(...).filter(**kwargs)
      

      对于我的目的来说似乎可以正常工作,并且比子类化QuerySet 要简单一些。

      【讨论】:

        【解决方案4】:

        这是已知在 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()

        【讨论】:

          【解决方案5】:

          从 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的功能。

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-07-11
                • 2017-12-26
                • 1970-01-01
                • 2023-01-07
                相关资源
                最近更新 更多