# Django 6.0.5 documentation

# Hur man skriver anpassade uppslagsord

Django erbjuder ett brett utbud av inbyggda uppslagsord för filtrering (till exempel `exakt` och `innehåller`). Denna dokumentation förklarar hur man skriver anpassade uppslagsord och hur man ändrar funktionen hos befintliga uppslagsord. För API-referenser för uppslagsord, se API-referens för uppslagning.

## Ett exempel på uppslagning

Låt oss börja med en liten anpassad uppslagning. Vi kommer att skriva en anpassad lookup `ne` som fungerar motsatt till `exact`. `Author.objects.filter(name__ne='Jack')` kommer att översättas till SQL:

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

Denna SQL är oberoende av backend, så vi behöver inte oroa oss för olika databaser.

Det finns två steg för att få detta att fungera. Först måste vi implementera uppslagningen, sedan måste vi berätta för Django om den:

    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

För att registrera `NotEqual`-uppslaget måste vi anropa `register_lookup` på den fältklass som vi vill att uppslaget ska vara tillgängligt för. I det här fallet är uppslaget meningsfullt för alla underklasser till `Field`, så vi registrerar det med `Field` direkt:

    from django.db.models import Field

    Field.register_lookup(NotEqual)

Registrering av uppslag kan också göras med hjälp av ett dekoratormönster:

    from django.db.models import Field

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

Vi kan nu använda `foo__ne` för alla fält `foo`. Du måste se till att denna registrering sker innan du försöker skapa några frågeuppsättningar med hjälp av den. Du kan placera implementationen i en `models.py`-fil, eller registrera uppslagningen i `ready()`-metoden i en `AppConfig`.

Om man tittar närmare på implementationen är det första obligatoriska attributet `lookup_name`. Detta gör det möjligt för ORM att förstå hur man tolkar `name__ne` och använda `NotEqual` för att generera SQL. Enligt konvention är dessa namn alltid gemena strängar som bara innehåller bokstäver, men det enda hårda kravet är att det inte får innehålla strängen `__`.

Vi måste sedan definiera metoden `as_sql`. Detta tar ett `SQLCompiler`-objekt, kallat `compiler`, och den aktiva databasanslutningen. `SQLCompiler` -objekt är inte dokumenterade, men det enda vi behöver veta om dem är att de har en `compile() `` -metod som returnerar en tuple som innehåller en SQL-sträng och parametrarna som ska interpoleras i den strängen. I de flesta fall behöver du inte använda den direkt utan kan skicka den vidare till ``process_lhs()` och `process_rhs()`.

En `Lookup` arbetar mot två värden, `lhs` och `rhs`, som står för vänster sida och höger sida. Den vänstra sidan är vanligtvis en fältreferens, men det kan vara vad som helst som implementerar query expression API. Den högra sidan är det värde som användaren anger. I exemplet `Author.objects.filter(name__ne='Jack')` är vänstersidan en referens till fältet `name` i modellen `Author` och `'Jack'` är högersidan.

Vi kallar `process_lhs` och `process_rhs` för att konvertera dem till de värden vi behöver för SQL med hjälp av `compiler`-objektet som beskrivs tidigare. Dessa metoder returnerar tuples som innehåller SQL och de parametrar som ska interpoleras till SQL, precis som vi behöver returnera från vår `as_sql`-metod. I exemplet ovan returnerar `process_lhs` `('"author"."name"', [])` och `process_rhs` returnerar `('"%s"', ['Jack'])`. I det här exemplet fanns det inga parametrar för den vänstra sidan, men det beror på vilket objekt vi har, så vi måste fortfarande inkludera dem i de parametrar vi returnerar.

Slutligen kombinerar vi delarna till ett SQL-uttryck med `<>` och anger alla parametrar för frågan. Vi returnerar sedan en tupel som innehåller den genererade SQL-strängen och parametrarna.

## Ett exempel på en transformator

Den anpassade lookupen ovan är bra, men i vissa fall kanske du vill kunna kedja ihop lookups. Låt oss till exempel anta att vi bygger en applikation där vi vill använda operatören `abs()`. Vi har en `Experiment`-modell som registrerar ett startvärde, slutvärde och förändringen (start - slut). Vi skulle vilja hitta alla experiment där förändringen var lika med ett visst belopp (`Experiment.objects.filter(change__abs=27)`), eller där den inte översteg ett visst belopp (`Experiment.objects.filter(change__abs__lt=27)`).

Observera

Detta exempel är något konstruerat, men det visar på ett bra sätt hur många funktioner som är möjliga på ett sätt som är oberoende av databasens backend, och utan att duplicera funktioner som redan finns i Django.

Vi börjar med att skriva en transformator för `AbsoluteValue`. Detta kommer att använda SQL-funktionen `ABS()` för att omvandla värdet före jämförelse:

    from django.db.models import Transform

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

Låt oss sedan registrera den för `IntegerField`:

    from django.db.models import IntegerField

    IntegerField.register_lookup(AbsoluteValue)

Vi kan nu köra de frågor vi hade tidigare. `Experiment.objects.filter(change__abs=27)` kommer att generera följande SQL:

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

Genom att använda `Transform` istället för `Lookup` innebär det att vi kan kedja ytterligare uppslagningar efteråt. Så `Experiment.objects.filter(change__abs__lt=27)` kommer att generera följande SQL:

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

Observera att om det inte finns någon annan uppslagning angiven, tolkar Django `change__abs=27` som `change__abs__exact=27`.

Detta gör också att resultatet kan användas i klausulerna `ORDER BY` och `DISTINCT ON`. Till exempel genererar `Experiment.objects.order_by('change__abs')`:

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

Och på databaser som stöder distinkta på fält (som PostgreSQL) genererar `Experiment.objects.distinct('change__abs')`:

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

När Django letar efter vilka uppslagningar som är tillåtna efter att `Transform` har tillämpats använder Django attributet `output_field`. Vi behöver inte ange detta här eftersom det inte ändras, men om vi antar att vi tillämpar `AbsoluteValue` på ett fält som representerar en mer komplex typ (till exempel en punkt i förhållande till ett ursprung eller ett komplext tal), kanske vi vill ange att transformationen returnerar en `FloatField`-typ för ytterligare uppslagningar. Detta kan göras genom att lägga till ett `output_field`-attribut till transform:

    from django.db.models import FloatField, Transform

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

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

Detta säkerställer att ytterligare uppslagningar som `abs__lte` beter sig som de skulle göra för en `FloatField`.

## Skriva en effektiv `abs__lt`-uppslagning

När man använder den ovan skrivna `abs`-uppslagningen kommer den SQL som produceras inte att använda index effektivt i vissa fall. I synnerhet när vi använder `change__abs__lt=27`, är detta likvärdigt med `change__gt=-27` AND `change__lt=27`. (För `lte`-fallet kan vi använda SQL `BETWEEN`).

Så vi vill att `Experiment.objects.filter(change__abs__lt=27)` ska generera följande SQL:

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

Genomförandet är:

    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)

Det finns ett par anmärkningsvärda saker som händer. För det första anropar inte `AbsoluteValueLessThan` `process_lhs()`. Istället hoppar den över omvandlingen av `lhs` som görs av `AbsoluteValue` och använder den ursprungliga `lhs`. Det vill säga, vi vill få fram `"experiments"."change"` inte `ABS("experiments"."change")`. Att referera direkt till `self.lhs.lhs` är säkert eftersom `AbsoluteValueLessThan` endast kan nås från `AbsoluteValue` lookup, det vill säga `lhs` är alltid en instans av `AbsoluteValue`.

Observera också att eftersom båda sidorna används flera gånger i frågan måste params innehålla `lhs_params` och `rhs_params` flera gånger.

Den sista frågan gör inversionen (`27` till `-27`) direkt i databasen. Anledningen till att vi gör detta är att om `self.rhs` är något annat än ett vanligt heltalsvärde (till exempel en `F()`-referens) kan vi inte göra omvandlingarna i Python.

Observera

Faktum är att de flesta lookups med `__abs` kan implementeras som intervallfrågor som detta, och på de flesta databasbackends är det troligtvis mer förnuftigt att göra det eftersom du kan använda indexen. Men med PostgreSQL kanske du vill lägga till ett index på ``abs (change) `` vilket gör att dessa frågor kan vara mycket effektiva.

## Ett exempel på en bilateral transformator

Exemplet `AbsoluteValue` som vi diskuterade tidigare är en transformation som gäller för den vänstra sidan av uppslagningen. Det kan finnas vissa fall där du vill att transformationen ska tillämpas på både vänster och höger sida. Till exempel om du vill filtrera en frågeuppsättning baserat på likheten mellan vänster och höger sida utan hänsyn till någon SQL-funktion.

Låt oss undersöka skiftlägesokänsliga transformationer här. Denna omvandling är inte särskilt användbar i praktiken eftersom Django redan har ett gäng inbyggda skiftlägesokänsliga uppslagningar, men det kommer att vara en trevlig demonstration av bilaterala omvandlingar på ett databasagnostiskt sätt.

Vi definierar en `UpperCase`-transformator som använder SQL-funktionen `UPPER()` för att omvandla värdena före jämförelse. Vi definierar `bilateral = True` för att ange att denna omvandling ska gälla för både `lhs` och `rhs`:

    from django.db.models import Transform

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

Nu ska vi registrera den:

    from django.db.models import CharField, TextField

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

Nu kommer frågeuppsättningen `Author.objects.filter(name__upper="doe")` att generera en skiftlägeskänslig fråga så här:

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

## Skriva alternativa implementationer för befintliga uppslagningar

Ibland kräver olika databasleverantörer olika SQL för samma operation. I det här exemplet kommer vi att skriva om en anpassad implementering för MySQL för operatorn NotEqual. Istället för `<>` kommer vi att använda operatorn `!=`. (Observera att i verkligheten stöder nästan alla databaser båda, inklusive alla officiella databaser som stöds av Django).

Vi kan ändra beteendet på en specifik backend genom att skapa en subklass av `NotEqual` med en `as_mysql`-metod:

    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)

Vi kan sedan registrera den med `Field`. Den ersätter den ursprungliga klassen `NotEqual` eftersom den har samma `lookup_name`.

När en fråga sammanställs letar Django först efter `as_%s % connection.vendor`-metoder och faller sedan tillbaka till `as_sql`. Leverantörsnamnen för de inbyggda backends är `qlite`, `postgresql`, `oracle` och `mysql`.

## Hur Django bestämmer vilka lookups och transformationer som ska användas

I vissa fall kanske du dynamiskt vill ändra vilken `Transform` eller `Lookup` som returneras baserat på det namn som skickas in, snarare än att fixa det. Som ett exempel kan du ha ett fält som lagrar koordinater eller en godtycklig dimension, och vill tillåta en syntax som `.filter(coords__x7=4)` för att returnera de objekt där den 7:e koordinaten har värdet 4. För att göra detta skulle du åsidosätta `get_lookup` med något liknande:

    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)

Du skulle då definiera `get_coordinate_lookup` på lämpligt sätt för att returnera en `Lookup` subklass som hanterar det relevanta värdet av `dimension`.

Det finns en metod med liknande namn som heter `get_transform()`. `get_lookup()` skall alltid returnera en `Lookup` subklass, och `get_transform()` en `Transform` subklass. Det är viktigt att komma ihåg att `Transform`-objekt kan filtreras ytterligare, medan `Lookup`-objekt inte kan det.

Vid filtrering, om det bara finns ett uppslagsnamn kvar att lösa, letar vi efter en `Lookup`. Om det finns flera namn letar vi efter en `Transform`. Om det bara finns ett namn och ingen `Lookup` hittas, letar vi efter en `Transform` och sedan den `exakta` lookupen på den `Transform`. Alla anropssekvenser slutar alltid med en `Lookup`. För att förtydliga:

- `.filter(myfield__mylookup)` kommer att anropa `myfield.get_lookup('mylookup')`.

- `.filter(myfield__mytransform__mylookup)` kommer att anropa `myfield.get_transform('mytransform')` och sedan `mytransform.get_lookup('mylookup')`.

- `.filter(myfield__mytransform)` kommer först att anropa `myfield.get_lookup('mytransform')`, vilket kommer att misslyckas, så det kommer att falla tillbaka till att anropa `myfield.get_transform('mytransform')` och sedan `mytransform.get_lookup('exact')`.