Nel capitolo precedente, abbiamo inserito nella nostra view del codice HTML e lo abbiamo integrato con il metodo per la data/ora. Vale a dire, l'HTML è stato scritto direttamente nel nostro codice Python:
def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
Anche se questa tecnica era utile per spiegare come funzionano le view, non è una buona idea scrivere HTML direttamente sulla view. Ecco perché:
- Qualsiasi modifica al progetto della pagina richiede una modifica al codice Python. La progettazione di un sito tende a cambiare molto più spesso di quanto debba cambiare il codice Python, per cui sarebbe davvero conveniente poter fare modifiche senza bisogno di modificare il codice backend.
- Scrivere codice Python e progettare HTML sono due discipline diverse, e la maggior parte degli ambienti di sviluppo web professionale ha diviso tali responsabilità a persone separate (o anche reparti separati). Designers e programmatori HTML/CSS non dovrebbero essere obbligati a modificare il codice Python per svolgere il loro lavoro.
- È molto più efficiente quella situazione in cui i programmatori possono lavorare solo sul codice Python ed i designer possono lavorare sui template allo stesso tempo, piuttosto che quella nella quale una persona debba attendere le modifiche dell'altro prima di iniziare a lavorare.
Per queste ragioni, è molto pulito e più gestibile separare la progettazione della pagina dal codice Python stesso. Possiamo farlo con il sistema di template di Django, di cui parleremo in questo capitolo.
Un template di Django è una stringa di testo che serve a separare la presentazione di un documento dai propri dati. Un template definisce un segnaposto e varie parti di logica di base (tag template) che regolano le modalità di visualizzazione del documento. Di solito, i modelli vengono utilizzati per la produzione di codice HTML, ma i modelli di Django sono ugualmente in grado di generare qualsiasi formato basato su testo.
Cominciamo con un template semplice di esempio. Questo template descrive una pagina HTML che ringrazia una persona per aver fatto un ordine con una società. Pensa a come una lettera tipo:
<html> <head><title>Ordering notice</title></head> <body> <h1>Ordering notice</h1> <p>Dear {{ person_name }},</p> <p>Thanks for placing an order from {{ company }}. It's scheduled to ship on {{ ship_date|date:"F j, Y" }}.</p> <p>Here are the items you've ordered:</p> <ul> {% for item in item_list %} <li>{{ item }}</li> {% endfor %} </ul> {% if ordered_warranty %} <p>Your warranty information will be included in the packaging.</p> {% else %} <p>You didn't order a warranty, so you're on your own when the products inevitably stop working.</p> {% endif %} <p>Sincerely,<br />{{ company }}</p> </body> </html>
Questo template è un codice HTML di base con alcune variabili e tag template che analizziamo riga per riga:
* Qualsiasi testo circondato da una coppia di parentesi graffe (ad esempio,
{{ person_name }}
) è una variabile. Questo significa dire a Django
"inserisci il valore della variabile che ha il nome incluso nelle parentesi."
(Come possiamo specificare i valori delle variabili? Ci arriveremo fra un
attimo).
Qualsiasi testo che è circondato da parentesi graffe e segni di percentuale (ad esempio, {% se ordered_warranty%}) è un tag template. La definizione di tag è abbastanza ampia: esso dice al sistema di template di "fare qualcosa".
Questo template di esempio contiene un tag
for
({% for item in item_list %}
) e un tagif
({% if ordered_warranty %}
).Ogni tag
for
funziona come una dichiarazione ,for
ti permette di fare iterazioni su un array. Un tagif
, come ci si potrebbe aspettare, agisce come unif
logico di Python. In questo caso particolare, il tag controlla se il valore della variabileordered_warranty
restituisceTrue
. Se èTrue
, il sistema di template mostra tutto quello che c'è tra il{% if ordered_warranty %}
e{% else %}
. In caso contrario, il sistema di template mostra tutto quello che c'è tra{% else %}
e{% endif %}
. Nota che{% else %}
è opzionale.Infine, il secondo paragrafo di questo template contiene un esempio di filtro, che è il modo più conveniente per alterare la formattazione di una variabile. In questo esempio,
{{ ship_date|date:"F j, Y" }}
, stiamo passando la variabileship_date
al filtrodate
, dandodate
al filtro con un parametro"F j, Y"
. Il formato del filtrodate
nel formato specificato dai parametro. I filtri vengono collegati mediante un carattere pipe ((|
), come negli ambienti Unix.
Ogni template Django ha accesso a diversi tag e filtri integrati, molti dei quali sono discussi nelle sezioni che seguono. L'appendice E contiene una lista completa dei tag e filtri, ed è una buona idea familiarizzare con quella lista in modo da sapere cosa è possibile fare. E 'anche possibile creare i propri filtri e tag; lo vedremo che nel capitolo 9.
Tuffiamoci nel sistema di template di Django in modo da vedere come funziona -- ma senza integrarlo con le cose che abbiamo creato nel capitolo precedente. Il nostro obiettivo è quello di mostrare come il sistema funziona indipendentemente dal resto di Django. (Detto in altro modo: di solito lo utilizzerai all'interno di una view di Django, ma vogliamo mettere in chiaro che il sistema di template è solo una libreria Python che si può usare ovunque, non solo in Django).
Ecco il modo più semplice per utilizzare il sistema di template di Django in codice Python:
- Creare un oggetto
Template
scrivendo il codice come se fosse una stringa; - Chiamare il metodo
render()
dell'oggettoTemplate
con un dato insieme di variabili (il contesto, context) usando la funzionerender()
. Questo restituisce un template completamente tradotto in una stringa, con tutte le variabili e tag template esaminati in base al contesto.
In codice, ecco come sembra:
>>> from django import template >>> t = template.Template('My name is {{ name }}.') >>> c = template.Context({'name': 'Adrian'}) >>> print t.render(c) My name is Adrian. >>> c = template.Context({'name': 'Fred'}) >>> print t.render(c) My name is Fred.
Le seguenti sezioni descrivono ogni passaggio in modo molto più dettagliato.
Il modo più semplice per creare un oggetto Template
è istanziarlo
direttamente. La classe Template
sta nel modulo django.template
ed il
suo costruttore accetta un argomento, il codice del template puro. Usiamo
l'interprete interattivo di Python per vedere come funziona tutto questo in
codice.
Dalla directory di progetto mysite
creata da django-admin.py
startproject
(come descritto nel Capitolo 2), digita python manage.py shell
per avviare l'interprete interattivo.
Uno speciale prompt di Python
Se hai utilizzato Python prima, ci si potrebbe chiedere perché stiamo
digitando python manage.py shell
invece del solo python
. Entrambi i
comandi avviano l'interprete interattivo, ma il comando manage.py shell
ha una differenza fondamentale: prima di avviare l'interprete, vengono
passate le impostazioni di Django. Molte parti di Django, tra cui il sistema
di template, si basano sulle impostazioni, e non saremmo in grado di usarli
a meno che il contesto sa quali impostazioni da utilizzare.
Se sei curioso, ecco come funziona dietro le quinte. Django cerca una
variabile d'ambiente chiamata DJANGO_SETTINGS_MODULE
, che dovrebbe
essere impostato sul percorso di importazione del vostro settings.py
.
Ad esempio, DJANGO_SETTINGS_MODULE
potrebbe essere impostato su
'mysite.settings'
, supponendo che mysite
sia il tuo percorso di
Python.
Quando si esegue python manage.py shell
, il comando si prende cura di
interpretare DJANGO_SETTINGS_MODULE
per noi. Incoraggiamo l'utilizzo del
python manage.py shell
in questi esempi per ridurre al minimo la
quantità di lavoro e di configurazione che bisogna fare.
Passiamo in rassegna alcuni principi fondamentali del sistema di template:
>>> from django.template import Template >>> t = Template('My name is {{ name }}.') >>> print t
Se stai seguendo la shell integrativa, vedrai qualcosa di simile a questo:
<django.template.Template object at 0xb7d5f24c>
Quel 0xb7d5f24c
sarà diverso ogni volta, e non è rilevante: è una cosa Python
(l'"identità" dell'oggetto Template
in Python, se proprio vuoi saperlo).
Quando si crea un oggetto Template
, il sistema compila per prima il codice
del template in una forma ottimizzata interna, pronta per il rendering. Ma se
il tuo codice di template include eventuali errori di sintassi, viene chiamata
la funzione Template()
che causerà un'eccezione TemplateSyntaxError`:
>>> from django.template import Template >>> t = Template('{% notatag %}') Traceback (most recent call last): File "<stdin>", line 1, in ? ... django.template.TemplateSyntaxError: Invalid block tag: 'notatag'
Il termine "block tag" qui si riferisce al {% notatag %}
. "Block tag" e
"template tag" sono sinonimi.
Il sistema genera un'eccezione TemplateSyntaxError
per uno qualsiasi dei
seguenti casi:
- Tag non validi
- Argomenti non validi per tag validi
- Filtri non validi
- Argomenti non validi per filtri validi
- Sintassi del template non valida
- Tag non chiusi (per i tag che richiedono tag di chiusura)
Una volta che si dispone di un oggetto Template
, è possibile passare dati e dare
un contesto (context). Un contesto è semplicemente un insieme di nomi di
variabili di template ed i relativi valori associati. Un template utilizza questo
meccanismo per popolare le sue variabili e valutare i casi.
Un contesto è rappresentata in Django dalla classe Context
, che sta nel
modulo django.template
. Il suo costruttore accetta un argomento opzionale:
un dizionario, che mappa i nomi a dei valori. Chiamare il metodo render()
dell'oggetto Template
con il contesto per "riempire" il template:
>>> from django.template import Context, Template >>> t = Template('My name is {{ name }}.') >>> c = Context({'name': 'Stephane'}) >>> t.render(c) u'My name is Stephane.'
Una cosa che dobbiamo sottolineare è che il valore di ritorno di t.render(c)
è un oggetto Unicode -- non una normale stringa Python. Si può dire questo per
via della u
davanti alla stringa. Django utilizza oggetti Unicode invece di
stringhe normali in tutta la struttura. Se si già capito cosa significa questo,
sarai grato per le cose raffinate che Django fa dietro le quinte per farlo
funzionare. Se non hai capito, non preoccuparti, per ora, è sufficiente sapere
che il supporto Unicode di Django rende relativamente indolore per le tue
applicazioni supportare una vasta gamma di set di caratteri al di là del "A-Z"
di base della lingua inglese.
Dizionari e Contesti
Un dizionario Python fa dei collegamenti tra chiavi note e valori variabili.
Un Context
è simile ad un dizionario, ma un Context
fornisce delle
funzionalità aggiuntive, di cui ci occuperemo nell capitolo 9.
I nomi delle variabili devono iniziare con una lettera (A-Z o a-z) e possono contenere altre lettere, numeri, caratteri di sottolineatura e punti. (I punti sono un caso particolare ci arriveremo in un attimo)(i nomi delle variabili sono case sensitive).
Ecco un esempio di compilazione e di rendering di un contesto, utilizzando un template simile a quello dell'esempio visto all'inizio di questo capitolo:
>>> from django.template import Template, Context >>> raw_template = """<p>Dear {{ person_name }},</p> ... ... <p>Thanks for placing an order from {{ company }}. It's scheduled to ... ship on {{ ship_date|date:"F j, Y" }}.</p> ... ... {% if ordered_warranty %} ... <p>Your warranty information will be included in the packaging.</p> ... {% else %} ... <p>You didn't order a warranty, so you're on your own when ... the products inevitably stop working.</p> ... {% endif %} ... ... <p>Sincerely,<br />{{ company }}</p>""" >>> t = Template(raw_template) >>> import datetime >>> c = Context({'person_name': 'John Smith', ... 'company': 'Outdoor Equipment', ... 'ship_date': datetime.date(2009, 4, 2), ... 'ordered_warranty': False}) >>> t.render(c) u"<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor Equipment. It's scheduled to\nship on April 2, 2009.</p>\n\n\n<p>You didn't order a warranty, so you're on your own when\nthe products inevitably stop working.</p>\n\n\n<p>Sincerely,<br />Outdoor Equipment </p>"
Analizziamo il codice un'istruzione alla volta:
In primo luogo, abbiamo importato le classi
Template
eContext
, che stanno nel modulodjango.template
.Salviamo il testo del nostro template nella variabile
raw_template
. Si noti che usiamo segni di triple virgolette per indicare stringhe che si espandono su più righe. Al contrario, le stringhe racchiuse tra virgolette singole non possono essere scritte su più righe.Quindi, creiamo un oggetto template,
t
, passandoraw_template
al costruttore della classeTemplate
.Importiamo il modulo
datetime
dalla libreria standard di Python, perché ne avremo bisogno nella seguente dichiarazione.Quindi, creiamo un oggetto
Context
,c
. Il costruttoreContext
prende un dizionario Python, che mappa i nomi delle variabili di valori. Qui, per esempio, si precisa cheperson_name
è'John Smith'
, l'azienda è'Outdoor Equipment'
, e così via.Infine, chiamiamo il metodo
render()
sul nostro oggetto template, passandogli il contesto. Questo restituisce il template renderizzato -- vale a dire, che sostituisce le variabili del template con i valori attuali delle variabili, e viene rieseguito per ogni tag dei template.Nota che il paragrafo "You didn't order a warranty" è stato mostrato perché la variabile
ordered_warranty
è risultataFalse
. Da notare anche la data,April 2, 2009
, che viene mostrata in base alla stringa di formato'F j, Y'
. (Parleremo delle stringhe di formato per i filtri data fra un po ')Se sei nuovo in Python, potresti chiederti perché questo output include caratteri speciali come l'accapo (
'\n'
) anziché effettivamente vedere l'accapo. Questo avviene per una sottigliezza nell'interprete interattivo di Python: la chiamata at.render(c)
restituisce una stringa, e per impostazione predefinita, l'interprete interattivo mostra la rappresentazione della stringa, piuttosto che il valore stampato della stringa. Se vuoi vedere la stringa con interruzioni di riga visualizzati come veri a capo, piuttosto che caratteri'\n'
, bisogna utilizzare l'istruzione di stampaprint
print t.render(c)
.
Queste sono le nozioni fondamentali sull'utilizzo del sistema di template di
Django: basta scrivere una stringa di template, creare un oggetto Template
,
creare un Context
e chiamare il metodo render()
.
Una volta che si ha un oggetto Template
, è possibile rendere più contesti
attraverso di esso. Per esempio:
>>> from django.template import Template, Context >>> t = Template('Hello, {{ name }}') >>> print t.render(Context({'name': 'John'})) Hello, John >>> print t.render(Context({'name': 'Julie'})) Hello, Julie >>> print t.render(Context({'name': 'Pat'})) Hello, Pat
Ogni volta che si utilizza lo stesso oggetto template per rendere più contesti
come questo, è più efficiente creare l'oggetto Template
una volta, e
quindi chiama render()
su di esso più volte:
# Cattivo for name in ('John', 'Julie', 'Pat'): t = Template('Hello, {{ name }}') print t.render(Context({'name': name})) # Buono t = Template('Hello, {{ name }}') for name in ('John', 'Julie', 'Pat'): print t.render(Context({'name': name}))
L'analisi del template di Django è abbastanza veloce. Dietro le quinte, la maggior parte del parsing avviene tramite una chiamata ad una singola espressione regolare. Questo è in netto contrasto con i motori di template basati su XML, che non sopportano un parser XML e tendono ad essere più lenti di ordini di grandezza rispetto al motore di rendering di template incluso in Django.
Negli esempi visti finora, abbiamo passato valori semplici nei contesti -- per
lo più stringhe, oltre a un esempio datetime.date
. Tuttavia, il sistema di
template gestisce con eleganza strutture di dati più complesse, come ad esempio
liste, dizionari e oggetti personalizzati.
La chiave per navigare strutture dati complesse nei template di Django è il
carattere punto (.
). Utilizza un punto per accedere alle chiavi di un
dizionario, gli attributi, i metodi o agli indici di un oggetto.
Questo si spiega meglio con alcuni esempi. Per esempio, supponiamo di stare passando un dizionario Python per un template. Per accedere ai valori chiave di quel dizionario bisogna usare un punto:
>>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) u'Sally is 43 years old.'
Allo stesso modo, i punti permettono anche l'accesso agli attributi dell'oggetto.
Ad esempio, un oggetto Python datetime.date
ha year
, month
e day
e degli attributi, ed è possibile utilizzare un punto per accedere a questi
attributi in un template di Django:
>>> from django.template import Template, Context >>> import datetime >>> d = datetime.date(1993, 5, 2) >>> d.year 1993 >>> d.month 5 >>> d.day 2 >>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.') >>> c = Context({'date': d}) >>> t.render(c) u'The month is 5 and the year is 1993.'
Questo esempio utilizza una classe personalizzata e mostra che le variabili consentono inoltre l'accesso ad ogni attributo su oggetti arbitrari:
>>> from django.template import Template, Context >>> class Person(object): ... def __init__(self, first_name, last_name): ... self.first_name, self.last_name = first_name, last_name >>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.') >>> c = Context({'person': Person('John', 'Smith')}) >>> t.render(c) u'Hello, John Smith.'
I punti possono fare riferimento anche ai metodi degli oggetti. Ad esempio,
ogni stringa di Python ha i metodi upper()
e isdigit()
, che si possono
chiamare nei template usando la stessa sintassi del punto:
>>> from django.template import Template, Context >>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}') >>> t.render(Context({'var': 'hello'})) u'hello -- HELLO -- False' >>> t.render(Context({'var': '123'})) u'123 -- 123 -- True'
Da notare che non si devono includere le parentesi nelle chiamate del metodo. Inoltre, non è possibile passare argomenti ai metodi, si possono solo chiamare metodi che non hanno argomenti richiesti. (Spiegheremo questa filosofia più avanti in questo capitolo).
Infine, i punti sono anche utilizzati per accedere agli indici delle liste, ad esempio:
>>> from django.template import Template, Context >>> t = Template('Item 2 is {{ items.2 }}.') >>> c = Context({'items': ['apples', 'bananas', 'carrots']}) >>> t.render(c) u'Item 2 is carrots.'
Indici negativi nelle liste non sono ammessi. Ad esempio, la variabile
{{ items.-1 }}
causerebbe un TemplateSyntaxError
.
Liste Python
Ricorda: le liste Python hanno indici che partono da 0. Il primo elemento è nella posizione di indice 0, il secondo è a indice 1, e così via.
Le ricerche con il punto possono essere riassunti in questo modo: quando il sistema di template incontra un punto in un nome di variabile, svolge le seguenti ricerche, in questo ordine:
- Dizionario di ricerca (ad esempio,
foo["bar"]
) - Attributo di ricerca (ad esempio,
foo.bar
) - Metodo di chiamata (ad esempio,
foo.bar()
) - Elenco-indice di ricerca (ad esempio,
foo[2]
)
Il sistema utilizza il primo tipo di ricerca che funziona. E' la logica del corto circuito.
Le ricerche con il punto possono essere nidificate su più livelli di profondità.
Per esempio, il seguente esempio utilizza {{ person.name.upper }}
,, che si
traduce in una ricerca nel dizionario (person['name']
) e poi una chiamata di
metodo (upper()
):
>>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name.upper }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) u'SALLY is 43 years old.'
Le chiamate ad un metodo sono leggermente più complesse rispetto agli altri tipi di ricerca. Qui ci sono alcune cose da tenere a mente:
Se, durante il metodo di ricerca, un metodo solleva un'eccezione, l'eccezione viene propagata, a meno che l'eccezione ha un attributo
silent_variable_failure
il cui valore èTrue
. Se l'eccezione ha un attributo di questo tipo, la variabile sarà resa come una stringa vuota, per esempio:>>> t = Template("My name is {{ person.first_name }}.") >>> class PersonClass3: ... def first_name(self): ... raise AssertionError, "foo" >>> p = PersonClass3() >>> t.render(Context({"person": p})) Traceback (most recent call last): ... AssertionError: foo >>> class SilentAssertionError(AssertionError): ... silent_variable_failure = True >>> class PersonClass4: ... def first_name(self): ... raise SilentAssertionError >>> p = PersonClass4() >>> t.render(Context({"person": p})) u'My name is .'
Una chiamata al metodo funziona solo se il metodo non ha argomenti richiesti. In caso contrario, il sistema si sposta al prossimo tipo di ricerca (ricerca elenco-indice).
Ovviamente, alcuni metodi hanno effetti collaterali, e sarebbe sciocco, e forse anche un buco di sicurezza, consentire al sistema di template di accedervi.
Supponiamo, ad esempio, che si dispone di un oggetto
BankAccount
che ha un metododelete()
. Se un template include qualcosa come{{ account.delete }}
, doveaccount
è un oggettoBankAccount
, l'oggetto viene eliminato quando sul template viene eseguito il rendering!Per evitare questo, impostare sul metodo l'attributo
alters_data
:def delete(self): # Delete the account delete.alters_data = True
Il template di sistema non esegue alcun metodo contrassegnato in questo modo. Continuando l'esempio precedente, se un template include
{{ account.delete }}
e il metododelete()
è laalters_data=True
, il metododelete()
non viene eseguito quando il template viene eseguito il rendering. Invece, fallirà silenziosamente.
Per impostazione predefinita, se una variabile non esiste, il sistema di template la traduce in una stringa vuota, in mancanza di niente. Per esempio:
>>> from django.template import Template, Context >>> t = Template('Your name is {{ name }}.') >>> t.render(Context()) u'Your name is .' >>> t.render(Context({'var': 'hello'})) u'Your name is .' >>> t.render(Context({'NAME': 'hello'})) u'Your name is .' >>> t.render(Context({'Name': 'hello'})) u'Your name is .'
Il sistema non sta silenzio, ma solleva un'eccezione perché è destinato persistere un errore umano. In questo caso, tutte le ricerche falliscono perché i nomi delle variabili sono il caso o il nome sbagliato. Nel mondo reale, è inaccettabile per un sito Web di diventare inaccessibile a causa di un piccolo errore di sintassi nel template.
Per la maggior parte del tempo, avrai a che fare con le istanze di oggetti
Context
usando un dizionario completamente popolato da Context()
. Ma è
possibile aggiungere ed eliminare elementi da un oggetto Context
anche una
volta che è stata creata l'istanza utilizzando la sintassi dizionario standard
di Python:
>>> from django.template import Context >>> c = Context({"foo": "bar"}) >>> c['foo'] 'bar' >>> del c['foo'] >>> c['foo'] Traceback (most recent call last): ... KeyError: 'foo' >>> c['newvariable'] = 'hello' >>> c['newvariable'] 'hello'
Come abbiamo già detto, è possibile usare diversi tag e filtri integrati nel sistema di template. Le sezioni che seguono, forniscono una panoramica dei tag e dei filtri più comuni.
Il tag {% if %}
valuta una condizione o una variabile, e se quella risulta
"True" (cioè, esiste, non è vuoto, e non è un valore booleano false), il sistema
visualizza tutto tra {% if %}
e {% endif %}
, ad esempio:
{% if today_is_weekend %} <p>Welcome to the weekend!</p> {% endif %}
Un tag {% else %}
è opzionale:
{% if today_is_weekend %} <p>Welcome to the weekend!</p> {% else %} <p>Get back to work.</p> {% endif %}
Python "Truthiness"
In Python e nel sistema di template Django, questi oggetti restituiscono
False
in un contesto booleano:
- Una lista vuota (
[]
) - Una tupla vuota (
()
) - Un dizionario vuoto (
{}
) - Una stringa vuota (
''
) - Zero (
0
) - L'oggetto speciale
None
- L'oggetto
False
(ovviamente) - Oggetti personalizzati che definiscono il proprio comportamento contesto booleano (uso avanzato Python)
Tutto il resto restituisce True
.
Il tag {% if %}
accetta and
, or
, or not
per lavorare su più
variabili, o per negare le variabili date. Per esempio:
{% if athlete_list and coach_list %} Both athletes and coaches are available. {% endif %} {% if not athlete_list %} There are no athletes. {% endif %} {% if athlete_list or coach_list %} There are some athletes or some coaches. {% endif %} {% if not athlete_list or coach_list %} There are no athletes or there are some coaches. {% endif %} {% if athlete_list and not coach_list %} There are some athletes and absolutely no coaches. {% endif %}
I tag {% if %}
non permettono clausole and
e or
all'interno della
stessa istruzione, poiché essa potrebbe avere una logica ambigua. Per esempio,
questa istruzione è invalida:
{% if athlete_list and coach_list or cheerleader_list %}
L'uso delle parentesi per dare uno specifico ordine alle operazioni di controllo
non è supportato. Se ti trovi a dover necessariamente usare le parentesi,
considera di ricreare l'esecuzione di logica al di fuori del template passando
il risultato come una variabile del template. Oppure, basta usare un tag
{% if %}
nidificato, in questo modo:
{% if athlete_list %} {% if coach_list or cheerleader_list %} We have athletes, and either coaches or cheerleaders! {% endif %} {% endif %}
Molteplici usi di uno stesso operatore logico vanno bene, ma non è possibile combinare diversi operatori. Per esempio, questo è valido:
{% if athlete_list or coach_list or parent_list or teacher_list %}
Non vi è alcun tag {% elif %}
. Basta usare un tag if nidificato {% if %}
per realizzare la stessa cosa:
{% if athlete_list %} <p>Here are the athletes: {{ athlete_list }}.</p> {% else %} <p>No athletes are available.</p> {% if coach_list %} <p>Here are the coaches: {{ coach_list }}.</p> {% endif %} {% endif %}
Assicurarsi di chiudere ogni {% if %}
con un {% endif %}
. In caso
contrario, Django solleva un TemplateSyntaxError
.
Il tag {% for %}
consente di fare delle iterazioni su ogni elemento di una
sequenza. Come in Python per la dichiarazione, la sintassi è for X in Y
,
dove Y
è la sequenza di un ciclo su e X
è il nome della variabile da
utilizzare per un particolare ciclo. Ogni iterazione del ciclo, il sistema di
template renderà tutto ciò presente fra {% for %}
e {% endfor %}
.
Ad esempio, per visualizzare un elenco di atleti, come la variabile
athlete_list
è possibile utilizzare la seguente istruzione:
<ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} </ul>
Aggiungendo reversed
al tag, è possibile ciclare in maniera inversa la lista:
{% for athlete in athlete_list reversed %} ... {% endfor %}
E' possibile creare tag {% for %}
annidati in questo modo:
{% for athlete in athlete_list %} <h1>{{ athlete.name }}</h1> <ul> {% for sport in athlete.sports_played %} <li>{{ sport }}</li> {% endfor %} </ul> {% endfor %}
Un uso comune è controllare la dimensione della lista prima di eseguire il loop, e poi stampare un testo speciale se la lista è vuota:
{% if athlete_list %} {% for athlete in athlete_list %} <p>{{ athlete.name }}</p> {% endfor %} {% else %} <p>There are no athletes. Only computer programmers.</p> {% endif %}
Poiché questo pattern è così comune, il tag for
supporta una clausola
{% empty %}
opzionale che permette di definire cosa scrivere se la lista è
vuota. Questo esempio è equivalente alla precedente:
{% for athlete in athlete_list %} <p>{{ athlete.name }}</p> {% empty %} <p>There are no athletes. Only computer programmers.</p> {% endfor %}
Non vi è alcun modo per "rompere" un ciclo prima che esso sia finito. Se vuoi farlo, è necessario modificare la variabile che stai iterando in modo da includere solo i valori su cui si desidera lavorare. Allo stesso modo, non vi è alcuna istruzione "continue" che fa ricominciare immediatamente il ciclo. (Leggi la sezione "Filosofia e limitazioni", più avanti in questo capitolo, che spiega la motivazione dietro questa decisione di progettazione).
All'interno di ogni ciclo {% for %}
, si ottiene l'accesso ad una variabile
chiamata forloop
. Questa variabile ha un paio di caratteristiche che ti
danno informazioni sullo stato di avanzamento del ciclo:
* ``forloop.counter`` è sempre impostato su un numero intero che rappresenta il
numero di volte che il ciclo è stato eseguito. Questo è una sorta di indice, quindi la prima volta che avviene il ciclo, il
forloop.counter
viene impostato ad1
. Ecco un esempio:{% for item in todo_list %} <p>{{ forloop.counter }}: {{ item }}</p> {% endfor %}
forloop.counter0
è comeforloop.counter
, tranne per il fatto che parte zero. Il suo valore sarà impostato a 0 la prima volta che viene eseguito il ciclo.forloop.revcounter
è sempre impostato su un numero intero che rappresenta il numero di elementi rimanenti nel ciclo. La prima esecuzione del ciclo,forloop.revcounter
viene impostato con il numero totale di elementi nel ciclo che stai eseguendo. All'ultima iterazione del ciclo,forloop.revcounter
viene impostato su1
.forloop.revcounter0
è comeforloop.revcounter
, tranne per il fatto che parte zero. La prima iterazione del ciclo,forloop.revcounter0
viene impostato con il numero di elementi del ciclo che stai eseguendo meno 1. L'ultima iterazione del ciclo, è impostata a0
.forloop.first
è un valore booleano impostato suTrue
se questa è la prima iterazione. Questo è comodo per creare caso particolari:{% for object in objects %} {% if forloop.first %}<li class="first">{% else %}<li>{% endif %} {{ object }} </li> {% endfor %}
forloop.last
è un valore booleano impostato su true se questa è l'ultima iterazione. Un uso comune per questo è di mettere i caratteri pipe tra una lista di link:{% for link in links %}{{ link }}{% if not forloop.last %} | {% endif %}{% endfor %}
Il codice del template precedente potrebbe produrre qualcosa di simile a questo:
Link1 | Link2 | Link3 | Link4
Un altro uso comune di questa variabile è usarla per mettere una virgola tra le parole in un elenco luoghi preferiti:
{% for p in places %}{{ p }}{% if not forloop.last %}, {% endif %}{% endfor %}
forloop.parentloop
è un riferimento all'oggettoforloop
del ciclo genitore, nel caso di cicli annidati. Ecco un esempio:{% for country in countries %} <table> {% for city in country.city_list %} <tr> <td>Country #{{ forloop.parentloop.counter }}</td> <td>City #{{ forloop.counter }}</td> <td>{{ city }}</td> </tr> {% endfor %} </table> {% endfor %}
La magica variabile forloop
è disponibile solo all'interno dei cicli. Dopo
che il parser del template ha raggiunto {% endfor %}
, forloop
scompare.
Contesto/Context e variabile forloop
All'interno del blocco {% for %}
le variabili esistenti vengono spostate
al di fuori per evitare di sovrascrivere la variabile forloop
. Django
mette questo contesto in forloop.parentloop
. In genere non è necessario
preoccuparsi di questo fatto, ma se si crea una variabile all'interno del
template chiamata forloop
(anche se si sconsiglia di farlo), essa viene
mantenuta al di fuori del ciclo in o chiamati i forloop.parentloop
mentre
all'interno del {% for %}
per blocco.
Il sistema di template Django volutamente non è un linguaggio di programmazione
a tutti gli effetti e, quindi, non consente di eseguire comandi Python. (Leggi
"Filosofia e limitazioni" per saperne su più questa idea), tuttavia, è piuttosto
comune dover confrontare due valori e mostrare a qualcosa se sono uguali in un
template -- e Django fornisce un tag {% ifequal %}
per questo scopo.
Il tag {% ifequal %}
confronta due valori e mostra tutto quello che c'è tra
{% ifequal %}
e {% endifequal %}
se i valori sono uguali.
Questo esempio confronta il template variabili user
e currentuser
:
{% ifequal user currentuser %} <h1>Welcome!</h1> {% endifequal %}
Gli argomenti possono essere stringhe hardcoded, con le virgolette singole o doppie, per cui vale quanto segue:
{% ifequal section 'sitenews' %} <h1>Site News</h1> {% endifequal %} {% ifequal section "community" %} <h1>Community</h1> {% endifequal %}
Proprio come {% if %}
, il {% ifequal %}
tag supporta un opzionale
{% else %}
:
{% ifequal section 'sitenews' %} <h1>Site News</h1> {% else %} <h1>No News Here</h1> {% endifequal %}
Solo le variabili di template, quindi stringhe, numeri interi e numeri decimali,
sono consentite come argomenti del tag {% ifequal %}
. Questi sono esempi
validi:
{% ifequal variable 1 %} {% ifequal variable 1.23 %} {% ifequal variable 'foo' %} {% ifequal variable "foo" %}
Eventuali altri tipi di variabili, come dizionari Python, elenchi o booleani,
non possono essere a livello di codice in {% ifequal %}
. Questi sono esempi
validi:
{% ifequal variable True %} {% ifequal variable [1, 2, 3] %} {% ifequal variable {'key': 'value'} %}
Se hai bisogno di verificare se qualcosa è vero o falso, utilizzare il tag
{% if %}
invece di {% ifequal %}
.
Proprio come in HTML o Python, il linguaggio di template Django permette i
commenti. Per designare un commento, si usa {# #}
:
{# Questo e' un commento #}
Il commento non sarà considerato quando il template viene renderizzato.
Utilizzando questa sintassi, i commenti non possono estendersi su più righe. Questa limitazione migliora le prestazioni del parse del template. Nel seguente template, l'output del rendering sarà esattamente lo stesso del template (ad esempio, il tag di commento non viene analizzato come un commento):
Questo e' un commento {# questo non e' un commento #} test.
Se si desidera utilizzare i commenti su più righe, è necessario utilizzare il
tag {% comment %}
, in questo modo:
{% comment %} Questo e' un commento multi-linea. {% endcomment %}
Come spiegato in precedenza in questo capitolo, i filtri dei template sono semplici modi di alterare il valore delle variabili prima di essere visualizzati. I filtri utilizzano un carattere pipe, come questo:
{{ name|lower }}
Visualizza il valore della {{ name }}
variabile, dopo essere stata filtrata
attraverso il filtro lower
, che converte il testo in lettere minuscole.
I filtri possono essere concatenati -- cioè, essi possono essere utilizzati in combinazione in modo che il risultato di un filtro venga applicato al successivo. Ecco un esempio che prende il primo elemento in una lista e lo converte in maiuscolo:
{{ my_list|first|upper }}
Alcuni filtri richiedono degli argomenti. Un argomento viene passato al filtro usando i due punti ed è si mette sempre tra virgolette. Per esempio:
{{ bio|truncatewords:"30" }}
Questo esempio visualizza le prime 30 parole della variabile bio
.
I seguenti sono alcuni dei filtri più importanti. Leggi l'Appendice E per scoprire il resto.
addslashes
: Aggiunge una barra rovesciata prima di ogni backslash, apostrofo, o virgolette. Questo è utile se il testo prodotto è incluso in una stringa JavaScript.date
: Formatta un oggettodate
odatetime
in base a una stringa di formato passata come parametro, ad esempio:{{ pub_date|date:"F j, Y" }}
Le stringhe di formato sono definite nell'Appendice E.
length
: Restituisce la lunghezza del valore. Per un elenco, restituisce il numero di elementi. Per una stringa, restituisce il numero di caratteri. (Esperti di Python, prendiate atto che questo funziona su qualsiasi oggetto Python che sa come determinare la sua lunghezza - ovvero, ogni oggetto che ha un metodo__len__()
).
Ora che hai toccato con mano il linguaggio di template di Django, segnaliamo alcuni dei suoi limiti intenzionali, insieme ad alcune filosofie che stanno dietro al perché funziona nel modo in cui funziona.
Più di ogni altra componente delle applicazioni Web, la sintassi del template è molto soggettiva, e le opinioni dei programmatori variano selvaggiamente. Il fatto che solo Python abbia decine, se non centinaia, di implementazioni open source di linguaggi di template rafforza questo punto. Ciascuno è stato probabilmente creato perché il suo sviluppatore ha ritenuto tutte le lingue di template esistenti inadeguati. (In realtà, si dice che sia un rito di passaggio per ogni sviluppatore Python scrivere il proprio linguaggio di template! Se non lo hai ancora fatto, considera di farlo. Si tratta di un esercizio divertente).
Tenendo questo a mente, si potrebbe essere interessati a sapere che Django non richiede di utilizzare il linguaggio di template. Perché Django è destinato ad essere un framework web "full-stack" (ovvero completo ed intercambiabile) che mette a disposizione tutti i pezzi necessari agli sviluppatori web per essere produttivi, molte volte è più comodo da usare sistema di template di Django di altre librerie di modelli di Python, ma non è un requisito rigoroso. Come si vedrà nel paragrafo "Uso dei template nelle View", è molto semplice usare un altro linguaggio di template con Django.
Eppure, è chiaro che abbiamo una forte preferenza per il modo in cui la lingua di template di Django funziona. Il template di sistema ha radici nel modo in cui lo sviluppo web è stato fatto al World Online e all'esperienza combinata dei creatori di Django. Ecco alcune di quelle filosofie:
La logica di business deve essere separata dalla logica di presentazione. Gli sviluppatori di Django vedono il template di sistema come uno strumento che controlla la presentazione e la logica della presentazione correlata -- e questo è tutto. Il template di sistema non dovrebbe supportare funzionalità che vanno al di là di questo obiettivo di base.
Per questo motivo, non è possibile chiamare direttamente codice Python all'interno dei modelli di Django. La "programmazione" è fondamentalmente limitata a ciò che i tag dei template possono fare. E' possibile scrivere dei tag per i template personalizzati che fanno cose arbitrarie, ma i tag "out-of-the-box" (ovvero predefiniti) dei template di Django volutamente non consentono l'esecuzione di codice Python arbitrario.
La sintassi dovrebbe essere disaccoppiato da HTML/XML. Sebbene il sistema di template di Django viene utilizzato principalmente per la produzione di codice HTML, ed è destinato ad essere altrettanto utile per i formati non HTML, come il testo normale. Alcune altre lingue template sono basate su XML, mettendo tutta la logica all'interno dei tag dei template o degli attributi XML, ma Django evita deliberatamente questa limitazione. Essi richiedono XML valido per scrivere i template ed introduce quindi un mondo di errori umani da capire con duri messaggi di errore, ed inoltre l'utilizzo di un motore XML per analizzare i template comporta un livello inaccettabile a prescindere dal come è fatto il template.
Si assume che i designer siano a loro agio con il codice HTML. Il template di sistema non è stato progettato in modo che siano visualizzati bene su editor WYSIWYG come Dreamweaver. Questo è una limitazione troppo severa e non avrebbe permesso alla sintassi di essere amichevole come è adesso. Django si aspetta autori di template che siano a loro agio direttamente con l'HTML.
Si assume che i designer non siano programmatori Python. Gli autori sanno che i template per le pagine web sono spesso scritti da designer, non da programmatori, e quindi non devono conoscere Python.
Tuttavia, il sistema intende inoltre ospitare piccole parti in cui i template siano creati da programmatori Python. Offre quindi un modo per estendere la sintassi del sistema di scrittura di codice Python. (Maggiori informazioni su questo nel capitolo 9).
L'obiettivo non è quello di inventare un linguaggio di programmazione. L'obiettivo è di offrire solo le funzionalità di programmazione semplice, come salti e cicli, che sono essenziali per prendere decisioni relative alla presentazione di una informazione.
Hai imparato le nozioni di base sull'uso del sistema di template, ora usiamo
questa conoscenza per creare una vista. Ricordiamo la vista current_datetime
in mysite.views
, che abbiamo iniziato nel capitolo precedente. Ecco come si
presenta:
from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
Cambiamo questa visualizzazione per utilizzare il sistema di template di Django. In un primo momento, si potrebbe pensare di fare qualcosa di simile a questo:
from django.template import Template, Context from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() t = Template("<html><body>It is now {{ current_date }}.</body></html>") html = t.render(Context({'current_date': now})) return HttpResponse(html)
Certo, utilizza il sistema di template, ma non risolve i problemi che abbiamo sottolineato nell'introduzione di questo capitolo. Vale a dire, il template è ancora incorporato nel codice Python, così la vera separazione fra dati e presentazione non è raggiunta. Cerchiamo di rimediare spostando il template in un file separato, che questa view carica.
Si potrebbe invece considerare di salvare prima il template da qualche parte sul
tuo filesystem e poi utilizzare le funzionalità di apertura file integrata in
Python per leggere il contenuto del template. Ecco come sembra il tutto,
prendendo per vero il fatto che il template sia stato salvato in questo modo
/home/djangouser/templates/mytemplate.html
:
from django.template import Template, Context from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() # Simple way of using templates from the filesystem. # This is BAD because it doesn't account for missing files! fp = open('/home/djangouser/templates/mytemplate.html') t = Template(fp.read()) fp.close() html = t.render(Context({'current_date': now})) return HttpResponse(html)
Tuttavia, questo approccio è poco elegante per questi motivi:
- Non gestisce il caso di un file mancante. Se il file
mytemplate.html
non esiste o non è leggibile, la chiamataopen()
genera un'eccezioneIOError
. - Il percorso del template potrebbe essere lungo. Se si sceglie di usare questa tecnica per ogni view, si dovrebbero duplicare le posizioni dei template. Per non parlare del fatto che comporta un bel po' di battitura!
- Comprende un sacco di codice standard noioso. Hai di meglio da fare che
scrivere chiamate a
open()
,fp.read()
, efp.close()
ogni volta che si carica un template.
Per risolvere questi problemi, useremo quindi il Template Loading (caricamento dei Template) e le directory dei template.
Django fornisce un'API comoda e potente per il caricamento dei template dal filesystem, con l'obiettivo di eliminare la ridondanza sia nelle chiamate ai template che nei template stessi.
Per poter utilizzare le API del Template Loading, in primo luogo è necessario
dire al framework dove si memorizzano i template. Il luogo per far ciò è nel
file di impostazioni -- il file settings.py
che abbiamo menzionato
nell'ultimo capitolo, quando abbiamo introdotto l'impostazione ROOT_URLCONF
.
Continuando così, aprire il settings.py
e trovare l'impostazione
TEMPLATE_DIRS
. Per impostazione predefinita, è una tupla vuota,
probabilmente contenente alcuni commenti generati automaticamente:
TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. )
Questa impostazione indica al meccanismo di caricamento di Django dove cercare i
modelli. Scegli una directory in cui vuoi memorizzare i template e aggiungilo a
TEMPLATE_DIRS
, in questo modo:
TEMPLATE_DIRS = ( '/home/django/mysite/templates', )
Ci sono alcune cose da notare:
È possibile specificare qualsiasi directory che si desidera, a condizione che la directory ed i template all'interno di tale directory siano leggibili dall'account utente in cui il server web viene eseguito. Se non hai idea del nome da dare ad una directory per i template, si consiglia di creare una cartella
templates
all'interno del progetto (cioè, all'interno della directorymysite
creata nel capitolo 2).Se il tuo
TEMPLATE_DIRS
contiene solo una directory, non dimenticare comunque la virgola alla fine della stringa di directory!Male:
# Virgola Mancante! TEMPLATE_DIRS = ( '/home/django/mysite/templates' )
Bene:
# Virgola correttamente inserita. TEMPLATE_DIRS = ( '/home/django/mysite/templates', )
La ragione di questo è che Python richiede virgole all'interno di tuple in un singolo elemento per rendere non ambiguo il fatto che sia una tupla. Questo è un comune ai Principianti.
Se sei in Windows, includere la lettera dell'unità e utilizzare le barre in stile Unix piuttosto che gli backslash, come segue:
TEMPLATE_DIRS = ( 'C:/www/django/templates', )
E' semplice usare percorsi assoluti (cioè percorsi di directory che iniziano alla radice del filesystem). Ma se si vuole essere un po' più flessibili e disaccoppiati, si può approfittare del fatto che i file delle impostazioni Django sono solo codice Python costruendo il contenuto del
TEMPLATE_DIRS
dinamicamente. Per esempio:import os.path TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'), )
Questo esempio utilizza la "magia" della variabile
__file__
di Python, che viene impostata automaticamente sul nome del modulo in esecuzione. Si prende il nome della directory che contienesettings.py
(os.path.dirname
), lo si unisce a quella dei modelli indipendentemente dal sistema in uso (os.path.join
) e ci si assicura che tutto utilizza le barre invece che il backslash (nel caso di Windows).Parlando di codice dinamico nel file di impostazione, bisogna dire che è molto importante evitare errori Python nel file di impostazioni. Se si introduce un errore di sintassi, o di un errore di runtime, il tuo sito Django-powered probabilmente lo si bloccherà.
Con TEMPLATE_DIRS
, il passo successivo è quello di modificare il codice al
fine di utilizzare la funzionalità di Template loading inclusa in Django,
piuttosto che a livello di codice. Tornando alla nostra view current_datetime
,
cambiamo il tutto in questo modo:
from django.template.loader import get_template from django.template import Context from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() t = get_template('current_datetime.html') html = t.render(Context({'current_date': now})) return HttpResponse(html)
In questo esempio, stiamo usando la funzione django.template.loader.get_template()
piuttosto che caricare il template dal filesystem manualmente. La funzione
get_template()
prende il nome del template come argomento, capisce dove sta
il template sul filesystem, lo apre il file, e restituisce un oggetto Template`
già compilato.
Il nostro template in questo esempio è current_datetime.html
, ma non c'è
niente di speciale al suo interno. Ha l'estensione .html
, ma è possibile
dare ai tuoi template una qualunque estensione per l'applicazione, oppure si può
proprio non usarne nessuna.
Per determinare la posizione del template sul tuo filesystem, get_template()
combina le directory template incluse in TEMPLATE_DIRS
con il nome del
template che si passa a get_template()
. Ad esempio, se i TEMPLATE_DIRS
è
impostato su '/home/django/mysite/templates'
, la chiamata get_template()
cercherà il template in /home/django/mysite/templates/current_datetime.html
.
Se get_template()
non riesce a trovare il template con il nome dato, solleva
un'eccezione TemplateDoesNotExist
. Per vedere come funziona il tutto,
avviare il server di sviluppo integrato in Django eseguendo ancora
python manage.py runserver
all'interno della directory del progetto Django.
Poi, puntare il browser alla pagina che attiva la visualizzazione
current_datetime
(ad esempio, http://127.0.0.1:8000/time/
). Assumendo
che l'impostazione DEBUG
sia impostata su True e non sia stato ancora creato
un template current_datetime.html
, si dovrebbe vedere una pagina di errore
Django evidenziando l'errore TemplateDoesNotExist
.
Questa pagina di errore è simile a quella che abbiamo spiegato nel capitolo 3, con una parte aggiuntiva di informazioni di debug: una sezione "Template-loader postmortem". Questa sezione mostra quali template Django ha cercato di caricare, insieme con il motivo per cui ogni tentativo è fallito (ad esempio, "File does not exist"). Queste informazioni sono preziose quando si sta cercando di fare un debug su errori relativi al template loading.
Andando oltre, crea il file current_datetime.html
all'interno della tua
directory template utilizzando il seguente codice di template:
<html><body>It is now {{ current_date }}.</body></html>
Aggiorna la pagina nel browser Web e si dovrebbe vedere la pagina renderizzata completamente.
Vi abbiamo mostrato come caricare un template, riempire un Context
e
restituire un oggetto HttpResponse
con il risultato del rendering di un
template. Abbiamo ottimizzato usando un get_template()
invece di codificare
i template con percorsi. Ma richiede comunque una buona dose di pazienza fare
quelle cose. Poiché questo è un cosa comunissima, Django fornisce una
scorciatoia che consente di caricare un template, renderizzarlo e restituire un
HttpResponse
-- tutto in una riga di codice.
Questa scorciatoia è una funzione chiamata render()
, che vive nel modulo
django.shortcuts
. La maggior parte del tempo, verrà usato render()
piuttosto che il caricamento dei modelli e la creazione di Context
e la
restituzione manuale di oggetti HttpResponse` -- a meno che il tuo datore di
lavoro giudichi il tuo lavoro dal numero di linee scritte.
Ecco l'esempio con la view current_datetime
riscritta per usare render()
:
from django.shortcuts import render import datetime def current_datetime(request): now = datetime.datetime.now() return render(request, 'current_datetime.html', {'current_date': now})
Che differenza! Analizziamo passo per passo questo codice:
- Non abbiamo più il compito di importare
get_template
,Template
,Context
, oHttpResponse
. Importiamo invece condjango.shortcuts.render
. Ilimport datetime
rimane. - All'interno della funzione
current_datetime
, calcoliamo ancoranow
, ma il template loading e la creazione del contesto, il rendering del template, e la creazione di unHttpResponse
lo fa la chiamatarender()
. Affinchérender()
restituisca un oggettoHttpResponse
, possiamo semplicemente restituire quel valore nella view con unreturn
.
Il primo argomento di render()
è request, il secondo è il nome del template
da utilizzare. Il terzo argomento, se indicato, deve essere un dizionario da
utilizzare nella creazione di un Context
per tale template. Se non si
fornisce un terzo argomento, render()
userà un dizionario vuoto.
Può diventare ingombrante memorizzare tutti i template in una singola directory. È possibile salvare i template in sottodirectory della tua directory dei template, e va bene. In realtà, si consiglia di farlo; alcune caratteristiche Django più avanzate (come ad esempio il sistema di viste generiche, di cui parliamo nel Capitolo 11) si aspetta questo template di layout come una convenzione di default.
Il salvataggio di modelli in sottodirectory della tua directory dei template è
facile. Nelle tue chiamate a get_template()
, basta inserire il nome della
sottodirectory e una barra prima del nome del template, in questo modo:
t = get_template('dateapp/current_datetime.html')
Poiché render()
è un piccolo wrapper di get_template()
, si può fare la
stessa cosa con il secondo argomento di render()
, in questo modo:
return render(request, 'dateapp/current_datetime.html', {'current_date': now})
Non c'è limite alla profondità dell'albero di sottodirectory. Sentiti libero di usare tutte le sottodirectory che ti pare.
Note
Gli utenti di Windows, devono assicurarsi di utilizzare le barre piuttosto
che backslash. get_template()
assume una denominazione il nome del file
in stile Unix.
Ora che abbiamo trattato il meccanismo di caricamento dei template, possiamo
introdurre un tag integrato che lo sfrutta: {% include %}
. Questo tag
consente di includere il contenuto di un altro template. L'argomento del tag
dovrebbe essere il nome del template da includere, e il nome del template può
essere una variabile o una stringa hard-coded (virgolettata), sia in virgolette
semplici o doppie. Ogni volta che si ha lo stesso codice in più template, è
consigliabile utilizzare un {% include %}
per eliminare la ripetizione.
Questi due esempi includono il contenuto del template nav.html
. Gli esempi
sono equivalenti e mostrano come le virgolette singole o doppie sono
consentite:
{% include 'nav.html' %} {% include "nav.html" %}
Questo esempio include il contenuto del template includes/nav.html
:
{% include 'includes/nav.html' %}
Questo esempio include il contenuto del template cui nome è contenuto nella
variabile template_name
:
{% include template_name %}
Come in get_template()
, il nome del file del template è determinato sommando
la directory dei template prese da TEMPLATE_DIRS
al nome del template
richiesto.
I template inclusi sono valutati con il contesto del template che li ha compresi. Per esempio, prendi in considerazione questi due template:
# mypage.html <html> <body> {% include "includes/nav.html" %} <h1>{{ title }}</h1> </body> </html> # includes/nav.html <div id="nav"> You are in: {{ current_section }} </div>
Se si esegue il rendering di mypage.html
con un contesto contenente
current_section
, allora la variabile sarà disponibile nel template "incluso",
come ci si aspetterebbe.
Se, in un tag {% include %}
, un template con il nome dato non viene trovato,
Django farà una delle due cose:
- Se
DEBUG
è impostato suTrue
, verrà mostrata l'eccezioneTemplateDoesNotExist
su una pagina di errore di Django. - Se
DEBUG
è impostato suFalse
, il tag fallirà in silenzio, non visualizzando nulla al posto del tag.
I nostri esempi di template finora sono stati snippet di codice HTML molto piccolo, ma nel mondo reale, potrai utilizzare il sistema di template di Django per creare intere pagine HTML. Questo porta ad un problema di sviluppo web comune: in un sito web, come ridurre la duplicazione e la ridondanza delle aree di pagina comuni, come la navigazione in tutto il sito?
Un classico modo di risolvere questo problema è quello di utilizzare un include
lato server, che è possibile incorporare nelle tue pagine HTML per "includere"
una pagina web all'interno di un'altra. Infatti, Django supporta questo
approccio, con il {% include %}
tag appena descritto. Ma il modo preferito
di risolvere questo problema con Django è utilizzare una strategia più elegante
chiamata ereditarietà dei template (Template Inheritance).
In sostanza, l'ereditarietà dei template consente di costruire un template "scheletro" di base che contiene tutte le parti comuni del tuo sito e definisce "blocchi" che i template figli possono ignorare.
Vediamo un esempio di questo con la creazione di un template più completo per la
nostra vista current_datetime
, modificando il file current_datetime.html
:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>The current time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>It is now {{ current_date }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
Che sembra vada bene, ma cosa succede quando si vuol creare un template per
un'altra vista -- per esempio, la vista hours_ahead
del capitolo 3? Se
vogliamo ancora una volta a fare un bel template, valido, completo HTML,
ci piacerebbe creare qualcosa di simile a:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>Future time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
Chiaramente, abbiamo appena duplicato un sacco di HTML. Immaginate se avessimo una pagina più generica, con una barra di navigazione, un paio di fogli di stile, forse un po' di JavaScript -- saremmo finiti a mettere tutti i tipi di codice HTML ridondante in ogni template.
La soluzione lato server con l'include a questo problema è quello di scomporre i
bit comuni in entrambi i modelli e salvarli in frammenti di template separati,
che vengono poi inclusi in ciascun template. Si salva il template in un file
chiamato header.html
:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head>
È forse può essere utile salvare il footer.html
:
<hr> <p>Thanks for visiting my site.</p> </body> </html>
Con una strategia basata su include, le intestazioni a piè di pagina sono
facili. E' la terra di mezzo che è disordinata. In questo esempio, entrambe le
pagine dispongono di un titolo -- <h1>My helpful timestamp site</h1>
-- ma
questo titolo non può essere inserito in header.html
perché il <title>
su entrambe le pagine è diverso. Se abbiamo inserito un <h1>
nell'intestazione, avremmo dovuto includere il <title>
, che non ci
permetterebbe di personalizzarlo per ogni pagina. Vedi dove stiamo andando?
Il Sistema di ereditarietà dei template di Django risolve questi problemi. Si può pensare ad esso come una versione "inside-out" di include lato server. Invece di definire i frammenti che sono comuni, si definiscono i frammenti che sono diversi.
Il primo passo è quello di definire un template di base -- uno scheletro della tua pagina che i template figli devono successivamente riempire. Ecco un template di base per il nostro esempio:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html>
Questo template, che chiameremo base.html
, definisce un semplice documento
HTML "scheletro" che useremo per tutte le pagine del sito. E' il lavoro dei
template figli ignorare, aggiungere o lasciare da soli i contenuti dei blocchi.
(Se si sta seguendo l'esempio, salvare il file nella directory dei template come
base.html
).
Stiamo usando un tag qui che non hai mai visto prima: il tag {% block %}
.
Tutti i tag {% block %}
dicono all motore di template che un template figlio
potrebbe voler ignorare quelle parti del template.
Ora che abbiamo questo template di base, possiamo modificare il nostro template
current_datetime.html
esistente per utilizzarlo:
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %} <p>It is now {{ current_date }}.</p> {% endblock %}
Mentre ci siamo, cerchiamo di creare un template per la view hours_ahead
dal
capitolo 3. (Se stai seguendo il codice, lasceremo a te cambiare hours_ahead
per utilizzare il sistema di template, invece di HTML a livello di codice). Ecco
come potrebbe apparire:
{% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %} <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> {% endblock %}
Non è bello? Ogni template contiene solo il codice che è unico per quel template.
Nessuna ridondanza necessaria. Se hai bisogno di fare una modifica di
progettazione a livello di sito, basta fare una modifica al base.html
, e
tutti gli altri template attueranno il cambiamento subito.
Ecco come funziona. Quando si carica il template current_datetime.html
,
l'engine dei template vede il tag {% extends %}
, notando che questo template
è un template figlio. Il motore carica subito il template padre -- in questo
caso, base.html
.
A quel punto, il motore di template nota i tre tag {% block %}
in
base.html
e sostituisce i blocchi con il contenuto del template figlio.
Così, {% block title %}
sarà utilizzato per il titolo definito, così come il
{% block content %}
per il contenuto.
Si noti che, poiché il template figlio non definisce il blocco footer
,
il template di sistema utilizza il valore del template padre, invece. Il
contenuto all'interno di un tag {% block %}` in un template genitore è sempre
usato come fallback.
L'ereditarietà non influenza il contesto del template. In altre parole, qualsiasi template ce fa parte della struttura di ereditarietà avrà accesso a tutte le tue variabili del contesto/context.
È possibile utilizzare un certo numero di livelli di ereditarietà, se necessario. Un modo comune di usare l'ereditarietà è il seguente approccio a tre livelli:
- Creare un template
base.html
che detiene l'aspetto principale ed il corpo del tuo sito. Questa è la roba che raramente, se mai, cambia. - Creare un template
base_SECTION.html
per ogni "sezione" del tuo sito (ad esempio,base_photos.html
ebase_forum.html
). Questi template estendonobase.html
e includono gli stili di design specifici per la sezione. - Creare dei singoli modelli per ogni tipo di pagina, ad esempio una pagina forum o una galleria fotografica. Questi modelli estendono il template della sezione appropriata.
Questo approccio ottimizza il riutilizzo del codice e rende facile aggiungere elementi alle aree comuni, come la sezione di navigazione.
Ecco alcune linee guida per lavorare con l'ereditarietà dei template:
- Se si utilizza
{% extends %}
in un template, deve essere il primo tag template in quel template. In caso contrario, l'ereditarietà dei template non funzionerà. - Generalmente, più tag
{% block %}
ci sono nel template di base, meglio è. Ricorda, i template figli devono definire tutti i blocchi principali, in modo da poter compilare di default una serie di blocchi ragionevole, e quindi definire solo quelle che ti servono nei template figli. E' meglio avere più appigli che non di meno. - Se ti trovi a duplicare il codice in diversi template, probabilmente significa
che dovresti spostare il codice con un
{% block %}
nel template padre. - Se hai bisogno di avere il contenuto del blocco del template padre,
utilizza
{{ block.super }}
, che è una variabile "magica" che fornisce il testo renderizzato del template padre. Questo è utile se si desidera aggiungere il contenuto di un blocco padre invece di sovrascriverlo completamente. - Non si può definire più di un tag
{% block %}
con lo stesso nome nello stesso template. Questa limitazione esiste perché un tag di blocco funziona in "due" direzioni. Ovvero, un tag di blocco non si limita a fornire un buco da riempire, ma definisce anche il contenuto che riempie il buco nel genitore. Se ci fossero due{% block %}
uguali in un template, il padre di quel template non avrebbe modo di sapere quale dei contenuti utilizzare. - Il nome del template si passa a
{% extends %}
e viene caricato utilizzando lo stesso metodo che utilizzaget_template()
. Cioè, il nome del template è collegato alla variabile d'ambienteTEMPLATE_DIRS
. - Nella maggior parte dei casi, l'argomento di
{% extends %}
sarà una stringa, ma può anche essere una variabile quando non si conosce il nome del template padre fino a runtime. Questo permette di essere freschi e dinamici.
Ora avete le basi del sistema di template di Django sotto la cintura. Cosa c'è adesso?
Molti siti web moderni sono basati su database: il contenuto del sito web è memorizzato in un database relazionale. Questo permette una netta separazione di dati e logica (come, allo stesso modo, view e template permettono di separare la logica dalla visualizzazione).
Il capitolo successivo Capitolo 5 analizza gli strumenti che Django mette a disposizione per interagire con un database.