Γράφοντας δικά σας 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()
παίρνει δύο ορίσματα:
Το όνομα του φίλτρου – τύπου string.
Τη συνάρτηση του φίλτρου – μια 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 φίλτρου εμπίπτει σε μια από τις δύο ακόλουθες καταστάσεις:
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 theis_safe
flag toTrue
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 στην έξοδο για την αποφυγή τυχόν προβλημάτων. Ομοίως, η αφαίρεση του;
μπορεί να μετατρέψει το&
σε&
, η οποία δεν είναι πλέον μια έγκυρη οντότητα και επομένως χρειάζεται περαιτέρω escaping. Οι περισσότερες περιπτώσεις δεν θα είναι τόσο πολύπλοκες αλλά προσέχετε για τέτοιου είδους προβλήματα όταν επανεξετάζετε τον κώδικα σας.Μαρκάροντας ένα φίλτρο ως
is_safe
θα αναγκάσει την επιστρεφόμενη τιμή του φίλτρου να γίνει ένα string. Αν το φίλτρο σας θα πρέπει να επιστρέψει μια τιμή τύπου boolean ή κάποια άλλη πέρα από string, μαρκάροντας το ωςis_safe
θα έχει πιθανόν ακούσιες συνέπειες (όπως τη μετατροπή της τιμής False τύπου boolean στο string ‘False’).Εναλλακτικά, ο κώδικας του φίλτρου σας μπορεί να χειριστεί χειροκίνητα, τυχόν αναγκαίο 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 argumentautoescape
σημαίνουν ότι η συνάρτηση μας θα ξέρει αν το 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, το flagis_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 informat_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 παραπάνω:
Το Thread 1 πραγματοποιεί την πρώτη επανάληψη του βρόγχου, η μέθοδος
CycleNode.render()
επιστρέφει ‘row1’Το Thread 2 πραγματοποιεί την πρώτη επανάληψη του βρόγχου, η μέθοδος
CycleNode.render()
επιστρέφει ‘row2’Το Thread 1 πραγματοποιεί την δεύτερη επανάληψη του βρόγχου, η μέθοδος
CycleNode.render()
επιστρέφει ‘row1’Το 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:
Το όνομα του template tag – ένα string. Αν αυτό παραλειφθεί, θα χρησιμοποιηθεί το όνομα της συνάρτησης του compilation.
Τη συνάρτηση του φίλτρου – μια 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()
θα επιστρέψει τρεις τιμές:
Το όνομα του tag,
format_time
.Το string
'blog_entry.date_updated'
(χωρίς τα μονά εισαγωγικά).Το 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
.