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>:

polls/templates/polls/detail.html
<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. Il nome di ogni radio button è "choice". Questo significa che quando qualcuno seleziona uno dei radio button ed invia il form, manderà il dato choice=# 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 impostiamo method="post". Usare method="post" (in opposizione a method="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, usa method="post". Questo consiglio non si applica solo a Django; è una buona pratica di sviluppo web, in generale.
  • forloop.counter indica quante volte il tag for 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:

polls/urls.py
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:

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 valori request.POST sono sempre stringhe.

    Nota che Django ti offre anche request.GET per accedere ai dati in GET allo stesso modo – ma stiamo esplicitamente usando request.POST nel nostro codice, per assicurarci che i dati vengano alterati solo con una chiamata POST.

  • request.POST['choice'] solleverà KeyError se choice non è presente tra i dati in POST. Il codice sopra controlla KeyError e mostra di nuovo il form della domanda con un messaggio di errore se choice non viene fornita.

  • Dopo aver incrementato il conteggio delle scelte, il codice restituisce una HttpResponseRedirect piuttosto che una normale HttpResponse. 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 costruttore HttpResponseRedirect. 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 chiamata reverse() restituirà una stringa come:

    '/polls/3/results/'
    

    dove il 3 è il valore di question.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:

polls/views.py
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 :

polls/templates/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».

Le view generiche fanno astrazione di pattern comuni al punto che non hai nemmeno più bisogno di codice Python per scrivere una app.

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.

  1. Converti la URLconf.
  2. Cancella alcune delle vecchie, non necessarie viste.
  3. 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ì:

polls/urls.py
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'),
]

Nota che il nome del pattern che combacia nelle stringhe dei percorsi del secondo e terzo pattern è cambiato da <question_id> a <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ì:

polls/views.py
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.

Stiamo usando due view generiche qui: ListView e DetailView. Rispettivamente, queste due view astraggono il concetto di «mostrare una lista di oggetti» e «mostrare una pagina di dettaglio per un particolare tipo di oggetto».

  • Ogni view generica ha bisogno di sapere su che modello agirà. Questa cosa viene definita con l’attributo model.
  • La view generica DetailView si aspetta che la chiave primaria che viene catturata dalla URL si chiami "pk", quindi abbiamo cambiato question_id in pk per le view generiche.

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.

Back to Top