# Dokumentasi Django 4.2.9.dev20231204082947

# Bagaimana menulis pencarian disesuaikan

Django menawarkan beragam luas dari built-in lookups untuk penyaringan (sebagai contoh, `exact` dan `icontains`). Dokumentasi ini menjelaskan bagaimana menulis penyesuaian pencarian dan bagaimana mengubah pekerjaan dari pencarian yang ada. Untuk acuan API dari pencarian, lihat Acuan Pencarian API.

## Contoh pencarian

Mari kita mulai dengan pencarian penyesuaian kecil. Kami akan menulis pencarian penyesuaian `ne` yang bekerj aberlawanan terhadap `exact`. `Author.objects.filter(name__ne='Jack')` akan menterjemahkan ke SQL:

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

Backend SQL berdisi sendiri, sehingga kita tidak perlu khawatir tentang basisdata berbeda.

Ada dua langkah untuk membuatnya bekerja. Pertama kami butuh menerapkan pencarian, kemudian kami butuh memberitahu Django mengenai itu:

    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

Untuk mendaftarkan pencarian `NotEqual` kami butuh memanggil `register_lookup` pada bidang kelas kami ingin cari agar tersedia. Dalam kasus ini, pencarian masuk akal pada semua subkelas `Field`, sehingga kami mendaftarkan itu langsung dengan `Field`:

    from django.db.models import Field

    Field.register_lookup(NotEqual)

Pendaftaran pencarian dapat juga dikerjakan menggunakan pola decorator:

    from django.db.models import Field

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

Kami sekarang dapat menggunakan `foo__ne` untuk setiap bidang `foo`. Anda akan butuh memastikan bahwa pendaftaran ini terjadi sebelum anda mencoba membuat kumpulan permintaan menggunakannya. Anda dapat menempatkan penerapan dalam sebuah berkas `models.py`, atau mendaftarkan pencarian dalam cara `ready()` dari sebuah `AppConfig`.

Melihat lebih dekat pada penerapan, atribut dibutuhkan pertama adalah `lookup_name`. Ini mengizinkan ORM untuk memahami bagaimana mengartikan `name__ne` dan menggunakan `NotEqual` untuk membangkitkan SQL. Berdasarkan pemufakatan, nama-nama ini selalu deretan karakter huruf kecil mengandung hanya huruf, tetapi persyaratan mutlak adalah bahwa itu harus mengandung deretan karakter `__`.

Kami lalu butuh menentukan cara `as_sql`. Ini mengambil obyek `SQLCompiler`, disebut `compiler`, dan hubungan aktif basisdata. Obyek `SQLCompiler` tidak didokumentasikan, tetapi satu-satunya kami butuh diketahui tentang mereka adalah bahwa mereka mempunyai cara `compile()` yang mengemblikan sebuah tuple mengandung deretan karakter SQL, dan parameter untuk disisipkan kedalam deretan karakter itu. Di kebanyakan kasus, anda tidak butuh menggunakannya secara langsung dan dapat melewatinya ke `process_lhs()` dan `process_rhs()`.

Sebuah `Lookup` bekerja terhadap dua nilai, `lhs` dan `rhs`, kepanjangan dari left-hand side dan right-hand side. Left-hand side biasanya acuan bidang, tetapi dia dapat menjadi apapun menerapkan query expression API. Right-hand adalah nilai diberikan oleh pengguna. Dalam contoh `Author.objects.filter(name__ne='Jack')`, sisi tangan-kanan adalah sebuah acuan pada bidang `name` dari model `Author`, dan `'Jack'` adalah the right-hand side.

Kami memanggil `process_lhs` dan `process_rhs` untuk merubah mereka kedalam nilai-nilai kami butuh untuk SQL menggunakan obyek `compiler` digambarkan sebelumnya. Cara ini mengembalikan tuple mengandung beberapa SQL dan parameter untuk ditambahkan kedaam SQL itu, seperti yang kita perlu untuk mengembalikan cara `as_sql` kami. Dalam contoh diatas, `process_lhs` mengembalikan `('"author"."name"', [])` dan `process_rhs` mengembalikan `('"%s"', ['Jack'])`. Dalam contoh ini tidak ada parameter untuk left hand side, tetapi ini akan tergantung pada obyek kita punya, jadi jami masih butuh menyertakan merekan dalam parameter kami kembalikan.

Akhirnya kami menggabungkan bagian-bagian kedalam sebuah pernyataan SQL dengan `<>`, dan memasok semua parameter untuk permintaan. Kami lalu mengambalikan sebuah tuple mengandung deretan karakter SQL dan parameter yang dibangkitkan.

## Contoh transformator

Penyesuaian pencarian diatas adalah hebat, tetapi dalam beberapa kasus anda mungkin ingin dapat merangkai pencarian bersama-sama. Sebagai contoh, mari kita misalnya kami sedang membangun sebuah aplikasi dimana kami ingin membuat penggunaan dari operator `abs()`. Kami mempunyai sebuah model `Experiment` yang merekam sebuah nilai awal, nilai akhir, dan perubahan (awal - akhir). Kami akan suka menemukan semua percobaan dimana perubahan setara pada bilangan tertentu (`Experiment.objects.filter(change__abs=27)`), atau dimana itu tidak melebihi bilangan tertentu (`Experiment.objects.filter(change__abs__lt=27)`).

Catatan

Contoh ini agak dibikin, tetapi dia menunjukkan jangkauan fungsionalitas yang memungkinkan dalam cara backend basisdata berdisi sendiri, tanpa menggandakan fungsionalitas yang sudah ada di Django.

Kami akan mulai dengan menulis sebuah perubahan `AbsoluteValue`. Ini akan menggunakan fungsi SQL `ABS()` untuk merubah nilai sebelum dibandingkan:

    from django.db.models import Transform

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

Selanjutnya, mari kita daftarkan sebagai `IntegerField`:

    from django.db.models import IntegerField

    IntegerField.register_lookup(AbsoluteValue)

Kami dapat menjalankan permintaan kami miliki sebelumnya. `Experiment.objects.filter(change__abs=27)` akan membangkitkan SQL berikut:

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

Dengan menggunakan `Transform` instead of `Lookup` itu berarti kami dapat rantai pencarian lebih lanjut sesudahnya. Sehingga``Experiment.objects.filter(change__abs__lt=27)`` akan membangkitkan SQL berikut:

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

Catat bahwa dalam kasus terdapat tidak ada pencarian lain yang ditentukan, Django menterjemahkan `change__abs=27` sebagai `change__abs__exact=27`.

Ini juga mengizinkan hasil digunakan dalam klausa `ORDER BY` dan `DISTINCT ON`. Sebagai contoh `Experiment.objects.order_by('change__abs')` membangkitkan:

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

Dan pada basisdata yang mendukung distinct pada bidang (seperti PostgreSQL), `Experiment.objects.distinct('change__abs')` membangkitkan:

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

Ketika mencari pencarian mana yang diizinkan setelah `Transform` diberlakukan, Django menggunakan atribut `output_field`. Kami tidak butuh menentukan ini disini jika itu tidak berubah, tetapi seharusnya kami memberlakukan `AbsoluteValue` pada beberapa bidang yang mewakili jenis lebih rumit (sebagai contoh sebuah titik relatif ke yang asli, atau angka rumit) kemudian kami mungkin ingin menentukan bahwa perubahan mengembalikan jenis `FloatField` untuk pencarian lebih lanjut. Ini dapat dikerjakan dengan menambahkan sebuah atribut `output_field` untuk perubahan:

    from django.db.models import FloatField, Transform

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

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

Ini memastikan bahwa pencarian lebih lanjut seperti `abs__lte` berperilaku seperti mereka lakukan untuk `FloatField`.

## Menulis sebuah pencarian `abs__lt` efisien

Ketika menggunakan penulisan diatas pencarian `abs`, keluaran SQL tidak menggunakan indeks secara efisien dalam beberapa kasus. Khususnya, ketika kami menggunakan `change__abs__lt=27`, ini setara pada `change__gt=-27` AND `change__lt=27`. (Untuk kasus `lte` kami akan menggunakan SQL `BETWEEN`).

Jadi kami ingin `Experiment.objects.filter(change__abs__lt=27)` membangkitkan SQL berikut:

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

Peneprapannya adalah:

    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)

Terdapat sepasang hal penting sedang terjadi. Pertama, `AbsoluteValueLessThan` tidak memanggil `process_lhs()`. Malahan dia melewati perubahan dari `lhs` dikerjakan oleh `AbsoluteValue` dan menggunakan `lhs` asli. Yaitu, kami ingin mendapatkan `"experiments"."change"` bukan `ABS("experiments"."change")`. Mengacu secara langsung pada `self.lhs.lhs` adalah aman `AbsoluteValueLessThan` dapat diakses hanya dari pencarian `AbsoluteValue`, yaitu `lhs` selalu sebuah instance dari `AbsoluteValue`.

Perhaikan juga bahwa kedua sisi menggunakan banyak waktu dalam permintaan parameter butuh untuk dikandung `lhs_params` dan `rhs_params` banyak waktu.

Permintaan akhir melakukan pembalikan (`27` ke `-27`) secara langsung di basisdata. Alasan untuk melakukan ini adalah bahwa jika `self.rhs` sesuatu lain daripada nilai integer polos (sebagai contoh sebuah acuan `F()`) kami tidak dapat melakukan perubahan dalam Python.

Catatan

Faktanya, kebanyakan pencarian dengan `__abs` dapat diterapkan sebagai jangkauan permintaan seperti ini, dan pada kebanyakan backend basisdata sepertinya lebih bijaksana untuk dilakukan sehingga anda dapat membuat penggunaan indeks. Bagaimanapun dengan PostgreSQL anda mungkin ingin menambahkan indeks pada `abs(change)` yang akan mengizinkan permintaan ini menjadi lebih efisien.

## Sebuah contoh perubahan timbal balik

Contoh `AbsoluteValue` kami obrolkan sebelumnya adalah sebuah perubahan yang berlaku pada left-hand side dari pencarian. Mungkin disana beberapa kasus dimana anda ingin perubahan diberlakukan pada kedua left-hand side and the right-hand side. Sebagai contoh, jika anda ingin menyaring kumpulan permintaan berdasarkan pada persamaan left and right-hand side kebal pada beberapa fungsi SQL.

Mari kita uji perubahan peka huruf besar-kecil disini. Perubahan ini tidak berguna dalam praktiknya ketika Django datang dengan sekelompok pencarian peka huruf besar-kecil siap pakai, tetapi itu akan menjadi pertunjukkan bagus dari perubahan dua belah pihak dalam cara basisdata-agnostik.

Kami menentukan sebuab perubahan `UpperCase` yang menggunakan fungsi SQL `UPPER()` untuk merubah nilai sebelum dibandingkan. Kami menentukan `bilateral = True` untuk mengindikasikan bahwa perubahan ini harus berlaku pada kedua `lhs` and `rhs`:

    from django.db.models import Transform

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

Selanjutnya, mari kita mendaftarkannya:

    from django.db.models import CharField, TextField

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

Sekarang, queryset `Author.objects.filter(name__upper="doe")` akan membangkitkan permintaan tidak peka seperti ini:

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

## Menulis penerapan cara lain untuk pencarian yang ada

Terkadang penjaja basisdata berbeda membutuhkan SQL berbeda untuk pekerjaan yang sama. Untuk contoh ini kami akan menulis kembali sebuah penyesuaian penerapan untuk MySQL untuk penghubung NotEqual. Dari pada `<>` kami akan menggunakan penghubung `!=`. (Catat bahwa dalam kenyataan hampir semua basisdata mendukung keduanya, termasuk semua basisdata resmi didukung oleh Django).

Kami dapat merubah perilaku pada backend khusus dengan membuat subkelas dari `NotEqual` dengan sebuah metode `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)

Kami dapat mendaftarkannya dengan `Field`. Dia memakan tempat dari kelas `NotEqual` asli seperti dia mempunyai `lookup_name` sama.

Ketika menyusun sebuah permintaan, Django pertama mencari cara `as_%s % connection.vendor`, dan kemudian kembali ke `as_sql`. Nama penjaja untuk membangun backend adalah `sqlite`, `postgresql`, `oracle` dan `mysql`.

## Bagaimana Django menentuka pencarian dan merubah yang sedang digunakan

Dalam beberapa kasus anda mungkin berharap untuk secara dinamis merubah `Transform` atau `Lookup` dikembalikan berdasarkan pada nama dilewatkan, daripada memperbaikinya. Sebagai sebuah contoh, anda dapat mempunyai sebuah bidang yang menyimpan kordinat atau dimensi berubah-ubah dan berharap untuk mengizinkan sebuah sintaksis seperti `.filter(coords__x7=4)` untuk mengembalikan obyek dimana kordinat 7 mempunyai nilai 4. Untuk melakukan ini, anda akan menimpa `get_lookup` dengan sesuatu seperti:

    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)

Anda kemudian akan menentukan `get_coordinate_lookup` dengan benar untuk mengembalikan sebuah subkelas `Lookup` yang menangani nilai yang terkait dari `dimension`.

Ada cara yang dinamai mirip dipanggil `get_transform()`. `get_lookup()` harus selalu mengembalikan sebuah subkelas `Lookup`, dan `Lookup` sebuah subkelas `Transform`. Itu sangat penting diingat bahwa obyek `Transform` dapat lebih jauh disaring, dan obyek `Lookup` tidak dapat.

Ketika menyaring, jika hanya ada satu nama pencarian tersisa untuk diselesaikan, kami akan mencari sebuah `Lookup`. Jika ada banyak nama, dia akan mencari sebuah `Transform`. Dalam keadaan dimana hanya ada satu nama dan sebuah `Lookup` tidak ditemukan, kami mencari sebuah `Transform` dan kemudian pencarian `exact` pada `Transform` tersebut. Semua panggilan selalu berurutan diakhiri dengan sebuah `Lookup`. Untuk menjelaskan:

- `.filter(myfield__mylookup)` akan memanggil `myfield.get_lookup('mylookup')`.
- `.filter(myfield__mytransform__mylookup)` akan memanggil `myfield.get_transform('mytransform')`, dan lalu `mytransform.get_lookup('mylookup')`.
- `.filter(myfield__mytransform)` akan memanggil pertama `myfield.get_lookup('mytransform')`, yang akan gagal, sehingga dia akan gagal kembali memanggil `myfield.get_transform('mytransform')` dan kemudian `mytransform.get_lookup('exact')`.