# documentação Django 4.0.8.dev20220803071047

# How to write custom lookups

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

Let’s start with a small custom lookup. We will write a custom lookup `ne` which works opposite to `exact`. `Author.objects.filter(name__ne='Jack')` will translate to the 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

To register the `NotEqual` lookup we will need to call `register_lookup` on the field class we want the lookup to be available for. In this case, the lookup makes sense on all `Field` subclasses, so we register it with `Field` directly:

    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 `` __``.

Quando então é necessário definir o método `as_sql`. Este recebe um objeto `SQL Compiler`, chamado `compiler`, e a conexão de banco de dados ativa. Os objetos `SQLCompiler` não são documentados, mas a única coisa que precisamos saber sobre eles é que eles tem um método `compile()` o qual retorna uma tupla contendo uma string SQL, e os parâmetros a serem interpolados naquela string. Na maioria dos casos você não precisa usá-lo diretamente e pode ser passá-lo para o `process_lhs()` e `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)

We can now run the queries we had before. `Experiment.objects.filter(change__abs=27)` will generate the following SQL:

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

By using `Transform` instead of `Lookup` it means we are able to chain further lookups afterward. So `Experiment.objects.filter(change__abs__lt=27)` will generate the following 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`.

This also allows the result to be used in `ORDER BY` and `DISTINCT ON` clauses. For example `Experiment.objects.order_by('change__abs')` generates:

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

And on databases that support distinct on fields (such as PostgreSQL), `Experiment.objects.distinct('change__abs')` generates:

    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.

Let’s examine case-insensitive transformations here. This transformation isn’t very useful in practice as Django already comes with a bunch of built-in case-insensitive lookups, but it will be a nice demonstration of bilateral transformations in a database-agnostic way.

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)

Now, the queryset `Author.objects.filter(name__upper="doe")` will generate a case insensitive query like this:

    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[1:])
                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")`.