Formulärhantering med klassbaserade vyer¶
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 former¶
Givet ett kontaktformulär:
forms.py
¶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
¶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:
FormView ärver
TemplateResponseMixin
såtemplate_name
kan användas här.Standardimplementeringen för
form_valid()
omdirigerar helt enkelt tillsuccess_url
.
Modellformer¶
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.
Observera
När du anger en anpassad formulärklass måste du fortfarande ange modellen, även om form_class
kan vara en ModelForm
.
Först måste vi lägga till get_absolute_url()
till vår Author
klass:
modeller.py
¶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
¶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")
Observera
Vi måste använda reverse_lazy()
istället för reverse()
, eftersom webbadresserna inte laddas när filen importeras.
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
¶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"),
]
Observera
Dessa vyer ärver SingleObjectTemplateResponseMixin
som använder template_name_suffix
för att konstruera template_name
baserat på modellen.
I detta exempel:
CreateView
ochUpdateView
användermyapp/author_form.html
DeleteView
användermyapp/author_confirm_delete.html
Om du vill ha separata mallar för CreateView
och UpdateView
kan du ange antingen template_name
eller template_name_suffix
på din vyklass.
Modeller och request.user
¶
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
¶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
¶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åll¶
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:
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()
:
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)
Metoden HttpRequest.get_preferred_type()
lades till.