Introduktion till klassbaserade vyer¶
Klassbaserade vyer är ett alternativt sätt att implementera vyer som Python-objekt i stället för funktioner. De ersätter inte funktionsbaserade vyer, men har vissa skillnader och fördelar jämfört med funktionsbaserade vyer:
Organisering av kod relaterad till specifika HTTP-metoder (
GET
,POST
, etc.) kan hanteras med separata metoder istället för villkorlig förgrening.Objektorienterade tekniker som mixins (multiple inheritance) kan användas för att dela upp koden i återanvändbara komponenter.
Förhållandet mellan och historien bakom generiska vyer, klassbaserade vyer och klassbaserade generiska vyer¶
I början fanns det bara view-funktionskontraktet, Django skickade din funktion en HttpRequest
och förväntade sig tillbaka en HttpResponse
. Detta var omfattningen av vad Django tillhandahöll.
Redan tidigt insåg man att det fanns vanliga idiom och mönster i utvecklingen av vyer. Funktionsbaserade generiska vyer introducerades för att abstrahera dessa mönster och underlätta utvecklingen av vyer för de vanligaste fallen.
Problemet med funktionsbaserade generiska vyer är att de visserligen täcker de enkla fallen väl, men att det inte finns något sätt att utöka eller anpassa dem utöver vissa konfigurationsalternativ, vilket begränsar deras användbarhet i många verkliga tillämpningar.
Klassbaserade generiska vyer skapades med samma mål som funktionsbaserade generiska vyer, att göra det enklare att utveckla vyer. Det sätt på vilket lösningen implementeras, genom användning av mixins, ger dock en verktygslåda som resulterar i att klassbaserade generiska vyer är mer utbyggbara och flexibla än sina funktionsbaserade motsvarigheter.
Om du tidigare har provat funktionsbaserade generiska vyer och funnit dem otillräckliga, bör du inte se klassbaserade generiska vyer som en klassbaserad motsvarighet, utan snarare som ett nytt sätt att lösa de ursprungliga problem som generiska vyer var avsedda att lösa.
Verktygslådan med basklasser och mixins som Django använder för att bygga klassbaserade generiska vyer är byggd för maximal flexibilitet och har därför många krokar i form av standardmetoderealiseringar och attribut som du troligen inte kommer att bry dig om i de enklaste användningsfallen. I stället för att begränsa dig till ett klassbaserat attribut för form_class
använder implementeringen till exempel en get_form
-metod, som anropar en get_form_class
-metod, som i sin standardimplementering returnerar klassens form_class
-attribut. Detta ger dig flera alternativ för att ange vilket formulär som ska användas, från ett attribut till en helt dynamisk, anropsbar krok. Dessa alternativ verkar lägga till ihålig komplexitet för enkla situationer, men utan dem skulle mer avancerade mönster vara begränsade.
Använda klassbaserade vyer¶
I grunden innebär en klassbaserad vy att du kan svara på olika HTTP-begärandemetoder med olika klassinstansmetoder, istället för med villkorligt förgrenad kod i en enda vyfunktion.
Så där koden för att hantera HTTP GET
i en vyfunktion skulle se ut ungefär som:
from django.http import HttpResponse
def my_view(request):
if request.method == "GET":
# <view logic>
return HttpResponse("result")
I en klassbaserad vy skulle detta bli:
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse("result")
Eftersom Djangos URL-resolver förväntar sig att skicka begäran och tillhörande argument till en anropbar funktion, inte en klass, har klassbaserade vyer en klassmetod as_view()
som returnerar en funktion som kan anropas när en begäran kommer för en URL som matchar det associerade mönstret. Funktionen skapar en instans av klassen, anropar setup()
för att initialisera dess attribut och anropar sedan dess dispatch()
-metod. dispatch
tittar på begäran för att avgöra om det är en GET
, POST
, etc, och vidarebefordrar begäran till en matchande metod om en sådan är definierad, eller ger upphov till HttpResponseNotAllowed
om inte:
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path("about/", MyView.as_view()),
]
Det är värt att notera att det som din metod returnerar är identiskt med det som du returnerar från en funktionsbaserad vy, nämligen någon form av HttpResponse
. Detta innebär att http shortcuts eller TemplateResponse
-objekt är giltiga att använda i en klassbaserad vy.
Även om en minimal klassbaserad vy inte kräver några klassattribut för att utföra sitt jobb, är klassattribut användbara i många klassbaserade designer, och det finns två sätt att konfigurera eller ange klassattribut.
Den första är det vanliga Python-sättet att subklassa och åsidosätta attribut och metoder i subklassen. Så om din överordnade klass hade ett attribut greeting
så här:
from django.http import HttpResponse
from django.views import View
class GreetingView(View):
greeting = "Good Day"
def get(self, request):
return HttpResponse(self.greeting)
Du kan åsidosätta det i en subklass:
class MorningGreetingView(GreetingView):
greeting = "Morning to ya"
Ett annat alternativ är att konfigurera klassattribut som nyckelordsargument till as_view()
-anropet i URLconf:
urlpatterns = [
path("about/", GreetingView.as_view(greeting="G'day")),
]
Observera
Medan din klass instansieras för varje begäran som skickas till den, konfigureras klassattribut som anges via as_view()
-ingångspunkten endast en gång vid den tidpunkt då dina webbadresser importeras.
Använda mixins¶
Mixins är en form av multipel nedärvning där beteenden och attribut från flera överordnade klasser kan kombineras.
Till exempel finns det i de generiska klassbaserade vyerna en mixin som heter TemplateResponseMixin
vars primära syfte är att definiera metoden render_to_response()
. I kombination med beteendet i basklassen View
blir resultatet en klass av typen TemplateView
som skickar förfrågningar till lämpliga matchningsmetoder (ett beteende som definieras i basklassen View
) och som har en render_to_response()
-metoden som använder ett template_name
-attribut för att returnera ett TemplateResponse
-objekt (ett beteende som definieras i TemplateResponseMixin
).
Mixins är ett utmärkt sätt att återanvända kod i flera klasser, men de har ett visst pris. Ju mer din kod är utspridd bland mixins, desto svårare blir det att läsa en underordnad klass och veta exakt vad den gör, och desto svårare blir det att veta vilka metoder från vilka mixins som ska åsidosättas om du subklassar något som har ett djupt arvsträd.
Observera också att du bara kan ärva från en generisk vy - det vill säga, bara en föräldraklass kan ärva från View
och resten (om någon) bör vara mixins. Att försöka ärva från mer än en klass som ärver från View
- till exempel att försöka använda ett formulär högst upp i en lista och kombinera ProcessFormView
och ListView
- kommer inte att fungera som förväntat.
Hantera formulär med klassbaserade vyer¶
En grundläggande funktionsbaserad vy som hanterar formulär kan se ut ungefär så här:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import MyForm
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect("/success/")
else:
form = MyForm(initial={"key": "value"})
return render(request, "form_template.html", {"form": form})
En liknande klassbaserad vy kan se ut på följande sätt:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View
from .forms import MyForm
class MyFormView(View):
form_class = MyForm
initial = {"key": "value"}
template_name = "form_template.html"
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {"form": form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect("/success/")
return render(request, self.template_name, {"form": form})
Detta är ett minimifall, men du kan se att du då skulle ha möjlighet att anpassa den här vyn genom att åsidosätta något av klassattributen, t.ex. form_class
, via URLconf-konfiguration, eller subklassa och åsidosätta en eller flera av metoderna (eller båda!).
Dekorera klassbaserade vyer¶
Utökningen av klassbaserade vyer är inte begränsad till att använda mixins. Du kan också använda dekoratorer. Eftersom klassbaserade vyer inte är funktioner fungerar dekorering av dem olika beroende på om du använder as_view()
eller skapar en subklass.
Dekorera i URLconf¶
Du kan justera klassbaserade vyer genom att dekorera resultatet av as_view()
-metoden. Det enklaste stället att göra detta är i URLconf där du distribuerar din vy:
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
from .views import VoteView
urlpatterns = [
path("about/", login_required(TemplateView.as_view(template_name="secret.html"))),
path("vote/", permission_required("polls.can_vote")(VoteView.as_view())),
]
Denna metod tillämpar dekoratorn per instans. Om du vill att varje instans av en vy ska dekoreras måste du använda ett annat tillvägagångssätt.
Inredning av klassen¶
För att dekorera varje instans av en klassbaserad vy måste du dekorera själva klassdefinitionen. För att göra detta tillämpar du dekoratorn på klassens dispatch()
-metod.
En metod i en klass är inte riktigt samma sak som en fristående funktion, så du kan inte bara använda en funktionsdekorator på metoden - du måste först omvandla den till en metoddekorator. Dekoratorn method_decorator
omvandlar en funktionsdekorator till en metoddekorator så att den kan användas på en instansmetod. Till exempel:
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = "secret.html"
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
Eller, mer kortfattat, du kan dekorera klassen istället och skicka namnet på den metod som ska dekoreras som nyckelordsargumentet name
:
@method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
template_name = "secret.html"
Om du har en uppsättning vanliga dekoratorer som används på flera ställen kan du definiera en lista eller tupel av dekoratorer och använda detta istället för att anropa method_decorator()
flera gånger. Dessa två klasser är likvärdiga:
decorators = [never_cache, login_required]
@method_decorator(decorators, name="dispatch")
class ProtectedView(TemplateView):
template_name = "secret.html"
@method_decorator(never_cache, name="dispatch")
@method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
template_name = "secret.html"
Dekoratörerna behandlar en begäran i den ordning som de skickas till dekoratören. I exemplet kommer never_cache()
att bearbeta begäran före login_required()
.
I det här exemplet kommer varje instans av ProtectedView
att ha inloggningsskydd. Dessa exempel använder login_required
, men samma beteende kan erhållas genom att använda LoginRequiredMixin
.
Observera
method_decorator
skickar *args
och **kwargs
som parametrar till den dekorerade metoden på klassen. Om din metod inte accepterar en kompatibel uppsättning parametrar kommer den att ge upphov till ett TypeError
undantag.