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

Generic views really shine when working with models. These generic views will automatically create a ModelForm, so long as they can work out which model class to use:

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

  • If get_object() returns an object, the class of that object will be used.

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

Model form views provide a form_valid() implementation that saves the model automatically. You can override this if you have any special requirements; see below for examples.

You don’t even need to provide a success_url for CreateView or UpdateView - they will use get_absolute_url() on the model object if available.

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

First we need to add get_absolute_url() to our Author class:

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)

    # ...

In the view, ensure that you don’t include created_by in the list of fields to edit, and override form_valid() to add the user:

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 prevents users who aren’t logged in from accessing the form. If you omit that, you’ll need to handle unauthorized users in 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)