# Documentação Django 6.0.7

# Como escrever pesquisadores customizadas

Django oferece uma grande variedade de: ref:lookups embutidas <field-lookups> para filtragem (por exemplo, `exact` e `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 exemplo de filtro

Vamos começar com um pequeno pesquisador customizado. Nós iremos escrever um pesquisador customizado `ne` que irá funcionar de maneira contrária ao `exact`. `Author.objects.filter(name__ne='Jack')` irá ser traduzido para o SQL:

    "author"."name" <> 'Jack'

Este SQL é independente, por isso, não precisa se preocupar com os bancos de dados diferentes.

Há dois passos para fazer isso funcionar. Primeiramente nós precisamos implementar o filtro, então precisamos informar ao Django sobre isso:

    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 o pesquisador `NotEqual` vamos precisar chamar `register_lookup` na classe de campo que queremos que o pesquisador esteja disponível. Nesse caso, o pesquisador faz sentido para todas as subclasses de `Field`, então vamos registrá-lo diretamente com `Field`:

    from django.db.models import Field

    Field.register_lookup(NotEqual)

O registro do filtro também pode ser feito usando um padrão de “decorator”:

    from django.db.models 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 query expression API. 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 `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 comandos SQL e os parâmetros a serem interpolados dentro destes comandos SQL, assim como nós precisamos para retornar do nosso método `as_sql`. No exemplo acima, `process_lhs` retorna `('"author"."name"', [])` 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.

FInalmente combinamos as partes em uma expressão SQL com `<>`, e fornecemos todos os parâmetros para a consulta. Então retornamos uma tupla contendo a string SQL gerada e os parâmetros.

## Um exemplo de transformador

O 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.

Nos vamos começar escrevendo um transformador `AbsoluteValue`. Isso vai 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 nós podemos rodar as consultas que tínhamos antes. `Experiment.objects.filter(change__abs=27)` irá gerar o seguinte SQL:

    SELECT ... WHERE ABS("experiments"."change") = 27

Ao usar `Transform` em vez de `Lookup` somos capazes de usar outros pesquisadores em combinação depois. `Experiment.objects.filter(change__abs__lt=27)` irá gerar o seguinte SQL:

    SELECT ... WHERE ABS("experiments"."change") < 27

Note que no caso de não haver nenhuma outra lookup especificada, o Django interpreta `change__abs = 27` como `change__abs__exact = 27`.

Isso também permite que o resultado seja usado nas clausálas `ORDER BY` e `DISTINCT ON`. Por exemplo `Experiment.objects.order_by('change__abs')` gera:

    SELECT ... ORDER BY ABS("experiments"."change") ASC

E em bancos de dados que tem suporte ao distinct em campos (como o PostgreSQL), `Experiment.objects.distinct('change__abs')` gera:

    SELECT ... DISTINCT ON ABS("experiments"."change")

Ao procurar quais lookups são permitidas depois de aplicar o `Transform`, o Django usa o atributo `output_field`. Não precisamos especificar isso aqui, já que não mudou, mas supondo que estivéssemos aplicando `AbsoluteValue` para algum campo que represente um tipo mais complexo (por exemplo um ponto relativo a uma origem ou um número complexo) então talvez querer especificar que o `transform` retorne um tipo `FloatField` para os próximos lookups. Isso pode ser feito adicionando um atributo `output_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 novos filtros como o `abs__lte` se comportem como deveriam para um `FloatField`.

## Escrevendo um filtro `abs__lt` eficiente

Quando usar o filtro `abs` escrito acima, em alguns casos o SQL gerado não irá usar os índices de maneira eficiente. Em particular, quando usamos `change__abs__lt=27`, isso é equivalente a `change__gt=-27` e `change__lt=27`. (Para o caso `lte` podemos usar o `BETWEEN` do SQL).

Então optaríamos por `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 ao ``self.lhs.lhs` é tão seguro quanto o `AbsoluteValueLessThan` pode ser acessado diretamente do filtro `AbsoluteValue`, que é `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()` ) não podemos fazer as transformações em Python.

Nota

Na verdade, a maioria das filtros com `__abs` poderiam ser implementados 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 na `abs(change)` o que permitiria que essas consultas fossem muito eficientes.

## Um exemplo bilateral de “transforme”

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 insensivelmente para alguma função SQL.

Vamos examinar transformações insensíveis a maiúsculas-minúsculas. Essa transformação não é muito útil na prática já que o Django já contém vários pesquisadores como esse built-in, mas é uma boa demonstração de transformações bilaterais agnóstica ao banco de dados.

Definimos um “transformer” de `UpperCase` que usa a função `UPPER()` do SQL para transformar os valores antes da comparação. Definimos :attr: bilateral = True <django.db.models.Transform.bilateral> para indicar que essa transformação deve ser aplicada a ambos `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, a queryset `Author.objects.filter(name__upper="doe")` irá gerar uma consulta insensível a maiúsculas-minúsculas como essa:

    SELECT ... WHERE UPPER("author"."name") = UPPER('doe')

## Escrever implementações alternativas para filtros existentes

À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 do operador NotEqual para o MySQL. 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 apoiadas pelo Django).

Nós podemos mudar o comportamento em um backend específico criado pela subclasse de `NotEqual` com um método `as_mysql`:

    class MySQLNotEqual(NotEqual):
        def as_mysql(self, compiler, connection, **extra_context):
            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, o Django primeiro procura pelos métodos `as_%s % connection.vendor`, e depois para `as_sql`. Os nomes de fornecedores para os back-ends suportados são `sqlite`, `postgresql`, `oracle` e `mysql`.

## Como o Django determina quais filtros e “transforms” são usados

Em alguns casos, talvez queira mudar dinamicamente qual `Transform` ou filtro é devolvido com base no nome passado, em vez de corrigí-lo. Como exemplo, você pode ter um campo o qual armazena coordenadas ou uma dimensão arbitrária, e deseja permitir 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.removeprefix("x"))
                except ValueError:
                    pass
                else:
                    return get_coordinate_lookup(dimension)
            return super().get_lookup(lookup_name)

Você deve então definir `` get_coordinate_lookup`` adequadamente para retornar uma subclasse de `Lookup` a qual 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.

Ao filtrar, se houver apenas um nome de filtro 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 filtro `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")`.