Inbyggda klassbaserade generiska vyer¶
Att skriva webbapplikationer kan vara enformigt, eftersom vi upprepar vissa mönster om och om igen. Django försöker ta bort en del av denna monotoni på modell- och mallnivåerna, men webbutvecklare upplever också denna tristess på vynivån.
Djangos generiska vyer utvecklades för att lindra den smärtan. De tar vissa vanliga idiom och mönster som finns i vyutveckling och abstraherar dem så att du snabbt kan skriva vanliga vyer över data utan att behöva skriva för mycket kod.
Vi kan känna igen vissa vanliga uppgifter, som att visa en lista med objekt, och skriva kod som visar en lista med valfritt objekt. Då kan modellen i fråga skickas som ett extra argument till URLconf.
Django levereras med generiska vyer för att göra följande:
Visar list- och detaljsidor för ett enda objekt. Om vi skulle skapa en applikation för att hantera konferenser så skulle en
TalkListView
och enRegisteredUserListView
vara exempel på listvyer. En enskild samtalssida är ett exempel på vad vi kallar en ”detalj”-vy.Presentera datumbaserade objekt på arkivsidor för år/månad/dag, tillhörande detalj- och ”senaste”-sidor.
Tillåt användare att skapa, uppdatera och ta bort objekt - med eller utan behörighet.
Sammantaget ger dessa vyer gränssnitt för att utföra de vanligaste uppgifterna som utvecklare stöter på.
Förlängning av generiska vyer¶
Det råder ingen tvekan om att generiska vyer kan snabba upp utvecklingen avsevärt. I de flesta projekt kommer det dock ett ögonblick när de generiska vyerna inte längre räcker till. Den vanligaste frågan som ställs av nya Django-utvecklare är faktiskt hur man får generiska vyer att hantera ett bredare spektrum av situationer.
Detta är en av anledningarna till att generiska vyer fick en ny design i 1.3-versionen - tidigare var de vyfunktioner med en förvirrande mängd alternativ; nu, i stället för att skicka in en stor mängd konfiguration i URLconf, är det rekommenderade sättet att utöka generiska vyer att underklassa dem och åsidosätta deras attribut eller metoder.
Med detta sagt har generiska vyer en gräns. Om du har svårt att implementera din vy som en underklass till en generisk vy kan det vara mer effektivt att bara skriva den kod du behöver med hjälp av dina egna klassbaserade eller funktionella vyer.
Fler exempel på generiska vyer finns i vissa tredjepartsapplikationer, eller så kan du skriva dina egna efter behov.
Generiska vyer av objekt¶
TemplateView
är förvisso användbar, men Djangos generiska vyer briljerar verkligen när det gäller att presentera vyer av ditt databasinnehåll. Eftersom det är en så vanlig uppgift kommer Django med en handfull inbyggda generiska vyer för att hjälpa till att generera list- och detaljvyer av objekt.
Låt oss börja med att titta på några exempel på hur man visar en lista med objekt eller ett enskilt objekt.
Vi kommer att använda dessa modeller:
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Meta:
ordering = ["-name"]
def __str__(self):
return self.name
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to="author_headshots")
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField("Author")
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
Nu måste vi definiera en vy:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherListView(ListView):
model = Publisher
Koppla slutligen den vyn till dina webbadresser:
# urls.py
from django.urls import path
from books.views import PublisherListView
urlpatterns = [
path("publishers/", PublisherListView.as_view()),
]
Det är all Python-kod vi behöver skriva. Vi behöver dock fortfarande skriva en mall. Vi kan uttryckligen berätta för vyn vilken mall som ska användas genom att lägga till ett template_name
-attribut till vyn, men i avsaknad av en uttrycklig mall kommer Django att härleda en från objektets namn. I det här fallet kommer den härledda mallen att vara "books/publisher_list.html"
- ”books”-delen kommer från namnet på appen som definierar modellen, medan ”publisher”-biten är den gemena versionen av modellens namn.
Observera
Således, när (till exempel) alternativet APP_DIRS
för en DjangoTemplates
backend är inställt på True i TEMPLATES
, kan en mallplats vara: /path/to/project/books/templates/books/publisher_list.html
Denna mall kommer att renderas mot ett sammanhang som innehåller en variabel som heter object_list
som innehåller alla utgivarobjekt. En mall kan se ut så här:
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
Det är verkligen allt det är. Alla de häftiga funktionerna i generiska vyer kommer från att ändra de attribut som ställts in på den generiska vyn. I generic views reference dokumenteras alla generiska vyer och deras alternativ i detalj; resten av det här dokumentet kommer att behandla några av de vanligaste sätten du kan anpassa och utöka generiska vyer.
Skapa ”vänliga” mallkontexter¶
Du kanske har lagt märke till att vår mall för förlagslistan lagrar alla förlag i en variabel med namnet ”object_list”. Även om detta fungerar bra är det inte så ”vänligt” mot mallförfattare: de måste ”bara veta” att de har att göra med utgivare här.
Tja, om du har att göra med ett modellobjekt är detta redan gjort åt dig. När du har att göra med ett objekt eller en queryset kan Django fylla i kontexten med den gemena versionen av modellklassens namn. Detta tillhandahålls utöver standardposten object_list
, men innehåller exakt samma data, dvs. publisher_list
.
Om detta fortfarande inte är en bra matchning kan du manuellt ställa in namnet på kontextvariabeln. Attributet context_object_name
på en generisk vy anger vilken kontextvariabel som ska användas:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherListView(ListView):
model = Publisher
context_object_name = "my_favorite_publishers"
Att tillhandahålla ett användbart context_object_name
är alltid en bra idé. Dina kollegor som designar mallar kommer att tacka dig.
Lägga till extra sammanhang¶
Ofta behöver du presentera lite extra information utöver den som tillhandahålls av den generiska vyn. Tänk till exempel på att visa en lista över alla böcker på varje förlags detaljsida. Den generiska vyn DetailView
ger utgivaren till sammanhanget, men hur får vi ytterligare information i den mallen?
Svaret är att underordna DetailView
och tillhandahålla din egen implementering av metoden get_context_data
. Standardimplementeringen lägger till objektet som visas i mallen, men du kan åsidosätta den för att skicka mer:
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetailView(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context["book_list"] = Book.objects.all()
return context
Observera
I allmänhet kommer get_context_data
att sammanfoga kontextdata för alla överordnade klasser med den aktuella klassens data. För att bevara detta beteende i dina egna klasser där du vill ändra kontexten, bör du se till att anropa get_context_data
på superklassen. När inga två klasser försöker definiera samma nyckel kommer detta att ge de förväntade resultaten. Men om någon klass försöker åsidosätta en nyckel efter att föräldraklasserna har ställt in den (efter anropet till super), måste alla barn till den klassen också uttryckligen ställa in den efter super om de vill vara säkra på att åsidosätta alla föräldrar. Om du har problem kan du se över metodupplösningsordningen för din vy.
Ett annat övervägande är att kontextdata från klassbaserade generiska vyer kommer att åsidosätta data som tillhandahålls av kontextprocessorer; se get_context_data()
för ett exempel.
Visning av delmängder av objekt¶
Låt oss nu ta en närmare titt på argumentet model
som vi har använt hela tiden. Argumentet model
, som anger den databasmodell som vyn ska arbeta med, är tillgängligt för alla generiska vyer som arbetar med ett enda objekt eller en samling objekt. Argumentet model
är dock inte det enda sättet att ange de objekt som vyn ska arbeta med - du kan också ange listan över objekt med argumentet queryset
:
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetailView(DetailView):
context_object_name = "publisher"
queryset = Publisher.objects.all()
Att ange model = Publisher
är en förkortning för att säga queryset = Publisher.objects.all()
. Men genom att använda queryset
för att definiera en filtrerad lista över objekt kan du vara mer specifik om de objekt som kommer att synas i vyn (se Göra förfrågningar för mer information om QuerySet
-objekt, och se class-based views reference för fullständiga detaljer).
För att ta ett exempel kanske vi vill sortera en lista med böcker efter utgivningsdatum, med den senaste först:
from django.views.generic import ListView
from books.models import Book
class BookListView(ListView):
queryset = Book.objects.order_by("-publication_date")
context_object_name = "book_list"
Det är ett ganska minimalt exempel, men det illustrerar idén på ett bra sätt. Du vill vanligtvis göra mer än att bara ordna om objekt. Om du vill presentera en lista över böcker från ett visst förlag kan du använda samma teknik:
from django.views.generic import ListView
from books.models import Book
class AcmeBookListView(ListView):
context_object_name = "book_list"
queryset = Book.objects.filter(publisher__name="ACME Publishing")
template_name = "books/acme_list.html"
Lägg märke till att vi tillsammans med ett filtrerat queryset
också använder ett anpassat mallnamn. Om vi inte gjorde det skulle den generiska vyn använda samma mall som ”vanilj”-objektlistan, vilket kanske inte är vad vi vill.
Observera också att det här inte är ett särskilt elegant sätt att göra förlagsspecifika böcker. Om vi vill lägga till ytterligare en förlagssida skulle vi behöva ytterligare en handfull rader i URLconf, och mer än ett fåtal förlag skulle bli orimliga. Vi tar itu med det här problemet i nästa avsnitt.
Observera
Om du får en 404 när du begär /books/acme/
, kontrollera att du faktiskt har ett förlag med namnet ’ACME Publishing’. Generiska vyer har en allow_empty
parameter för detta fall. Se :doc:``class-based-views reference</ref/class-based-views/index>` för mer information.
Dynamisk filtrering¶
Ett annat vanligt behov är att filtrera ner objekten som ges på en listsida efter någon nyckel i URL:en. Tidigare hårdkodade vi förlagsnamnet i URLconf, men tänk om vi ville skriva en vy som visade alla böcker från ett godtyckligt förlag?
Praktiskt nog har ListView
en get_queryset()
-metod som vi kan åsidosätta. Som standard returnerar den värdet på attributet queryset
, men vi kan använda det för att lägga till mer logik.
Den viktigaste delen för att få detta att fungera är att när klassbaserade vyer anropas lagras olika användbara saker på self
; förutom begäran (self.request
) inkluderar detta de positionella (self.args
) och namnbaserade (self.kwargs
) argumenten som fångas enligt URLconf.
Här har vi en URLconf med en enda fångad grupp:
# urls.py
from django.urls import path
from books.views import PublisherBookListView
urlpatterns = [
path("books/<publisher>/", PublisherBookListView.as_view()),
]
Nu ska vi skriva själva vyn PublisherBookListView
:
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookListView(ListView):
template_name = "books/books_by_publisher.html"
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
return Book.objects.filter(publisher=self.publisher)
Att använda get_queryset
för att lägga till logik i queryset-urvalet är lika bekvämt som det är kraftfullt. Om vi till exempel vill kan vi använda self.request.user
för att filtrera med hjälp av den aktuella användaren eller annan mer komplex logik.
Vi kan också lägga till utgivaren i sammanhanget samtidigt, så att vi kan använda den i mallen:
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in the publisher
context["publisher"] = self.publisher
return context
Utföra extra arbete¶
Det sista vanliga mönstret vi ska titta på innebär att man gör lite extra arbete före eller efter anropet av den generiska vyn.
Tänk dig att vi hade ett last_accessed
fält på vår Author
modell som vi använde för att hålla reda på den sista gången någon tittade på den författaren:
# models.py
from django.db import models
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to="author_headshots")
last_accessed = models.DateTimeField()
Den generiska klassen DetailView
vet ingenting om det här fältet, men återigen kan vi skriva en anpassad vy för att hålla fältet uppdaterat.
Först måste vi lägga till en författardetaljbit i URLconf för att peka på en anpassad vy:
from django.urls import path
from books.views import AuthorDetailView
urlpatterns = [
# ...
path("authors/<int:pk>/", AuthorDetailView.as_view(), name="author-detail"),
]
Sedan skriver vi vår nya vy – get_object
är den metod som hämtar objektet – så vi åsidosätter den och omsluter anropet:
from django.utils import timezone
from django.views.generic import DetailView
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
obj = super().get_object()
# Record the last accessed date
obj.last_accessed = timezone.now()
obj.save()
return obj
Observera
URLconf här använder den namngivna gruppen pk
- detta namn är det standardnamn som DetailView
använder för att hitta värdet på den primära nyckel som används för att filtrera frågeuppsättningen.
Om du vill kalla gruppen något annat kan du ställa in pk_url_kwarg
på vyn.