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

# How to create custom template tags and filters

Η γλώσσα template του Django έρχεται με μια μεγάλη ποικιλία από προεγκατεστημένα tags και φίλτρα που έχουν σχεδιαστεί για να αντιμετωπίσουν τις ανάγκες της παρουσίασης της εφαρμογής σας. Παρόλ” αυτά, ίσως χρειαστείτε κάποια λειτουργία η οποία δεν υπάρχει στο βασικό πακέτο των template tags και φίλτρων. Μπορείτε να επεκτείνετε την template μηχανή του Django ορίζοντας δικά σας tags και φίλτρα χρησιμοποιώντας, τι άλλο, Python. Αφού τα δημιουργήσετε μπορείτε να τα χρησιμοποιήσετε στα templates σας αφού πρώτα τα φορτώσετε με το tag `{% load %}`.

## Διάταξη κώδικα

Το πιο κοινό μέρος για να προσδιορίσετε τα δικά σας template tags και φίλτρα είναι μέσα στη Django εφαρμογή σας. Αν σχετίζονται, δε, και με την ήδη υπάρχουσα εφαρμογή, τότε είναι πολύ λογικό να ζουν μέσα στο φάκελο της. Αν όχι, τότε μπορούν να προστεθούν σε κάποια καινούργια εφαρμογή. Αν τα δικά σας template tags και φίλτρα αφορούν γενικά το project ή κάποια άλλη λειτουργία, μπορείτε να φτιάξετε μια νέα εφαρμογή πχ my_utils και να τα αποθηκεύσετε εκεί. Όταν ένα Django app προστίθεται στη λίστα `INSTALLED_APPS`, οποιοδήποτε tag που είναι ορισμένο στη συμβατική τοποθεσία που περιγράφεται παρακάτω, αυτόματα γίνεται διαθέσιμο για να φορτωθεί μέσα στα templates.

Η εφαρμογή πρέπει να περιέχει ένα φάκελο `templatetags`, στο ίδιο επίπεδο με τα αρχεία `models.py`, `views.py`, κλπ. Αν ο φάκελος δεν υπάρχει ήδη, δημιουργήστε τον – μη ξεχάσετε να προσθέσετε το αρχείο `__init__.py` για να εξασφαλίσετε ότι ο φάκελος αυτός θα αντιμετωπιστεί ως ένα Python package.

Ο development server δεν θα επανεκκινήσει αυτόματα

Αφού προσθέσετε το module `templatetags`, θα χρειαστεί να επανεκκινήσετε χειροκίνητα τον server πριν χρησιμοποιήσετε τα tags ή τα φίλτρα στα templates.

Τα δικά σας tags και φίλτρα θα υπάρχουν σε ένα module (όποτε λέμε module εννοούμε ένα φάκελο μαζί με το κενό αρχείο `__init__.py` μέσα του) μέσα στο `templatetags`. Το όνομα αυτού του module θα είναι αυτό που θα χρησιμοποιείτε αργότερα, για να φορτώνετε τα tags/φίλτρα, οπότε να είστε προσεκτικοί στην επιλογή του ονόματος ούτως ώστε να μην προκύψουν modules με το ίδιο όνομα σε άλλη εφαρμογή.

For example, if your custom tags/filters are in a file called `poll_extras.py`, your app layout might look like this:

    polls/
        __init__.py
        models.py
        templatetags/
            __init__.py
            poll_extras.py
        views.py

Και μέσα στο template σας θα μπορείτε να κάνετε διαθέσιμα τα tags/φίλτρα αυτού του αρχείου ως εξής:

    {% load poll_extras %}

Η εφαρμογή που περιέχει τα δικά σας tags πρέπει να βρίσκεται μέσα στα `INSTALLED_APPS` προκειμένου το tag `{% load %}` να δουλέψει. Αυτό γίνεται για λόγους ασφαλείας: σας επιτρέπει να έχετε Python κώδικα από πολλές template βιβλιοθήκες σε μια μηχανή χωρίς να δοθεί πρόσβαση σε όλες αυτές για κάθε εγκατάσταση του Django.

Δεν υπάρχει όριο στον αριθμό των modules που θα βάλετε μέσα στο `templatetags`. Θυμηθείτε όμως ότι το tag `{% load %}` θα φορτώσει τα tags/φίλτρα για το δοθέν όνομα του Python module που βρίσκεται μέσα στο `templatetags`, όχι το όνομα της εφαρμογής.

Για να είναι μια έγκυρη tag βιβλιοθήκη, το module πρέπει να περιέχει μια μεταβλητή, επιπέδου module, με το όνομα `register`, ήτοι ένα instance της κλάσης `template.Library`, με την οποία όλα τα tags και τα φίλτρα θα γίνουν register. Επομένως, στην αρχή του module `poll_extras.py`, προσθέστε τα ακόλουθα:

    from django import template

    register = template.Library()

Εναλλακτικά, τα modules του template tag μπορούν να γίνουν register μέσω του argument `'libraries'` της κλάσης `DjangoTemplates`. Αυτό είναι χρήσιμο αν θέλετε να χρησιμοποιήσετε ένα διαφορετικό label από το όνομα του module όταν φορτώνετε τα template tags. Επίσης σας επιτρέπει να κάνετε register τα tags χωρίς την εγκατάσταση κάποια εφαρμογής.

Πίσω από τις σκηνές

For a ton of examples, read the source code for Django’s default filters and tags. They’re in django/template/defaultfilters.py and django/template/defaulttags.py, respectively.

Για περισσότερες πληροφορίες σχετικά με το tag `load`, διαβάστε το εγχειρίδιο του.

## Γράφοντας δικά σας template φίλτρα

Custom filters are Python functions that take one or two arguments:

- Την τιμή της μεταβλητής (input) – όχι απαραίτητα τύπου string.

- Την τιμή του argument (αν υπάρχει) – αυτή μπορεί να είναι μια προεπιλεγμένη τιμή ή να μην υπάρχει καθόλου.

Για παράδειγμα, μέσα στη σύνταξη του φίλτρου `{{ var|foo:"bar" }}`, το φίλτρο `foo` θα λάβει μια μεταβλητή `var` (input) και ένα argument `"bar"`.

Εφόσον η γλώσσα του template δεν παρέχει exception handling, κάθε exception που θα γίνει raised από ένα template filter θα εμφανιστεί ως server error. Επομένως, οι συναρτήσεις των φίλτρων θα πρέπει να αποφεύγουν να κάνουν raise τυχόν exceptions αν υπάρχει κάποια λογική τιμή να επιστραφεί, ως fallback. Σε περίπτωση κάποιου input λόγω του οποίου φαίνεται ξεκάθαρα κάποιο bug στο template, το να κάνετε raise κάποιο exception είναι καλύτερο από το να το αφήσετε απαρατήρητο κάτι το οποίο θα κρύψει το bug.

Παρακάτω φαίνεται ένα παράδειγμα ορισμού ενός φίλτρου:

    def cut(value, arg):
        """Removes all values of arg from the given string"""
        return value.replace(arg, "")

Και εδώ φαίνεται ένα παράδειγμα του πως θα χρησιμοποιηθεί αυτό το φίλτρο στο template:

    {{ somevariable|cut:"0" }}

Most filters don’t take arguments. In this case, leave the argument out of your function:

    def lower(value):  # Only one argument.
        """Converts a string into all lowercase"""
        return value.lower()

### Κάνοντας register τα δικά σας φίλτρα

django.template.Library.filter()

Όταν γράψετε τον ορισμό του φίλτρου σας, θα χρειαστεί να το κάνετε register με το instance της κλάσης `Library`, για να το κάνετε διαθέσιμο στη γλώσσα template του Django:

    register.filter("cut", cut)
    register.filter("lower", lower)

Η μέθοδος `Library.filter()` παίρνει δύο ορίσματα:

1. Το όνομα του φίλτρου – τύπου string.

2. Τη συνάρτηση του φίλτρου – μια Python συνάρτηση (όχι το όνομα της συνάρτησης ως string).

Μπορείτε, αντ” αυτού, να χρησιμοποιήσετε τη μέθοδο `register.filter()` ως decorator:

    @register.filter(name="cut")
    def cut(value, arg):
        return value.replace(arg, "")

    @register.filter
    def lower(value):
        return value.lower()

Αν δεν περάσετε το όρισμα `name`, όπως στο δεύτερο παράδειγμα παραπάνω, το Django θα χρησιμοποιήσει το όνομα της συνάρτησης ως το όνομα του φίλτρου.

Εν τέλει, η μέθοδος `register.filter()` δέχεται τρία keyword arguments, το `is_safe`, το `needs_autoescape` και το `expects_localtime`. Αυτά τα arguments περιγράφονται στα φίλτρα και auto-escaping και στα φίλτρα και ζώνες ώρας παρακάτω.

### Template φίλτρα που περιμένουν strings

django.template.defaultfilters.stringfilter()

Αν γράφετε ένα template φίλτρο το οποίο περιμένει ένα argument τύπου string ως πρώτο argument, θα πρέπει να χρησιμοποιήσετε τον decorator `stringfilter`. Αυτό θα μετατρέψει το object στην string τιμή του πριν περαστεί στη συνάρτηση σας:

    from django import template
    from django.template.defaultfilters import stringfilter

    register = template.Library()

    @register.filter
    @stringfilter
    def lower(value):
        return value.lower()

Με αυτό τον τρόπο, θα μπορείτε να περνάτε, ας πούμε, έναν integer σε αυτό το φίλτρο και αυτό δεν θα προκαλέσει κάποιο `AttributeError` (επειδή οι integers δεν έχουν την μέθοδο `lower()`).

### Φίλτρα και auto-escaping

When writing a custom filter, give some thought to how the filter will interact with Django’s auto-escaping behavior. Note that two types of strings can be passed around inside the template code:

- Raw strings are the native Python strings. On output, they’re escaped if auto-escaping is in effect and presented unchanged, otherwise.

- Ασφαλή strings: είναι τα strings τα οποία έχουν μαρκαριστεί ως ασφαλή από περαιτέρω escaping κατά την παρουσίαση τους. Όλα τα απαραίτητα escapings έχουν γίνει μέχρι εδώ. Χρησιμοποιούνται συνήθως για έξοδο η οποία περιέχει σκέτη HTML και προορίζεται να μεταφραστεί ως έχει από τη μεριά του client.

  Internally, these strings are of type `SafeString`. You can test for them using code like:

      from django.utils.safestring import SafeString

      if isinstance(value, SafeString):
          # Do something with the "safe" string.
          ...

Ο κώδικας ενός template φίλτρου εμπίπτει σε μια από τις δύο ακόλουθες καταστάσεις:

1. Your filter does not introduce any HTML-unsafe characters (`<`, `>`, `'`, `"` or `&`) into the result that were not already present. In this case, you can let Django take care of all the auto-escaping handling for you. All you need to do is set the `is_safe` flag to `True` when you register your filter function, like so:

       @register.filter(is_safe=True)
       def myfilter(value):
           return value

   Αυτό το flag λέει στο Django ότι αν ένα «ασφαλές» string περαστεί στο φίλτρο σας, το αποτέλεσμα θα εξακολουθήσει να είναι «ασφαλές» ακόμη και αν ένα μη-ασφαλές string περαστεί, το Django, αυτόματα θα το κάνει escape, εάν χρειαστεί.

   Μπορείτε να το σκεφτείτε αλλιώς: «αυτό το φίλτρο είναι ασφαλές – δεν υπάρχει πιθανότητα τυχόν μη ασφαλούς HTML».

   The reason `is_safe` is necessary is because there are plenty of normal string operations that will turn a `SafeData` object back into a normal `str` object and, rather than try to catch them all, which would be very difficult, Django repairs the damage after the filter has completed.

   Για παράδειγμα, ας υποθέσουμε ότι έχετε ένα φίλτρο το οποίο προσθέτει το string `xx` στο τέλος κάθε input. Επειδή αυτό δεν εισάγει επικινδύνους HTML χαρακτήρες στο αποτέλεσμα (εκτός αυτού που είναι ήδη παρόν), θα πρέπει να μαρκάρετε το φίλτρο σας με το `is_safe`:

       @register.filter(is_safe=True)
       def add_xx(value):
           return "%sxx" % value

   Όταν αυτό το φίλτρο χρησιμοποιείται σε ένα template όπου το auto-escaping είναι ενεργοποιημένο, το Django θα κάνει escape την έξοδο οποτεδήποτε το input δεν είναι μαρκαρισμένο ως «ασφαλές».

   Από προεπιλογή, το flag `is_safe` είναι `False` και μπορείτε να το παραλείψετε από τα φίλτρα όπου δεν χρειάζεται.

   Να είστε προσεκτικοί όταν αποφασίζετε αν το φίλτρο σας αφήνει όντως τα safe strings ως safe. Εάν αφαιρείτε χαρακτήρες, μπορεί ακούσια να αφήσετε μη-ισορροπημένα HTML tags (χωρίς το start ή το end tag) ή οντότητες στο αποτέλεσμα. Για παράδειγμα, όταν αφαιρείτε ένα `>` από το input μπορεί να μετατρέψει ένα `<a>` σε `<a`, το οποίο θα χρειαστεί να γίνει escaped στην έξοδο για την αποφυγή τυχόν προβλημάτων. Ομοίως, η αφαίρεση του `;` μπορεί να μετατρέψει το `&amp;` σε `&amp`, η οποία δεν είναι πλέον μια έγκυρη οντότητα και επομένως χρειάζεται περαιτέρω escaping. Οι περισσότερες περιπτώσεις δεν θα είναι τόσο πολύπλοκες αλλά προσέχετε για τέτοιου είδους προβλήματα όταν επανεξετάζετε τον κώδικα σας.

   Marking a filter `is_safe` will coerce the filter’s return value to a string. If your filter should return a boolean or other non-string value, marking it `is_safe` will probably have unintended consequences (such as converting a boolean False to the string “False”).

2. Εναλλακτικά, ο κώδικας του φίλτρου σας μπορεί να χειριστεί χειροκίνητα, τυχόν αναγκαίο escaping. Αυτό είναι απαραίτητο όταν εισάγετε καινούργιο HTML κώδικα στο αποτέλεσμα σας. Θα θέλετε να μαρκάρετε την έξοδο ως ασφαλή από περαιτέρω escaping ούτως ώστε ο HTML κώδικας να μην γίνει περαιτέρω escape, οπότε θα χρειαστεί εσείς να χειριστείτε το input.

   Για να μαρκάρετε την έξοδο ως ασφαλή string, χρησιμοποιήστε τη συνάρτηση `django.utils.safestring.mark_safe()`.

   Προσέχετε όμως. Θα χρειαστεί να κάνετε παραπάνω πράγματα από το να μαρκάρετε μόνο την έξοδο ως ασφαλή. Θα χρειαστεί να εξασφαλίσετε ότι είναι όντως ασφαλή και ότι κάνετε εξαρτάται από το αν το auto-escaping είναι ενεργοποιημένο ή όχι. Η ιδέα είναι να γράφετε φίλτρα τα οποία μπορούν να δρουν σε templates όπου το auto-escaping είναι είτε ενεργοποιημένο ή όχι, προκειμένου να κάνετε ευκολότερη τη δουλειά για τους συντάκτες των templates.

   Για να ξέρει το φίλτρο σας την κατάσταση του auto-escaping, θέστε το flag `needs_autoescape` σε `True` όταν κάνετε register το φίλτρο σας. (Αν δεν θέσετε αυτό το flag, η προεπιλεγμένη του τιμή είναι `False`). Αυτό το flag λέει στο Django ότι η συνάρτηση του φίλτρου σας δέχεται ένα επιπλέον keyword argument, με το όνομα `autoescape`, δηλαδή `True` αν το auto-escaping είναι ενεργοποιημένο και `False` αν όχι. Προτείνεται να θέσετε την προεπιπλεγμένη τιμή της παραμέτρου `autoescape` σε `True`, ούτως ώστε αν καλέσετε τη συνάρτηση του φίλτρου σας από τον Python κώδικα σας, το φίλτρο σας θα έχει το escaping ενεργοποιημένο από προεπιλογή.

   Για παράδειγμα, ας γράψουμε ένα φίλτρο το οποίο δίνει έμφαση στον πρώτο χαρακτήρα ενός string:

       from django import template
       from django.utils.html import conditional_escape
       from django.utils.safestring import mark_safe

       register = template.Library()

       @register.filter(needs_autoescape=True)
       def initial_letter_filter(text, autoescape=True):
           first, other = text[0], text[1:]
           if autoescape:
               esc = conditional_escape
           else:
               esc = lambda x: x
           result = "<strong>%s</strong>%s" % (esc(first), esc(other))
           return mark_safe(result)

   The `needs_autoescape` flag and the `autoescape` keyword argument mean that our function will know whether automatic escaping is in effect when the filter is called. We use `autoescape` to decide whether the input data needs to be passed through `django.utils.html.conditional_escape` or not. (In the latter case, we use the identity function as the «escape» function.) The `conditional_escape()` function is like `escape()` except it only escapes input that is not a `SafeData` instance. If a `SafeData` instance is passed to `conditional_escape()`, the data is returned unchanged.

   Στο παραπάνω παράδειγμα, θυμόμαστε να μαρκάρουμε το αποτέλεσμα ως ασφαλή ούτως ώστε η HTML που θα εισαχθεί απ” ευθείας μέσα στο template να μην υποστεί περαιτέρω escaping.

   Δεν υπάρχει λόγος να ανησυχούμε για το flag `is_safe` σε αυτή την περίπτωση (παρόλου που το περιλαμβάνουμε δεν βλάπτει κανέναν). Οποτεδήποτε χειρίζεστε χειροκίνητα τα θέματα του auto-escaping και επιστρέφετε ένα ασφαλή string, το flag `is_safe` δεν θα αλλάξει τίποτα με τον ένα ή τον άλλο τρόπο.

Προειδοποίηση

Αποφεύγοντας τα τρωτά σημεία του XSS όταν επαναχρησιμοποιείτε τα προεγκατεστημένα φίλτρα του Django

Τα προεγκατεστημένα φίλτρα του Django έχουν το `autoescape=True` από προεπιλογή, προκειμένου να έχουν την κατάλληλη autoescaping συμπεριφορά και για να αποφύγουν το τρωτό σημείο ενός cross-site script.

Σε παλαιότερες εκδόσεις του Django, να είστε προσεκτικοί όταν χρησιμοποιείτε τα προεγκατεστημένα φίλτρα του Django επειδή η προεπιλεγμένη τιμή του keyword argument `autoescape` είναι `None`. Θα χρειαστεί να το δηλώσετε ως `autoescape=True` για να έχετε autoescaping.

Για παράδειγμα, αν θέλετε να γράψετε ένα δικό σας φίλτρο με το όνομα `urlize_and_linebreaks` το οποίο συνδυάζει το φίλτρο `urlize` με `linebreaksbr`, τότε το φίλτρο σας θα έδειχνε κάπως έτσι:

    from django.template.defaultfilters import linebreaksbr, urlize

    @register.filter(needs_autoescape=True)
    def urlize_and_linebreaks(text, autoescape=True):
        return linebreaksbr(urlize(text, autoescape=autoescape), autoescape=autoescape)

Μετά το:

    {{ comment|urlize_and_linebreaks }}

θα ήταν ισοδύναμο με αυτό:

    {{ comment|urlize|linebreaksbr }}

### Φίλτρα και ζώνες ώρας

Αν γράφετε δικά σας φίλτρα τα οποία δρουν σε objects του τύπου `datetime`, συνήθως θα το κάνετε register με το flag `expects_localtime` ως `True`:

    @register.filter(expects_localtime=True)
    def businesshours(value):
        try:
            return 9 <= value.hour < 17
        except AttributeError:
            return ""

Όταν αυτό το flag είναι `True`, αν η πρώτη παράμετρος του φίλτρου σας είναι ένα datetime με επίγνωση της ζώνης ώρας (time zone aware datetime), το Django θα το μετατρέψει στην τρέχουσα ζώνη ώρας, κατά περίπτωση, πριν το περάσει στο φίλτρο σας, σύμφωνα με τους κανόνες μετατροπής ζωνών ώρας στα templates.

## Γράφοντας δικάς σας template tags

Τα tags είναι περισσότερο πολύπλοκα από τα φίλτρα, επειδή τα tags μπορούν να κάνουν τα πάντα. Το Django παρέχει έναν αριθμό από συντομεύσεις που κάνει τη συγγραφή των περισσότερων τύπων tags ευκολότερη. Πρώτα θα εξερευνήσουμε αυτές τις συντομεύσεις και ύστερα θα εξηγήσουμε πως να γράψετε ένα tag από την αρχή για τις περιπτώσεις που κάποια συντόμευση δεν σας καλύπτει.

### Απλά tags

django.template.Library.simple_tag()

Πολλά template tags παίρνουν έναν αριθμό από παραμέτρους – strings ή μεταβλητές template – και επιστρέφουν ένα αποτέλεσμα αφού κάνουν κάποια επεξεργασία βασιζόμενα στις παραμέτρους και σε τυχόν εξωτερικές πληροφορίες. Για παράδειγμα, ένα tag με το όνομα `current_time` μπορεί να δεχτεί ένα string μορφοποίησης και να επιστρέψει την ώρα ως string, μορφοποιημένη σύμφωνα με αυτό που περάστηκε.

Για να διευκολύνουμε τη δημιουργία αυτού του τύπου των tags, το Django παρέχει μια βοηθητική συνάρτηση, τη `simple_tag`. Αυτή η συνάρτηση, η οποία είναι μέθοδος της κλάσης `django.template.Library`, δέχεται μια συνάρτηση η οποία δέχεται παραμέτρους ανεξαρτήτως αριθμού, την κάνει wrap σε μια render συνάρτηση και άλλων απαραίτητων στοιχείων που αναφέρθηκαν παραπάνω και την κάνει register με το σύστημα του template.

Η συνάρτηση μας `current_time` θα μπορούσε, επομένως, να γραφτεί κάπως έτσι:

    import datetime
    from django import template

    register = template.Library()

    @register.simple_tag
    def current_time(format_string):
        return datetime.datetime.now().strftime(format_string)

Μερικά πράγματα που πρέπει να σημειώσετε για την βοηθητική συνάρτηση `simple_tag`:

- Ο έλεγχος του αριθμού των απαιτούμενων παραμέτρων κλπ, έχει ήδη γίνει τη στιγμή που η συνάρτηση μας καλείται, οπότε δεν θα χρειαστεί να γίνει κάτι τέτοιο.

- The quotes around the argument (if any) have already been stripped away, so we receive a plain string.

- Αν η παράμετρος ήταν μια μεταβλητή template, τότε η συνάρτηση μας θα περνούσε την τιμή της μεταβλητής και όχι την ίδια τη μεταβλητή.

Σε αντίθεση με λειτουργίες άλλων tags, το `simple_tag` περνάει την έξοδο του μέσα από τη συνάρτηση `conditional_escape()` αν το template context είναι σε λειτουργία autoescape, για να εξασφαλίσει σωστή HTML και για να σας προστατέψει από τρωτά σημεία του XSS.

Αν δεν επιθυμείτε κάποιο πρόσθετο escaping, θα χρειαστεί να χρησιμοποιήσετε τη συνάρτηση `mark_safe()` αν είστε απόλυτα σίγουροι ότι ο κώδικας σας δεν είναι ευάλωτος σε XSS επιθέσεις. Για το χτίσιμο μικρών HTML αποσπασμάτων, η χρήση της συνάρτησης `format_html()` προτείνεται έναντι της `mark_safe()`.

Αν το template tag χρειάζεται να έχει πρόσβαση στο τρέχων template context, μπορείτε να χρησιμοποιήσετε την παράμετρο `takes_context` όταν κάνετε register το tag σας:

    @register.simple_tag(takes_context=True)
    def current_time(context, format_string):
        timezone = context["timezone"]
        return your_get_current_time_method(timezone, format_string)

Σημειώστε ότι η πρώτη παράμετρος πρέπει να ονομαστεί `context`.

Για περισσότερες πληροφορίες σχετικά με τη λειτουργία της παραμέτρου `takes_context`, δείτε στην ενότητα inclusion tags.

Αν χρειαστεί να μετονομάσετε το tag σας, μπορείτε να του δώσετε ένα δικό σας όνομα:

    register.simple_tag(lambda x: x - 1, name="minusone")

    @register.simple_tag(name="minustwo")
    def some_function(value):
        return value - 2

Οι συναρτήσεις `simple_tag` μπορούν να δεχτούν απεριόριστο αριθμό από positional ή keyword arguments. Για παράδειγμα:

    @register.simple_tag
    def my_tag(a, b, *args, **kwargs):
        warning = kwargs["warning"]
        profile = kwargs["profile"]
        ...
        return ...

Μετά, στο template, όλα τα arguments διαχωρισμένα με κενά (space character) μπορούν να περαστούν στο template tag. Όπως και στην Python, οι τιμές των keyword arguments ορίζονται με το σύμβολο της ισότητας (»`=`») και πρέπει να δηλωθούν μετά τα positional arguments. Για παράδειγμα:

    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}

Είναι πιθανόν να αποθηκεύσετε το αποτέλεσμα ενός tag σε μια template μεταβλητή παρά να την εμφανίζετε απ” ευθείας στο template σας. Αυτό γίνεται χρησιμοποιώντας το argument `as` ακολουθούμενο από το όνομα της template μεταβλητής όπου και θα αποθηκευτεί. Με αυτό τον τρόπο μπορείτε να εκτυπώσετε την τιμή αυτή οπουδήποτε αλλού στο template σας ή και με κάποια επιπλέον επεξεργασία (είτε σε αλλο tag ή φίλτρο):

    {% current_time "%Y-%m-%d %I:%M %p" as the_time %}
    <p>The time is {{ the_time }}.</p>

### Simple block tags

New in Django 5.2.

django.template.Library.simple_block_tag()

When a section of rendered template needs to be passed into a custom tag, Django provides the `simple_block_tag` helper function to accomplish this. Similar to `simple_tag()`, this function accepts a custom tag function, but with the additional `content` argument, which contains the rendered content as defined inside the tag. This allows dynamic template sections to be easily incorporated into custom tags.

For example, a custom block tag which creates a chart could look like this:

    from django import template
    from myapp.charts import render_chart

    register = template.Library()

    @register.simple_block_tag
    def chart(content):
        return render_chart(source=content)

The `content` argument contains everything in between the `{% chart %}` and `{% endchart %}` tags:

    {% chart %}
      digraph G {
          label = "Chart for {{ request.user }}"
          A -> {B C}
      }
    {% endchart %}

If there are other template tags or variables inside the `content` block, they will be rendered before being passed to the tag function. In the example above, `request.user` will be resolved by the time `render_chart` is called.

Block tags are closed with `end{name}` (for example, `endchart`). This can be customized with the `end_name` parameter:

    @register.simple_block_tag(end_name="endofchart")
    def chart(content):
        return render_chart(source=content)

Which would require a template definition like this:

    {% chart %}
      digraph G {
          label = "Chart for {{ request.user }}"
          A -> {B C}
      }
    {% endofchart %}

A few things to note about `simple_block_tag`:

- The first argument must be called `content`, and it will contain the contents of the template tag as a rendered string.

- Variables passed to the tag are not included in the rendering context of the content, as would be when using the `{% with %}` tag.

Just like simple_tag, `simple_block_tag`:

- Validates the quantity and quality of the arguments.

- Strips quotes from arguments if necessary.

- Escapes the output accordingly.

- Supports passing `takes_context=True` at registration time to access context. Note that in this case, the first argument to the custom function must be called `context`, and `content` must follow.

- Supports renaming the tag by passing the `name` argument when registering.

- Supports accepting any number of positional or keyword arguments.

- Supports storing the result in a template variable using the `as` variant.

Content Escaping

`simple_block_tag` behaves similarly to `simple_tag` regarding auto-escaping. For details on escaping and safety, refer to `simple_tag`. Because the `content` argument has already been rendered by Django, it is already escaped.

#### A complete example

Consider a custom template tag that generates a message box that supports multiple message levels and content beyond a simple phrase. This could be implemented using a `simple_block_tag` as follows:

`testapp/templatetags/testapptags.py`

    from django import template
    from django.utils.html import format_html

    register = template.Library()

    @register.simple_block_tag(takes_context=True)
    def msgbox(context, content, level):
        format_kwargs = {
            "level": level.lower(),
            "level_title": level.capitalize(),
            "content": content,
            "open": " open" if level.lower() == "error" else "",
            "site": context.get("site", "My Site"),
        }
        result = """
        <div class="msgbox {level}">
          <details{open}>
            <summary>
              <strong>{level_title}</strong>: Please read for <i>{site}</i>
            </summary>
            <p>
              {content}
            </p>
          </details>
        </div>
        """
        return format_html(result, **format_kwargs)

When combined with a minimal view and corresponding template, as shown here:

`testapp/views.py`

    from django.shortcuts import render

    def simpleblocktag_view(request):
        return render(request, "test.html", context={"site": "Important Site"})

`testapp/templates/test.html`

    {% extends "base.html" %}

    {% load testapptags %}

    {% block content %}

      {% msgbox level="error" %}
        Please fix all errors. Further documentation can be found at
        <a href="http://example.com">Docs</a>.
      {% endmsgbox %}

      {% msgbox level="info" %}
        More information at: <a href="http://othersite.com">Other Site</a>/
      {% endmsgbox %}

    {% endblock %}

The following HTML is produced as the rendered output:

    <div class="msgbox error">
      <details open>
        <summary>
          <strong>Error</strong>: Please read for <i>Important Site</i>
        </summary>
        <p>
          Please fix all errors. Further documentation can be found at
          <a href="http://example.com">Docs</a>.
        </p>
      </details>
    </div>

    <div class="msgbox info">
      <details>
        <summary>
          <strong>Info</strong>: Please read for <i>Important Site</i>
        </summary>
        <p>
          More information at: <a href="http://othersite.com">Other Site</a>
        </p>
      </details>
    </div>

### Inclusion tags

django.template.Library.inclusion_tag()

Ένας άλλο κοινός τύπος ενός template tag είναι ο τύπος που εμφανίζει κάποια δεδομένα κάνοντας rendering ένα άλλο template. Για παράδειγμα, το admin interface του Django χρησιμοποιεί δικά του template tags για να εμφανίζει τα κουμπιά στο κάτω μέρος της σελίδας της φόρμας που προσθέτετε ή αλλάζετε κάποιο object. Αυτά τα κουμπιά δείχουν παντού το ίδιο, αλλά το target του link αλάζει ανάλογα το object που επεξεργάζεται εκείνη τη στιγμή – αποτελούν λοιπόν μια τέλεια περίπτωση για τη χρήση μικρών template το οποίο είναι γεμάτο λεπτομέρειες από το τρέχων object. (Στην περίπτωση του admin, το tag ονομάζεται `submit_row`.)

Αυτού του είδους τα tags ονομάζονται «inclusion tags».

Η συγγραφή των inclusion tags φαίνεται καλύτερα μέσα από ένα παράδειγμα. Ας γράψουμε ένα tag το οποίο εξάγει μια λίστα από επιλογές για ένα δοθέν `Poll` object, το οποίο δημιουργήθηκε μέσα από το δεύτερο μέρος του οδηγού μας. Θα χρησιμοποιήσουμε το tag ως εξής:

    {% show_results poll %}

…και η έξοδος του θα μοιάζει κάπως έτσι:

    <ul>
      <li>First choice</li>
      <li>Second choice</li>
      <li>Third choice</li>
    </ul>

Πρώτα, πρέπει να ορίσετε τη συνάρτηση η οποία παίρνει το argument και επιστρέφει ένα dictionary από δεδομένα. Το σημαντικό εδώ είναι ότι το μόνο που χρειάζεται να επιστρέψουμε είναι ένα dictionary, όχι κάτι περίπλοκο. Αυτό θα χρησιμοποιηθεί ως template context για το κομμάτι του template. Παράδειγμα:

    def show_results(poll):
        choices = poll.choice_set.all()
        return {"choices": choices}

Next, create the template used to render the tag’s output. This template is a fixed feature of the tag: the tag writer specifies it, not the template designer. Following our example, the template is very short:

    <ul>
    {% for choice in choices %}
        <li> {{ choice }} </li>
    {% endfor %}
    </ul>

Τώρα, δημιουργήστε και κάντε register το inclusion tag καλώντας τη μέθοδο `inclusion_tag()` σε ένα object της κλάσης `Library`. Ακολουθώντας το παράδειγμα μας, αν το παραπάνω template βρίσκεται σε ένα αρχείο με το όνομα `results.html` σε κάποιο φάκελο που είναι ανιχνεύσιμος από τον template loader, θα κάναμε register το tag κάπως έτσι:

    # Here, register is a django.template.Library instance, as before
    @register.inclusion_tag("results.html")
    def show_results(poll): ...

Εναλλακτικά μπορείτε να κάνετε register το inclusion tag χρησιμοποιώντας ένα instance της κλάσης :class:`django.template.

    from django.template.loader import get_template

    t = get_template("results.html")
    register.inclusion_tag(t)(show_results)

…όταν πρωτο-δημιουργείτε τη συνάρτηση.

Μερικές φορές, τα inclusion tags μπορεί να χρειάζονται έναν μεγάλο αριθμό από παραμέτρους, κάνοντας δύσκολο για τους συντάκτες των templates να περάσουν όλες τις παραμέτρους και να θυμούνται τη σειρά τους. Για την αντιμετώπιση αυτού του προβλήματος, το Django προσφέρει την επιλογή `takes_context` τύπου boolean για inclusion tags. Αν τη θέσετε ``True `` όταν δημιουργείτε τη συνάρτηση, το tag δεν θα έχει απαιτούμενες παραμέτρους και η Python συνάρτηση του tag θα έχει μόνο μια παράμετρο – το template context όπως είναι όταν κλήθηκε το tag.

Για παράδειγμα, ας υποθέσουμε ότι γράφετε ένα inclusion tag το οποίο θα χρησιμοποιείται πάντα σε κάποιο context που περιέχει τις μεταβλητές `home_link` και `home_title` οι οποίες θα οδηγούν πίσω στην αρχική σελίδα. Η Python συνάρτηση του tag θα δείχνει κάπως έτσι:

    @register.inclusion_tag("link.html", takes_context=True)
    def jump_link(context):
        return {
            "link": context["home_link"],
            "title": context["home_title"],
        }

Σημειώστε ότι η πρώτη παράμετρος της συνάρτησης πρέπει να ονομάζεται `context`.

Στη γραμμή `register.inclusion_tag()`, προσδιορίσαμε το keyword argument `takes_context=True` και το όνομα του template. Το template `link.html` θα δείχνει κάπως έτσι:

    Jump directly to <a href="{{ link }}">{{ title }}</a>.

Έπειτα, κάθε φορά που θέλετε να χρησιμοποιήσετε αυτό το tag, φορτώστε τη βιβλιοθήκη (`{% load <όνομα_αρχείου> %}`) και καλέσετε τη χωρίς ορίσματα, ως εξής:

    {% jump_link %}

Σημειώστε ότι, όταν χρησιμοποιείτε το `takes_context=True`, δεν χρειάζεται να περάσετε παραμέτρους μέσα στο template tag. Αυτόματα, έχει πρόσβαση στο template context.

Η προεπιλεγμένη τιμή της παραμέτρου `takes_context` είναι `False`. Όταν τη θέτετε `True`, περνάει το context object μέσα στο tag, όπως σε αυτό το παράδειγμα. Αυτή είναι η μόνη διαφορά μεταξύ αυτής της περίπτωσης και του προηγούμενου `inclusion_tag` παραδείγματος.

Οι συναρτήσεις των `inclusion_tag` μπορούν να δεχτούν αναρίθμητα positional ή keyword arguments. Για παράδειγμα:

    @register.inclusion_tag("my_template.html")
    def my_tag(a, b, *args, **kwargs):
        warning = kwargs["warning"]
        profile = kwargs["profile"]
        ...
        return ...

Μετά, στο template, όλα τα arguments διαχωρισμένα με κενά (space character) μπορούν να περαστούν στο template tag. Όπως και στην Python, οι τιμές των keyword arguments ορίζονται με το σύμβολο της ισότητας (»`=`») και πρέπει να δηλωθούν μετά τα positional arguments. Για παράδειγμα:

    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}

### Παραμετροποιήσιμα template tags για προχωρημένους

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

### Μια γρήγορη ματιά

Το σύστημα του template λειτουργεί ακολουθώντας μια διαδικασία δύο σταδίων: compiling και rendering. Για να ορίσετε ένα δικό σας template tag, πρέπει να προσδιορίσετε πως δουλεύει το compilation και πως το rendering.

When Django compiles a template, it splits the raw template text into nodes. Each node is an instance of `django.template.Node` and has a `render()` method. A compiled template is a list of `Node` objects. When you call `render()` on a compiled template object, the template calls `render()` on each `Node` in its node list, with the given context. The results are all concatenated together to form the output of the template.

Επομένως, για να ορίσετε ένα δικό σας template tag, προσδιορίζετε πως ένα template tag μετατρέπεται σε ένα `Node` (η συνάρτηση του compilation) και τι κάνει η μέθοδος `render()` του node.

### Γράφοντας τη συνάρτηση του compilation

Για κάθε template tag που βρίσκει ο template parser, καλείται μια Python συνάρτηση με τα περιεχόμενα του tag και το ίδιο το parser object. Αυτή η συνάρτηση είναι υπεύθυνη να επιστρέψει ένα instance του `Node` βασιζόμενη στα περιεχόμενα του tag.

For example, let’s write a full implementation of our template tag, `{% current_time %}`, that displays the current date/time, formatted according to a parameter given in the tag, in `strftime()` syntax. It’s a good idea to decide the tag syntax before anything else. In our case, let’s say the tag should be used like this:

    <p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>

Ο parser για αυτή τη συνάρτηση θα πρέπει να παίρνει την παράμετρο και να δημιουργεί ένα object του `Node`:

    from django import template

    def do_current_time(parser, token):
        try:
            # split_contents() knows not to split quoted strings.
            tag_name, format_string = token.split_contents()
        except ValueError:
            raise template.TemplateSyntaxError(
                "%r tag requires a single argument" % token.contents.split()[0]
            )
        if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
            raise template.TemplateSyntaxError(
                "%r tag's argument should be in quotes" % tag_name
            )
        return CurrentTimeNode(format_string[1:-1])

Σημειώσεις

- Η παράμετρος `parser` είναι ένα template parser object. Δεν το χρειαζόμαστε σε αυτό το παράδειγμα.

- Το `token.contents` είναι ένα string των περιεχομένων του tag. Στο δικό μας παράδειγμα, είναι το `'current_time "%Y-%m-%d %I:%M %p"'`.

- Η μέθοδος `token.split_contents()` διαχωρίζει, με βάση τον χαρακτήρα του διαστήματος, τις παραμέτρους ενώ παράλληλα κρατάει τα strings, ανέπαφα, με τον χαρακτήρα που έχουν δηλωθεί (`’...’` ή `”...”`). Αν χρησιμοποιούνταν η `token.contents.split()` τότε θα διαχώριζε όλα τα περιεχόμενα του tag με βάση το χαρακτήρα του διαστήματος, συμπεριλαμβανομένων και των κενών που υπάρχουν στο string. Αυτό βέβαια δεν είναι βολικό. Καλό είναι, λοιπόν, να χρησιμοποιείτε τη μέθοδο `token.split_contents()`.

- Η συνάρτηση που φτιάξαμε, είναι υπεύθυνη να κάνει raise το exception `django.template.TemplateSyntaxError`, με βοηθητικά μηνύματα για τυχόν συντακτικά λάθη.

- The `TemplateSyntaxError` exceptions use the `tag_name` variable. Don’t hardcode the tag’s name in your error messages, because that couples the tag’s name to your function. `token.contents.split()[0]` will always be the name of your tag – even when the tag has no arguments.

- The function returns a `CurrentTimeNode` with everything the node needs to know about this tag. In this case, it passes the argument – `"%Y-%m-%d %I:%M %p"`. The leading and trailing quotes from the template tag are removed in `format_string[1:-1]`.

- Το parsing γίνεται σε πολύ χαμηλό επίπεδο. Οι Django developers έχουν πειραματιστεί στο να γράφουν μικρά frameworks πάνω σε αυτό το σύστημα parsing, χρησιμοποιώντας τεχνικές όπως οι γραμματικές EBNF (EBNF grammars), αλλά αυτά τα πειράματα έκαναν την μηχανή του template πολύ αργή. Γίνεται, λοιπόν, σε χαμηλό επίπεδο επειδή αυτό είναι γρηγορότερο.

### Γράφοντας τον renderer

Το δεύτερο βήμα για τη δημιουργία δικών μας tags είναι ο ορισμός της subclass της `Node` η οποία έχει μια μέθοδο `render()`.

Συνεχίζοντας το παραπάνω παράδειγμα, χρειάζεται να ορίσουμε το `CurrentTimeNode`:

    import datetime
    from django import template

    class CurrentTimeNode(template.Node):
        def __init__(self, format_string):
            self.format_string = format_string

        def render(self, context):
            return datetime.datetime.now().strftime(self.format_string)

Σημειώσεις

- Η μέθοδος `__init__()` λαμβάνει τη μεταβλητή `format_string` από τη συνάρτηση `do_current_time()`. Πάντα να περνάτε τυχόν options/parameters/arguments σε ένα `Node` μέσω της `__init__()` μεθόδου του.

- Η μέθοδος `render()` είναι εκεί όπου γίνονται όλα.

- Η `render()` θα πρέπει γενικά να αποτυγχάνει σιωπηρά, ειδικά σε παραγωγικό περιβάλλον (production environment). Ωστόσο, σε μερικές περιπτώσεις, ειδικά αν το `context.template.engine.debug` είναι `True`, αυτή η μέθοδος θα πρέπει να κάνει raise ένα exception για να κάνει το debugging ευκολότερο. Για παράδειγμα, πολλά προεγκατεστημένα tags του Django κάνουν raise το `django.template.TemplateSyntaxError` αν λάβουν λάθος αριθμό ή τύπο παραμέτρων.

Τελικώς, αυτή η αποσύνδεση μεταξύ του compilation και του rendering έχει ως αποτέλεσμα ένα αποδοτικό σύστημα template, επειδή ένα template μπορεί να κάνει render πολλαπλά contexts χωρίς να χρειαστεί να γίνει parsed πολλές φορές.

### Εκτιμήσεις σχετικά με το auto-escaping

Η έξοδος των template tags δεν περνούν αυτόματα μέσα από auto-escaping φίλτρα (εκτός της μεθόδου `simple_tag()`, όπως περιγράφηκε παραπάνω). Ωστόσο, υπάρχουν ακόμη μερικά πράγματα που πρέπει να έχετε στο νου σας όταν γράφετε ένα template tag.

If the `render()` method of your template tag stores the result in a context variable (rather than returning the result in a string), it should take care to call `mark_safe()` if appropriate. When the variable is ultimately rendered, it will be affected by the auto-escape setting in effect at the time, so content that should be safe from further escaping needs to be marked as such.

Επίσης, αν το template tag σας δημιουργεί ένα καινούργιο context για να εκτελέσει κάποιο sub-rendering, θέστε το attribute του auto-escape στην τιμή του τρέχοντος context. Η μέθοδος `__init__` για την κλάση `Context` δέχεται μια παράμετρο με το όνομα `autoescape`, την οποία μπορείτε να χρησιμοποιήσετε για αυτό το σκοπό. Για παράδειγμα:

    from django.template import Context

    def render(self, context):
        # ...
        new_context = Context({"var": obj}, autoescape=context.autoescape)
        # ... Do something with new_context ...

Αυτό δεν είναι μια συνηθισμένη περίπτωση, αλλά είναι χρήσιμο να το ξέρετε αν κάνετε rendering ένα template εσείς οι ίδιοι.

    def render(self, context):
        t = context.template.engine.get_template("small_fragment.html")
        return t.render(Context({"var": obj}, autoescape=context.autoescape))

Αν δεν περνάγαμε την τιμή του `context.autoescape` στο νέο μας `Context` σε αυτό το παράδειγμα, τα αποτελέσματα θα γινόντουσαν πάντα αυτόματα escaped, κάτι το οποίο μπορεί να μην ήταν η επιθυμητική συμπεριφορά αν το template tag χρησιμοποιούταν μέσα στο block tag `{% autoescape off %}`.

### Εκτιμήσεις σχετικά με την ασφάλεια των threads

Όταν ένα node γίνει parsed, η `render` μέθοδος του μπορεί να καλεστεί πολλές φορές. Επειδή το Django, μερικές φορές, τρέχει σε περιβάλλοντα multi-threaded, ένα απλό node μπορεί ταυτοχρόνως να γίνει rendering με διαφορετικά contexts ως response για δύο ξεχωριστά requests. Επομένως, είναι σημαντικό να εξασφαλίσετε ότι τα template tags σας είναι thread safe.

Για να το εξασφαλίσετε αυτό, δεν θα πρέπει να αποθηκεύετε ποτέ τις πληροφορίες της κατάστασης στο ίδιο το node. Για παράδειγμα, το Django παρέχει ένα προεγκατεστημένο template tag `cycle` το οποίο ανατρέχει μια λίστα από δοθέντα strings κάθε φορά που γίνεται rendered:

    {% for o in some_list %}
        <tr class="{% cycle 'row1' 'row2' %}">
            ...
        </tr>
    {% endfor %}

Μια απλοϊκή υλοποίηση του `CycleNode` θα έμοιαζε κάπως έτσι:

    import itertools
    from django import template

    class CycleNode(template.Node):
        def __init__(self, cyclevars):
            self.cycle_iter = itertools.cycle(cyclevars)

        def render(self, context):
            return next(self.cycle_iter)

Αλλά, ας υποθέσουμε ότι την ίδια χρονική στιγμή, δύο templates κάνουν rendering το κομμάτι του template παραπάνω:

1. Το Thread 1 πραγματοποιεί την πρώτη επανάληψη του βρόγχου, η μέθοδος `CycleNode.render()` επιστρέφει “row1”

2. Το Thread 2 πραγματοποιεί την πρώτη επανάληψη του βρόγχου, η μέθοδος `CycleNode.render()` επιστρέφει “row2”

3. Το Thread 1 πραγματοποιεί την δεύτερη επανάληψη του βρόγχου, η μέθοδος `CycleNode.render()` επιστρέφει “row1”

4. Το Thread 2 πραγματοποιεί την δεύτερη επανάληψη του βρόγχου, η μέθοδος `CycleNode.render()` επιστρέφει “row2”

The CycleNode is iterating, but it’s iterating globally. As far as Thread 1 and Thread 2 are concerned, it’s always returning the same value. This is not what we want!

Για την αποφυγή τέτοιων καταστάσεων, το Django παρέχει ένα `render_context` το οποίο σχετίζεται με το `context` του template το οποίο γίνεται εκείνη τη στιγμή rendered. Το `render_context` συμπεριφέρεται όπως ένα Python dictionary και θα πρέπει να χρησιμοποιείται για να αποθηκεύονται οι καταστάσεις του `Node` μεταξύ κλήσεων της μεθόδου `render`.

Ας αλλάξουμε την υλοποίηση της κλάσης `CycleNode` για να χρησιμοποιεί το `render_context`:

    class CycleNode(template.Node):
        def __init__(self, cyclevars):
            self.cyclevars = cyclevars

        def render(self, context):
            if self not in context.render_context:
                context.render_context[self] = itertools.cycle(self.cyclevars)
            cycle_iter = context.render_context[self]
            return next(cycle_iter)

Σημειώστε ότι δεν είναι καθόλου λάθος να αποθηκεύετε ως attribute καθολικές πληροφορίες οι οποίες δεν αλλάζουν κατά τη διάρκεια της ζωής ενός `Node`. Στην περίπτωση του `CycleNode`, το argument `cyclevars` δεν αλλάζει μετά την αρχικοποίηση του `Node`, οπότε δεν χρειάζεται να το βάλουμε μέσα στο `render_context`. Αλλά η πληροφορία της κατάστασης η οποία είναι συγκεκριμένη στο template το οποίο γίνεται rendered, όπως η τρέχουσα επανάληψη βρόγχου του `CycleNode`, θα πρέπει να αποθηκευτεί μέσα στο `render_context`.

Σημείωση

Σημειώστε το πως χρησιμοποιήσαμε το `self` για να έχουμε πρόσβαση σε συγκεκριμένη πληροφορία του `CycleNode` μέσα από το `render_context`. Μπορεί να υπάρχουν πολλαπλά `CycleNodes` μέσα σε ένα template, οπότε πρέπει να είμαστε προσεκτικοί να μην αφαιρέσουμε την πληροφορία της κατάστασης άλλου node. Ο πιο εύκολος τρόπος για να επιτευχθεί κάτι τέτοιο είναι πάντα να χρησιμοποιείτε το `self` ως το key μέσα στο `render_context`. Αν παρακολουθείτε πολλές ματαβλητές κατάστασης (state variables), κάντε το `render_context[self]` ένα dictionary.

### Κάνοντας register το tag

Finally, register the tag with your module’s `Library` instance, as explained in writing custom template tags above. Example:

    register.tag("current_time", do_current_time)

Η μέθοδος `tag()` λαμβάνει δύο arguments:

1. Το όνομα του template tag – ένα string. Αν αυτό παραλειφθεί, θα χρησιμοποιηθεί το όνομα της συνάρτησης του compilation.

2. Τη συνάρτηση του φίλτρου – μια Python συνάρτηση (όχι το όνομα της συνάρτησης ως string).

Όπως και με το registration των φιλτρών, μπορείτε να χρησιμοποιήσετε έναν decorator:

    @register.tag(name="current_time")
    def do_current_time(parser, token): ...

    @register.tag
    def shout(parser, token): ...

Αν παραλείψετε την παράμετρο `name`, όπως στο δεύτερο παράδειγμα παραπάνω, το Django θα χρησιμοποιήσει το όνομα της συνάρτησης ως το όνομα του tag.

### Περνώντας template μεταβλητές στο tag

Όπως γνωρίζετε μπορείτε να περάσετε οποιοδήποτε αριθμό από παραμέτρους σε ένα template tag και χρησιμοποιώντας τη μέθοδο `token.split_contents()` οι παράμετροι θα είναι διαθέσιμες σε εσάς ως strings. Ωστόσο, χρειάζεται λίγη περισσότερη δουλειά για να περάσετε δυναμικό περιεχόμενο (μια template μεταβλητή) στο template tag ως παράμετρο.

Ενώ τα προηγούμενα παραδείγματα μορφοποιούσαν την τρέχουσα ώρα σε ένα string και επέστρεφαν το string, υποθέστε τώρα ότι θέλετε να περάσετε ένα `DateTimeField` από ένα object και θέλετε το template tag να μορφοποιήσει αυτό το object (αυτή την ημερομηνία-ώρα, αν θέλετε):

    <p>This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.</p>

Αρχικά, η μέθοδος `token.split_contents()` θα επιστρέψει τρεις τιμές:

1. Το όνομα του tag, `format_time`.

2. Το string `'blog_entry.date_updated'` (χωρίς τα μονά εισαγωγικά).

3. Το string που θα χρησιμοποιηθεί για την μορφοποίηση `'"%Y-%m-%d %I:%M %p"'`. Η επιστρεφόμενη τιμή από τη μέθοδο `split_contents()` θα συμπεριλάβει τα διπλά εισαγωγικά στην αρχή και το τέλος των strings (όπως ακριβώς εισήχθησαν στο tag μέσα στο template).

Τώρα το tag θα αρχίσει να μοιάζει κάπως έτσι:

    from django import template

    def do_format_time(parser, token):
        try:
            # split_contents() knows not to split quoted strings.
            tag_name, date_to_be_formatted, format_string = token.split_contents()
        except ValueError:
            raise template.TemplateSyntaxError(
                "%r tag requires exactly two arguments" % token.contents.split()[0]
            )
        if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
            raise template.TemplateSyntaxError(
                "%r tag's argument should be in quotes" % tag_name
            )
        return FormatTimeNode(date_to_be_formatted, format_string[1:-1])

You also have to change the renderer to retrieve the actual contents of the `date_updated` property of the `blog_entry` object. This can be accomplished by using the `Variable()` class in `django.template`.

To use the `Variable` class, instantiate it with the name of the variable to be resolved, and then call `variable.resolve(context)`. So, for example:

    class FormatTimeNode(template.Node):
        def __init__(self, date_to_be_formatted, format_string):
            self.date_to_be_formatted = template.Variable(date_to_be_formatted)
            self.format_string = format_string

        def render(self, context):
            try:
                actual_date = self.date_to_be_formatted.resolve(context)
                return actual_date.strftime(self.format_string)
            except template.VariableDoesNotExist:
                return ""

Η αναζήτηση της μεταβλητής (variable resolution) θα κάνει throw ένα exception `VariableDoesNotExist` αν δεν μπορεί να γίνει resolve το string (`'blog_entry.date_updated'`) που περάστηκε στο τρέχων context της σελίδας.

### Θέτοντας μια μεταβλητή στο context

The above examples output a value. Generally, it’s more flexible if your template tags set template variables instead of outputting values. That way, template authors can reuse the values that your template tags create.

To set a variable in the context, use dictionary assignment on the context object in the `render()` method. Here’s an updated version of `CurrentTimeNode` that sets a template variable `current_time` instead of outputting it:

    import datetime
    from django import template

    class CurrentTimeNode2(template.Node):
        def __init__(self, format_string):
            self.format_string = format_string

        def render(self, context):
            context["current_time"] = datetime.datetime.now().strftime(self.format_string)
            return ""

Σημειώστε ότι η μέθοδος `render()` επιστρέφει ένα κενό string. Η `render()` θα πρέπει πάντα να επιστρέφει ένα string. Αν το μόνο που κάνει ένα template tag είναι να ορίζει μια μεταβλητή, τότε η `render()` θα πρέπει να επιστρέφει ένα κενό string.

Παρακάτω φαίνεται πως μπορείτε να χρησιμοποιείτε τη νέα αυτή έκδοση του tag:

    {% current_time "%Y-%m-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>

Το variable scope του context

Οποιαδήποτε μεταβλητή οριστεί στο context, θα είναι διαθέσιμη στο ίδιο `block` του template στο οποίο ορίστηκε. Αυτή η συμπεριφορά είναι σκόπιμη. Παρέχει ένα scope για μεταβλητές ούτως ώστε να μην συγκρούονται με κάποιο context από άλλα blocks.

But, there’s a problem with `CurrentTimeNode2`: The variable name `current_time` is hardcoded. This means you’ll need to make sure your template doesn’t use `{{ current_time }}` anywhere else, because the `{% current_time %}` will blindly overwrite that variable’s value. A cleaner solution is to make the template tag specify the name of the output variable, like so:

    {% current_time "%Y-%m-%d %I:%M %p" as my_current_time %}
    <p>The current time is {{ my_current_time }}.</p>

Για να το κάνετε αυτό, θα χρειαστεί να αλλάξετε και τη συνάρτηση του compilation και την κλάση `Node`, ως εξής:

    import re

    class CurrentTimeNode3(template.Node):
        def __init__(self, format_string, var_name):
            self.format_string = format_string
            self.var_name = var_name

        def render(self, context):
            context[self.var_name] = datetime.datetime.now().strftime(self.format_string)
            return ""

    def do_current_time(parser, token):
        # This version uses a regular expression to parse tag contents.
        try:
            # Splitting by None == splitting by spaces.
            tag_name, arg = token.contents.split(None, 1)
        except ValueError:
            raise template.TemplateSyntaxError(
                "%r tag requires arguments" % token.contents.split()[0]
            )
        m = re.search(r"(.*?) as (\w+)", arg)
        if not m:
            raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name)
        format_string, var_name = m.groups()
        if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
            raise template.TemplateSyntaxError(
                "%r tag's argument should be in quotes" % tag_name
            )
        return CurrentTimeNode3(format_string[1:-1], var_name)

Η διαφορά εδώ είναι ότι η μέθοδος `do_current_time()` παίρνει το format μορφοποίησης και το όνομα της μεταβλητής και δίνει και τα δύο στην κλάση `CurrentTimeNode3`.

Τέλος, αν θέλετε μόνο να έχετε μια απλή σύνταξη για το δικό σας template tag που αλληλεπιδρά με το context και το ενημερώνει, σκεφτείτε να χρησιμοποιήσετε τη συντόμευση `simple_tag()`, η οποία υποστηρίζει τον ορισμό μεταβλητής της εξόδου ενός tag (μέσω της λέξη-κλειδί `as`, όπως είδαμε παραπάνω).

### Κάνοντας parsing μέχρι να βρεθεί κάποιο άλλο block tag

Τα template tags μπορούν να δουλέψουν αλυσιδωτά. Για παράδειγμα, το στάνταρντ tag `{% comment %}` αγνοεί τα πάντα μέχρι να βρει το tag `{% endcomment %}`. Για να δημιουργήσετε ένα template tag όπως αυτό, χρησιμοποιήστε τη μέθοδο `parser.parse()` μέσα στη συνάρτηση του compilation.

Εδώ φαίνεται η υλοποίηση της απλοποιημένης έκδοσης του tag `{% comment %}`:

    def do_comment(parser, token):
        nodelist = parser.parse(("endcomment",))
        parser.delete_first_token()
        return CommentNode()

    class CommentNode(template.Node):
        def render(self, context):
            return ""

Σημείωση

Η υλοποίηση του tag `{% comment %}` διαφέρει ελαφρώς στο ότι επιτρέπει την εμφάνιση broken template tags μεταξύ του `{% comment %}` και του `{% endcomment %}`. Το κάνει αυτό καλώντας τη μέθοδο `parser.skip_past('endcomment')` αντί της `parser.parse(('endcomment',))` ακολουθούμενη από τη μέθοδο `parser.delete_first_token()`, με αποτέλεσμα την αποφυγή της δημιουργίας μιας λίστας από nodes.

`parser.parse()` takes a tuple of names of block tags to parse until. It returns an instance of `django.template.NodeList`, which is a list of all `Node` objects that the parser encountered before it encountered any of the tags named in the tuple.

Στο `"nodelist = parser.parse(('endcomment',))"` στο παραπάνω παράδειγμα, η `nodelist` είναι μια λίστα από όλα τα nodes μεταξύ του `{% comment %}` και του `{% endcomment %}`, χωρίς τα ίδια τα `{% comment %}` και `{% endcomment %}`.

Αφού καλεστεί η `parser.parse()`, ο parser δεν έχει «καταναλώσει» ακόμα το `{% endcomment %}` tag, οπότε ο κώδικας χρειάζεται να καλέσει ρητώς τη μέθοδο `parser.delete_first_token()`.

`CommentNode.render()` returns an empty string. Anything between `{% comment %}` and `{% endcomment %}` is ignored.

### Κάνοντας parsing μέχρι να βρεθεί κάποιο άλλο block tag και αποθήκευση περιεχομένων

Στο προηγούμενο παράδειγμα, η συνάρτηση `do_comment()` απέρριπτε τα πάντα μεταξύ του blck tag `{% comment %}` και `{% endcomment %}`, επειδή θεωρούνταν σχόλια και αυτά δεν γινόντουσαν render στην τελική σελίδα που θα έβλεπε ο χρήστης. Αντί να γίνεται αυτό, μπορείτε να κάνετε κάτι με τον κώδικα μεταξύ των block tags.

Για παράδειγμα, εδώ φαίνεται ένα παραμετροποιήσιμο template tag, με το όνομα `{% upper %}`, το οποίο μετατρέπει σε κεφαλαία γράμματα όλους τους χαρακτήρες μεταξύ του ίδιου και του `{% endupper %}`.

Χρήση

    {% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %}

Όπως στο προηγούμενο παράδειγμα, θα χρησιμοποιήσουμε τη μέθοδο `parser.parse()`. Αλλά αυτή τη φορά, θα περάσουμε τη `nodelist` στο `Node`, εν αντιθέσει με την προηγούμενη φορά που επιστρέφαμε το `CommentNode()` χωρίς κάποιο `nodelist` μέσα:

    def do_upper(parser, token):
        nodelist = parser.parse(("endupper",))
        parser.delete_first_token()
        return UpperNode(nodelist)

    class UpperNode(template.Node):
        def __init__(self, nodelist):
            self.nodelist = nodelist

        def render(self, context):
            output = self.nodelist.render(context)
            return output.upper()

Η μοναδική καινούργια έννοια εδώ είναι το `self.nodelist.render(context)` μέσα στη μέθοδο `UpperNode.render()`.

For more examples of complex rendering, see the source code of `{% for %}` in django/template/defaulttags.py and `{% if %}` in django/template/smartif.py.