Django oferece uma grande variedade de: ref: pesquisas embutidos <field-lookups> `Para filtragem (por exemplo, e exact` icontains``). Esta documentação explica como escrever lookups personalizadas e como alterar o funcionamento de lookups existentes. Para as referências da API de lookups, consulte o: doc: / ref / models / lookups.
Vamos iniciar com uma simples pesquisa personalizada. Escreveremos uma simples pesquisa ne
que funciona em frente ao exato
. ``Author.objects.filter(name__ne=’Jack’) traduzindo para SQL.
"author"."name" <> 'Jack'
Este SQL é independente, por isso, não precisa se preocupar com os bancos de dados diferentes.
Há duas etapas para fazer este trabalho. Em primeiro lugar precisamos implementar a “lookup”, então precisamos dizer ao Django sobre ele. A implementação é bastante simples
from django.db.models import Lookup
class NotEqual(Lookup):
lookup_name = 'ne'
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return '%s <> %s' % (lhs, rhs), params
Para registrar a lookup NotEqual
precisamos somente chamar register_lookup
no campo da classe que queremos que a lookup esteja disponível. Neste caso, a lookup faz sentido em todos os Field
da sub-classe, então registramos isso com `` Field`` diretamente
from django.db.models.fields import Field
Field.register_lookup(NotEqual)
O registro da “lookup” também pode ser feito usando um pattern decorador
from django.db.models.fields import Field
@Field.register_lookup
class NotEqualLookup(Lookup):
# ...
foi adicionado a capacidade de usar o pattern decorator.
Agora podemos usar `` foo__ne`` para qualquer campo `` foo``. Tenha certeza de registrar antes de tentar criar qualquer “querysets” que o use. É possível colocar a implementação no `` models.py``, ou registar a lookup no método ready()
do `` AppConfig``.
Olhando mais perto da implementação, o primeiro atributo obrigatório é `` lookup_name``. Isso permite que o ORM entenda como interpretar `` name__ne`` e use `` NotEqual`` para gerar o SQL. Por convenção, estes nomes são sempre em strings caixa baixa contendo apenas letras, mas a única exigência é que não contenha a string `` __``.
We then need to define the as_sql
method. This takes a SQLCompiler
object, called compiler
, and the active database connection.
SQLCompiler
objects are not documented, but the only thing we need to know
about them is that they have a compile()
method which returns a tuple
containing an SQL string, and the parameters to be interpolated into that
string. In most cases, you don’t need to use it directly and can pass it on to
process_lhs()
and process_rhs()
.
A Lookup
trabalha em dois valores, e lhs
e rhs
, entendendo como left-hand side e right-hand side. O lado esquerdo é geralmente uma referência de campo, mas pode ser qualquer coisa que implemente :ref: query expression API <query-expression>. O lado direito é o valor dado pelo usuário. No exemplo Author.objects.filter (name__ne = 'Jack') ``, o lado esquerdo é uma referência para o campo ``name
campo do modelo Author
, e `` ‘Jack ‘ `` é o lado direito.
Nós chamamos e process_lhs
e process_rhs
para convertê-los para os valores que precisamos para SQL usando o objeto compiler
descrito antes. Esses métodos retornam tuplas contendo alguns SQL e os parâmetros a serem interpolados dentro deste SQL, assim como nós precisamos para retornar do nosso método as_sql
. No exemplo acima, process_lhs
retorna ( "autor". "Nome" ', []) `` e ``process_rhs
retorna ( "% s"', [ 'Jack' ])
. Neste exemplo não havia parâmetros para o lado esquerdo, mas isso depende do objeto que temos, portanto ainda precisamos incluí-los nos parâmetros que retornamos.
Finally we combine the parts into an SQL expression with <>
, and supply all
the parameters for the query. We then return a tuple containing the generated
SQL string and the parameters.
A lookup personalizado acima é boa, mas em alguns casos você queira ser capaz de lookups encadeadas. Por exemplo, vamos supor que estamos construindo uma aplicação onde queremos fazer uso do operador abs()
. Temos um modelo Experiment
que registra um valor inicial, o valor final e a mudança (início - fim). Gostaríamos de encontrar todos os experimentos em que a alteração foi igual a uma certa quantidade ( Experiment.objects.filter (change__abs = 27)
), ou onde não exceda um certo montante ( Experiment.objects.filter (change__abs__lt = 27)
).
Nota
Este exemplo é um pouco artificial, mas demonstra muito bem a gama de funcionalidades que é possível de uma maneira independente em um backend de banco de dados., e sem duplicar funcionalidades já existentes no Django.
Vamos começar por escrever um tradutor AbsoluteValue
. Isto irá usar a função SQL ABS ()
para transformar o valor antes da comparação:
from django.db.models import Transform
class AbsoluteValue(Transform):
lookup_name = 'abs'
function = 'ABS'
Em seguida, vamos registrá-lo para `` IntegerField``
from django.db.models import IntegerField
IntegerField.register_lookup(AbsoluteValue)
agora podemos executar as queries que tínhamos antes. ``Experiment.objects.filter (change__abs = 27) `` irá gerar o seguinte SQL
SELECT ... WHERE ABS("experiments"."change") = 27
Ao usar `` Transform`` ao invés de Lookup
isso significa que somos capazes de encadear lookups umas as outras. Então Experiment.objects.filter (change__abs__lt = 27)
irá gerar o seguinte SQL
SELECT ... WHERE ABS("experiments"."change") < 27
Note-se que no caso não há nenhuma outra lookup especificada, o Django interpreta change__abs = 27
como change__abs__exact = 27
.
Quando olhamos par quais lookups são permitidas depois de ter aplicado o Trnasform
, Django usa o atibuto òutput_field`. Não é necessário especificar isso aqui, já que não muda, más supondo que fosse aplicado AbsoluteValue``para algum campo que represente um tipo mais complexo (por exemplo um ponto reativo a uma origem ou um número complexo) então talvez quereríamos especificar que retornasse um tipo ``FloatField
para próximas lookups. Isso pode ser feito adicionando um atributo `òutput_field``ao transform:
from django.db.models import FloatField, Transform
class AbsoluteValue(Transform):
lookup_name = 'abs'
function = 'ABS'
@property
def output_field(self):
return FloatField()
Isso garante que novas pesquisas como abs__lte
se comportem como fariam para um FloatField
.
abs__lt
eficiente¶Quando usado a lookup abs
escrita acima, o SQL gerado não irá usar índices de maneira eficiente em alguns casos. Em particular, quando usado change__abs__lt=27
, é equivalente a change__gt=-27
e change__lt=27
. (Para o caso lte
podemos usar o SQL BETWEEN
).
Então nós gostaríamos Experiment.objects.filter (change__abs__lt=27)
para gerar o seguinte SQL:
SELECT .. WHERE "experiments"."change" < 27 AND "experiments"."change" > -27
A implementação é:
from django.db.models import Lookup
class AbsoluteValueLessThan(Lookup):
lookup_name = 'lt'
def as_sql(self, compiler, connection):
lhs, lhs_params = compiler.compile(self.lhs.lhs)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params + lhs_params + rhs_params
return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params
AbsoluteValue.register_lookup(AbsoluteValueLessThan)
Há algumas coisas notáveis acontecendo. Em primeiro lugar, AbsoluteValueLessThan
não está chamando process_lhs ()
. Em vez disso ele ignora a transformação do lhs
feito por AbsoluteValue
e usa o lhs
original. Ou seja, queremos pegar `` “experiments”.”change” `` não ABS( "experiments". "change") ``. Referindo-se diretamente a ``self.lhs.lhs
é tão seguro quanto AbsoluteValueLessThan
pode ser acessado diretamente da lookup AbsoluteValue
, que é a lhs
é sempre uma instância de AbsoluteValue
.
Note também que como ambos os lados são usados várias vezes na consulta os parâmetros precisam conter lhs_params
e rhs_params
várias vezes.
A consulta final faz a inversão ( 27
para `` -27``) diretamente no banco de dados. A razão para isso é que, se o `` self.rhs`` é algo mais do que um valor inteiro simples (por exemplo, uma referência F()
) que não podemos fazer as transformações em Python.
Nota
Na verdade, a maioria das lookups com __abs
poderias ser implementadas como consultas de intervalos como esta, e na maioria dos backends de banco de dados, é provável que seja mais sensato fazê-lo já que podería fazer uso dos índices. No entanto, com PostgreSQL você pode querer adicionar um índice em na abs(change)
o que permitiria que essas consultas serem muito eficientes.
O exemplo AbsoluteValue
discutido anteriormente é uma transformação que se aplica para o lado esquerdo da lookup. Pode haver alguns casos onde você quer que a transformação seja aplicada tanto para o lado esquerdo quanto para o lado direito. Por exemplo, se você quiser filtrar um queryset com base em uma igualdade de lado esquerdo e direito inperseptível para alguma função SQL.
Vamos examinar o exemplo simples de transformação case-insensitive aqui. Essa transformação não é muito útil na prática, como Django já vem com um monte de pesquisas de maiúsculas e minúsculas embutidos, mas será uma boa demonstração de transformações bilaterais de uma forma agnóstica ao banco de dados.
Definimos um transformador UpperCase
que usa a função SQL UPPER()
para transformar os valores antes de comparação. Definimos :attr: bilateral = True <django.db.models.Transform.bilateral> para indicar que essa transformação deverá aplicar-se a lhs
e rhs
:
from django.db.models import Transform
class UpperCase(Transform):
lookup_name = 'upper'
function = 'UPPER'
bilateral = True
Em seguida, registre
from django.db.models import CharField, TextField
CharField.register_lookup(UpperCase)
TextField.register_lookup(UpperCase)
Agora, o queryset Author.objects.filter(name__upper = "doe")
irá gerar uma query case-insensitive como esta:
SELECT ... WHERE UPPER("author"."name") = UPPER('doe')
Às vezes, diferentes fornecedores de banco de dados requerem diferentes SQL para a mesma operação. Para este exemplo, vamos reescrever uma implementação personalizada para o MySQL para o operador NotEqual. Em vez de <>
usaremos o operador !=
. (Note que, na realidade, quase todos os bancos de dados suportam ambos, incluindo todas as bases de dados oficiais apoiados por Django).
Nós podemos mudar o comportamento em um backend específico, criando uma subclasse de NotEqual
com um método as_mysql
class MySQLNotEqual(NotEqual):
def as_mysql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return '%s != %s' % (lhs, rhs), params
Field.register_lookup(MySQLNotEqual)
Podemos então registrá-lo com Field
. Ele toma o lugar da classe NotEqual
original já que tem o mesmo lookup_name
.
Ao compilar uma consulta, Django primeiro procura pelos métodos as_%s % connection.vendor
, e depois para as_sql
. Os nomes de fornecedores para os back-ends suportador são sqlite
, postgresql
, oracle
e mysql
.
Em alguns casos, talvez queira mudar dinamicamente qual Transform
ou Lookup
é devolvido com base no nome passado, em vez de corrigi-lo. Como exemplo, é possível ter um campo que armazena coordenadas ou uma dimensão arbitrária, e deseja permitir que uma sintaxe como .filter (coords__x7=4)
para retornar os objetos onde a 7ª coordenada tem valor 4. Para fazer isso, substitua get_lookup
com algo como:
class CoordinatesField(Field):
def get_lookup(self, lookup_name):
if lookup_name.startswith('x'):
try:
dimension = int(lookup_name[1:])
except ValueError:
pass
else:
return get_coordinate_lookup(dimension)
return super(CoordinatesField, self).get_lookup(lookup_name)
Defina então o `` get_coordinate_lookup`` adequadamente para retornar uma subclasse Lookup
que lida com o valor relevante da dimension
.
Existe um método de nome semelhante chamado get_transform() ``. `` Get_lookup () `` deve sempre retornar uma subclasse de ``Lookup
, e get_transform()
uma subclasse de Transform
. É importante lembrar que objetos do tipo Transform
podem ainda ser filtrados, e objetos do tipo Lookup
não podem.
Ao filtrar, se houver apenas um nome lookup remanescente a ser resolvido, olhamos para um Lookup
. Se houver vários nomes, ele irá procurar por um Transform
. Em uma situação onde há apenas um nome e um Lookup
não é encontrado, procuramos por um Transform
e então o lookup exact
naquele Transform
. Todas as sequências de chamadas sempre terminam com um Lookup
. Para esclarecer:
.filter(myfield__mylookup)
irá chamar myfield.get_lookup(‘mylookup’)`.
.filter(myfield__mytransform__mylookup)
irá chamar myfield.get_transform('mytransform')
, e em seguida mytransform.get_lookup('mylookup')
.
.filter(myfield__mytransform)
irá chamar primeiro myfield.get_lookup( 'mytransform')
, o qual irá falhar, por isso vai retornar e chamar myfield.get_transform('mytransform')
e em seguida, mytransform.get_lookup( "exact")
.
ago 01, 2016