Nel capitolo precedente, abbiamo spiegato come creare un progetto Django e come eseguire il server di sviluppo integrato. In questo capitolo, imparerai le basi della creazione di pagine web dinamiche con Django.
Come nostro primo obiettivo, cerchiamo di creare una pagina web che mostra il più famoso messaggio di esempio: "Hello world".
Per pubblicare un semplice "Hello world" in una pagina web senza un framework,
basta semplicemente digitare "Hello world" in un file di testo, chiamarlo
hello.html
, e caricarlo in una directory su un server Web da qualche parte.
Da notare che in questo processo, sono stati specificati due informazioni chiave
su quella pagina web: il suo contenuto (la stringa "Hello world"`) e il relativo
URL (http://www.example.com/hello.html
, o forse http://www.example.com/files/hello.html
se lo hai inserito in una sottodirectory).
Con Django, si specificano le stesse due cose, ma in un modo diverso. I contenuti della pagina sono prodotti da una funzione di visualizzazione e l'URL è specificato in un URLconf. In primo luogo, scriviamo la funzione di visualizzazione "Hello world".
All'interno della directory miosito dove abbiamo fatto django-admin.py startproject
nel capitolo precedente, creare un file vuoto chiamato views.py
. Questo
modulo Python conterrà le nostre elucubrazioni per questo capitolo. Da notare
che non c'è niente di speciale nel nome views.py
-- Django non si cura del
nome del file, come vedremo tra un po' -- ma è una buona idea lasciarlo views.py
come convenzione, per il bene di altri sviluppatori che leggono il tuo codice.
La nostra view/vista "Hello world" è semplice. Ecco l'intera funzione, oltre
alle istruzioni di importazione, da digitare nel file views.py
:
from django.http import HttpResponse def hello(request): return HttpResponse("Hello world")
Chiariamo ciò che abbiamo scritto per ogni riga di codice:
In primo luogo, importiamo la classe
HttpResponse
, che sta nel modulodjango.http
. Abbiamo bisogno di importare questa classe perché è utilizzato in seguito nel nostro codice.Più avanti, definiamo una funzione chiamata hello` -- la funzione di visualizzazione.
Ogni funzione di visualizzazione richiede almeno un parametro, chiamato
request
("richiesta", in inglese) per convenzione. Questo è un oggetto che contiene informazioni relative alla richiesta web corrente che ha innescato questa vista/view, ed è un'istanza della classedjango.http.HttpRequest
. In questo esempio, non facciamo nulla conrequest
, ma deve essere comunque il primo parametro della view/vista.Da notare che il nome della funzione di visualizzazione non è importante, non deve essere nominata in un certo modo affinché Django lo riconosca. L'abbiamo chiamata
hello
qui perché questo nome indica chiaramente il senso della view/vista, ma potrebbe benissimo essere nominatohello_wonderful_beautiful_world
, o qualcosa di altrettanto rivoltante. La sezione successiva, "Il tuo primo URLconf", farà luce su come Django trova questa funzione.La funzione è un semplice: si limita a restituire un oggetto
HttpResponse
che è stato istanziato con il testo"Hello world"
.
La lezione principale è questa: una vista è semplicemente una funzione Python
che prende un HttpRequest
come primo parametro e restituisce un'istanza di
HttpRequest
. Affinché una funzione Python sia una view/vista per Django,
deve fare queste due cose. (Ci sono delle eccezioni, ma arriveremo a quelli
più tardi)
Se, a questo punto, è stato eseguito di nuovo python manage.py runserver
,
dovresti ancora vedere il messaggio "Welcome to Django", senza alcuna traccia
della nostra view/vista "Hello world" da nessuna parte. Questo perché il nostro
progetto mysite
non sa ancora della view hello`, abbiamo bisogno di dire
esplicitamente a Django che stiamo attivando questa view in un determinato URL.
(Continuando la nostra precedente analogia con la pubblicazione di file HTML
statici, a questo punto abbiamo creato il file HTML, ma non l'abbiamo ancora
caricato in una directory sul server). Per aggiungere una funzione di
visualizzazione ad un particolare URL in Django, bisogna utilizzare un URLconf.
Un URLconf è come una tabella di contenuti per il tuo sito web Django-powered.
In sostanza, si tratta di un mapping (ovvero creare delle relazioni) tra URL e
funzioni di visualizzazione che dovrebbero essere chiamate per tali URL. E' come
dire a Django, "Per questo URL, devi chiamare questo codice, e per questo URL,
devi chiamare quel codice". Per esempio, "Quando qualcuno visita l'URL /foo/
,
chiama la funzione vista foo_view()
, che sta nel modulo views.py
".
Quando si esegue django-admin.py startproject
nel capitolo precedente, lo
script crea un URLconf per te automaticamente: il file urls.py
. Per
impostazione predefinita, è qualcosa di simile a questo:
from django.conf.urls import patterns, include, url # Uncomment the next two lines to enable the admin: # from django.contrib import admin # admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'mysite.views.home', name='home'), # url(r'^mysite/', include('mysite.foo.urls')), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: # url(r'^admin/', include(admin.site.urls)), )
Questo URLconf di default include alcune caratteristiche di Django comunemente utilizzate commentate, in modo che l'attivazione di queste caratteristiche sia facile, basta togliere il commento alle righe appropriate. Se ignoriamo il codice commentato, ecco l'essenza di un URLconf:
from django.conf.urls.defaults import patterns, include, url urlpatterns = patterns('', )
Esaminiamo questo codice una riga alla volta:
- Nella prima linea, stiamo importando tre moduli da
django.conf.urls.defaults
, che è la base per gli URLconf di Django:patterns
include
eurls
; - La seconda riga chiama le funzioni
patterns
e salva il risultato in una variabile chiamataurlpatterns
. Ad essa viene passato un solo argomento -- la stringa vuota. (La stringa può essere utilizzata per fornire un prefisso comune a tutte le funzioni, come vedremo nel :doc:`chapter08`).
La cosa più importante da notare qui è vedere come variabile urlpatterns
,
che Django si aspetta di trovare nel tuo modulo URLconf. Questa variabile
definisce il mapping tra URL e codici che gestiscono tali URL. Per impostazione
predefinita, come possiamo vedere, l'URLconf è vuoto -- l'applicazione Django è
un tabula rasa. (Come nota a margine, è il modo che Django ha per mostrare la
pagina "Welcome to Django" nel capitolo precedente. Se il tuo URLconf è vuoto,
Django assume che hai appena iniziato un nuovo progetto e, quindi, mostra quel
messaggio).
Per aggiungere un URL all'URLconf, basta aggiungere una relazione tra un pattern
relativo all'URL e la funzione di visualizzazione. Ecco come collegare la nostra
view hello
:
from django.conf.urls.defaults import patterns, include, url from mysite.views import hello urlpatterns = patterns('', url(r'^hello/$', hello), )
(Si noti che abbiamo rimosso il codice commentato per brevità. È possibile lasciare quelle linee senza problemi).
Abbiamo fatto due modifiche qui:
- In primo luogo, abbiamo importato la view
hello
dal suo modulo --mysite/views.py
, che si traduce conmysite.views
nella sintassi di import. (Premessomysite/views.py
è sul tuo percorso Python, vedi la barra laterale per i dettagli); - Successivamente, abbiamo aggiunto la linea
url(r'^hello/$', hello),
aurlpatterns
. Questa linea è detta come un URLpattern. La funzioneurl()
dice a Django come gestire l'url che si sta configurando. Il primo argomento è una stringa pattern-matching (una espressione regolare; ne discuteremo meglio fra un po') e il secondo argomento è la funzione di visualizzazione da utilizzare per quel pattern.url()
può assumere altri argomenti opzionali, che vedremo più in dettaglio in :doc:`chapter08`.
Note
Il dettaglio più importante che abbiamo introdotto qui è il carattere r
che sta davanti alla stringa di espressione regolare. Questo dice a Python che
la stringa è una "stringa raw" -- il suo contenuto non deve essere
interpretato. Nelle normali stringhe Python, i backslash sono usati per
scrivere caratteri speciali - come la stringa '\n'
per andare a capo.
Quando si aggiunge la r
per renderlo una stringa raw, Python non lo
considera un carattere di escape -- così, r'\n'
è una stringa di due
caratteri che contiene una barra rovesciata e un carattere minuscolo "n".
C'è una problematica naturale fra l'utilizzo di backslash in Python ed i
backslash che si trovano nelle espressioni regolari, quindi è fortemente
consigliato l'uso di stringhe raw in tutti i casi in cui si sta definendo
un'espressione regolare in Python. Tutte gli URLpattern in questo libro
sono stringhe raw.
In poche parole, abbiamo appena detto a Django che qualsiasi richiesta all'URL
/hello/
dovrebbe essere gestita dalla funzione di visualizzazione hello
.
Il tuo percorso di Python
Il tuo percorso di Python è l'elenco delle directory del sistema in cui
Python appare quando si utilizza l'istruzione import
Python.
Ad esempio, supponiamo che il vostro percorso di Python è impostato su ['',
'/usr/lib/python2.7/site-packages', '/home/username/djcode']
. Se si esegue
l'istruzione Python from foo import bar
, Python cercherà un modulo
chiamato foo.py
nella directory corrente. (La prima voce nel percorso di
Python, una stringa vuota, significa sostanzialmente "la directory corrente").
Se questo file non esiste, Python cercherà il file /usr/lib/python2.7/site-packages/foo.py
.
Se questo file non esiste, proverà /home/username/djcode/foo.py
.
Infine, se neanche questo file non esiste, solleva l'eccezione ImportError
.
Se sei interessato a vedere il suddetto percorso, avviare l'interprete interattivo Python e digitare:
>>> import sys >>> print sys.path
Generalmente non devi preoccuparti di impostare il percorso di Python --
Python e Django si prendono automaticamente cura di queste cose per noi
dietro le quinte. (l'impostazione del percorso di Python è una delle cose
che fa lo script manage.py
).
Vale la pena discutere la sintassi di questo urlPattern, in quanto potrebbe
non essere immediatamente evidente. Anche se vogliamo lavorare con l'URL
/hello/
, il pattern è un po' diverso da questo. Ecco perché:
Django rimuove la barra dalla parte anteriore di ogni URL in ingresso prima di controllare gli URLpatterns. Questo significa che il nostro urlPattern non include la barra iniziale in
/hello/
. (In un primo momento, può sembrare poco intuitivo, ma questo requisito semplifica le cose -- come l'inserimento di alcuni URLconf all'interno di altri URLconf, come vedremo nel capitolo 8);Il pattern include un accento circonflesso (
^
) e il simbolo del dollaro ($
). Questi sono i tipici caratteri delle espressioni regolari e che hanno un significato particolare: l'accento circonflesso significa "richiedo che il pattern corrisponda all'inizio della stringa," e il simbolo del dollaro significa "richiedo che il pattern corrisponda alla fine della stringa";Questo concetto si spiega meglio con un esempio. Se avessimo usato invece il pattern
'^hello/'
(senza il simbolo del dollaro alla fine), quindi qualsiasi URL che inizia con/hello/
potrebbe corrispondere, ad esempio/hello/foo
e/hello/bar
, e non solo/hello/
. Allo stesso modo, se avessimo lasciato fuori l'accento circonflesso iniziale (vale a dire,'hello/$'
), Django avrebbe scelto qualsiasi URL che termina conhello/
come/foo/bar/hello/
. Se avessimo usato semplicementehello/
, senza un segno di accento circonflesso o dollaro, qualsiasi URL contenentehello/
sarebbe stato valido, come/foo/hello/bar
. Quindi, usiamo sia il segno di accento circonflesso che il dollaro per garantire che solo gli URL/hello/
siano validi -- niente di più, niente di meno.La maggior parte dei tuoi URLpattern iniziano con accenti e terminano con segni di dollaro, ma è bello avere comunque la flessibilità necessaria per svolgere compiti più sofisticati.
Ci si potrebbe chiedere cosa succederebbe se qualcuno richiedess l'URL
/hello
(cioè senza lo slash finale). Poiché il nostro urlPattern richiede una slash finale, tale URL non dovrebbe funzionare. Tuttavia, per impostazione predefinita, qualsiasi richiesta per un URL che non corrisponde a un urlPattern e non termina con uno slash verrà comunque reindirizzato allo stesso URL con una barra finale. (questa regola è data dall'impostazioneAPPEND_SLASH
di Django, trattata nell'Appendice D).Se sei il tipo di persona che ama tutti gli URL che terminano con gli slash (che è la preferenza degli sviluppatori di Django), tutto quello che bisogna fare è aggiungere uno slash per ciascun urlPattern e lasciare
APPEND_SLASH
impostata suTrue
. Se si preferisce invece che gli URL non debbano avere lo slash, o se si vuole decidere in maniera mirata come comportarsi, impostareAPPEND_SLASH
suFalse
ed inserire gli slash finali negli URLpattern come meglio si crede.
L'altra cosa da notare in questo URLconf è che abbiamo passato la view hello
come un oggetto, senza chiamare la funzione. Questa è una caratteristica
fondamentale di Python (e altri linguaggi dinamici): le funzioni sono oggetti di
prima classe, il che significa che è possibile passarli in giro, proprio come
tutte le altre variabili. Belle cose eh?
Per testare le nostre modifiche a URLconf, avviare il server di sviluppo, come
fatto nel capitolo 2, eseguendo il comando python manage.py runserver
. (se
lo si aveva lasciato in esecuzione, va bene, poiché il server di sviluppo rileva
automaticamente le modifiche al codice Python e si ricarica, se necessario, in
modo che non sia necessario riavviare il server per vedere i cambiamenti). Il
server è in esecuzione all'indirizzo http://127.0.0.1:8000/
, quindi aprire
un browser ed aprire l'indirizzo http://127.0.0.1:8000/hello/
. Si dovrebbe
vedere il testo "Hello world" -- La tua view in Django.
Evviva! Hai creato la tua prima pagina web Django-powered.
Espressioni regolari
Le espressioni regolari (o regex) sono un modo compatto di specificare dei pattern nel testo. Mentre gli URLconf consentono di scrivere regex arbitrarie per fare qualunque magheggio con gli URL, probabilmente userai solo pochi simboli regex nella pratica. Ecco una selezione di simboli comuni:
Per ulteriori informazioni sulle espressioni regolari, leggere la pagina all'indirizzo http://www.djangoproject.com/r/python/re-module/.
A questo punto, il nostro URLconf definisce un solo urlPattern: quello che
gestisce le richieste per l'URL /hello/
. Cosa succede quando si richiede un
URL diverso?
Per scoprirlo, prova ad eseguire il server di sviluppo e visitare una pagina
come http://127.0.0.1:8000/goodbye/
o http://127.0.0.1:8000/hello/subdirectory/
,
o anche http://127.0.0.1:8000/
(la "root" del sito web). Si dovrebbe ricevere
un errore con il messaggio "Page not found" (vedi Figura 3-1). Django mostra
questo messaggio perché hai richiesto un URL che non è definito in URLconf.
L'utilità di questa pagina va oltre il messaggio di errore 404 di base. Ci dice anche con precisione quali URLconf ha usato Django e ogni modello in quell'URLconf. Da queste informazioni, si dovrebbe essere in grado di dire perché l'URL richiesto ha provocato un errore 404.
Naturalmente, si tratta di informazioni sensibili destinato solo per te, sviluppatore web. Se fossimo su un sito di produzione distribuito su Internet, non vorremmo esporre tali informazioni al pubblico. Per questo motivo, questa pagina "Pagina non trovata" viene mostrata solo se il progetto Django è in modalità di debug. Spiegheremo come disattivare la modalità di debug in seguito. Per ora, è sufficiente sapere che ogni progetto Django è in modalità di debug quando viene creato per la prima volta, e se il progetto non è in modalità di debug, Django genera delle pagine di risposta di 404 differenti.
Come spiegato nel paragrafo precedente, viene mostrato un messaggio di errore
404 se si naviga verso la root del sito -- http://127.0.0.1:8000/
. Django non
aggiunge magicamente nulla alla root del sito, l'URL non è speciale o diverso in
alcun modo da qualunque altro. Sta a noi assegnarlo ad un urlPattern, proprio
come ogni altra voce della URLconf.
Però, l'urlPattern da abbinare alla radice del sito è poco intuitivo, vale
quindi la pena specificare meglio. Quando si è pronti ad implementare una
view/vista per la root principale del sito, utilizzare l'urlPattern '^$'
,
che corrisponde a una stringa vuota. Per esempio:
from mysite.views import hello, my_homepage_view urlpatterns = patterns('', url(r'^$', my_homepage_view), # ... )
Prima di continuare con le view, facciamo una pausa per imparare un po' di più
come funziona Django. In particolare, quando viene mostrato il messaggio
"Hello world" visitando http://127.0.0.1:8000/hello/
nel browser, cosa fa
Django dietro le quinte?
Tutto inizia con il file delle impostazioni. Quando si esegue python
manage.py runserver
, lo script cerca un file chiamato settings.py nella
directory miosito interna. Questo file contiene tutti i tipi di configurazione
per questo particolare progetto Django, tutto in maiuscolo, TEMPLATE_DIRS
,
DATABASES
ecc. L'impostazione più importante è chiamata ROOT_URLCONF
.
ROOT_URLCONF
dice a Django quale modulo Python dovrebbe essere usato come
URLconf per questo sito web.
Ricordate quando django-admin.py startproject
ha creato il file settings.py
e urls.py
? Il settings.py
autogenerato contiene un'impostazione ROOT_URLCONF
che punta al file urls.py
generato automaticamente. Apri il file settings.py
e guarda di persona, dovrebbe essere qualcosa di simile a questo:
ROOT_URLCONF = 'mysite.urls'
Questo corrisponde al file mysite/urls.py
.
Quando arriva una richiesta per un URL specifico - per esempio, una richiesta
per /hello/
-- Django carica la URLconf dall'impostazione ROOT_URLCONF
.
Infine controlla ciascuna delle URLpatterns in tale URLconf, in ordine dall'alto
verso il basso, confrontando l'URL richiesto con i modelli di uno alla volta,
fino a che non ne trova uno che corrisponde. Quando ne trova uno che corrisponde,
chiama la funzione di visualizzazione associata a tale modello, passandogli un
oggetto HttpRequest
come primo parametro. (parleremo delle specifiche HttpRequest
più tardi).
Come abbiamo visto nella nostra prima view, ad esempio, le funzioni per la
visualizzazione devono restituire un HttpResponse
. Una volta che lo fa,
Django fa il resto, convertendo l'oggetto Python in una giusta risposta web, con
intestazioni HTTP appropriate e corpo (ad esempio, il contenuto della pagina
web).
In sintesi:
- Arriva una richiesta per
/hello/
. - Django determina la root degli URLconf, cercando nell'impostazione
ROOT_URLCONF
. - Django legge tutti gli URLpatterns nell'URLconf e sceglie il primo che corrisponde a / ciao /.
- Se trova una corrispondenza, chiama la funzione di visualizzazione associata.
- La funzione di visualizzazione restituisce un
HttpResponse
.
6. Django converte l'HttpResponse
nella risposta HTTP corretta, che risulta
in una pagina web.
Ora conosci le basi per creare pagine Django-powered. E' abbastanza semplice, in realtà -- basta solo scrivere le funzioni di visualizzazione ed il file URLconf con gli url.
La nostra view "Hello world" è stata istruttiva nel mostrare le basi di come
funziona Django, ma non è stato un esempio di una pagina Web dinamica, perché il
contenuto della pagina è sempre lo stesso. Ogni volta che si richiede /hello/
,
vedremo la stessa cosa, come se fosse un normale file HTML statico.
Per la nostra seconda view, creiamo qualcosa di più dinamico -- una pagina Web che visualizza la data e l'ora attuali. Questo è passo semplice perché non comporta l'uso di un database o di qualsiasi input dell'utente -- mostriamo solo dati già in nostro possesso. E' poco più emozionante di "Hello world," ma dimostrerà alcuni nuovi concetti.
Questa view ha bisogno di fare due cose: calcolare la data e l'ora corrente e
restituire un HttpResponse
contenente tale valore. Se avete esperienza con
Python, si sa che include un modulo datetime
per calcolare le date. Ecco come
usarlo:
>>> import datetime >>> now = datetime.datetime.now() >>> now datetime.datetime(2008, 12, 13, 14, 9, 39, 2731) >>> print now 2008-12-13 14:09:39.002731
Questo è abbastanza semplice e non ha nulla a che fare con Django. E' solo codice Python. (Vogliamo sottolineare che si dovrebbe essere consapevoli di ciò che è codice "solo Python" rispetto a ciò che è specifico di Django. Mentre impari ad utilizzare Django, vorremmo che tu sia in grado di applicare le tue conoscenze ad altri progetti Python che non usano necessariamente Django).
Per fare una view che mostra la data e l'ora correnti, quindi, abbiamo solo
bisogno di collegare l'istruzione datetime.datetime.now()
in una view e
restituire il suo risultato in un HttpResponse
. Ecco come dovrebbe sembrare:
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)
Come con la nostra funzione di visualizzazione hello
, questo dovrebbe stare
all'interno di views.py
. Si noti che abbiamo nascosto la funzione hello
da questo esempio per brevità, ma per amor di completezza, ecco come somiglia
l'intera views.py
:
from django.http import HttpResponse import datetime def hello(request): return HttpResponse("Hello world") def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
(D'ora in poi, noi non mostreremo più il codice di esempi precedenti, se non necessario. Si dovrebbe essere in grado di capire dal contesto se le parti di un esempio sono nuove o vecchie).
Stiliamo un sommario dei cambiamenti che abbiamo apportato a views.py
per
mostrare la vista/view current_datetime
.
Abbiamo aggiunto un datetime import alla parte superiore del modul
views.py
, in modo da poter calcolare le date.La nuova funzione
current_datetime
calcola la data e l'ora corrente, come un oggettodatetime.datetime
, e funziona come la variabile localenow
.La seconda riga di codice all'interno della vista costruisce una risposta HTML con la funzionalità di formattazione delle stringhe integrata in Python. Il
%s
all'interno della stringa è un "segnaposto", mentre il segno di percentuale dopo la stringa significa "Sostituire il%s
nella stringa precedente con il valore della variabile". La variabile ora è tecnicamente un oggetto datetime.datetime, non una stringa , ma il%s
converte implicitamente nella rappresentazione l'oggetto in stringa, che è qualcosa come"2008-12-13 14:09:39.002731"
. Questo si traduce in una stringa HTML come"<html><body>It is now 2008-12-13 14:09:39.002731.</body></html>"
.(Sì, il nostro codice HTML non è valido, ma stiamo cercando di mantenere l'esempio semplice e breve).
Infine, la vista restituisce un oggetto
HttpResponse
che contiene la risposta generata -- proprio come abbiamo fatto inhello
.
Dopo aver aggiunto tutto questo a views.py
, ci tocca aggiungere un
urlPattern di urls.py
per dire a Django quale URL dovrebbe gestire questa
view. Qualcosa di simile a /time/
potrebbe avere un senso:
from django.conf.urls.defaults import patterns, include, url from mysite.views import hello, current_datetime urlpatterns = patterns('', url(r'^hello/$', hello), url(r'^time/$', current_datetime), )
Abbiamo fatto due cambiamenti qui. Prima, abbiamo importato la funzione
current_datetime
in alto. In secondo luogo, ancor più importante, abbiamo
aggiunto il collegamento fra urlPattern e URL /time/
a quella nuova view.
Come vedere tutto questo di questo?
Con la view scritta e l'URLconf aggiornato, lanciare il comando runserver
e
visitare http://127.0.0.1:8000/time/
nel browser. Si dovrebbero vedere la
data e l'ora correnti.
Il fuso orario di Django
A seconda del computer, data e ora possono avere un paio d'ore di differenza.
Questo perché Django ha come impostazioni predefinite il fuso orario
America/Chicago
. (Per un qualche difetto con cui bisogna fare i conti,
questo è il fuso orario in cui vivono gli sviluppatori originali). Se si
vive altrove, ti consigliamo di cambiarla da settings.py
. Leggi la
pagina segnalata per avere una idea di tutti i fuso orario del mondo.
Ora è un buon momento per evidenziare una filosofia chiave dietro gli URLconf e dietro Django in generale: il principio di accoppiamento lasco. In poche parole, l'accoppiamento lasco è un approccio allo sviluppo software che valorizza l'importanza di costruire pezzi intercambiabili. Se due pezzi di codice non sono accoppiati fra loro, poi le modifiche apportate a uno dei pezzi avranno poco o nessun effetto sull'altro.
Gli URLconf di Django sono un buon esempio di questo principio nella pratica. In una applicazione web Django, le definizioni di URL e le view che le richiamano sono debolmente accoppiate, cioè la decisione di ciò che l'URL deve essere per una data funzione, e l'attuazione della funzione stessa, risiede in due luoghi separati. Ciò consente di manipolare un unico pezzo senza influenzare l'altro.
Per esempio, consideriamo la nostra view current_datetime
. Se volessimo
modificare l'URL per l'applicazione -- per esempio, per spostarla da /time/
a /current-time/
-- potremmo fare una rapida modifica al'URLconf, senza
doverci preoccupare della vista stessa. Allo stesso modo, se volessimo cambiare
la funzione di visualizzazione -- alterarne la logica in qualche modo --
potremmo farlo senza intaccare l'URL a cui è legata la funzione.
Inoltre, se avessimo voluto mostrare la funzionalità current_datetime
a
diversi URL, potremmo semplicemente modificare l'URLconf, senza dover toccare
il codice della view. In questo esempio, il nostro current_datetime
è
disponibile a due URL. E' un esempio forzato, ma questa tecnica può tornare
utile:
urlpatterns = patterns('', url(r'^hello/$', hello), url(r'^time/$', current_datetime), url(r'^another-time-page/$', current_datetime), )
Le URLconf e le view sono sciolti accoppiamento in azione. Continueremo a sottolineare degli esempi di questa importante filosofia in questo libro.
A nostro avviso la vista current_datetime
, il contenuto della pagina --
la data/ora di oggi -- erano dinamica, ma l'URL (/time/
) era statico. Nella
maggior parte delle applicazioni web dinamiche, però, un URL contiene parametri
che influenzano l'output della pagina. Ad esempio, una libreria online potrebbe
dare ad ogni libro il proprio URL, come /books/243/
e /books/81196/
.
Creiamo un terzo punto di vista che mostra la data e l'ora correnti compensato
da un certo numero di ore. L'obiettivo è quello di predisporre un sito web in
modo tale che la pagina /time/plus/1/
mostri la data/ora di un'ora nel
futuro, la pagina /time/plus/2/
mostra la data/ora di due ore nel futuro, la
pagina /time/plus/3/
mostra la data/ora di tre ore nel futuro, e così
via.
Un principiante potrebbe pensare di codificare una funzione di visualizzazione separata per ogni ora di offset, che potrebbe tradursi in una URLconf come questo:
urlpatterns = patterns('', url(r'^time/$', current_datetime), url(r'^time/plus/1/$', one_hour_ahead), url(r'^time/plus/2/$', two_hours_ahead), url(r'^time/plus/3/$', three_hours_ahead), url(r'^time/plus/4/$', four_hours_ahead), )
Chiaramente, questa linea di pensiero è scorretta. Non solo questo risultato è fatta da funzioni ridondanti, ma anche l'applicazione è fondamentalmente limitata a sostenere solo le gamme di ore predefinite - uno, due, tre o quattro ore. Volendo creare una pagina che mostra il tempo di cinque ore nel futuro, dovremmo creare una vista separata ed una nuova linea di URLconf per questo, favorire la duplicazione. Abbiamo bisogno di fare un po' di astrazione qui.
Una parola riguardo ai "Pretty URL"
Se siete esperti in un'altra piattaforma di sviluppo web, come PHP o Java,
si potrebbe pensare: "Ehi, usiamo un parametro di stringa di query!" --
qualcosa come /time/plus?hours=3
, in cui le hours
sarebbero
designate dal parametro ore nella stringa di query dell'URL (la parte dopo
il ?
).
È possibile farlo con Django (e ti diremo come nel Capitolo 7), ma una
delle filosofie di base di Django è che gli URL dovrebbe essere belli.
L'URL /time/plus/3/
è molto più pulito, più semplice, più leggibile,
più facile da dire ad alta voce e qualcuno... semplicemente più bella
rispetto al suo omologo di una stringa di query. I pretty URL sono una
caratteristica di un'applicazione Web di qualità.
Il sistema URLconf di Django incoraggia la creazione di URL di questo tipo, rendendoli più semplici da usare rispetto agli altri che non lo sono.
Come possiamo allora riprogettare la nostra applicazione per gestire offset
arbitrari? La chiave è quella di utilizzare gli URLpattern jolly. Come
abbiamo accennato in precedenza, un urlPattern è un'espressione regolare, di
conseguenza, siamo in grado di utilizzare il modello di espressione regolare
\d+
per poter abbinare una o più cifre:
urlpatterns = patterns('', # ... url(r'^time/plus/\d+/$', hours_ahead), # ... )
(Stiamo usando il # ...
per dire che potrebbero esserci altri URLpattern
che abbiamo rimosso da questo esempio).
Questo nuovo urlPattern corrisponderà a qualsiasi URL, da /time/plus/2/
, a
/time/plus/25/
o anche /time/plus/100000000000/
. Vale la pena pensarci
bene, cerchiamo di limitare il tutto in modo che il massimo consentito sia di
99 ore. Questo significa che vogliamo consentire l'inserimento di uno o due
valori numerici - o nella sintassi delle espressioni regolari, che si traduce
in \d{1,2}
:
url(r'^time/plus/\d{1,2}/$', hours_ahead),
Note
Quando si creano applicazioni Web, è sempre importante considerare l'input dati più stravagante possibile, e decidere se l'applicazione deve supportare o meno tale ingresso. Abbiamo ridotto le possibilità qui limitando l'offset a 99 ore.
Ora che abbiamo indicato un jolly per l'URL, abbiamo bisogno di un modo di
trasmettere tali dati "jolly" per la funzione di visualizzazione, in modo da
poter utilizzare una singola funzione di visualizzazione per ogni ora offset
arbitrario. Lo facciamo mettendo tra parentesi i dati degli urlPattern che
vogliamo salvare. Nel caso del nostro esempio, vogliamo salvare qualsiasi numero
presente nell'URL, quindi cerchiamo di mettere tra parentesi la \d{1,2}
,
come questo:
url(r'^time/plus/(d{1,2})/$', hours_ahead),
Se hai familiarità con le espressioni regolari, ti troverai a casa e sai che stiamo usando le parentesi per acquisire i dati dal testo corrispondente.
L'URLconf finale, comprese le nostre precedenti view, si presentano così:
from django.conf.urls.defaults import * from mysite.views import hello, current_datetime, hours_ahead urlpatterns = patterns('', url(r'^hello/$', hello), url(r'^time/$', current_datetime), url(r'^time/plus/(\d{1,2})/$', hours_ahead), )
Fatto questo, scriviamo la view hours_ahead
.
hours_ahead
è molto simile alla view current_datetime
che abbiamo
scritto in precedenza, con una differenza fondamentale: ci vuole un argomento in
più, il numero di ore da aggiungere. Ecco il codice:
from django.http import Http404, HttpResponse import datetime def hours_ahead(request, offset): try: offset = int(offset) except ValueError: raise Http404() dt = datetime.datetime.now() + datetime.timedelta(hours=offset) html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt) return HttpResponse(html)
Analizziamo questo codice una riga alla volta:
La funzione di visualizzazione,
hours_ahead
accetta due parametri:request
eoffset
.La
request
è un oggettoHttpRequest
, proprio come inhello
ecurrent_datetime
. Lo scriviamo ancora: ogni view ha sempre un oggettoHttpRequest
come primo parametro.offset
è la stringa catturata dalle parentesi nell'urlPattern. Ad esempio, se l'URL richiesto era/time/plus/3/
, la stringaoffset
è'3'
. Se l'URL richiesto era/time/plus/21/
, l'offset è la stringa'21'
. Si noti che i valori acquisiti saranno sempre stringhe, non interi, anche se la stringa è composta da solo cifre, ad esempio'21'
.(Tecnicamente, i valori acquisiti saranno sempre gli oggetti Unicode, non semplici stringhe di byte Python, ma non preoccuparti di questa distinzione al momento).
Abbiamo deciso di chiamare la variabile
offset
, ma si può chiamare come vuoi, a patto che si tratti di un identificatore Python valido. Il nome della variabile non importa, ciò che davvero conta è che sia il secondo argomento della funzione, doporequest
. (E 'anche possibile utilizzare una parole chiave, piuttosto che una variabile posizionale in un URLconf. Lo vedremo nel capitolo 8).
La prima cosa che facciamo all'interno della funzione è chiamare
int()
suoffset
. Questo converte la stringa in un intero.Si noti che Python solleva un'eccezione
ValueError
se si chiamaint()
su un valore che non può essere convertito in un intero, come ad esempio la stringa'foo'
. In questo esempio, se incontriamo laValueError
, solleviamo l'eccezionedjango.http.Http404
, che, come puoi immaginare, si traduce in un errore 404, "Page not found".I lettori più attenti si chiederanno: come potremmo mai raggiungere il caso
ValueError
, in ogni caso, dato che l'espressione regolare nel nostro urlPattern --(\d{1,2})
-- raccoglie solo cifre, e pertanto l'offset
sarà sempre e solo essere una stringa composta da cifre? La risposta è, non lo faremo, perché l'urlPattern fornisce un livello modesto, ma utile, di convalida dell'input, ma dobbiamo ancora verificare ilValueError
nel caso in cui questa funzione di visualizzazione venga chiamata in qualche altro modo. E' buona norma implementare funzioni tali che non facciano alcuna ipotesi riguardo i loro parametri. Accoppiamento lasco, ricordi?Nella riga successiva della funzione, calcoliamo la data/ora corrente e aggiungiamo il numero adeguato di ore. Abbiamo già visto
datetime.datetime.now()
dalla vistacurrent_datetime
, il nuovo concetto qui è che è possibile eseguire la somma aritmetica sulla data/ora con la creazione di un oggettodatetime.timedelta
e l'aggiunta di un oggettodatetime.datetime
. Il nostro risultato viene memorizzato nella variabiledt
.Questa linea mostra anche perché abbiamo chiamato
int()
suoffset
-- la funzionedatetime.timedelta
richiede il parametrohours
per essere un numero intero.Più avanti, costruiamo l'output HTML di questa view, proprio come abbiamo fatto in
current_datetime
. Una piccola differenza in questa linea dalla linea precedente è che utilizza la stringa di formattazione Python con due, valori non solo uno. Quindi, ci sono due simboli%s
nella stringa e una tupla di valori da inserire:(offset, dt)
.Infine, ritorniamo un
HttpResponse
del HTML. Ormai, lo abbiamo capito.
Avendo scritto la funzione di visualizzazione ed il corrispondente URLconf,
avviare il server di sviluppo (se non è già in esecuzione), e visita
http://127.0.0.1:8000/time/plus/3/
per verificarne il funzionamento. Quindi
prova http://127.0.0.1:8000/time/plus/5/
. Poi http://127.0.0.1:8000/time/plus/24/
.
Infine, visita http://127.0.0.1:8000/time/plus/100/
per verificare che il
pattern configurato da URLconf accetta solo o una o due cifre; Django dovrebbe
visualizzare un errore "Page not found" in questo caso, proprio come abbiamo
visto nella sezione "Una breve nota sui 404 Errori" ("Page not found")
precedenti. Anche gli URL http://127.0.0.1:8000/time/plus/
(senza l'ora)
dovrebbero sollevare un 404.
Ordine di programmazione
In questo esempio, abbiamo scritto per prima l'urlPattern e la view in secondo luogo, ma negli esempi precedenti, abbiamo scritto la vista, poi l'urlPattern. Quale tecnica è migliore?
Beh, ogni sviluppatore è diverso.
Se sei un tipo di persona che immagina in grande, può avere più senso per te scrivere tutti gli URLpatterns per la tua applicazione, allo stesso tempo, all'inizio del progetto, e quindi codificare le view. Questo ha il vantaggio di dare una chiara "to-do list", e definisce essenzialmente i requisiti dei parametri per le funzioni di visualizzazione che avrai bisogno di scrivere.
Se sei più di uno sviluppatore più build&fix preferirai scrivere prima le view, e poi determinare gli URL dopo. Va bene allo stesso modo.
Alla fine, si tratta di quale tecnica si adatta meglio il tuo cervello. Entrambi gli approcci sono validi.
Prenditi un momento per ammirare l'applicazione che abbiamo fatto finora... ora
cerchiamo di rompere! Deliberatamente, introduciamo un errore di Python nel
nostro file views.py
commentando offset = int(offset)
nella vista
hours_ahead
:
def hours_ahead(request, offset): # try: # offset = int(offset) # except ValueError: # raise Http404() dt = datetime.datetime.now() + datetime.timedelta(hours=offset) html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt) return HttpResponse(html)
Carica il server di sviluppo e visita la pagina /time/plus/3/
. Vedrai una
pagina di errore con una notevole quantità di informazioni, compreso un
messaggio TypeError
mostrato in cima: "unsupported type for timedelta hours
component: unicode"
("tipo non supportato per timedelta componente ore: unicode").
Che cosa è successo? Beh, la funzione datetime.timedelta
si aspetta che il
parametro hours
debba essere un numero intero, e abbiamo commentato il
codice che fa proprio questo. Questo ha fatto sollevare a datetime.timedelta
un errore TypeError
. È in genere un tipico bug che ogni programmatore
incontra a un certo punto.
L'obiettivo di questo esempio è mostrare le pagine di errore di Django. Prenditi del tempo per esplorare la pagina di errore e conoscere i vari pezzi di informazioni che ti restituisce.
Qui ci sono alcune cose da notare:
Nella parte superiore della pagina, si trovano le informazioni chiave: il tipo di eccezione, tutti i parametri dell'eccezione (il messaggio
"unsupported type"
in questo caso), il file in cui è stata sollevata l'eccezione ed il numero della riga implicata.Sotto queste informazioni, la pagina mostra l'intero traceback di Python relativo a questa eccezione. Questo è simile alla traccia standard che si riceve quando si è davanti all'interprete della riga di comando di Python. Per ogni livello ("frame") nella pila, Django mostra il nome del file, il nome della funzione/metodo, il numero di riga e il codice sorgente presente in quella linea.
Cliccare sulla riga di codice sorgente (in grigio scuro), e sarà possibile vedere diverse linee di codice prima e dopo la linea che ha causato l'errore, per darci il contesto.
Cliccando su "Local vars" sotto qualsiasi fotogramma dello stack per vedere una tabella di tutte le variabili locali ed i loro valori attuali nel punto esatto del codice in cui è stata sollevata l'eccezione. Queste informazioni di debug possono essere di grande aiuto.
Nota il testo "Switch to copy-and-paste view" sotto l'intestazione "Traceback". Cliccando su quelle parole, il traceback passerà ad una versione alternativa che può essere facilmente copiata e incollata. Utilizza questa funzione quando vuoi condividere il traceback dell'eccezione con altri per ottenere supporto tecnico - come ad esempio le gentili persone nella chat room di Django o sulla mailing list degli utenti Django.
Più in basso, il pulsante "Share this traceback on a public Web site" ("Condividi questa traceback su un pubblico sito Web", in inglese) farà questo lavoro per noi in un solo click. Cliccando, il traceback viene inserito sul servizio web http://www.dpaste.com/ con un indirizzo univoco che è possibile condividere con altre persone.
Successivamente, la sezione "Richiedi informazioni" comprende una serie di informazioni sulla richiesta web in ingresso che ha generato l'errore: le informazioni GET e POST, i valori dei cookie, e altre meta-informazioni, come ad esempio le intestazioni CGI. L'appendice G ha un riferimento completo di tutte le informazioni presenti su un oggetto di richiesta.
All'interno della sezione "Request information" ("Richiedi informazioni"), la sezione "Settings" elenca tutte le impostazioni di questa particolare installazione di Django. (Abbiamo già scritto di
ROOT_URLCONF
, e vi mostreremo le varie impostazioni Django in tutto il libro. Tutte le impostazioni disponibili sono trattate in dettaglio in Appendice D).
La pagina di errore è in grado di mostrare ancor più informazioni in taluni casi
particolari, come ad esempio nel caso di errori di sintassi nella costruzione di
un template. Arriveremo a quelli più tardi, quando discuteremo del sistema di
template incluso in Django. Ora, è possibile rimuovere il commento
all'istruzione offset = int(offset)
per far tornare a funzionare tutto
correttamente.
Sei il tipo di programmatore che ama scrivere il codice usando print
accuratamente posizionati? È possibile utilizzare la pagina di errore Django per
farlo -- ma senza print
. In qualsiasi punto della tua view, è possibile
inserire temporaneamente un assert False
per attivare la pagina di errore.
Quindi, è possibile visualizzare le variabili locali e l'intero stato del
programma. Ecco un esempio, utilizzando la view hours_ahead
:
def hours_ahead(request, offset): try: offset = int(offset) except ValueError: raise Http404() dt = datetime.datetime.now() + datetime.timedelta(hours=offset) assert False html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt) return HttpResponse(html)
Infine, è ovvio che molte di queste informazioni sono sensibili -- poiché espone le "interiora" delle impostazioni e del codice Python e Django -- e sarebbe sciocco per visualizzare queste informazioni su pubblicamente. Un utente malevolo potrebbe usarlo per tentare di decodificare l'applicazione Web e fare cose brutte. Per questo motivo, la pagina di errore Django viene mostrata solo quando il progetto è in modalità di debug. Spiegheremo come disattivare la modalità di debug nel Capitolo 12. Per ora, è sufficiente sapere che ogni progetto Django è in modalità di debug automaticamente quando lo si avvia.
(Ti suona familiare? Gli errori "Page not found", descritti in precedenza in questo capitolo, funzionano allo stesso modo).
Finora, abbiamo scritto le nostre view in HTML a livello di codice direttamente nel codice Python. Lo abbiamo fatto per mantenere le cose semplici, ed abbiamo dimostrato concetti fondamentali, ma nel mondo reale, questa è quasi sempre una cattiva idea.
Vedremo che Django ha un motore di template semplice ma potente che permette di separare la progettazione della pagina dal codice sottostante. Ci immergeremo all'interno di questo motore di template nel prossimo capitolo Capitolo 4.