Django-de

Django Dokumentation

Das Sites-Framework

Diese Dokumentation gilt für Djangos Entwicklerversion, die zum Teil erhebliche Unterschiede zur letzten veröffentlichen Version aufweist.

Eine optionale Komponente von Django ist das “sites”-Framework, das es ermöglicht, einzelne Objekte oder Funktionen bestimmten Webseiten zuzuordnen. Zudem dient es als Speicher der Domain-Namen bzw. der eigentlichen Namen deiner Django-Webseiten.

Du solltest dieses Framework nutzen, wenn deine Django-Anwendung für mehr als nur eine Seite verwendet werden soll und du einfach zwischen diesen Seiten unterscheiden können möchtest.

Das gesamte Sites-Framework basiert auf zwei einfachen Grundbausteinen:

  • Das Site-Modell (in django.contrib.sites) hat die Felder domain und name.
  • Die SITE_ID-Einstellung entspricht der ID des Site-Objektes in der Datenbank, das mit genau dieser Settings-Datei verwendet werden soll.

Wie du dieses Framework nun verwendest, ist dir vollkommen freigestellt, jedoch verwendet Django es an manchen Stellen automatisch anhand einfacher Konventionen.

Beispiel

Warum solltest du das Sites-Framework verwenden? Am besten kann man dies anhand von Beispielen erklären.

Assoziiere Content mit mehreren Seiten

Die mit Django gebauten Seiten LJWorld.com und Lawrence.com werden innerhalb der selben Nachrichten-Organisation — der Lawrence Journal-World Zeitung in Lawrence, Kansas — betrieben. LJWorld.com spezialisiert sich auf Nachrichten, während Lawrence.com den Schwerpunkt auf lokale Unterhaltung legt. Manchmal jedoch möchten Redakteure einen Artikel auf beiden Seiten veröffentlichen.

Die naive Lösung für dieses Problem wäre, dass die Ersteller der Seiten den selben Artikel zweimal veröffentlichen: Einmal für LJWorld.com und einmal für Lawrence.com. Es ist jedoch ineffizient und redundant, mehrere Kopien ein und desselben Artikels in der Datenbank zu speichern.

Hierfür gibt es eine bessere und dennoch zugleich einfache Lösung: Beide Seite verwenden dieselbe Artikel-Datenbank und ein Artikel ist mit einer oder mehreren Seiten assoziiert. In Djangos Modellterminologie wird dies durch ein ManyToManyField im Article-Modell abgebildet:

from django.db import models
from django.contrib.sites.models import Site

class Article(models.Model):
    headline = models.CharField(max_length=200)
    # ...
    sites = models.ManyToManyField(Site)

Dies ermöglich Folgendes:

  • Man kann sämtlichen Content — von beiden Seiten — in einer einzigen Oberfläche (der Django-Administrationsoberfläche) bearbeiten.

  • Derselbe Artikel muss nicht doppelt in der Datenbank veröffentlich werden; er hat nur einen einzigen Eintrag in der Datenbank.

  • Diese Lösung ermöglicht, dass Entwickler denselben Django-View-Code für beide Seiten verwenden können. Der Code für den View, der einen bestimmten Artikel darstellt, überprüft, ob sich dieser auf der aktuellen Seite befindet. Dies könnte zum Beispiel so aussehen:

    from django.conf import settings
    
    def article_detail(request, article_id):
        try:
            a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID)
        except Article.DoesNotExist:
            raise Http404
        # ...
    

Assoziiere Content mit einer einzigen Seite

Ähnlich dem Ansatz von oben kannst du ein Modell auch durch eine Many-To-One-Relation mit dem Site-Modell assoziieren, indem du einen ForeignKey verwendest.

Wenn, zum Beispiel, ein Artikel nur zu einer einzigen Seite gehören darf, würdest du ein Model wie das Folgende verwenden:

from django.db import models
from django.contrib.sites.models import Site

class Article(models.Model):
    headline = models.CharField(max_length=200)
    # ...
    site = models.ForeignKey(Site)

Diese Lösung hat dieselben Vorteile wie die Lösung, die im vorherigen Abschnitt beschrieben wurde.

Seiten unterscheiden innerhalb von Views

Auf einer sehr tiefen Ebene kannst du das Sites-Framework auch dazu verwenden, um gewisse Dinge innerhalb eines Views abhängig von der aktuellen Seite zu tun. Zum Beispiel:

from django.conf import settings

    def my_view(request):
        if settings.SITE_ID == 3:
            # Mach etwas.
        else:
            # Mach etwas anderes.

Natürlich ist es nicht schön, Site-IDs direkt in den Code zu schreiben. So etwas eignet sich eigentlich nur für schnelle Fixes. Eine etwas schönere Lösung, die genau das gleiche Ergebnis liefert, ist der Vergleich mit der aktuellen Domain:

from django.conf import settings
from django.contrib.sites.models import Site

def my_view(request):
    current_site = Site.objects.get(id=settings.SITE_ID)
    if current_site.domain == 'foo.com':
        # Mach etwas.
    else:
        # Mach etwas anderes.

Sehr häufig will man auch einfach nur das Site-Objekt abfragen, das sich auf die aktuelle Seite bezieht. Aus diesem Grund verfügt der Manager des Site-Modells über die Methode get_current(). Das folgende Beispiel nutzt diese Methode, um das gleiche Ergebnis wie im vorherigen Beispiel zu erzielen:

from django.contrib.sites.models import Site

def my_view(request):
    current_site = Site.objects.get_current()
    if current_site.domain == 'foo.com':
        # Mach etwas.
    else:
        # Mach etwas anderes.

Anzeige der aktuellen Domain

Sowohl LJWorld.com als auch Lawrence.com verfügen über eine E-mail-Benachrichtigung, die es Lesern ermöglicht, sich benachrichtigen zu lassen, wenn es etwas Neues gibt. Die ganze Sache ist recht einfach: Ein Leser meldet sich durch ein Web-Formular an und bekommt umgehend eine Bestätigungs-E-Mail.

Es wäre ineffizient und redundant, diesen Anmeldeprozess zweimal zu implementieren, folglich verwenden beide Seiten denselben Code im Hintergrund. Der Dankeschön-Text muss jedoch für jede Seite etwas anders aussehen. Indem wir nun die Site-Objekte verwenden, können wir die Dankeschön-Nachricht abstrahieren und die entsprechenden Werte für name und domain verwenden.

Hier ein Beispiel, wie der View hinter dem Formular aussieht:

from django.contrib.sites.models import Site
from django.core.mail import send_mail

    def register_for_newsletter(request):
        # Formularfelder etc. überprüfen und lege das Abo für den Benutzer an
        # ...

        current_site = Site.objects.get_current()
        send_mail('Danke, dass du %s Benachrichtigungen abonniert hast' % current_site.name,
            'Danke für dein Abonnement. Wir sind wirklich dankbar.\n\n-Das %s-Team.' % current_site.name,
            'editor@%s' % current_site.domain,
            [user.email])

        # ...

Auf Lawrence.com hat diese E-Mail den Betreff “Thanks for subscribing to lawrence.com alerts”. Auf LJWorld.com hat die E-Mail den Betreff “Thanks for subscribing to LJWorld.com alerts”. Dasselbe gilt für den Inhalt der E-Mail.

Natürlich kannst du hierfür auch Djangos Template-System verwenden, was jedoch etwas schwergewichtiger ist. Gehen wir davon aus, dass Lawrence.com und LJWorld.com unterschiedliche Template-Verzeichnisse (TEMPLATE_DIRS) haben. In diesem Fall könntest du zum Beispiel so vorgehen:

from django.core.mail import send_mail
from django.template import loader, Context

def register_for_newsletter(request):
    # Formularfelder überprüfen etc. und Abo des Benutzers anlegen.
    # ...

    subject = loader.get_template('alerts/subject.txt').render(Context({}))
    message = loader.get_template('alerts/message.txt').render(Context({}))
    send_mail(subject, message, 'editor@ljworld.com', [user.email])

    # ...

In diesem Fall muss du eine subject.txt- und eine message.txt-Templatedatei in beiden Template-Verzeichnissen (also dem von LJWorld.com und auch in dem von Lawrence.com) erstellen. Diese Lösung ist flexibler, jedoch auch komplexer.

Es ist normalerweise ein gute Idee, die Site-Objekte wo immer möglich zu nutzen, um unnötige Komplexität und Redundanz zu vermeiden.

Wie kommt man an vollständige URLs zur aktuellen Domain?

Djangos get_absolute_url()-Konvention ist nützlich, wenn man die URL eines Objektes ohne Domännamen haben möchte, jedoch manchmal möchtest du vielleicht die vollständige URL eines Objektes — also mit http:// usw. — anzeigen. Auch hierfür kannst du das Site-Framework einsetzen. Hier ein einfaches Beispiel:

>>> 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'
>>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
'http://example.com/mymodel/objects/3/'

Zwischenspeichern des aktuellen Site-Objektes

Da das aktuelle Site-Objekt in der Datenbank gespeichert wird, sollte eigentlich jeder Aufruf von Site.objects.get_current() eine erneute Datenbankabfrage bewirken. Aber Django ist clever: Bei der ersten Anfrage wird das aktuelle Objekt zwischengespeichert und alle weiteren Aufrufe dieser Methode geben diese zwischengespeicherten Daten zurück, anstatt sie erneut aus der Datenbank abzufragen.

Wenn du aus irgendeinem Grund trotzdem die Daten direkt aus der Datenbank beziehen möchtest, kannst du mit Site.objects.clear_cache() Django anweisen, diesen Cache zu leeren:

# Erster Aufruf; die aktuelle Site wird aus der Datenbank geladen
current_site = Site.objects.get_current()
# ...

# Zweiter Aufruf; die aktuelle Site wird aus dem Cache geladen
current_site = Site.objects.get_current()
# ...

# Erzwinge eine Datenbankabfrage für den dritten Aufruf
Site.objects.clear_cache()
current_site = Site.objects.get_current()

Der CurrentSiteManager

Wenn Sites eine wichtige Rolle in deiner Anwendung spielen, sollte du zumindest in Erwägung ziehen, den CurrentSiteManager in deinen Modellen zu verwenden. Hierbei handelt es sich um einen Modell-Manager, der alle Anfragen automatisch hinsichtlich Objekten der aktuellen Seite filtert.

Du kannst den CurrentSiteManager verwenden, indem du ihn explizit zu deinem Modell hinzufügst. Zum Beispiel:

from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager

class Photo(models.Model):
    photo = models.FileField(upload_to='/home/photos')
    photographer_name = models.CharField(max_length=100)
    pub_date = models.DateField()
    site = models.ForeignKey(Site)
    objects = models.Manager()
    on_site = CurrentSiteManager()

Bei diesem Modell gibt Photo.objects.all() alle Fotos in der Datenbank zurück, während Photo.on_site.all() nur Objekte für die aktuelle Seite (bestimmt durch die SITE_ID-Einstellung) zurückgibt.

Anders gesagt, die folgenden zwei Zeilen sind äquivalent:

Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()

Woher weiß der CurrentSiteManager, welches Feld des Photo-Modells die Site referenziert? Standardmäßig sucht er nach einem Feld mit dem Namen site. Wenn dein Modell ein anders ForeignKey- oder ManyToMany-Feld als Referenz verwendet, musst du seinen Namen explizit als Parameter für den CurrentSiteManager angeben. Das folgende Modell, das ein Feld mit dem Namen publish_on aufweist, soll dies demonstrieren:

from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager

class Photo(models.Model):
    photo = models.FileField(upload_to='/home/photos')
    photographer_name = models.CharField(max_length=100)
    pub_date = models.DateField()
    publish_on = models.ForeignKey(Site)
    objects = models.Manager()
    on_site = CurrentSiteManager('publish_on')

Wenn du versuchst, den CurrentSiteManager zu verwenden und einen Feldnamen für die Site-Referenz angibst, der nicht existiert, wird Django einen ValueError werfen.

Zu guter Letzt, solltest du in der Regel einen “normalen” Manager in deinem Modell belassen, selbst wenn du den CurrentSiteManager verwendest. Wie in der Manager-Dokumentation beschrieben, erstellt Django automatisch keinen objects = models.Manager()-Manager für dich, wenn du einen Manager händisch angegeben hast. Ausserdem bedenke, dass gewisse Teile von Django — konkret der Administrationsbereich und die generischen Views — denjenigen Manager verwenden, der als erstes im Modell definiert wird. Wenn du folglich in deinem Administrationbereich Zugriff auf alle Objekte (also nicht nur diejenigen der aktuellen Seite) haben willst, füge objects = models.Manager() in dein Modell ein und zwar bevor du den CurrentSiteManager definierst.

Wie Django das Site-Framework verwendet

Obwohl du das Sites-Framework nicht unbedingt verwenden musst, ist es doch sehr empfehlenswert, da Django es intern an mehreren Stellen verwendet. Selbst wenn dein Django-Projekt lediglich für eine einzige Seite verwendet wird, solltest du dir 2 Sekunden Zeit nehmen, um das Site-Objekt mit deiner domain und dem name deiner Seite zu erstellen und die SITE_ID-Einstellung auf die ID dieses Objektes zu setzen.

So verwendet nun Django das Sites-Framework:

  • Im Redirects-Framework ist jedes Redirect-Objekt mit einer bestimmten Seite assoziiert. Wenn Django nach einem Redirect sucht, tut es dies abhängig von der aktuellen SITE_ID.
  • Im Comments-Framework ist jedes Kommentar mit einer bestimmten Seite assoziiert. Wenn ein neues Kommentar erstellt wird, wird sein site-Feld auf die aktuelle SITE_ID gesetzt und wenn Kommentare mit dem entsprechenden Template-Tag aufgelistet werden sollen, scheinen nur diejenigen der aktuellen Seite auf.
  • Im Flatpages-Framework ist jede Flatpage mit einer bestimmten Seite assoziiert. Beim Erstellen einer Flatpage gibst du die Seite an. Diese wird dann durch die FlatpageFallbackMiddleware mit der aktuellen SITE_ID verglichen, um die entsprechenden Flatpages für die Anzeige abzufragen.
  • Im Syndication-Framework haben die Templates für title und description automatisch Zugriff zur {{ site }}-Variable, welche das aktuell gültige Site-Objekt darstellt. Das domain-Feld des aktuellen Site-Objektes wird zudem im Hook zur Bereitstellung von Item-URLs verwendet, falls du keine vollständige Domain angegeben hast.
  • Im Authentifizierung-Framework stellt der django.contrib.auth.views.login-View den Namen der aktuellen Site in der {{ site_name }}-Variable zur Verfügung.
  • Der Shortcut-View (django.views.defaults.shortcut) verwendet die Domain des aktuellen Site-Objektes zur Berechnung der URL eines Objektes.
  • Auf der Administrationsoberfläche nutzt der ‘’view on site’‘-Link das aktuelle Site-Objekt, um die Domain der Seite zu bestimmen, auf die der Betrachter weitergeleitet werden soll.

RequestSite-Objekte

Einige django.contrib-Anwendungen nutzen das Sites-Framework, verfügen jedoch über eine Architektur, durch die dieses Framework nicht unbedingt auch in der Datenbank installiert sein muss. (Einige Leute wollen oder sind nicht in der Lage, die für das Sites-Framework benötigten zusätzlichen Datenbanktabellen anzulegen.) Um auch diese Fälle abzudecken, bietet das Framework die RequestSite-Klasse, welche als Fallback genutzt werden kann, sollte das Datenbank-basierte Backend für das Sites-Framework nicht verfügbar sein.

Das RequestSite-Objekt hat weitestgehend dasselbe Interface wie normale Site-Objekte, jedoch akzeptiert die __init__()-Methode ein HttpRequest-Objekt als Parameter, aus dem sie die domain- und name-Felder ableiten kann. Um dasselbe Interface wie das Site-Objekt aufzuweisen, verfügt RequestSite-Objekt auch über eine save()- und delete()-Methode, welche jedoch einen NotImplementedError werfen.