# Django 6.0.7 documentation

# Ramverket för ”webbplatser”

Django levereras med ett valfritt ”sites”-ramverk. Det är en hook 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` och `name` 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

It’s fragile to hardcode the site IDs like that, in case they change. The cleaner way of accomplishing the same thing is to check the current site’s domain:

    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:

1. Lägg till `'django.contrib.sites'` i inställningen `INSTALLED_APPS`.

2. Definiera en `SITE_ID`-inställning:

       SITE_ID = 1

3. 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 `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()

How did `CurrentSiteManager` know which field of `Photo` was the `Site`? By default, `CurrentSiteManager` looks for either a `ForeignKey` called `site` or a `ManyToManyField` called `sites` to filter on. If you use a field named something other than `site` or `sites` to identify which `Site` objects your object is related to, then you need to explicitly pass the custom field name as a parameter to `CurrentSiteManager` on your model. The following model, which has a field called `publish_on`, demonstrates this:

    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 dess `Site`, och `FlatpageFallbackMiddleware` kontrollerar den aktuella webbplatsen för att hämta flatpages att visa.

- I `syndication framework` har mallarna för `title` och `description` automatiskt tillgång till en variabel `{{ site }}`, som är `Site`-objektet som representerar den aktuella webbplatsen. Den hook för att tillhandahålla URL:er för objekt kommer också att använda `domän` från det aktuella `Site`-objektet om du inte anger en fullständigt kvalificerad domän.

- I `autentiseringsramverket`, `django.contrib.auth.views.LoginView` skickar det aktuella `Site`-namnet till mallen som `{{site_name }}`.

- Genvägsvyn (`django.contrib.contenttypes.views.shortcut`) använder domänen för det aktuella `Site`-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 attributen `domain` och `name`) men hämtar sina data från ett Django `HttpRequest`-objekt snarare än från en databas.

  __init__(request)

    Ställer in attributen `name` och `domain` till värdet av `get_host()`.

A `RequestSite` object has a similar interface to a normal `Site` object, except its `__init__()` method takes an `HttpRequest` object. It’s able to deduce the `domain` and `name` by looking at the request’s domain. It has `save()` and `delete()` methods to match the interface of `Site`, but the methods raise `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 aktuella `Site`-objektet eller ett `RequestSite`-objekt baserat på begäran. Den letar upp den aktuella webbplatsen baserat på `request.get_host()` om inställningen `SITE_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ör `RequestSite` som alltid kommer att använda den omodifierade värden.