Scrivere la tua prima applicazione in Django, parte 3

Questo tutorial inizia dove si è interrotto il Tutorial 2 . Stiamo proseguendo con l’applicazione dei questionari online e ci focalizzeremo nella creazione dell’interfaccia pubblica – «views.»

Dove trovare aiuto:

Se hai difficoltà a completare questo tutorial, per favore vai alla sezione Getting Help delle FAQ.

Panoramica

Una view è un «tipo» di pagina web nella tua applicazione Django, che generalmente serve una specifica funzione ed ha un template specifico. Per esempio, in una applicazione blog, potresti avere le seguenti views:

  • Home page del blog – mostra le ultime voci.
  • Pagina di «dettaglio» della voce – pagina permanente per una singola voce.
  • Pagina di archivio in base all’anno – visualizza tutti i mesi con le voci nell’anno specificato.
  • L’archivio mensile – mostra tutti i giorni con le relative pubblicazioni in un dato mese.
  • L’archivio giornaliero – mostra tutte le pubblicazioni in un determinato giorno.
  • L’azione per i commenti – gestisce la possibilità di commentare una determinata pubblicazione.

Nella nostra applicazione di sondaggi, avremo le seguenti quattro visualizzazioni:

  • L” «indice» delle domande – mostra una selezione delle ultime domande.
  • La pagina «dettagli» della domanda – mostra il testo della domanda, senza risultati ma con un form per votare.
  • La pagina «risultati» delle domande – mostra i risultati per una determinata domanda.
  • L’azione del voto – gestisce il voto per una scelta particolare in una azione particolare.

In Django, le pagine web e gli altri contenuti sono serviti dalle views. Ogni view è rappresentata da una funzione Python (o un metodo, in caso di views basate su classi). Django sceglierà una view esaminando la URL che viene richiesta (per essere precisi, la parte di URL dopo il nome di dominio).

Adesso, nel tempo che hai trascorso sul web potresti essere incappato in amenità come ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B. Sarai felice di sapere che Django ci permette di utilizzare pattern per le URL che sono molto più eleganti di quelli.

Un pattern URL è la forma generale di una URL - per esempio: /newsarchive/<year>/<month>/.

Per prendere una URL da una view, Django usa quelle che sono chiamate “URLconfs”. Una URLconf mappa i pattern delle URL sulle view.

Questo tutorial fornisce istruzioni di base per l’uso di URLconfs e puoi fare riferimento a URL dispatcher per maggiori informazioni.

Scrivere altre visualizzazioni

Adesso aggiungiamo poche altre view a polls/views.py.  Queste view sono leggermente differenti, perchè prendono un argomento:

polls/views.py
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Collega queste nuove view nel modulo polls.urls aggiungendo le seguenti chiamate path():

polls/urls.py
from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

Dai un’occhiata al tuo browser, su «/polls/34/». Lancerà il metodo detail() e mostrerà qualsiasi ID gli fornirai nella URL. Prova anche «/polls/34/results/» e «/polls/34/vote/» – mostreranno il placeholder dei risultati e le pagine per le votazioni.

Quando qualcuno richiede una pagina dal tuo sito web – diciamo, «/polls/34/», Django caricherà il modulo Python mysite.urls perchè è puntato nell’impostazione ROOT_URLCONF. Cerca la variabile chiamata urlpatterns e scorre i pattern in ordine. Dopo aver trovato un match con 'polls/', toglie la parte di testo che combacia ('polls/') ed invia la parte rimanente del testo – "34/" – alla URLconf “polls.urls” per ulteriori elaborazioni. Lì combacia con '<int:question_id>/', finendo in una chiamata alla view detail() in questo modo:

detail(request=<HttpRequest object>, question_id=34)

La parte question_id=34 viene da <int:question_id>. L’utilizzo delle parentesi angolari «cattura» parte della URL e la invia come argomento keyword alla funzione della view. La parte question_id della stringa definisce un nome che sarà utilizzato per identificare il pattern individuato, e la parte int è un elemento di conversione che determina quali pattern dovrebbero essere individuati da questa parte del percorso URL. I due punti (:) separano il convertitore dal nome del pattern.

Scrivi delle visualizzazioni che fanno effettivamente qualcosa

Ogni view è responsabile di fare una delle due cose: restituire un oggetto HttpResponse con il contenuto della pagina richiesta oppure sollevare una eccezione come Http404. Il resto spetta a te.

La tua view può leggere dati dal database oppure no. Può usare un sistema di template come quello di Django – o un sistema di template per Python di terze parti – o no. Può generare un file PDF, un XML in output, creare un file ZIP sul momento, usare qualsiasi libreria Python tu voglia.

Tutto quel che Django vuole è quella HttpResponse. Oppure un’eccezione.

Dal momento che conviene, usiamo l’API database di Django, che abbiamo visto nella guida 2. Ecco una prova per la nuova view index(), che mostra le ultime 5 domande dei sondaggi nel sistema, separati dalla virgola, secondo la data di pubblicazione:

polls/views.py
from django.http import HttpResponse

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged

Eppure c’è un problema qui: il design della pagina è codificato nella view. Se vuoi cambiare il modo in cui la pagina appare, dovrai modificare questo codice Python. Usiamo il sistema di template di Django per separare il design da Python creando un template che la view possa usare.

Per prima cosa, crea una directory chiamata templates nella tua directory polls. Django cercherà i templates lì dentro.

L’impostazione TEMPLATES del tuo progetto descrive come Django caricherà e mostrerà i template. Il file di impostazioni di default configura un backend DjangoTemplates` la cui opzione APP_DIRS è impostata a True. Per convenzione DjangoTemplates cerca una sottocartella «templates» in ognuna delle INSTALLED_APPS.

Nella directory templates che hai appena creato, crea un’altra directory chiamata polls, e in quella crea un file chiamato index.html. In altre parole, il tuo template dovrebbe trovarsi in polls/templates/polls/index.html. Siccome il caricamento dei template delle app_directories lavora come spiegato poco fa, puoi fare riferimento a questo template in Django come polls/index.html.

Spazio nomi template

Adesso dovremmo essere in grado di cavarcela mettendo i template direttamente in polls/templates (invece che creare un’altra sottodirectory polls), ma sarebbe una cattiva idea. Django sceglierà il primo template che troverà il cui nome combaci con quello che cerca e sei hai un template con lo stesso nome in una applicazione differente, Django potrebbe non essere in grado di operare una distinzione. Dobbiamo essere in grado di far puntare Django a quello giusto, ed il miglior modo di assicurarcene è facendone il namespacing. Il che significa, mettendo questi template in un”altra directory chiamata come l’applicazione stessa.

Inserisci il seguente codice nel template:

polls/templates/polls/index.html
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Nota

Per rendere più corto il tutorial, tutti gli esempi dei templati utilizzano un HTML incompleto. Nei tuoi progetti devi usare documenti HTML completi.

Ora aggiorniamo la nostra vista index in polls/views.py per utilizzare il modello:

polls/views.py
from django.http import HttpResponse
from django.template import loader

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

Il codice carica il template chiamato polls/index.html e gli passa un context. Il context è un dizionario che associa i nomi delle variabili nel template agli oggetti Python.

Carica la pagina puntando il browser su «/polls/», e dovresti vedere una lista puntata che contiene la domanda «Come va» dal Tutorial 2. Il link punta alla pagina di dettaglio della domanda.

Una scorciatoia: render()

E” un idioma molto comune per caricare un template, quello di riempire un context e restituire un oggetto HttpResponse con il risultato del template mostrato. Django fornisce una scorciatoia. Ecco la view index() completa, riscritta:

polls/views.py
from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

Nota che una volta che abbiamo fatto questo in tutte le view, non abbiamo più bisogno di importare loader e HttpResponse (potresti voler tenere HttpResponse nel caso tu non abbia ancora i metodi stub per detail, results e vote).

La funzione render() prende l’oggetto request come primo argomento, un nome di template come secondo argomento ed un dizionario come terzo argomento opzionale. Restituisce un oggetto HttpResponse del template dato reso con il contesto dato.

Generazione di un errore 404

Adesso affrontiamo la questione della view del dettaglio – la pagina che mostra il testo della domanda per un dato sondaggio. Ecco la view:

polls/views.py
from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

Il concetto nuovo qui: La view solleva l’eccezione Http404 se non esiste un sondaggio con l’ID richiesto.

Discuteremo cosa potresti mettere in quel template polls/detail.html un po” più tardi, ma se ti piacerebbe avere l’esempio di cui sopra funzionale in un tempo rapido, un file che contiene solo:

polls/templates/polls/detail.html
{{ question }}

ti farà iniziare per ora.

Una scorciatoia: get_object_or_404()

E” un idioma molto comune usare get() e sollevare Http404 se l’oggetto non esiste. Django offre una scorciatoia. Ecco la view detail(), riscritta:

polls/views.py
from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

La funzione get_object_or_404() prende un modello Django come primo argomento e un numero arbitrario di argomenti keyword, che passa alla funzione get() del manager del modello. Solleva Http404 se l’oggetto non esiste.

Filosofia

Perchè usiamo una funzione helper get_object_or_404() invece di catturare automaticamente l’eccezione ObjectDoesNotExist ad un livello più alto o di avere l’API del modello che solleva Http404 invece di ObjectDoesNotExist?

Perchè quello accoppierebbe il layer del modello alla layer della view. Uno dei primi obiettivi di design di Django è di mantenere un accoppiamento blando. Un po” di accoppiamento controllato è introdotto nel modulo django.shortcuts .

Esiste anche un funzione get_list_or_404() , che funziona esattamente come get_object_or_404() – ad eccezione di usare filter() invece di get(). Solleva Http404 se la lista è vuota.

Utilizzare il sistema di template

Torniamo alla view detail() della nostra applicazione dei sondaggi. Data la variabile di contesto question`, ecco come potrebbe apparire il template polls/detail.html:

polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

Il sistema di template usa una sintassi «a ricerca con il punto» per accedere alle variabili attributo. Nell’esempio di {{ question.question_text }}, prima Django effettua una ricerca a dizionario sull’oggetto question. Fallendo quella, prova una ricerca dell’attributo – che funziona, in questo caso. Se la ricerca dell’attributo avesse fallito, avrebbe provato con una ricerca sugli indici della lista.

La chiamata a metodo avviene nel {% for %} loop: question.choice_set.all viene interpretato come il codice Python question.choice_set.all(), che restituisce un iterable di oggetti Choice ed è adatto ad essere usato nel tag {% for %}.

Vedi la template guide per maggiori informazioni sui templates.

Rimuovere le URL codificate nei template

Ricorda, quando abbiamo scritto il link ad una domanda nel template polls/index.html, il link era parzialmente hardcodato come:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

Il problema con questo approccio codificato e strettamente accoppiato è che diventa difficile cambiare URL su progetti con molti template. Comunque, dal momento che hai definito l’argomento nome nelle funzioni path() nel modulo polls.urls, puoi rimuovere la dipendenza da percorsi URL specifici definiti nelle tue configurazioni url usando il tag di template {% url %}.

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

Il modo in cui funziona è ricercando la definizione della URL così come è definita nel modulo polls.urls. Puoi vedere esattamente dove il nome della URL di “detail” è definita qui sotto:

...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...

Se vuoi cambiare la URL della vista di dettaglio del sondaggio a qualcos’altro, magari a qualcosa come polls/specifics/12/ invece di farlo nel template (o nei template) lo cambieresti in polls/urls.py:

...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...

Assegnazione dello spazio dei nomi alle URL

Il progetto del tutorial ha solo una app, polls. Nei veri progetti Django, ci possono essere cinque, dieci, venti app o anche più. Come fa Django a differenziare i nomi delle URL tra di esse? Per esempio, l’app polls ha una view detail, e così potrebbe averla una app sullo stesso progetto che serve per un blog. Come si a fare in modo che Django sappia quale view di quale app creare da un url quando si usa il tag di template {% url %}?

La risposta è di aggiungere namespaces al tuo URLconf. Nel file polls/urls.py, procedi ed inserisci un app_name per impostare il namespace della applicazione.

polls/urls.py
from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

Adesso cambia il tuo template polls/index.html da:

polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

per puntare al namespace detail della vista:

polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Quando ti senti a tuo agio a scrivere le view, leggi la parte 4 di questo tutorial per imparare le basi del processamento dei form e le view generiche.

Back to Top