简体   繁体   中英

Django Custom Queryset managers: Implicit chained filter()?

In Django, when it comes to M2M relationships, we know that these two queries:

# 1
MyModel.objects.filter(othermodel__attr1="someval1", othermodel__attr2="someval2")
# 2
MyModel.objects.filter(othermodel__attr1="someval1").filter(othermodel__attr2="someval2")

Will not necessarily return the same results ( docs ).

I've recently started utilizing custom QuerySet managers and it suddenly dawned on me that I may get this undesired behavior (for me at least) if I'm doing something like this:

# defined as customobjects = MyModelQuerySet.as_manager() in `MyModel`

class MyModelQuerySet(models.QuerySet): 
      def method1(self):
          return self.filter(othermodel__attr1="someval1")
      
      def method2(self):
          return self.filter(othermodel__attr2="someval2")

and using MyModelQuerySet like this:

results = MyModel.customobjects.method1().method2()

Then I may be getting the behavior of chained filters. For me this makes using a custom QuerySet manager completely useless as I need an AND behavior most of the time. But I'm not sure this is indeed the behaviour. If it is, are there are any workarounds while using managers? (I want the ability to flexibly use different filters and mix and match them with managers, but I don't want the chained filters behavior)

I bet you can modify the behavior of filter and only call it when you need to get the result with _fetch_all :

class MyModelQuerySet(models.QuerySet):
      def __init__(self, *args, **kwards):
          super().__init__(*args, **kwards)
          self.__filter_copy = self.filter
          self.__filters_chain_args = []
          self.__filters_chain_kwargs = {}
      
      def filter(self, *args, **kwards):
          self.__filters_chain_args = [*self.__filters_chain_args , *args]
          self.__filters_chain_kwargs = {**self.__filters_chain_kwargs, **kwards}
          return self

      def _fetch_all(self, *args, **kwargs):
          # apply real filter here
          self.__filter_copy(*self.__filters_chain_args, **self.__filters_chain_kwargs)
          return super()._fetch_all(*args, **kwargs)
      
      def method1(self):
          return self.filter(othermodel__attr1="someval1")
      
      def method2(self):
          return self.filter(othermodel__attr2="someval2")

One workaround that could work in this case would be this approach:

from django.db.models import Q

class MyModelQuerySet(models.QuerySet): 
    def _method_m2m_1(self, val: str):
        return Q(othermodel__attr1=val)
    
    def _method2_m2m_2(self, val: str):
        return Q(othermodel__attr2=val)
      
    def method_m2m(self, val_1: str = None, val_2: str = None):
        List[Q]: queries = []

        if val_1:
            queries.append(self._mehtod_m2m_1(val=val_1))
        if val_2:
            queries.append(self._mehtod_m2m_2(val=val_2))
        
        if queries:
           self.filter(*queries)
        
        return self

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM