# Django 5.2.8 documentation

# Skriva din första Django-app, del 4

Denna handledning börjar där Tutorial 3 slutade. Vi fortsätter med web-poll applikationen och kommer att fokusera på formulärbehandling och att minska vår kod.

Var du kan få hjälp:

Om du har problem med att gå igenom den här handledningen kan du gå till avsnittet Att få hjälp i FAQ.

## Skriv ett minimalt formulär

Låt oss uppdatera vår mall för detaljer om omröstningen (”polls/detail.html”) från den senaste handledningen, så att mallen innehåller ett HTML-element `<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>

En snabb genomgång:

- Ovanstående mall visar en alternativknapp för varje frågealternativ. Varje alternativknapps `värde` är det associerade frågevalets ID. Namnet på varje alternativknapp är ”val”. Det betyder att när någon väljer en av alternativknapparna och skickar in formuläret, skickar den POST-data `choice=#` där # är ID för det valda valet. Detta är det grundläggande konceptet för HTML-formulär.

- Vi ställer in formulärets `action` till `{% url 'polls:vote' question.id %}`, och vi ställer in `method="post"`. Att använda `method="post"` (i motsats till `method="get"`) är mycket viktigt, eftersom handlingen att skicka in detta formulär kommer att ändra data på serversidan. När du skapar ett formulär som ändrar data på serversidan, använd `method="post"`. Detta tips är inte specifikt för Django; det är bra webbutvecklingspraxis i allmänhet.

- `forloop.counter` anger hur många gånger `for`-taggen har gått igenom sin loop

- Eftersom vi skapar ett POST-formulär (som kan ha effekten av att ändra data) måste vi oroa oss för Cross Site Request Forgeries. Tack och lov behöver du inte oroa dig för hårt, eftersom Django kommer med ett användbart system för att skydda mot det. Kort sagt, alla POST-formulär som är inriktade på interna webbadresser bör använda `{% csrf_token %}` malltagg.

Låt oss nu skapa en Django-vy som hanterar de inskickade uppgifterna och gör något med dem. Kom ihåg att vi i Tutorial 3 skapade en URLconf för polls-programmet som innehåller den här raden:

`polls/urls.py`

    path("<int:question_id>/vote/", views.vote, name="vote"),

Vi skapade också en dummyimplementering av funktionen `vote()`. Låt oss skapa en riktig version. Lägg till följande i `polls/views.py`:

`polls/views.py`

    from django.db.models import F
    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 = F("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,)))

Den här koden innehåller några saker som vi ännu inte har gått igenom i den här handledningen:

- `request.POST` är ett ordboksliknande objekt som låter dig komma åt inskickade data med nyckelnamn. I det här fallet returnerar `request.POST['choice']` ID:t för det valda valet som en sträng. `request.POST`-värden är alltid strängar.

  Observera att Django också tillhandahåller `request.GET` för åtkomst till GET-data på samma sätt - men vi använder uttryckligen `request.POST` i vår kod för att säkerställa att data endast ändras via ett POST-anrop.

- `request.POST['choice']` kommer att ge upphov till `KeyError` om `choice` inte angavs i POST-data. Ovanstående kod kontrollerar för `KeyError` och visar frågeformuläret på nytt med ett felmeddelande om `choice` inte har angetts.

- `F("votes") + 1` instruerar databasen att öka antalet röster med 1.

- Efter att ha ökat antalet val returnerar koden en `HttpResponseRedirect` snarare än en vanlig `HttpResponse`. `HttpResponseRedirect` tar ett enda argument: URL:en till vilken användaren kommer att omdirigeras (se följande punkt för hur vi konstruerar URL:en i det här fallet).

  Som Python-kommentaren ovan påpekar bör du alltid returnera en `HttpResponseRedirect` efter framgångsrik hantering av POST-data. Det här tipset är inte specifikt för Django; det är god praxis för webbutveckling i allmänhet.

- Vi använder funktionen `reverse()` i konstruktören `HttpResponseRedirect` i det här exemplet. Denna funktion hjälper till att undvika att behöva hårdkoda en URL i vyfunktionen. Den får namnet på den vy som vi vill skicka kontroll till och den variabla delen av URL-mönstret som pekar på den vyn. I det här fallet, med hjälp av URLconf som vi ställde in i Tutorial 3, kommer detta `reverse()`-anrop att returnera en sträng som

      "/polls/3/results/"

  där `3` är värdet av `question.id`. Denna omdirigerade URL kommer sedan att anropa vyn `'results` för att visa den slutliga sidan.

Som nämnts i Tutorial 3 är `request` ett `HttpRequest`-objekt. För mer information om `HttpRequest`-objekt, se dokumentation om begäran och svar.

När någon har röstat på en fråga omdirigeras vyn `vote()` till resultatsidan för frågan. Låt oss skriva den vyn:

`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})

Detta är nästan exakt samma sak som vyn `detail()` från Tutorial 3. Den enda skillnaden är mallnamnet. Vi kommer att åtgärda denna redundans senare.

Skapa nu mallen `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>

Gå nu till `/polls/1/` i din webbläsare och rösta på frågan. Du bör se en resultatsida som uppdateras varje gång du röstar. Om du skickar in formuläret utan att ha gjort något val bör du se ett felmeddelande.

## Använd generiska vyer: Mindre kod är bättre

Vyerna `detail()` (från Tutorial 3) och `results()` är mycket korta - och, som nämnts ovan, överflödiga. Vyn `index()`, som visar en lista med omröstningar, är liknande.

Dessa vyer representerar ett vanligt fall av grundläggande webbutveckling: hämta data från databasen enligt en parameter som skickas i webbadressen, ladda en mall och returnera den renderade mallen. Eftersom detta är så vanligt tillhandahåller Django en genväg, kallad ”generic views” -systemet.

Generiska vyer abstraherar vanliga mönster till den punkt där du inte ens behöver skriva Python-kod för att skriva en app. Till exempel abstraherar de generiska vyerna `ListView` och `DetailView` begreppen ”visa en lista med objekt” respektive ”visa en detaljsida för en viss typ av objekt”.

Låt oss konvertera vår poll-app till att använda det generiska vy-systemet, så att vi kan ta bort en hel del av vår egen kod. Vi måste ta några steg för att göra konverteringen. Vi kommer att göra det:

1. Konvertera URLconf.

2. Ta bort några av de gamla, onödiga vyerna.

3. Introducera nya vyer baserade på Djangos generiska vyer.

Läs vidare för mer information.

Varför kodomläggningen?

När du skriver en Django-app utvärderar du i allmänhet om generiska vyer passar bra för ditt problem, och du använder dem från början, snarare än att refaktorisera din kod halvvägs igenom. Men denna handledning har avsiktligt fokuserat på att skriva vyerna ”på det hårda sättet” fram till nu, för att fokusera på kärnkoncept.

Du bör kunna grundläggande matematik innan du börjar använda en miniräknare.

### Ändra URLconf

Först öppnar du URLconf i `polls/urls.py` och ändrar den så här:

`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"),
    ]

Observera att namnet på det matchade mönstret i söksträngarna för det andra och tredje mönstret har ändrats från `<question_id>` till `<pk>`. Detta är nödvändigt eftersom vi kommer att använda den generiska vyn `DetailView` för att ersätta våra vyer `detail()` och `results()`, och den förväntar sig att det primära nyckelvärdet som fångas från URL:en ska heta `"pk"`.

### Ändra synpunkter

Därefter ska vi ta bort våra gamla vyer `index`, `detail` och `results` och använda Djangos generiska vyer istället. För att göra det, öppna filen `polls/views.py` och ändra den så här:

`polls/views.py`

    from django.db.models import F
    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.
        ...

Varje generisk vy måste veta vilken modell den ska agera utifrån. Detta tillhandahålls antingen med attributet `model` (i detta exempel `model = Question` för `DetailView` och `ResultsView`) eller genom att definiera metoden `get_queryset()` (som visas i `IndexView`).

Som standard använder den generiska vyn `DetailView` en mall som heter `<app name>/<model name>_detail.html`. I vårt fall skulle den använda mallen `"polls/question_detail.html"`. Attributet `template_name` används för att tala om för Django att använda ett specifikt mallnamn istället för det autogenererade standardmallnamnet. Vi anger också `template_name` för listvyn `results` - detta säkerställer att resultatvyn och detaljvyn har ett annat utseende när de renderas, även om de båda är en `DetailView` bakom kulisserna.

På samma sätt använder den generiska vyn `ListView` en standardmall som heter `<app name>/<model name>_list.html`; vi använder `template_name` för att tala om för `ListView` att använda vår befintliga mall `"polls/index.html"`.

I tidigare delar av handledningen har mallarna försetts med en kontext som innehåller kontextvariablerna `question` och `latest_question_list`. För `DetailView` tillhandahålls `question`-variabeln automatiskt - eftersom vi använder en Django-modell (`Question`) kan Django bestämma ett lämpligt namn för kontextvariabeln. För ListView är dock den automatiskt genererade kontextvariabeln `question_list`. För att åsidosätta detta tillhandahåller vi attributet `context_object_name`, som anger att vi vill använda `latest_question_list` istället. Som ett alternativt tillvägagångssätt kan du ändra dina mallar för att matcha de nya standardkontextvariablerna - men det är mycket lättare att berätta för Django att använda den variabel du vill ha.

Kör servern och använd din nya polling-app baserad på generiska vyer.

För fullständig information om generiska vyer, se dokumentation om generiska vyer.

När du känner dig bekväm med formulär och generiska vyer kan du läsa del 5 av denna handledning för att lära dig hur du testar vår polls app.