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

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

Η γλώσσα 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 με το ίδιο όνομα σε άλλη εφαρμογή.

Για παράδειγμα, αν τα δικά σας tags/φίλτρα βρίσκονται σε ένα αρχείο με το όνομα `poll_extras.py`, η δομή των αρχείων της εφαρμογής σας θα δείχνει κάπως έτσι:

    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 χωρίς την εγκατάσταση κάποια εφαρμογής.

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

Για πάρα πολλά παραδείγματα, διαβάστε τους πηγαίους κώδικες του Django σχετικά με τα προεγκατεστημένα φίλτρα και tags. Βρίσκονται στο `django/template/defaultfilters.py` και `django/template/defaulttags.py`, αντίστοιχα.

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

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

Τα παραμετροποιήσιμα φίλτρα είναι απλώς Python συναρτήσεις οι οποίες δέχονται ένα ή δύο 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" }}

Τα περισσότερα φίλτρα δεν παίρνουν arguments. Σε αυτή την περίπτωση, απλώς αφήστε το εκτός από την συνάρτηση σας. Παράδειγμα:

    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

Όταν γράφετε ένα δικό σας φίλτρο, σκεφτείτε λιγάκι το πως θα αλληλεπιδρά με την auto-escaping συμπεριφορά του Django. Όταν λέμε auto-escaping εννοούμε τη δυνατότητα αυτόματης αποφυγής εμφάνισης συγκεκριμένων χαρακτήρων-λέξεων οι οποίοι αν εμφανιζόντουσαν θα προκαλούσαν, ενδεχομένως, πρόβλημα στο rendering της σελίδας. Σημειώστε ότι υπάρχουν τρεις τύποι strings οι οποίοι μπορούν να περαστούν μέσα σε κώδικα template:

- Σκέτα strings: είναι τα παραδοσιακά Python strings, τύπου `str` ή `unicode`. Στην έξοδο, αυτά τα string γίνονται escaped αν το auto-escaping είναι ενεργοποιημένο, ειδάλλως παρουσιάζονται όπως είναι.

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

  Εσωτερικώς, αυτά τα strings είναι τύπου `SafeBytes` ή `SafeText`. Μοιράζονται μια κοινή κλάση της `SafeData`, με αποτέλεσμα να μπορείτε να κάνετε τεστ σε αυτά με κώδικα όπως:

      if isinstance(value, SafeData):
          # 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”.

   Ο λόγος που το flag `is_safe` είναι απαραίτητο είναι επειδή υπάρχουν πολλών ειδών λειτουργίες για τα strings οι οποίες θα μετατρέψουν ένα object τύπου `SafeData` πίσω σε ένα κανονικό object τύπου `str` ή `unicode` (παρά να προσπαθήσουν να πιάσουν όλα τα strings κάτι το οποίο είναι δύσκολο) και το Django θα επιδιορθώσει τυχόν ζημιά αφού το φίλτρο έχει ολοκληρώσει τις εργασίες του.

   Για παράδειγμα, ας υποθέσουμε ότι έχετε ένα φίλτρο το οποίο προσθέτει το 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. Οι περισσότερες περιπτώσεις δεν θα είναι τόσο πολύπλοκες αλλά προσέχετε για τέτοιου είδους προβλήματα όταν επανεξετάζετε τον κώδικα σας.

   Μαρκάροντας ένα φίλτρο ως `is_safe` θα αναγκάσει την επιστρεφόμενη τιμή του φίλτρου να γίνει ένα string. Αν το φίλτρο σας θα πρέπει να επιστρέψει μια τιμή τύπου boolean ή κάποια άλλη πέρα από string, μαρκάροντας το ως `is_safe` θα έχει πιθανόν ακούσιες συνέπειες (όπως τη μετατροπή της τιμής False τύπου boolean στο 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)

   Tο flag `needs_autoescape` και το keyword argument `autoescape` σημαίνουν ότι η συνάρτηση μας θα ξέρει αν το automatic escaping είναι ενεργοποιημένο ή όχι όταν το φίλτρο μας τρέξει. Χρησιμοποιούμε το `autoescape` για να αποφασίσουμε αν τα input data (το string που πέρασε στο φίλτρο μέσα από την HTML) θα χρειαστεί να περάσουν μέσα από την μέθοδο `django.utils.html.conditional_escape` ή όχι. (Στην τελευταία περίπτωση χρησιμοποιούμε μια lambda συνάρτηση ως την “escape” συνάρτηση.) Η συνάρτηση `conditional_escape()` είναι σαν την `escape()` αλλά κάνει escape το input το οποίο δεν είναι instance της κλάσης `SafeData`. Αν ένα instance μιας κλάσης `SafeData` περαστεί στη συνάρτηση `conditional_escape()`, τα δεδομένα θα επιστραφούν ανέπαφα.

   Στο παραπάνω παράδειγμα, θυμόμαστε να μαρκάρουμε το αποτέλεσμα ως ασφαλή ούτως ώστε η 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`:

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

- Τα εισαγωγικά (`””` ή `''`) γύρω από την παράμετρο (αν υπάρχουν) έχουν ήδη αποκοπεί, οπότε έχουμε να κάνουμε με ένα απλό string μέσα στη συνάρτηση του template tag.

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

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

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

Προστέθηκε η λειτουργία του auto-escaping για το `simple_tag`, όπως περιγράφηκε στις προηγούμενες δύο παραγράφους.

Αν το 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>

### 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}

Επόμενο βήμα είναι να δημιουργήσετε το template το οποίο θα κάνει render την έξοδο του tag. Αυτό το template είναι ένα μόνιμο χαρακτηριστικό του tag: το προσδιορίζει ο συντάκτης του tag και όχι ο σχεδιαστής του template. Εν ακολουθία του παραδείγματος μας, το template είναι πολύ απλό:

    <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 %}

### Assignment tags

`django.template.Library.``assignment_tag`()

Αποσύρθηκε στην έκδοση 1.9: Το `simple_tag` θα πρέπει να χρησιμοποιείται καθώς μπορεί πλέον να αποθηκεύει αποτελέσματα σε μια μεταβλητή template.

Για την διευκόλυνση της δημιουργίας μια μεταβλητής στο template context μέσω των tags, το Django παρέχει μια βοηθητική συνάρτηση, την `assignment_tag`. Αυτή η συνάρτηση δουλεύει με τον ίδιο τρόπο όπως η μέθοδος `simple_tag()` με τη μόνη διαφορά ότι η `assignment_tag` αποθηκεύει το αποτέλεσμα του tag σε μια μεταβλητή του context αντί να την εκτυπώνει απ’ ευθείας στο template.

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

    @register.assignment_tag
    def get_current_time(format_string):
        return datetime.datetime.now().strftime(format_string)

Μπορείτε πλέον να αποθηκεύσετε το αποτέλεσμα σε μια μεταβλητή του template χρησιμοποιώντας το argument `as` ακολουθούμενο από το όνομα της μεταβλητής. Έπειτα μπορείτε να εκτυπώσετε την τιμή της μεταβλητής οπουδήποτε μέσα στο template επιθυμείτε:

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

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

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

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

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

Όταν το Django συντάσσει (compiles) ένα template, χωρίζει το κείμενο του template σε μια λίστα από ‘’nodes’‘. Κάθε node είναι ένα instance της κλάσης `django.template.Node` και έχει μια μέθοδο `render()`. Ένα compiled template είναι, απλώς, μια λίστα από objects τύπου `Node`. Όταν καλείτε τη μέθοδο `render()` σε ένα compiled template object, το template καλεί τη μέθοδο `render()` για καθένα `Node` που βρίσκεται στη λίστα του, με το δοθέν context. Τα αποτελέσματα ενώνονται όλα μεταξύ τους για να συνθέσουν την έξοδο του template.

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

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

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

Για παράδειγμα, ας γράψουμε μια πλήρης υλοποίηση του απλού μας template tag, `{% current_time %}`, το οποίο εμφανίζει την τρέχουσα ημερομηνία/ώρα, μορφοποιημένη σύμφωνα με τις παραμέτρους (σύνταξη func:~time.strftime) που δόθηκαν στο tag. Είναι καλή ιδέα να αποφασίσετε τη σύνταξη του tag πριν από οτιδήποτε άλλο. Στη δικιά μας περίπτωση, ας πούμε ότι το tag θα πρέπει να χρησιμοποιείται κάπως έτσι:

    <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`, με βοηθητικά μηνύματα για τυχόν συντακτικά λάθη.

- Τα exceptions τύπου `TemplateSyntaxError` χρησιμοποιούν τη μεταβλητή `tag_name`. Μην γράφετε μόνοι σας το όνομα του tag στα μηνύματα σφάλματος, επειδή αυτό δεσμεύει το όνομα του tag με τη συνάρτηση σας. Το `token.contents.split()[0]` θα περιέχει ‘’πάντα’’ το όνομα του tag σας – ακόμη και αν το tag δεν έχει παραμέτρους.

- The function returns a `CurrentTimeNode` with everything the node needs to know about this tag. In this case, it just 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.

Αν η συνάρτηση `render()` του template σας αποθηκεύει το αποτέλεσμα σε μια context variable (αντί να επιστρέφει το αποτέλεσμα σε ένα string), θα πρέπει να φροντίσει να καλέσει την `mark_safe()`, αν αυτό είναι απαραίτητο. Όταν η μεταβλητή γίνει, τελικώς, rendered (εμφανιστεί-τυπωθεί στο template, αν θέλετε), θα επηρεαστεί από τη ρύθμιση του auto-escape (αναλόγως αν είναι ενεργοποιημένη ή όχι), οπότε το περιεχόμενο που πρέπει να είναι safe από περαιτέρω escaping χρειάζεται να μαρκαριστεί.

Επίσης, αν το 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’

Το CycleNode κάνει την επανάληψη (iterating), αλλά την κάνει καθολικά (globally). Όσον αφορά τα Thread 1 και Thread 2, το καθένα επιστρέφει πάντα την ίδια τιμή. Αυτό, φυσικά, δεν είναι αυτό που θέλουμε!

Για την αποφυγή τέτοιων καταστάσεων, το 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

Τέλος, κάντε register το tag με το instance του module `Library`, όπως εξηγήθηκε στην ενότητα γράφοντας δικά σας template φίλτρα παραπάνω. Παράδειγμα:

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

Θα χρειαστεί επίσης να αλλάξετε και τον renderer για να λάβει τα πραγματικά περιεχόμενα του `date_updated` property του object `blog_entry`. Αυτό μπορεί να επιτευχθεί χρησιμοποιώντας την κλάση `Variable()` μέσα στο `django.template`.

Για να χρησιμοποιήσετε την κλάση `Variable`, απλώς αρχικοποιήστε τη με το όνομα της μεταβλητής η οποία θα γίνει resolved και μετά καλέστε τη μέθοδο `variable.resolve(context)`. Άρα, για παράδειγμα:

    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

Το παραπάνω παράδειγμα απλώς εξάγει μια τιμή. Γενικότερα, είναι περισσότερο ευέλικτο αν τα template tags σας θέτουν template μεταβλητές αντί να εξάγουν (εκτυπώνουν) τις τιμές τους. Με αυτό τον τρόπο, οι συντάκτες των template θα μπορούν να επαναχρησιμοποιούν τις τιμές τις οποίες δημιούργησαν τα template tags σας.

Για να θέσετε μια μεταβλητή στο context, απλώς χρησιμοποιήστε την εκχώρηση της στο dictionary του context object μέσα στη μέθοδο `render()`. Παρακάτω φαίνεται μια ενημερωμένη έκδοση της κλάσης `CurrentTimeNode` η οποία θέτει την template μεταβλητή `current_time` αντί απλώς να την εκτυπώνει:

    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.

Αλλά υπάρχει ένα πρόβλημα με την κλάση `CurrentTimeNode2`: Το όνομα της μεταβλητής `current_time` είναι γραμμένο hard-coded. Αυτό σημαίνει ότι πρέπει να σιγουρευτείτε πως το template σας δεν χρησιμοποιεί πουθενά αλλού το `{{ current_time }}`, επειδή το `{% current_time %}` θα έκανε overwrite την τιμή της μεταβλητής. Μια πιο καθαρή λύση είναι να κάνετε το template tag να προσδιορίσει ένα όνομα για τη μεταβλητή, ως εξής:

    {% 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()` παίρνει ένα tuple από ονόματα block tags ‘’να γίνει parse μέχρι’‘. Επιστρέφει ένα instance του `django.template.NodeList`, το οποίο είναι μια λίστα από `Node` objects τα οποία βρήκε ο parser ‘’πριν’’ βρει κάποιο από τα tags που είναι μέσα στο tuple.

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

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

Η μέθοδος `CommentNode.render()` απλώς επιστρέφει ένα κενό string. Οτιδήποτε μεταξύ του `{% comment %}` και `{% endcomment %}` αγνοείται.

### Κάνοντας 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()`.

Για περισσότερα παραδείγματα περίπλοκων renderings, δείτε τον πηγαίο κώδικα του tag `{% for %}` στο αρχείο `django/template/defaulttags.py` και το tag `{% if %}` στο αρχείο `django/template/smartif.py`.