# Τεκμηρίωση του Django - 5.2.8

# How to write custom lookups

Το Django προσφέρει μια μεγάλη ποικιλία από προεγκατεστημένα lookups για το φιλτράρισμα (π.χ `exact` και `icontains`) των δεδομένων σας από την βάση δεδομένων. Το παρών εγχειρίδιο εξηγεί πως να γράψετε δικά σας lookups και πως να αλλάξετε τη συμπεριφορά των ήδη υπαρχουσών lookups. Για την σχετική αναφορά στο API των lookups, δείτε στο άρθρο Lookup API reference.

## A lookup example

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'

Η παραπάνω εντολή SQL είναι ανεξάρτητη από τη βάση δεδομένων που χρησιμοποιείτε, οπότε δεν θα χρειαστεί να ανησυχούμε γι” αυτό. Όλες οι βάσεις καταλαβαίνουν αυτή τη σύνταξη.

There are two steps to making this work. Firstly we need to implement the lookup, then we need to tell Django about it:

    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)

Το registration του lookup μπορεί να γίνει και με τη χρήση ενός decorator ως εξής:

    from django.db.models import Field

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

Τώρα μπορούμε να χρησιμοποιήσουμε το `foo__ne` για κάθε τύπο πεδίου του `foo`. Θα χρειαστεί να βεβαιωθείτε ότι το registration του lookup, γίνεται πριν προσπαθήσετε να δημιουργήσετε querysets χρησιμοποιώντας το. Θα μπορούσατε να τοποθετήσετε τον παραπάνω κώδικα μέσα στο αρχείο `models.py` ή να κάνετε register το lookup μέσα στη μέθοδο `ready()` στο αντίστοιχο `AppConfig` της εφαρμογής σας.

Ρίχνοντας μια πιο προσεκτική ματιά στην υλοποίηση αυτού του lookup, το πρώτο απαραίτητο attribute που χρειάζεται είναι το `lookup_name`. Αυτό επιτρέπει στο Django ORM να καταλάβει πως να μεταφράσει το `name__ne` και να χρησιμοποιήσει την κλάση `NotEqual` για να παράξει την SQL. Συμβατικά, αυτά τα ονόματα είναι πάντα πεζοί χαρακτήρες (μόνο γράμματα) και η μοναδική αυστηρή απαίτηση είναι να μην περιέχουν δύο συνεχόμενες κάτω παύλες, `__`.

Έπειτα χρειάζεται να ορίσουμε την μέθοδο `as_sql`. Αυτή δέχεται ως όρισμα ένα `SQLCompiler` object, με το όνομα `compiler` και την ενεργή σύνδεση της βάσης δεδομένων με το όνομα `connection`. Τα `SQLCompiler` objects δεν βρίσκονται κάπου γραμμένα στο εγχειρίδιο του Django αλλά το μόνο πράγμα που χρειάζεται να ξέρετε γι” αυτά είναι ότι έχουν μια μέθοδο `compile()` η οποία επιστρέφει ένα tuple που περιέχει ένα SQL string και κάποιες παραμέτρους οι οποίες “γεμίζουν” αυτό το SQL string. Στις περισσότερες περιπτώσεις δεν θα χρειαστεί να την χρησιμοποιήσετε και μπορείτε να συνεχίσετε με τις μεθόδους `process_lhs()` και `process_rhs()`.

Ένα `Lookup` συνεργάζεται με δύο τιμές, την `lhs` και την `rhs`, οι οποίες είναι σύντμηση των λέξεων left-hand side (αριστερό μέρος) και right-hand side (δεξί μέρος). Το αριστερό μέρος αναφέρεται συνήθως σε κάποιο πεδίο αλλά μπορεί να είναι οτιδήποτε αρκεί να έχει υλοποιήσει το API για εκφράσεις ερωτημάτων. Το δεξί μέρος είναι η τιμή η οποία δίνεται από τον χρήστη. Στο παράδειγμα `Author.objects.filter(name__ne='Jack')`, το αριστερό μέρος αναφέρεται στο πεδίο `name` του μοντέλου `Author` και το string `'Jack'` αποτελεί το δεξί μέρος.

Καλούμε την `process_lhs` και την `process_rhs` για να μετατρέψουμε το `Author.objects.filter(name__ne='Jack')` στις τιμές που χρειαζόμαστε για την SQL χρησιμοποιώντας το `compiler` object που περιγράψαμε παραπάνω. Αυτές οι μέθοδοι επιστρέφουν tuples που περιέχουν λίγη SQL και κάποιες παραμέτρους που θα “γεμίσουν” αυτή την SQL. Ότι ακριβώς χρειαζόμαστε για να επιστρέψει η μέθοδος `as_sql`. Στο παραπάνω παράδειγμα, η `process_lhs` επιστρέφει `('"author"."name"', [])` και η `process_rhs` επιστρέφει `('"%s"', ['Jack'])`. Όπως θα είδατε, δεν υπάρχουν παράμετροι για το αριστερό μέρος, αλλά αυτό εξαρτάται από την περίπτωση. Όπως και να έχει πάντως, θα πρέπει πάντα να περιλαμβάνονται στο τελικό tuple προς επιστροφή (η μεταβλητή `params` στην `as_sql`).

Στο τέλος ενώνουμε τα μέρη σε μια SQL εντολή με τη χρήση του `<>` και παρέχουμε όλες τις παραμέτρους για το query (ερώτημα). Αυτό που επιστρέφουμε είναι ένα tuple που περιέχει την παραγόμενη SQL εντολή και τις παραμέτρους, κενές ή όχι. Το tuple που θα επιστραφεί, αναφερόμενοι στην ανωτέρω `as_sql`, είναι το `('"author"."name" <> "%s"', ['Jack'])`.

## A transformer example

Το παραπάνω lookup που φτιάξαμε είναι ωραίο, αλλά σε αρκετές περιπτώσεις θα θέλετε να έχετε τη δυνατότητα να ενώνετε τα lookups μεταξύ τους. Για παράδειγμα, ας υποθέσουμε ότι χτίζουμε μια εφαρμογή όπου θέλουμε να κάνουμε χρήση της μεθόδου `abs()`, που επιστρέφει το απόλυτο ενός αριθμού. Έχουμε ένα μοντέλο με το όνομα `Experiment` το οποίο καταγράφει μια τιμή έναρξης (start), μια τέλους (end) και την αλλαγή (αρχή – τέλος). Θα θέλαμε να βρούμε όλα τα πειράματα (experiments) όπου η αλλαγή ήταν ίση με μια συγκεκριμένη τιμή (`Experiment.objects.filter(change__abs=27)`) ή η αλλαγή ήταν μικρότερη ή ίση μιας συγκεκριμένης τιμής (`Experiment.objects.filter(change__abs__lt=27)`). Εδώ βλέπετε τη σύνδεση μεταξύ των lookups που αναφέραμε πριν (`abs__lt`).

Σημείωση

Αυτό το παράδειγμα είναι λίγο πολύ σκηνοθετημένο αλλά επιδεικνύει όμορφα τις λειτουργίες που είναι δυνατόν να υλοποιηθούν χωρίς την εξάρτηση πάνω σε μια συγκεκριμένη βάση δεδομένων και χωρίς να ξαναγράφουμε κώδικα που υπάρχει ήδη στο Django.

We will start by writing an `AbsoluteValue` transformer. This will use the SQL function `ABS()` to transform the value before comparison:

    from django.db.models import Transform

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

Επόμενο βήμα είναι κάνουμε register την κλάση μας με το πεδίο `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

Σημειώστε ότι στην περίπτωση που δεν υπάρχει άλλο lookup στο τέλος, το Django μεταφράζει το `change__abs=27` ως `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")

Για να βρει το Django τον τύπο των lookups που επιτρέπονται μετά το δικό μας (`AbsoluteValue`), κοιτάζει το attribute `output_field` το οποίο δεν χρειάστηκε να το ορίσουμε στο συγκεκριμένο παράδειγμα γιατί έτυχε ο αριθμός που συγκρίνουμε, στο δεξί μέρος, να είναι ακέραιος (27). Αν μπερδευτήκατε, ας το ξεκαθαρίσουμε: Η `Transform` προσφέρει τη μετατροπή ενός πεδίου (πχ του `change`) σε κάποια άλλη τιμή (πχ από -27 που μπορεί να ήταν, σε 27) προτού συγκριθεί με το δεξί μέρος και γίνει το lookup στη βάση δεδομένων. Ο τύπος της νέας αυτής τιμής (πχ 27 αντί -27) δηλώνεται στο `output_field` του δικού μας `AbsoluteValue ``. Δεν το δηλώσαμε γιατί και οι δύο τιμές είναι τύπου int. Αν υποθέσουμε όμως ότι έχουμε πιο περίπλοκα πεδία (πχ καρτεσιανές συντεταγμένες ή μιγαδικούς αριθμούς) τότε ίσως θα θέλαμε να προσδιορίσουμε τον τύπο της νέας τιμής ως ``FloatField` για περαιτέρω lookups. Αυτό μπορεί να γίνει θέτοντας το attribute `` output_field`` ως property της δικής μας κλάσης, ως εξής:

    from django.db.models import FloatField, Transform

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

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

Με αυτό τον τρόπο είμαστε σίγουροι ότι το δικό μας transformation θα επιστρέψει μια τιμή τύπου float και τα επόμενα lookups (αν υπάρχουν, πχ το lte στο `abs__lte`) θα “δουν” την τιμή αυτή ως float και όχι ως int όπως ήταν στην αρχή. Ένα άλλο πολύ κοινό παράδειγμα της `Transform` είναι η μετατροπή ενός `DateField` στην τιμή του έτους μόνο. Για παράδειγμα, θα θέλατε να δείτε ποια πειράματα έγιναν το έτος 2016. Θα θέλατε να το γράψετε ως εξής: `Experiment.objects.filter(date__year=2016)`. Το `date` είναι το πεδίο του μοντέλου μας, τύπου `DateField` και το `year` το lookup (που υπάρχει ήδη μέσα στο Django). Το `year` lookup “ξηλώνει” μόνο το έτος από την ημερομηνία και το συγκρίνει με το 2016. Έτσι, μετατρέπεται η ημερομηνία (`DateField`) σε έτος (`IntegerField`) και συγκρίνεται με το δεξί μέρος.

## Γράφοντας ένα πιο αποτελεσματικό `abs__lt` lookup

Όταν χρησιμοποιούμε το παραπάνω `abs` lookup, η παραγόμενη SQL, σε μερικές περιπτώσεις, δεν χρησιμοποιεί αποτελεσματικά τα ευρετήρια (indexes) της βάσης δεδομένων. Πιο συγκεκριμένα, όταν χρησιμοποιούμε το `change__abs__lt=27`, αυτό είναι ισοδύναμο με το `change__gt=-27` AND `change__lt=27`. (Στη περίπτωση του `lte` θα μπορούσαμε να χρησιμοποιήσουμε την εντολή SQL `BETWEEN`).

So we would like `Experiment.objects.filter(change__abs__lt=27)` to generate the following SQL:

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

Η υλοποίηση αυτού φαίνεται παρακάτω:

    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)

Υπάρχουν μερικά αξιοσημείωτα πράγματα εδώ. Πρώτα απ’ όλα το `AbsoluteValueLessThan` δεν καλεί τη μέθοδο `process_lhs()`. Αντιθέτως, υπερπηδά τη μετατροπή του `lhs` που γίνεται από την `AbsoluteValue` και χρησιμοποιεί την αρχική τιμή του `lhs` (το `self.lhs` είναι η τιμή μετά την μετατροπή ενώ το `self.lhs.lhs` είναι η αρχική τιμή του `change`). Αυτό γίνεται γιατί θέλουμε να έχουμε το `"experiments"."change"` και όχι το `ABS("experiments"."change")`. Είναι ασφαλής η αναφορά μας απ’ ευθείας στο `self.lhs.lhs` γιατί η `Lookup` κλάση `AbsoluteValueLessThan` μπορεί να προσπελασθεί μόνο από την `Transform` κλάση `AbsoluteValue` (λόγω του register στην τελευταία γραμμή), δηλαδή το `lhs` θα είναι πάντα ένα instance της Transform κλάσης `AbsoluteValue`.

Επισημαίνεται επίσης το γεγονός ότι όχι μόνο και τα δύο μέρη (αριστερό και δεξί) χρησιμοποιούνται πολλές φορές μέσα στο SQL query αλλά και οι παράμετροι επίσης (`lhs_params` και `rhs_params`).

Ο λόγος που γίνεται αυτό είναι γιατί, σε περίπτωση που το `self.rhs` είναι κάτι διαφορετικό από έναν ακέραιο (πχ μια αναφορά σε ένα `F()`) δε θα μπορούμε να κάνουμε τις μετατροπές στην Python.

Σημείωση

Στην πραγματικότητα, τα περισσότερα lookups που εμπεριέχουν το `__abs` θα μπορούσαν να υλοποιηθούν με την παραπάνω μέθοδο και στις περισσότερες βάσεις δεδομένων θα μπορέσετε να εκμεταλλευτείτε τη δυνατότητα των ευρετηρίων (indexes) τους. Ωστόσο, στην PostgreSQL ίσως θα θέλατε να προσθέσετε ένα ευρετήριο για το `abs(change)` το οποίο θα επιτρέψει σε αυτά τα queries να είναι πολύ αποτελεσματικά.

## Ένα παράδειγμα ενός διμερούς transformer

Το παράδειγμα της `Transformer` κλάσης `AbsoluteValue` που περιγράψαμε παραπάνω είναι μια μετατροπή η οποία εφαρμόζεται στο αριστερό μέρος του lookup (στο πεδίο `change`). Ίσως να υπάρξουν, μερικές περιπτώσεις όπου θα θέλατε η μετατροπή να εφαρμοστεί και στο αριστερό και στο δεξί μέρος. Για παράδειγμα, αν θέλετε να φιλτράρετε ένα queryset βασιζόμενο στην ισότητα των δύο μερών (αριστερό και δεξί) με την χρήση κάποιας 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.

Ορίζουμε έναν transformer `UpperCase` ο οποίος χρησιμοποιεί την SQL συνάρτηση `UPPER()` για να μετατρέψει τις τιμές πριν την σύγκριση. Θέτουμε το attribute `bilateral = True` για να υποδείξουμε ότι η μετατροπή θα πρέπει να γίνει όχι μόνο στο αριστερό μέρος, `lhs`, αλλά και στο δεξί, `rhs`:

    from django.db.models import Transform

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

Έπειτα το κάνουμε register με τους ανάλογους τύπους πεδίων:

    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')

## Γράφοντας εναλλακτικές υλοποιήσεις για ήδη υπάρχοντα lookups

Κάποιες φορές, μερικές βάσεις δεδομένων απαιτούν έναν διαφορετικό τρόπο γραφής της SQL για την ίδια λειτουργία. Για παράδειγμα, κάποιες βάσεις για να προσδιορίσουν τη λογική «όχι ίσο με», αντί του συμβόλου `<>` απαιτούν το `!=`. (Στην πραγματικότητα σχεδόν όλες οι βάσεις δεδομένων υποστηρίζουν και τα δύο αυτά σύμβολα, συμπεριλαμβανομένων και όλων των επίσημων βάσεων δεδομένων που υποστηρίζει το Django). Σε αυτό το παράδειγμα θα ξαναγράψουμε μια δικιά μας υλοποίηση, για την MySQL, της λειτουργίας NotEqual.

We can change the behavior on a specific backend by creating a subclass of `NotEqual` with an `as_mysql` method:

    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)

Μπορούμε, έπειτα, να κάνουμε register την κλάση αυτή με το `Field`. Η κλάση `MySQLNotEqual` αντικαθιστά την κλάση `NotEqual`, μόνο για τις MySQL βάσεις δεδομένων, αφού μοιράζονται το ίδιο `lookup_name`.

Κατά τη διάρκεια μετάφρασης του query, το Django ψάχνει για τις μεθόδους``as_%s % connection.vendor`` και αν δεν βρει κάποια τότε χρησιμοποιεί την `as_sql`. Τα ονόματα των vendor για τις προεγκατεστημένες στο Django database backends είναι τα `sqlite`, `postgresql`, `oracle` και `mysql`.

## Πως προσδιορίζει το Django τα lookups και τα transforms που χρησιμοποιούνται

Σε μερικές περιπτώσεις θα θέλατε να αλλάξετε, δυναμικά, το `Transform` ή το `Lookup` που επιστρέφεται ανάλογα το όνομα που δίνεται στο query παρά να μπείτε στη διαδικασία να φτιάξετε ένα για το καθένα. Θα μπορούσατε, για παράδειγμα, να έχετε ένα πεδίο το οποίο αποθηκεύει συντεταγμένες ή κάποια αυθαίρετη διάσταση και θέλετε να μπορείτε να γράφετε το `.filter(coords__x7=4)` και να σας επιστρέφει τα objects όπου η 7η συντεταγμένη έχει την τιμή 4. Για να το κάνετε αυτό, θα πρέπει να παρακάμψετε (override) την `get_lookup` με κάτι σαν αυτό:

    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)

Μετά θα ορίσετε τη μέθοδο `get_coordinate_lookup`, καταλλήλως, για να επιστρέφει μια subclass της `Lookup` η οποία θα χειριστεί την σχετική τιμή της μεταβλητής `dimension`.

Ομοίως, υπάρχει μια μέθοδος για την `Transform` με το όνομα `get_transform()`. Η μέθοδος `get_lookup()` θα πρέπει πάντα να επιστρέφει μια subclass της `Lookup` ενώ η `get_transform()` μια subclass της `Transform`. Είναι σημαντικό να θυμάστε ότι τα objects της κλάσης `Transform` μπορούν να φιλτραριστούν περαιτέρω ενώ τα objects της κλάσης `Lookup` όχι.

Όταν φιλτράρετε και έχει απομείνει μόνο ένα lookup όνομα, το Django ψάξει για κάποιο `Lookup`. Αν υπάρχουν πολλά τότε θα ψάξει για κάποιο `Transform`. Στην περίπτωση που υπάρχει μόνο ένα όνομα και δεν βρεθεί κάποιο `Lookup`, τότε το Django θα ψάξει για ένα `Transform` και μετά το `exact` lookup πάνω σε αυτό το `Transform`. Στο τέλος, το Django, πάντα θα ψάχνει για ένα `Lookup`. Για να ξεκαθαρίσουμε λίγο το τοπίο:

- Το `.filter(myfield__mylookup)` θα καλέσει την μέθοδο `myfield.get_lookup('mylookup')`.

- Το `.filter(myfield__mytransform__mylookup)` θα καλέσει τη μέθοδο `myfield.get_transform('mytransform')` και μετά την `mytransform.get_lookup('mylookup')`.

- Το `.filter(myfield__mytransform)` θα καλέσει πρώτα τη μέθοδο `myfield.get_lookup('mytransform')`, η οποία θα αποτύχει. Έπειτα θα καλέσει την μέθοδο `myfield.get_transform('mytransform')` και τέλος την `mytransform.get_lookup('exact')`.