Pesquisas personalizadasLink para este cabeçalho
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.
Um simples exemple de pesquisaLink para este cabeçalho
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):
# ...
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.
Um exemplo simples transformadorLink para este cabeçalho
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)).
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.
Escrevendo uma lookup abs__lt eficienteLink para este cabeçalho
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.
Um exemplo bilateral transformadorLink para este cabeçalho
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')
Escrever implementações alternativas para pesquisas existentesLink para este cabeçalho
À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.
Como o Django determina as lookups e transformações que são usadosLink para este cabeçalho
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á chamarmyfield.get_transform('mytransform'), e em seguidamytransform.get_lookup('mylookup')..filter(myfield__mytransform)irá chamar primeiromyfield.get_lookup( 'mytransform'), o qual irá falhar, por isso vai retornar e chamarmyfield.get_transform('mytransform')e em seguida,mytransform.get_lookup( "exact").