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
TemplateResponseMixinsåtemplate_namekan användas här.Standardimplementeringen för
form_valid()omdirigerar helt enkelt tillsuccess_url.
Modellformer¶
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
modelanges kommer den modellklassen att användas.If
get_object()returns an object, the class of that object will be used.Om en
querysetanges, 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.
Observera
När du anger en anpassad formulärklass måste du fortfarande ange modellen, även om form_class kan vara en ModelForm.
First we need to add get_absolute_url() to our
Author class:
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:
CreateViewochUpdateViewanvändermyapp/author_form.htmlDeleteViewanvä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)
# ...
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¶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å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.