Como posso encontrar a interseção de dois querysets do Django?

Eu tenho um modelo do Django com dois methods gerenciadores personalizados. Cada um retorna um subconjunto diferente dos objects do modelo, com base em uma propriedade diferente do object.

class FeatureManager(models.Manager): def without_test_cases(self): return self.get_query_set().annotate(num_test_cases=models.Count('testcase_set')).filter(num_test_cases=0) def standardised(self): return self.get_query_set().annotate(standardised=Count('documentation_set__standard')).filter(standardised__gt=0) 

(Ambos testcase_set e documentation_set referem-se a ManyToManyField em outros modelos.)

Existe alguma maneira de obter um queryset, ou apenas uma lista de objects, que é a interseção dos querysets retornados por cada método de gerenciador?

Refatorar

 class FeatureManager(models.Manager): @staticmethod def _test_cases_eq_0( qs ): return qs.annotate( num_test_cases=models.Count('testcase_set') ).filter(num_test_cases=0) @staticmethod def _standardized_gt_0( qs ): return qs.annotate( standardised=Count('documentation_set__standard') ).filter(standardised__gt=0) def without_test_cases(self): return self._test_cases_eq_0( self.get_query_set() ) def standardised(self): return self._standardized_gt_0( self.get_query_set() ) def intersection( self ): return self._test_cases_eq_0( self._standardized_gt_0( self.get_query_set() ) ) 

Na maioria dos casos, você pode simplesmente escrever (explorando a parte “Set” do QuerySet):

 intersection = Model.objects.filter(...) & Model.objects.filter(...) 

Isso não está muito bem documentado, mas deve se comportar quase exatamente como usar condições AND em condições de ambas as consultas. Código relevante: https://github.com/django/django/blob/1.8c1/django/db/models/query.py#L203

Você pode apenas fazer algo assim:

 intersection = queryset1 & queryset2 

Para fazer uma união basta replace & by |

De acordo com o Django 1.11, agora está disponível a interseção da function ()

 >>> qs1.intersection(qs2, qs3) 

Eu acredito qs1.filter (pk__in = qs2) deve funcionar (geralmente). Parece funcionar para um caso semelhante para mim, faz sentido que funcione, e a consulta gerada parece sensata. (Se um dos seus querysets usa valores () para não selecionar a coluna da chave primária ou algo estranho, eu posso acreditar que iria quebrar, embora …)

Se você quiser fazer isso em python, não no database:

 intersection = set(queryset1) & set(queryset2) 

O problema é que, se você usar annotations diferentes nas consultas devido às annotations adicionadas, os objects poderão parecer diferentes …

Uma maneira pode ser usar o módulo de conjuntos python e fazer apenas uma interseção:

faça alguns conjuntos de consultas que se sobreponham em id = 5:

 In [42]: first = Location.objects.filter(id__lt=6) In [43]: last = Location.objects.filter(id__gt=4) 

“conjuntos de importação” primeiro (recebe um aviso de depreciação … ummm … oh bem). Agora construa e cruze-os – obtemos um elemento no conjunto:

 In [44]: sets.Set(first).intersection(sets.Set(last)) Out[44]: Set([]) 

Agora pegue o id dos elementos de interseção para verificar se realmente é 5:

 In [48]: [s.id for s in sets.Set(first).intersection(sets.Set(last))] Out[48]: [5] 

Isso obviamente atinge o database duas vezes e retorna todos os elementos do conjunto de consultas – a melhor maneira seria encadear os filtros em seus gerentes e isso deve ser possível em um único hit do database e no nível do SQL. Não consigo ver um método QuerySet.and / ou (QuerySet).

Se você realmente está usando apenas uma anotação para filtrar com base em se a contagem é zero ou não, isso deve funcionar em vez disso:

 class FeatureManager(models.Manager): def without_test_cases(self): return self.get_query_set().filter(testcase__pk__isnull=True) def standardised(self): return self.get_query_set().filter(documentation_set__standard__isnull=False) 

Como você não está mais preocupado com annotations, as duas consultas devem se cruzar muito bem.