Scrivere la tua prima applicazione in Django, parte 4¶
Questo tutorial inizia dove si è interrotto il tutorial Tutorial 3. Continuiamo con l’applicazione web dei questionari e ci focalizzeremo sul processamento dei form e sul ridurre il nostro codice.
Dove trovare aiuto:
Se hai difficoltà a completare questo tutorial, per favore vai alla sezione Getting Help delle FAQ.
Scrivere un modulo minimale¶
Aggiorniamo il nostro template per i dettagli del questionario («polls/detail.html») dall’ultimo tutorial, così che il template contenga un elemento HTML <form>
:
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>
Una rapida carrellata:
- Il template di cui sopra mostra un radio button per ogni scelta del sondaggio. Il
valore
di ogni radio button è l’ID associato alla scelta. Ilnome
di ogni radio button è"choice"
. Questo significa che quando qualcuno seleziona uno dei radio button ed invia il form, manderà il datochoice=#
in POST, dove # è l’ID della scelta selezionata. Questo è il concetto alla base dei form HTML. - Impostiamo la
action
del form a{% url 'polls:vote' question.id %}
ed impostiamomethod="post"
. Usaremethod="post"
(in opposizione amethod="get"
) è molto importante perchè il submit di questo form altererà i dati a lato server. Tutte le volte che crei un form che altera i dati a lato server, usamethod="post"
. Questo consiglio non si applica solo a Django; è una buona pratica di sviluppo web, in generale. forloop.counter
indica quante volte il tagfor
ha attraversato suo loop- Siccome stiamo creando un form POST (che può avere l’effetto di modificare i dati), dobbiamo preoccuparci delle Cross Site Request Forgeries. Per fortuna, non ce ne dobbiamo preoccupare troppo, perchè Django ci offre un sistema che ci aiuta molto a proteggerci. In breve, tutti i form POST che sono indirizzati dalle url interne devono usare il tag
{% csrf_token %}
.
Adesso, creiamo una view Django che gestisce i dati in submit e ci fa qualcosa. Ricorda che nel Tutorial 3, abbiamo creato una URLconf per l’applicazione sondaggio che include questa linea:
path("<int:question_id>/vote/", views.vote, name="vote"),
Abbiamo anche creato una implementazione dummy della funzione vote()
. Creiamone una versione reali. Aggiungi quanto di seguito a polls/views.py
:
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Choice, Question
# ...
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST["choice"])
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(
request,
"polls/detail.html",
{
"question": question,
"error_message": "You didn't select a choice.",
},
)
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
Questo codice comprende alcuni aspetti non ancora trattati in questo tutorial:
request.POST
è un oggetto simil-dizionario che ti lascia accedere ai dati in submit per nome di chiave. In questo caso,request.POST['choice']
restituisce l’ID della scelta selezionata, come stringa. I valorirequest.POST
sono sempre stringhe.Nota che Django ti offre anche
request.GET
per accedere ai dati in GET allo stesso modo – ma stiamo esplicitamente usandorequest.POST
nel nostro codice, per assicurarci che i dati vengano alterati solo con una chiamata POST.request.POST['choice']
solleveràKeyError
sechoice
non è presente tra i dati in POST. Il codice sopra controllaKeyError
e mostra di nuovo il form della domanda con un messaggio di errore sechoice
non viene fornita.Dopo aver incrementato il conteggio delle scelte, il codice restituisce una
HttpResponseRedirect
piuttosto che una normaleHttpResponse
.HttpResponseRedirect
prende un solo argomento: la URL alla quale l’utente sarà redirezionato (vedi il punto seguente per sapere come costruiamo la URL in questo caso).Come il commento Python qui sopra fa notare, dovresti sempre restituire un
HttpResponseRedirect
dopo aver avuto a che fare con successo con dati in POST. Questo consiglio non riguarda nello specifico solo Django; è una buona pratica per lo sviluppo web, in generale.In questo esempio stiamo usando la funzione
reverse()
nel costruttoreHttpResponseRedirect
. Questa funzione aiuta ad evitare di dover codificare la URL nella funzione view. E” dato il nome della view a cui vogliamo passare il controllo e la porzione della URL relativa alla variabile che punta alla URL. In questo caso, usando la URLconf che abbiamo impostato nel Tutorial 3, questa chiamatareverse()
restituirà una stringa come:"/polls/3/results/"
dove il
3
è il valore diquestion.id
. Questa URL redirezionata poi chiamerà la view'results'
per mostrare la pagina finale.
Come menzionato in Tutorial 3, request
è un oggetto HttpRequest
. Per saperne di più sugli oggetti HttpRequest
, vedi la documentazione su request e response.
Dopo che qualcuno ha votato una domanda, la vista vote()
reindirizza alla pagina dei risultati per la domanda. Scriviamo quella vista:
from django.shortcuts import get_object_or_404, render
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/results.html", {"question": question})
Questa è praticamente la stessa view di detail()
dal Tutorial 3. L’unica differenza è il nome di template. Metteremo a posto questa ridondanza più tardi.
Ora, crea un template polls/results.html
:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
Adesso, vai su /polls/1/
nel browser e vota il sondaggio. Dovresti vedere la pagina dei risultati che si aggiorna ogni volta che voti. Se invii il form senza scegliere una risposta, dovresti vedere il messaggio di errore.
Nota
Il codice per la view vote()
ha un piccolo problema. Prende prende l’oggetto selected_choice
dal db, poi li risalva nel database. Se due utenti del sito cercano di votare esattamente allo stesso momento, qualcosa potrebbe andare storto: lo stesso valore, diciamo 42, verrebbe ritirato per votes
. Poi, per entrambi gli utenti verrebbe calcolato il valore 43 e salvato ma 44 sarebbe il valore giusto.
Questa viene chiamata race condition. Se sei interessato, puoi leggere Avoiding race conditions using F() per sapere come risolvere questo problema.
Usa viste generiche: meno codice è meglio¶
Le view detail()
(dal Tutorial 3) e results()
sono molto brevi – e, come menzionato sopra, ridondanti. La view index()
, che mostra una lista di sondaggi, è simile.
Queste views rappresentano un caso comune di sviluppo web di base; prendere dei dati dal database in accordo con un parametro passato via URL, caricare un template e restituire il template per il rendering. Dal momento che questo è molto comune, Django fornisce una scorciatoia, chiamata sistema «generic views».
Generic views abstract common patterns to the point where you don’t even need to
write Python code to write an app. For example, the
ListView
and
DetailView
generic views
abstract the concepts of «display a list of objects» and
«display a detail page for a particular type of object» respectively.
Convertiamo la nostra app dei sondaggi affinchè utilizzi il sistema delle view generiche, in modo da poter cancellare un sacco di codice. Dovremo fare la conversione in qualche step.
- Converti la URLconf.
- Cancella alcune delle vecchie, non necessarie viste.
- Introduci nuove visualizzazioni basate sulle generic views di Django.
Continua a leggere per i dettagli.
Perchè il code-shuffle?
Solitamente, quando scrivi un’applicazione in Django, dovresti valutare quali viste generiche vanno bene per il tuo problema, invece di ristrutturare il codice a metà strada Ma questa guida era intenzionalmente focalizzato fino ad ora, sullo scrivere le viste nel modo più difficile, per mettere a punto i concetti principali.
Dovresti conoscere la matematica di base prima di iniziare a usare una calcolatrice.
Correggere la URLconf¶
Prima di tutto, apri la URLconf polls/urls.py
e cambiala così:
from django.urls import path
from . import views
app_name = "polls"
urlpatterns = [
path("", views.IndexView.as_view(), name="index"),
path("<int:pk>/", views.DetailView.as_view(), name="detail"),
path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
path("<int:question_id>/vote/", views.vote, name="vote"),
]
Note that the name of the matched pattern in the path strings of the second and
third patterns has changed from <question_id>
to <pk>
. This is
necessary because we’ll use the
DetailView
generic view to replace our
detail()
and results()
views, and it expects the primary key value
captured from the URL to be called "pk"
.
Correggere le viste¶
Poi, andremo a rimuovere le nostre vecchie view index
, detail
e results
per usare le view generiche di Django. Per farlo, apri il file polls/views.py
e cambialo così:
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by("-pub_date")[:5]
class DetailView(generic.DetailView):
model = Question
template_name = "polls/detail.html"
class ResultsView(generic.DetailView):
model = Question
template_name = "polls/results.html"
def vote(request, question_id):
... # same as above, no changes needed.
Each generic view needs to know what model it will be acting upon. This is
provided using either the model
attribute (in this example, model =
Question
for DetailView
and ResultsView
) or by defining the
get_queryset()
method (as
shown in IndexView
).
Di default, la view generica DetailView
usa un template chiamato <app name>/<model name>_detail.html`. Nel nostro caso, dovrebbe usare il template "polls/question_detail.html"
. L’attributo template_name
è utile per usare un template specifico invece del nome di template generato automaticamente. Specifichiamo anche il template_name
per la list view results
– questo ci assicura che la view dei risultati e la view dei dettagli abbiamo una interfaccia diversa quando vengono mostrate, anche se, dietro le quinte, sono entrambe DetailView
.
In modo simile, la view generica ListView
usa un template di default chiamato <app name>/<model name>_list.html
; usiamo template_name
per dire a ListView
di usare il nostro template esistente "polls/index.html"
.
Nella parte precedente di questo tutorial, i template sono stati forniti con un contesto che contiene le variabili question
e latest_question_list
. Per DetailView
la variabile question
viene fornita automaticamente – visto che stiamo usando un modello Django (Question
), Django è in grado di determinare un nome appropriato per la variabile di contesto. Comunque, per ListView, la variabile di contesto generata automaticamente è question_list
. Per sovrascrivere questo, forniamo l’attributo context_object_name`, specificando che vogliamo usare latest_question_list
al suo posto. Come approccio alternativo, puoi cambiare i tuoi template per rispecchiare le nuove variabili di contesto di default – ma è molto più facile dire a Django di usare le variabili che vuoi.
Esegui il server e usa la tua nuova applicazione di sondaggio basata su visualizzazioni generiche.
Per i dettagli completi sulle viste generiche, vedere la: doc:generic views documentation </topics/class-based-views/index>.
Quando hai dimestichezza con i moduli e le visualizzazioni generiche, leggi la parte 5 di questo tutorial per scoprire come testare la nostra app per i sondaggi.