# Documentazione Django 5.2.8

# Come scrivere lookup personalizzati

Django offre una ampia varietà di built-in lookups per la selezione (per esempio, `exact` e `icontains`). Questo documento spiega come scrivere criteri di ricerca personalizzati e come modificare il comportamento di quelli esistenti. Per i riferimenti alle API dei criteri di ricerca, vedi Lookup API reference.

## Un esempio di criterio di ricerca

Iniziamo con un piccolo lookup custom. Scriveremo un lookup custom `ne` che lavora in modo opposto ad `exact`. `Author.objects.filter(name__ne='Jack')` si tradurrà nell’SQL:

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

Questo comando SQL è indipendente dal backend, non ci dobbiamo quindi preoccupare di differenti basi di dati.

Sono richiesti due passaggi per farlo funzionare. Primo devi implementare il criterio di ricerca, poi occorre dichiaralo in Django.

    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

Per registrare il lookup `NotEqual` dovremo chiamare `register_lookup` sul campo della classe in cui vogliamo che il lookup sia disponibile. In questo caso, il lookup ha senso su tutte le sottoclassi `Field`, così possiamo registrarlo direttamente con `Field`:

    from django.db.models import Field

    Field.register_lookup(NotEqual)

La registrazione di un criterio di ricerca si puo” fare anche usando un modello decoratore:

    from django.db.models import Field

    @Field.register_lookup
    class NotEqualLookup(Lookup): ...

Adesso possiamo usare `foo__ne` per ogni campo `foo`. Ti devi assicurare che questa registrazione avvenga prima che cerchi di creare un qualunque queryset che la usi. Puoi mettere l’implementazione in un file `models.py` , o registrare il criterio di ricerca nel metodo `ready()` di un `AppConfig`.

Dando un’occhiata più attenta all’implementazione, il primo attributo richiesto é `lookup_name`. Questo permette alla ORM di capire come interpretare `name__ne` e usare `NotEqual` per generare il codice SQL. Per convenzione, questi nomi sono sempre stringhe in minuscolo contenenti solo lettere, ma l’unico requisito mandatorio è che non contengano la stringa `__`.

Poi dobbiamo definire il metodo `as_sql` . Questo usa un oggetto `SQLCompiler` , chiamato `compiler`, e la connessione al database attivo. Gli oggetti `SQLCompiler` non sono documentati, ma la sola cosa che dobbiamo sapere a loro riguardo è che hanno un metodo `compile()` che restituisce una tupla contenente una stringa SQL, e i parametri da interpolare in quella stringa. Nella maggior parte dei casi, non hai necessità di usarli direttamente e puoi passarli a `process_lhs()` e `process_rhs()`.

Un `Lookup` lavora verso due valori, `lhs` e `rhs`, che rappresentano la parte sinistra e la parte destra. La parte sinistra di solito è un riferimento a un campo, ma può essere qualunque cosa implementi una query expression API. La parte destra è il valore dato dall’utente. Nell’esempio `Author.objects.filter(name__ne='Jack')`, la parte sinistra è un riferimento al campo `name` del modello `Author` , e `'Jack'` è la parte destra.

Chiamiamo `process_lhs` e `process_rhs` per convertirli nei valori che ci servono per il codice SQL usando l’oggetto `compiler` descritto in precedenza. Questi metodi ritornano tuple contenenti il codice SQL e i parametri che devono essere interpolati in quel codice SQL, esattamente come si devono essere restituiti dal nostro metodo `as_sql` . Nell’esempio precedente, `process_lhs` restituisce `('"author"."name"', [])` e `process_rhs` restituisce `('"%s"', ['Jack'])`. In questo esempio non c’erano parametri per la parte sinistra, ma può dipendere dall’oggetto che abbiamo, per questo dobbiamo includerli nei parametri che restituiamo.

Finalmente combiniamo le parti in una espressione SQL con `<>`, e riforniamo? con tutti i parametri per l’interrogazione. Dopodiché ritorniamo una tupla contenente la stringa SQL e i parametri generati.

## Un esempio di trasformatore

La ricerca personalizzata sopra indicata è fantastica, ma in alcuni casi potresti aver bisogno di concatenare le ricerche insieme. Per esempio, supponiamo che stiamo costruendo la nostra applicazione laddove vogliamo fare uso del operatore `abs()`. Abbiamo un modello `Experiment` che registra un valore inizio, un valore fine, e che un valore cambio ( inizio - fine ). Ci piacerebe cercare tutti gli esperimenti dove valore cambio è uguale ad alcuni valori (`Experiment.objects.filter(change__abs=27)`),o qualsiasi vaolore che non supera un certo quantitativo (`Experiment.objects.filter(change__abs__lt=27)`).

Nota

Questo esempio è in qualche modo ?forzato?, ma ci mostra il range di funzionalita possibili nel database backend in maniera indipendete, e senza duplicare funzionalita gia esistenti in Django

Cominceremo con la scrittura del convertitore``AbsoluteValue``. Utilizzeremo le funzioni SQL `ABS()` per convertire il valore prima del confronto:

    from django.db.models import Transform

    class AbsoluteValue(Transform):
        lookup_name = "abs"
        function = "ABS"

Successivamente registriamolo per il campo `IntegerField`:

    from django.db.models import IntegerField

    IntegerField.register_lookup(AbsoluteValue)

Adesso possiamo lanciare le query che avevamo prima. `Experiment.objects.filter(change__abs=27)` genererà il seguente SQL:

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

Usare `Transform` invece di `Lookup` significa che saremo in grado di concatenare ulteriori lookup in seguito. Così `Experiment.objects.filter(change__abs__lt=27)` genererà il seguente SQL:

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

Nel caso non ci siano altre ricerche specificate, Django interpreta `change__abs=27` come `change__abs__exact=27`.

Questo consente anche al risultato di essere usato in espressioni `ORDER BY` e `DISTINCT ON`. Per esempio `Experiment.objects.order_by('change__abs')` produce:

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

E nei database che supportano distinct sui campi (come PostgreSQL), `Experiment.objects.distinct('change__abs')` produce:

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

Quando cerca quali lookup sono disponibili dopo avere applicato la `Transform`, Django usa l’attributo `output_field`. Se l’attributo non è cambiato, non abbiamo necessità di specificarlo, ma supponiamo che stavamo applicando la `AbsoluteValue` ad un campo che rappresenta un tipo più complesso (per esempio un punto relativo ad una origine, o un numero complesso), allora per ulteriori lookup avremmo voluto specificare il fatto che la trasformazione ritorna un tipo `FloatField`. Ciò può essere fatto aggiungendo un attributo `output_field` alla trasformazione:

    from django.db.models import FloatField, Transform

    class AbsoluteValue(Transform):
        lookup_name = "abs"
        function = "ABS"

        @property
        def output_field(self):
            return FloatField()

Questo assicura che future ricerce come `abs__lte` si comportino come per `FloatField`.

## Scrivere una `abs__lt` ricerca efficiente

Usando il lookup `abs` di cui sopra, l’SQL che viene prodotto in qualche caso non utilizzerà gli indici prodotti efficientemente. In particolare, l’utilizzo di `change__abs__lt=27` è equivalente all’utilizzo di `change__gt=-27` AND `change__lt=27`. (Per il caso `lte` avremmo potuto usare il `BETWEEN` di SQL).

Ci piacerebbe che `Experiment.objects.filter(change__abs__lt=27)` generi la seguente SQL:

    SELECT .. WHERE "experiments"."change" < 27 AND "experiments"."change" > -27

L’implemetazione è:

    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)

Ci sono un paio di cose che accadono e che vale la pena di notare. La prima è che `AbsoluteValueLessThan` non sta chiamando `process_lhs()`. Piuttosto, salta la trasformazione di `lhs` fatta da `AbsoluteValue` e utilizza l” `lhs` originale. Cioè, vogliamo ottenere `"experiments"."change"` e non `ABS("experiments"."change")`. Fare riferimento direttamente a `self.lhs.lhs` è sicuro perchè `AbsoluteValueLessThan` può essere acceduto solo dal lookup `AbsoluteValue`, quindi `lhs` è sempre una istanza di `AbsoluteValue`.

Nota anche che poichè entrambe le parti sono utilizzate più volte nella query, i parametri devono contenere `lhs_params` e `rhs_params` più volte.

La query finale fà l’inversione (`27` a `-27`) direttamente nel database. La ragione per cui fà così è che se `self.rhs` non è un semplice valore intero (per esempio una ``F()` reference) non possiamo fare la trasformazione in Python.

Nota

In effetti, molti lookup con `__abs` potrebbero essere implementati con delle query range come questa e su molti database di backend sarebbe anche più ragionevole, perchè potrebbero usare gli indici. In ogni caso con PostgreSQL potresti voler aggiungere un indice su `abs(change)` che permetterebbe a queste query di essere molto efficienti.

## Un esempio di trasformatore bilaterale

L’esempio `AbsoluteValue` che abbiamo discusso in precedenza è una trasformazione che si applica al lato sinistro della ricerca. Potrebbero esserci alcuni casi in cui si desidera applicare la trasformazione sia al lato sinistro che al lato destro. Ad esempio, se si desidera filtrare un set di query in base all’uguaglianza del lato sinistro e destro in modo insensibile ad alcune funzioni SQL.

Esaminiamo qui le trasformazioni senza distinzione tra maiuscole e minuscole. Questa trasformazione non è molto utile in pratica poiché Django viene già fornito con una serie di ricerche integrate senza distinzione tra maiuscole e minuscole, ma sarà una bella dimostrazione di trasformazioni bilaterali in modo indipendente dal database.

Definiamo un trasformatore `Maiuscolo` che utilizza la funzione SQL `UPPER()` per trasformare i valori prima del confronto. Definiamo `bilateral = True` per indicare che questa trasformazione dovrebbe applicarsi sia a `lhs` che a `rhs`:

    from django.db.models import Transform

    class UpperCase(Transform):
        lookup_name = "upper"
        function = "UPPER"
        bilateral = True

Successivamente registriamolo:

    from django.db.models import CharField, TextField

    CharField.register_lookup(UpperCase)
    TextField.register_lookup(UpperCase)

Ora, il queryset `Author.objects.filter(name__upper="doe")` genererà una query senza distinzione tra maiuscole e minuscole come questa:

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

## Scrivendo implementazioni alternative per le ricerche esistenti

A volte database vendor differenti richiedono SQL diverso per le stesse operazioni. Per questo esempio, riscriveremo una implementazione custom dell’operatore NotEqual per MySQL. Invece di `<>` useremo l’operatore `!=`. (Nota che in realtà praticamente tutti i database li supportano entrambi, inclusi quelli supportati da Django).

Puoi cambiare il comportamento di uno specifico backend creando una sottoclasse di «NotEqual» con un metodo «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)

Poi possiamo registrarlo con `Field`. Prende il posto della classe `NotEqual` originale dal momento che ha lo stesso `lookup_name`.

Quando compila una query, Django prima cerca i metodi `as_%s % connection.vendor` e poi ricade su `as_sql`. I nomi dei vendor per i backends built-in sono `sqlite`, `postgresql`, `oracle` e `mysql`.

## Come Django determina le ricerche e trasforma quelle in uso

In alcuni casi potresti voler cambiare dinamicamente quale `Transform` o `Lookup` viene restituito a seconda del nome che gli passi, piuttosto che metterlo a posto. Per esempio, potresti avere un campo che contiene coordinate di una dimensione arbitraria e vorresti poter consentire una sintassi di tipo `.filter(coords__x7=4)` per restituire gli oggetti in cui la settima coordinata vale 4. Per fare questo, faresti override di `get_lookup` con qualcosa come:

    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)

Poi definiresti in modo appropriato `get_coordinate_lookup` per restituire una sottoclasse di `Lookup` che gestisce la dimensione rilevante di `dimension`.

Esiste un metodo che è chiamato `get_transform()`, in modo simile. `get_lookup()` dovrebbe sempre restituire una sottoclasse `Lookup` e `get_transform()` una sottoclasse `Transform`. E” importante ricordare che gli oggetti `Transform` possono essere ulteriormente filtrati, cosa che non possibile fare per gli oggetti `Lookup`.

In fase di filtering, se rimane un solo lookup name da risolvere, cercheremo un `Lookup`. Se ci sono più nomi, si cercherà per un `Transform`. Nella situazione in cui ci sia solo un nome e non si trovi un `Lookup`, cercheremo un `Transform` e poi il lookup `exact` su quel `Transform`. Tutte le sequenze di chiamate terminano sempre con un `Lookup`. Per fare chiarezza:

- `.filter(myfield__mylookup)` chiamera `myfield.get_lookup('mylookup')`.

- `.filter(myfield__mytransform__mylookup)` chimaerà `myfield.get_transform('mytransform')`, e poi `mytransform.get_lookup('mylookup')`.

- `.filter(myfield__mytransform)` chiamerà prima `myfield.get_lookup('mytransform')`, che fallirà, così farà fallback su `myfield.get_transform('mytransform')` e poi `mytransform.get_lookup('exact')`.