Ramverket för ”webbplatser”¶
Django levereras med ett valfritt ”sites”-ramverk. Det är en krok för att associera objekt och funktionalitet till specifika webbplatser, och det är en plats för domännamnen och ”verbose”-namnen på dina Django-drivna webbplatser.
Använd den om din enda Django-installation driver mer än en webbplats och du behöver skilja mellan dessa webbplatser på något sätt.
Webbplatsernas ramverk bygger huvudsakligen på denna modell:
- class models.Site¶
En modell för att lagra attributen
domain
ochname
för en webbplats.- domain¶
Det fullständigt kvalificerade domännamnet som är kopplat till webbplatsen. Till exempel:
www.example.com
.
- name¶
Ett mänskligt läsbart, ”mångordigt” namn för webbplatsen.
Inställningen SITE_ID
anger databas-ID för objektet Site
som är associerat med just den inställningsfilen. Om inställningen utelämnas kommer funktionen get_current_site()
att försöka hämta den aktuella webbplatsen genom att jämföra domain
med värdnamnet från metoden request.get_host()
.
Hur du använder detta är upp till dig, men Django använder det på ett par sätt automatiskt via ett par konventioner.
Exempel på användning¶
Varför skulle du använda webbplatser? Det förklaras bäst med hjälp av exempel.
Koppla innehåll till flera webbplatser¶
Webbplatserna LJWorld.com och Lawrence.com drevs av samma nyhetsorganisation - tidningen Lawrence Journal-World i Lawrence, Kansas. LJWorld.com fokuserade på nyheter, medan Lawrence.com fokuserade på lokal underhållning. Men ibland ville redaktörerna publicera en artikel på båda webbplatserna.
Det naiva sättet att lösa problemet skulle vara att kräva att sajtproducenterna publicerar samma artikel två gånger: en gång för LJWorld.com och en gång för Lawrence.com. Men det är ineffektivt för sajtproducenterna, och det är överflödigt att lagra flera kopior av samma artikel i databasen.
En bättre lösning tar bort dupliceringen av innehållet: Båda webbplatserna använder samma artikeldatabas, och en artikel är associerad med en eller flera webbplatser. I Djangos modellterminologi representeras detta av en ManyToManyField
i modellen Article
:
from django.contrib.sites.models import Site
from django.db import models
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
sites = models.ManyToManyField(Site)
Detta åstadkommer flera saker på ett bra sätt:
Det låter webbplatsens producenter redigera allt innehåll - på båda webbplatserna - i ett enda gränssnitt (Django-admin).
Det innebär att samma berättelse inte behöver publiceras två gånger i databasen, utan att den bara har en enda post i databasen.
Det gör att webbplatsutvecklarna kan använda samma Django-visningskod för båda webbplatserna. Vykoden som visar en viss berättelse kontrollerar att den begärda berättelsen finns på den aktuella webbplatsen. Det ser ut ungefär så här:
from django.contrib.sites.shortcuts import get_current_site def article_detail(request, article_id): try: a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id) except Article.DoesNotExist: raise Http404("Article does not exist on this site") # ...
Koppla innehåll till en enskild webbplats¶
På samma sätt kan du associera en modell till modellen Site
i en många-till-en-relation med hjälp av ForeignKey
.
Om en artikel till exempel bara är tillåten på en enda webbplats använder du en modell som denna:
from django.contrib.sites.models import Site
from django.db import models
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
site = models.ForeignKey(Site, on_delete=models.CASCADE)
Detta har samma fördelar som beskrivs i det sista avsnittet.
Ansluta till den aktuella webbplatsen från vyer¶
Du kan använda sites-ramverket i dina Django-vyer för att göra särskilda saker baserat på den webbplats där vyn anropas. Till exempel:
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
pass
else:
# Do something else.
pass
Det är vanskligt att hårdkoda webbplats-ID:n på det sättet, om de skulle ändras. Det renare sättet att åstadkomma samma sak är att kontrollera den aktuella webbplatsens domän:
from django.contrib.sites.shortcuts import get_current_site
def my_view(request):
current_site = get_current_site(request)
if current_site.domain == "foo.com":
# Do something
pass
else:
# Do something else.
pass
Detta har också fördelen att kontrollera om webbplatsramverket är installerat och returnera en RequestSite
-instans om det inte är det.
Om du inte har tillgång till request-objektet kan du använda metoden get_current()
i Site
-modellens manager. Du bör då se till att din inställningsfil innehåller inställningen SITE_ID
. Detta exempel är likvärdigt med det föregående:
from django.contrib.sites.models import Site
def my_function_without_request():
current_site = Site.objects.get_current()
if current_site.domain == "foo.com":
# Do something
pass
else:
# Do something else.
pass
Hämta den aktuella domänen för visning¶
LJWorld.com och Lawrence.com har båda en funktion för e-postaviseringar, som gör det möjligt för läsare att registrera sig för att få meddelanden när nyheter inträffar. Det är ganska enkelt: En läsare registrerar sig via ett webbformulär och får omedelbart ett e-postmeddelande med texten ”Tack för din prenumeration”
Det skulle vara ineffektivt och överflödigt att implementera den här koden för registrering två gånger, så webbplatserna använder samma kod bakom kulisserna. Men meddelandet ”tack för att du registrerade dig” måste vara olika för varje webbplats. Genom att använda Site
-objekt kan vi abstrahera ”tack”-meddelandet för att använda värdena för den aktuella webbplatsens name
och domain
.
Här är ett exempel på hur formulärhanteringsvyn ser ut:
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import send_mail
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
current_site = get_current_site(request)
send_mail(
"Thanks for subscribing to %s alerts" % current_site.name,
"Thanks for your subscription. We appreciate it.\n\n-The %s team."
% (current_site.name,),
"editor@%s" % current_site.domain,
[user.email],
)
# ...
På Lawrence.com har det här e-postmeddelandet ämnesraden ”Tack för att du prenumererar på lawrence.com-varningar.” På LJWorld.com har e-postmeddelandet ämnet ”Tack för att du prenumererar på LJWorld.com-varningar.” Samma sak gäller för e-postmeddelandets brödtext.
Observera att ett ännu mer flexibelt (men mer tungrott) sätt att göra detta skulle vara att använda Djangos mallsystem. Om vi antar att Lawrence.com och LJWorld.com har olika mallkataloger (DIRS
), kan du använda mallsystemet så här:
from django.core.mail import send_mail
from django.template import loader
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
subject = loader.get_template("alerts/subject.txt").render({})
message = loader.get_template("alerts/message.txt").render({})
send_mail(subject, message, "editor@ljworld.com", [user.email])
# ...
I det här fallet skulle du behöva skapa subject.txt
och message.txt
mallfiler för både LJWorld.com och Lawrence.com mallkataloger. Det ger dig mer flexibilitet, men det är också mer komplext.
Det är en bra idé att utnyttja Site
-objekten så mycket som möjligt för att ta bort onödig komplexitet och redundans.
Hämta den aktuella domänen för fullständiga webbadresser¶
Djangos konvention get_absolute_url()
är bra för att få dina objekts URL utan domännamnet, men i vissa fall kanske du vill visa den fullständiga URL:en - med https://
och domänen och allt - för ett objekt. För att göra detta kan du använda webbplatsramverket. Ett exempel:
>>> from django.contrib.sites.models import Site
>>> obj = MyModel.objects.get(id=3)
>>> obj.get_absolute_url()
'/mymodel/objects/3/'
>>> Site.objects.get_current().domain
'example.com'
>>> "https://%s%s" % (Site.objects.get_current().domain, obj.get_absolute_url())
'https://example.com/mymodel/objects/3/'
Möjliggörande av ramverket för webbplatser¶
Följ dessa steg för att aktivera ramverket för webbplatser:
Lägg till
'django.contrib.sites'
i inställningenINSTALLED_APPS
.Definiera en
SITE_ID
-inställning:SITE_ID = 1
Kör
migrate
.
django.contrib.sites
registrerar en post_migrate
signalhanterare som skapar en standardwebbplats med namnet example.com
med domänen example.com
. Denna webbplats kommer också att skapas efter att Django skapar testdatabasen. För att ställa in rätt namn och domän för ditt projekt kan du använda en datamigrering.
För att kunna betjäna olika webbplatser i produktionen skulle du skapa en separat inställningsfil med varje SITE_ID
(kanske importera från en gemensam inställningsfil för att undvika att duplicera delade inställningar) och sedan ange lämplig DJANGO_SETTINGS_MODULE
för varje webbplats.
Cachelagring av det aktuella Site
-objektet¶
Eftersom den aktuella webbplatsen lagras i databasen kan varje anrop till Site.objects.get_current()
resultera i en databasfråga. Men Django är lite smartare än så: vid den första begäran cachelagras den aktuella webbplatsen och alla efterföljande anrop returnerar de cachelagrade data istället för att slå i databasen.
Om du av någon anledning vill tvinga fram en databasfråga kan du be Django att rensa cachen med hjälp av Site.objects.clear_cache()
:
# First call; current site fetched from database.
current_site = Site.objects.get_current()
# ...
# Second call; current site fetched from cache.
current_site = Site.objects.get_current()
# ...
# Force a database query for the third call.
Site.objects.clear_cache()
current_site = Site.objects.get_current()
Den ”aktuella webbplatsadministratören¶
- class managers.CurrentSiteManager¶
Om Site
spelar en nyckelroll i din applikation, överväg att använda den användbara CurrentSiteManager
i dina modeller. Det är en modell manager som automatiskt filtrerar sina frågor för att endast inkludera objekt som är associerade med den aktuella Site
.
Obligatorisk :inställning:`SITE_ID`
CurrentSiteManager
är endast användbar när inställningen SITE_ID
är definierad i dina inställningar.
Använd CurrentSiteManager
genom att uttryckligen lägga till den i din modell. Till exempel:
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
from django.db import models
class Photo(models.Model):
photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
site = models.ForeignKey(Site, on_delete=models.CASCADE)
objects = models.Manager()
on_site = CurrentSiteManager()
Med den här modellen kommer Photo.objects.all()
att returnera alla Photo
-objekt i databasen, men Photo.on_site.all()
kommer endast att returnera de Photo
-objekt som är associerade med den aktuella webbplatsen, enligt inställningen SITE_ID
.
Uttryckt på ett annat sätt är dessa två påståenden likvärdiga:
Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()
Hur visste CurrentSiteManager
vilket fält i Photo
som var Site
? Som standard letar CurrentSiteManager
efter antingen en ForeignKey
som heter site
eller en ManyToManyField
som heter sites
att filtrera på. Om du använder ett fält som heter något annat än site
eller sites
för att identifiera vilka Site
-objekt ditt objekt är relaterat till, måste du uttryckligen skicka det anpassade fältnamnet som en parameter till CurrentSiteManager
på din modell. Följande modell, som har ett fält som heter publish_on
, demonstrerar detta:
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
from django.db import models
class Photo(models.Model):
photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site, on_delete=models.CASCADE)
objects = models.Manager()
on_site = CurrentSiteManager("publish_on")
Om du försöker använda CurrentSiteManager
och skickar ett fältnamn som inte finns, kommer Django att göra ett ValueError
.
Slutligen, notera att du förmodligen vill behålla en normal (icke-platsspecifik) Manager
på din modell, även om du använder CurrentSiteManager
. Som förklaras i manager documentation, om du definierar en manager manuellt, kommer Django inte att skapa den automatiska objects = models.Manager()
-managern åt dig. Observera också att vissa delar av Django - nämligen Djangos administratörssida och generiska vyer - använder den hanterare som definieras först i modellen, så om du vill att din administratörssida ska ha tillgång till alla objekt (inte bara platsspecifika), lägg till objects = models.Manager()
i din modell innan du definierar CurrentSiteManager
.
Middleware för webbplatsen¶
Om du ofta använder detta mönster:
from django.contrib.sites.models import Site
def my_view(request):
site = Site.objects.get_current()
...
För att undvika upprepningar, lägg till django.contrib.sites.middleware.CurrentSiteMiddleware
till MIDDLEWARE
. Middlewaret ställer in attributet site
på varje request-objekt, så att du kan använda request.site
för att hämta den aktuella webbplatsen.
Hur Django använder ramverket för webbplatser¶
Även om det inte krävs att du använder webbplatsramverket, uppmuntras det starkt, eftersom Django utnyttjar det på några ställen. Även om din Django-installation bara driver en enda webbplats, bör du ta de två sekunderna för att skapa webbplatsobjektet med din domän
och namn
och peka på dess ID i din SITE_ID
-inställning.
Så här använder Django ramverket för webbplatser:
I
redirects-ramverket
är varje redirect-objekt associerat med en viss webbplats. När Django söker efter en omdirigering tar den hänsyn till den aktuella webbplatsen.I
flatpages-ramverket
är varje flatpage associerad med en viss webbplats. När en flatpage skapas anger du dessSite
, ochFlatpageFallbackMiddleware
kontrollerar den aktuella webbplatsen för att hämta flatpages att visa.I
syndication framework
har mallarna förtitle
ochdescription
automatiskt tillgång till en variabel{{ site }}
, som ärSite
-objektet som representerar den aktuella webbplatsen. Kroken för att tillhandahålla URL:er för objekt kommer också att användadomän
från det aktuellaSite
-objektet om du inte anger en fullständigt kvalificerad domän.I
autentiseringsramverket
,django.contrib.auth.views.LoginView
skickar det aktuellaSite
-namnet till mallen som{{site_name }}
.Genvägsvyn (
django.contrib.contenttypes.views.shortcut
) använder domänen för det aktuellaSite
-objektet när den beräknar ett objekts URL.I admin-ramverket använder länken ”Visa på webbplatsen” den aktuella
Site
för att räkna ut domänen för den webbplats som den kommer att omdirigera till.
RequestSite
-objekt¶
Vissa django.contrib applikationer drar nytta av sites-ramverket men är uppbyggda på ett sätt som inte kräver att sites-ramverket installeras i din databas. (Vissa människor vill inte, eller kan inte installera den extra databastabell som sites-ramverket kräver) För dessa fall tillhandahåller ramverket en django.contrib.sites.requests.RequestSite
-klass, som kan användas som en reserv när det databasstödda sites-ramverket inte är tillgängligt.
- class requests.RequestSite¶
En klass som delar det primära gränssnittet för
Site
(dvs. den har attributendomain
ochname
) men hämtar sina data från ett DjangoHttpRequest
-objekt snarare än från en databas.- __init__(request)¶
Ställer in attributen
name
ochdomain
till värdet avget_host()
.
Ett RequestSite
-objekt har ett liknande gränssnitt som ett normalt Site
-objekt, förutom att dess __init__()
-metod tar ett HttpRequest
-objekt. Den kan härleda domän
och namn
genom att titta på begärandets domän. Den har metoderna save()
och delete()
för att matcha gränssnittet för Site
, men metoderna ger upphov till NotImplementedError
.
genväg till get_current_site
¶
Slutligen, för att undvika repetitiv fallback-kod, tillhandahåller ramverket en django.contrib.sites.shortcuts.get_current_site()
-funktion.
- shortcuts.get_current_site(request)¶
En funktion som kontrollerar om
django.contrib.sites
är installerat och returnerar antingen det aktuellaSite
-objektet eller ettRequestSite
-objekt baserat på begäran. Den letar upp den aktuella webbplatsen baserat pårequest.get_host()
om inställningenSITE_ID
inte är definierad.Både en domän och en port kan returneras av
request.get_host()
när Host-headern har en port uttryckligen angiven, t.ex.example.com:80
. I sådana fall, om uppslagningen misslyckas eftersom värden inte matchar en post i databasen, tas porten bort och uppslagningen görs om med endast domändelen. Detta gäller inte förRequestSite
som alltid kommer att använda den omodifierade värden.