Formulärhantering med klassbaserade vyerLink to this heading

Formulärhantering har i allmänhet 3 vägar:

  • Initial GET (blankett eller förifyllt formulär)

  • POST med ogiltiga data (vanligtvis visas formuläret på nytt med fel)

  • POST med giltiga data (bearbeta data och typiskt omdirigera)

Att implementera detta själv resulterar ofta i en hel del upprepad boilerplate-kod (se Använda ett formulär i en vy). För att hjälpa till att undvika detta tillhandahåller Django en samling generiska klassbaserade vyer för formulärbehandling.

Grundläggande formerLink to this heading

Givet ett kontaktformulär:

forms.py
Python
from django import forms


class ContactForm(forms.Form):
    name = forms.CharField()
    message = forms.CharField(widget=forms.Textarea)

    def send_email(self):
        # send email using the self.cleaned_data dictionary
        pass

Vyn kan byggas upp med hjälp av en FormView:

Vyer.py
Python
from myapp.forms import ContactForm
from django.views.generic.edit import FormView


class ContactFormView(FormView):
    template_name = "contact.html"
    form_class = ContactForm
    success_url = "/thanks/"

    def form_valid(self, form):
        # This method is called when valid form data has been POSTed.
        # It should return an HttpResponse.
        form.send_email()
        return super().form_valid(form)

Anteckningar:

ModellformerLink to this heading

Generiska vyer lyser verkligen när man arbetar med modeller. Dessa generiska vyer kommer automatiskt att skapa en ModelForm, så länge de kan räkna ut vilken modellklass som ska användas:

  • Om attributet model anges kommer den modellklassen att användas.

  • Om get_object() returnerar ett objekt, kommer klassen för det objektet att användas.

  • Om en queryset anges, kommer modellen för den queryset att användas.

Modellformulärsvyer tillhandahåller en form_valid()-implementering som sparar modellen automatiskt. Du kan åsidosätta detta om du har några speciella krav; se nedan för exempel.

Du behöver inte ens tillhandahålla en uccess_url för CreateView eller UpdateView - de kommer att använda get_absolute_url() på modellobjektet om det finns tillgängligt.

Om du vill använda en anpassad ModelForm (t.ex. för att lägga till extra validering), ange form_class på din vy.

Först måste vi lägga till get_absolute_url() till vår Author klass:

modeller.py
Python
from django.db import models
from django.urls import reverse


class Author(models.Model):
    name = models.CharField(max_length=200)

    def get_absolute_url(self):
        return reverse("author-detail", kwargs={"pk": self.pk})

Sedan kan vi använda CreateView och dess vänner för att göra det faktiska arbetet. Lägg märke till hur vi bara konfigurerar de generiska klassbaserade vyerna här; vi behöver inte skriva någon logik själva:

Vyer.py
Python
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author


class AuthorCreateView(CreateView):
    model = Author
    fields = ["name"]


class AuthorUpdateView(UpdateView):
    model = Author
    fields = ["name"]


class AuthorDeleteView(DeleteView):
    model = Author
    success_url = reverse_lazy("author-list")

Attributet fields fungerar på samma sätt som attributet fields på den inre Meta klassen på ModelForm. Om du inte definierar formulärklassen på ett annat sätt krävs attributet och vyn kommer att ge upphov till ett ImproperlyConfigured undantag om det inte är det.

Om du anger både attributen fields och form_class, kommer ett ImproperlyConfigured undantag att uppstå.

Slutligen kopplar vi in dessa nya vyer i URLconf:

urls.py
Python
from django.urls import path
from myapp.views import AuthorCreateView, AuthorDeleteView, AuthorUpdateView

urlpatterns = [
    # ...
    path("author/add/", AuthorCreateView.as_view(), name="author-add"),
    path("author/<int:pk>/", AuthorUpdateView.as_view(), name="author-update"),
    path("author/<int:pk>/delete/", AuthorDeleteView.as_view(), name="author-delete"),
]

Modeller och request.userLink to this heading

För att spåra den användare som skapade ett objekt med hjälp av en CreateView kan du använda en anpassad ModelForm för att göra detta. Lägg först till den främmande nyckelrelationen i modellen:

modeller.py
Python
from django.contrib.auth.models import User
from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=200)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)

    # ...

I vyn ska du se till att du inte inkluderar created_by i listan över fält som ska redigeras och åsidosätta form_valid() för att lägga till användaren:

Vyer.py
Python
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author


class AuthorCreateView(LoginRequiredMixin, CreateView):
    model = Author
    fields = ["name"]

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super().form_valid(form)

LoginRequiredMixin hindrar användare som inte är inloggade från att komma åt formuläret. Om du utelämnar det måste du hantera obehöriga användare i form_valid().

Exempel på förhandling om innehållLink to this heading

Här är ett exempel som visar hur du kan gå tillväga för att implementera ett formulär som fungerar med ett API-baserat arbetsflöde såväl som ”vanliga” formulärposts:

Code
from django.http import JsonResponse
from django.views.generic.edit import CreateView
from myapp.models import Author


class JsonableResponseMixin:
    """
    Mixin to add JSON support to a form.
    Must be used with an object-based FormView (e.g. CreateView)
    """

    def form_invalid(self, form):
        response = super().form_invalid(form)
        if self.request.accepts("text/html"):
            return response
        else:
            return JsonResponse(form.errors, status=400)

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        response = super().form_valid(form)
        if self.request.accepts("text/html"):
            return response
        else:
            data = {
                "pk": self.object.pk,
            }
            return JsonResponse(data)


class AuthorCreateView(JsonableResponseMixin, CreateView):
    model = Author
    fields = ["name"]

I exemplet ovan antas att om klienten har stöd för text/html, så kommer de att föredra det. Detta kanske dock inte alltid är sant. Vid begäran om en .css-fil kommer många webbläsare att skicka rubriken Accept: text/css,*/*;q=0.1, vilket indikerar att de föredrar CSS, men allt annat går bra. Detta innebär att request.accepts("text/html") kommer att vara True.

För att bestämma rätt format, med hänsyn till klientens preferenser, använd django.http.HttpRequest.get_preferred_type():

Code
class JsonableResponseMixin:
    """
    Mixin to add JSON support to a form.
    Must be used with an object-based FormView (e.g. CreateView).
    """

    accepted_media_types = ["text/html", "application/json"]

    def dispatch(self, request, *args, **kwargs):
        if request.get_preferred_type(self.accepted_media_types) is None:
            # No format in common.
            return HttpResponse(
                status_code=406, headers={"Accept": ",".join(self.accepted_media_types)}
            )

        return super().dispatch(request, *args, **kwargs)

    def form_invalid(self, form):
        response = super().form_invalid(form)
        accepted_type = self.request.get_preferred_type(self.accepted_media_types)
        if accepted_type == "text/html":
            return response
        elif accepted_type == "application/json":
            return JsonResponse(form.errors, status=400)

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        response = super().form_valid(form)
        accepted_type = self.request.get_preferred_type(self.accepted_media_types)
        if accepted_type == "text/html":
            return response
        elif accepted_type == "application/json":
            data = {
                "pk": self.object.pk,
            }
            return JsonResponse(data)