No Django, como filtrar um QuerySet com pesquisas de campo dinâmico?

160

Dada uma classe:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=20)

É possível e, em caso afirmativo, como ter um QuerySet que filtra com base em argumentos dinâmicos? Por exemplo:

 # Instead of:
 Person.objects.filter(name__startswith='B')
 # ... and:
 Person.objects.filter(name__endswith='B')

 # ... is there some way, given:
 filter_by = '{0}__{1}'.format('name', 'startswith')
 filter_value = 'B'

 # ... that you can run the equivalent of this?
 Person.objects.filter(filter_by=filter_value)
 # ... which will throw an exception, since `filter_by` is not
 # an attribute of `Person`.
Brian M. Hunt
fonte

Respostas:

310

A expansão de argumentos do Python pode ser usada para resolver este problema:

kwargs = {
    '{0}__{1}'.format('name', 'startswith'): 'A',
    '{0}__{1}'.format('name', 'endswith'): 'Z'
}

Person.objects.filter(**kwargs)

Este é um idioma Python muito comum e útil.

Daniel Naab
fonte
6
Apenas uma dica rápida: verifique se as strings nos kwargs são do tipo str e não unicode, caso contrário filter () resmunga.
Steve Jalim 04/04
1
@santiagobasulto Também se refere a um parâmetro de embalagem / descompactação, e suas variações.
precisa
7
legal, legal e legal !
Oscar Mederos
5
@DanielNaab, mas isso funcionará apenas em kwargs trabalhando na filtragem de condição AND, qualquer alternativa para a condição OR.
Prateek099
3
@prateek você sempre pode usar objetos Q: stackoverflow.com/questions/13076822/…
deecodameeko
6

Um exemplo simplificado:

Em um aplicativo de pesquisa do Django, eu queria uma lista de seleção HTML mostrando usuários registrados. Mas como temos 5.000 usuários registrados, eu precisava de uma maneira de filtrar essa lista com base em critérios de consulta (como apenas pessoas que concluíram um determinado workshop). Para que o elemento da pesquisa seja reutilizável, eu precisava que a pessoa que cria a pergunta da pesquisa pudesse anexar esses critérios a essa pergunta (não deseja codificar a consulta no aplicativo).

A solução que eu encontrei não é 100% amigável (requer a ajuda de um técnico para criar a consulta), mas resolve o problema. Ao criar a pergunta, o editor pode inserir um dicionário em um campo personalizado, por exemplo:

{'is_staff':True,'last_name__startswith':'A',}

Essa sequência é armazenada no banco de dados. No código de exibição, ele retorna como self.question.custom_query. O valor disso é uma string que se parece com um dicionário. Nós o transformamos novamente em um dicionário real com eval () e, em seguida, o colocamos no conjunto de consultas com ** kwargs:

kwargs = eval(self.question.custom_query)
user_list = User.objects.filter(**kwargs).order_by("last_name")   
shacker
fonte
Estou imaginando o que seria necessário para criar um ModelField / FormField / WidgetField personalizado que implementasse o comportamento para permitir que o usuário, no lado da GUI, basicamente "construísse" uma consulta, nunca vendo o texto real, mas usando uma interface para faça isso. Soa como um projeto puro ...
T. Pedra
1
T. Stone - Eu imagino que seria fácil criar essa ferramenta de uma maneira simplista se os modelos que precisam ser consultados fossem simples, mas muito difíceis de serem feitos de uma maneira completa que expusesse todas as opções possíveis, especialmente se os modelos fossem complexo.
shacker
5
-1 chamar eval()a importação de usuários é uma má ideia, mesmo se você confiar totalmente em seus usuários. Um campo JSON seria uma ideia melhor aqui.
John Carter
5

O Django.db.models.Q é exatamente o que você deseja do Django.

Brent81
fonte
7
Você (ou alguém) poderia fornecer um exemplo de como usar objetos Q no uso de nomes de campos dinâmicos?
precisa saber é o seguinte
3
É o mesmo que na resposta de Daniel Naab. A única diferença é que você passa os argumentos para o construtor de objetos Q. Q(**filters), se você desejar criar objetos Q dinamicamente, poderá colocá-los em uma lista e usá-los .filter(*q_objects)ou usar os operadores bit a bit para combinar os objetos Q.
Will S
5
Essa resposta deve realmente incluir um exemplo do uso de Q para resolver o problema do OP.
precisa saber é o seguinte
-2

Um formulário de pesquisa realmente complexo geralmente indica que um modelo mais simples está tentando descobrir o caminho.

Como exatamente você espera obter os valores para o nome e a operação da coluna? Onde você obtém os valores de 'name'um 'startswith'?

 filter_by = '%s__%s' % ('name', 'startswith')
  1. Um formulário de "pesquisa"? Você vai - o que? - escolha o nome de uma lista de nomes? Escolha a operação em uma lista de operações? Embora abertas, a maioria das pessoas acha isso confuso e difícil de usar.

    Quantas colunas possuem esses filtros? 6? 12? 18?

    • Um pouco? Uma lista de opções complexa não faz sentido. Alguns campos e algumas declarações if fazem sentido.
    • Um grande número? Seu modelo não parece certo. Parece que o "campo" é realmente a chave para uma linha em outra tabela, não uma coluna.
  2. Botões de filtro específicos. Espere ... É assim que o administrador do Django funciona. Filtros específicos são transformados em botões. E a mesma análise acima se aplica. Alguns filtros fazem sentido. Um grande número de filtros geralmente significa um tipo de primeira violação de forma normal.

Muitos campos semelhantes geralmente significam que deveria haver mais linhas e menos campos.

S.Lott
fonte
9
Com respeito, é presunçoso fazer recomendações sem saber nada sobre o design. Para "simplesmente implementar" esse aplicativo geraria funções astronômicas (> 200 aplicativos ^ 21 foos) para atender aos requisitos. Você está lendo propósito e intenção no exemplo; você não deveria. :)
Brian M. caça
2
Conheço muitas pessoas que acham que seu problema seria trivial para resolver se as coisas fossem (a) mais genéricas eb) funcionassem da maneira que imaginavam. Dessa forma, encontra-se uma frustração sem fim, porque as coisas não são do jeito que imaginavam. Eu já vi muitas falhas decorrentes da "correção da estrutura".
S.Lott 22/11/2008
2
As coisas funcionam como esperado e desejado pela resposta de Daniel. Minha pergunta era sobre sintaxe, não design. Se eu tivesse tempo de escrever o design, teria feito isso. Tenho certeza de que sua opinião seria útil, mas não é apenas uma opção prática.
Brian M. Hunt
8
S.Lott, sua resposta nem sequer remotamente responde a esta pergunta. Se você não souber uma resposta, deixe a pergunta em branco. Não responda com conselhos de design não solicitados quando não tiver absolutamente nenhum conhecimento do design!
slypete
2
@lylypete: Se uma alteração no design remover o problema, o problema será resolvido. Continuar no caminho com base em um design ruim é mais caro e complexo do que o necessário. Resolver problemas de causa raiz é melhor do que resolver outros problemas decorrentes de más decisões de design. Lamento que você não goste da análise de causa raiz. Mas quando algo é realmente difícil, geralmente significa que você está tentando a coisa errada para começar.
S.Lott