2017-04-26 120 views
3

相關模型的自定義查詢集比方說,我有兩個型號:BookAuthor過濾器通過在Django

class Author(models.Model): 
    name = models.CharField() 
    country = models.CharField() 
    approved = models.BooleanField() 


class Book(models.Model): 
    title = models.CharField() 
    approved = models.BooleanField() 
    author = models.ForeignKey(Author) 

每個兩款車型都有一個approved屬性,它顯示或隱藏從網站的對象。如果Book未被批准,它將被隱藏。如果Author未被批准,他所有的書都隱藏起來。

爲了在乾燥方式來定義這些標準,使得自定義查詢集看起來像一個完美的解決方案:

class AuthorQuerySet(models.query.QuerySet): 
    def for_site(): 
     return self.filter(approved=True) 

class BookQuerySet(models.query.QuerySet): 
    def for_site(): 
     reuturn self.filter(approved=True).filter(author__approved=True) 

掛鉤這些QuerysSets到相應的模式後,可以查詢這樣的:Book.objects.for_site() ,而不需要每次都對所有的過濾進行硬編碼。


儘管如此,這種解決方案仍然不夠完美。後來,我能夠決定另一個過濾器添加到作者:

class AuthorQuerySet(models.query.QuerySet): 
    def for_site(): 
     return self.filter(approved=True).exclude(country='Problematic Country') 

而且這個新的過濾器將只在Author.objects.for_site()工作,但不是在Book.objects.for_site(),因爲它是硬編碼。


所以我的問題是:是否有可能應用過濾在不同的模式時,以便它類似於此相關模型的自定義查詢集:

class BookQuerySet(models.query.QuerySet): 
    def for_site(): 
     reuturn self.filter(approved=True).filter(author__for_site=True) 

其中for_siteAuthor模型的自定義QuerySet。

+1

我不知道是否有幫助,但嘗試[這](https://simpleisbetterthancomplex.com/tips/2016/08/16/django-tip-11-custom-manager-with-chainable-querysets。 html) –

+0

這正是我所說的*把這些QuerysSets連接到相應的模型*之後。但我不明白它如何幫助使用該管理器來過濾其他相關模型的實例。也許我錯過了什麼? – BartoNaz

+0

你不會錯過任何東西,我真的認爲這是一個很好的問題,我不認爲你會找到一些開箱即用的東西。你可以看看[自定義查找文檔](https://docs.djangoproject.com/en/1.11/howto/custom-lookups/)併爲自己編寫代碼。 –

回答

0

我想,我已經想出了一個基於Q對象的解決方案,這些對象在official documentation中描述。這絕對不是可以發明的最優雅的解決方案,但它很有效。請參閱下面的代碼。

from django.db import models 
from django.db.models import Q 


######## Custom querysets 
class QuerySetRelated(models.query.QuerySet): 
    """Queryset that can be applied in filters on related models""" 

    @classmethod 
    def _qq(cls, q, related_name): 
     """Returns a Q object or a QuerySet filtered with the Q object, prepending fields with the related_name if specified""" 
     if not related_name: 
      # Returning Q object without changes 
      return q 
     # Recursively updating keywords in this and nested Q objects 
     for i_child in range(len(q.children)): 
      child = q.children[i_child] 
      if isinstance(child, Q): 
       q.children[i_child] = cls._qq(child, related_name) 
      else: 
       q.children[i_child] = ('__'.join([related_name, child[0]]), child[1]) 
     return q 


class AuthorQuerySet(QuerySetRelated): 

    @classmethod 
    def for_site_q(cls, q_prefix=None): 
     q = Q(approved=True) 
     q = q & ~Q(country='Problematic Country') 
     return cls._qq(q, q_prefix) 


    def for_site(self): 
     return self.filter(self.for_site_q()) 


class BookQuerySet(QuerySetRelated): 

    @classmethod 
    def for_site_q(cls, q_prefix=None): 
     q = Q(approved=True) & AuthorQuerySet.for_site_q('author') 
     return cls._qq(q, q_prefix) 


    def for_site(self): 
     return self.filter(self.for_site_q()) 



######## Models 
class Author(models.Model): 
    name = models.CharField(max_length=255) 
    country = models.CharField(max_length=255) 
    approved = models.BooleanField() 

    objects = AuthorQuerySet.as_manager() 


class Book(models.Model): 
    title = models.CharField(max_length=255) 
    approved = models.BooleanField() 
    author = models.ForeignKey(Author) 

    objects = BookQuerySet.as_manager() 

這種方式,每當AuthorQuerySet.for_site_q()方法改變時,它將被自動反映在BookQuerySet.for_site()方法。

這裏定製QuerySet類通過組合不同的Q目的,而不是使用在對象級別filter()exclude()方法在類級進行選擇。有一個Q對象允許3種不同使用它的方式:

  1. 把它放在一個filter()調用內部,以過濾代替查詢集;
  2. 使用& (AND)| (OR)運營商其它Q對象合併它;
  3. 通過訪問其children屬性,這是在超類中django.utils.tree.Node

在每個自定義QuerySet類中定義的_qq()方法需要預先考慮指定related_name到所有的護理定義動態地改變在Q對象使用的關鍵字的名稱過濾器鍵。

如果我們有一個q = Q(approved=True)對象,那麼我們可以有以下輸出:

  1. self._qq(q) - 相當於self.filter(approved=True);
  2. self._qq(q, 'author') - 相當於self.filter(author__approved=True)

這個解決方案仍然具有嚴重的缺陷:

  1. 一個具有進口和調用自定義QuerySet類明確了相關模型;
  2. 對於每個過濾器的方法之一具有以限定兩個方法filter_q(類方法)和filter(實例方法);

UPDATE:

# in class QuerySetRelated 
    @classmethod 
    def add_filters(cls, names): 
     for name in names: 
      method_q = getattr(cls, '{0:s}_q'.format(name)) 
      def function(self, *args, **kwargs): 
       return self.filter(method_q(*args, **kwargs)) 
      setattr(cls, name, function) 

AuthorQuerySet.add_filters(['for_site']) 
BookQuerySet.add_filters(['for_site']) 

因此,如果有人想出了一個更好的解決方案,請建議:缺點2.可以通過動態地創建過濾器的方法來部分地減小它。這將非常感激。