Django-de

Django Dokumentation

Das contenttypes-Framework

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

Django beinhaltet eine “contenttypes”-Anwendung, die alle Datenmodelle verfolgen kann, die in deinem Django-Projekt installiert sind und stellt ein höheres, generisches Interface zur Arbeit mit deinen Datenmodellen bereit.

Überblick

Im Herzen der contenttypes-Anwendung ist das Datenmodel ContentType, das sich in django.contrib.contenttypes.model.ContentType befindet. Instanzen von ContentType repräsentieren und speichern Informationen über die im Projekt installierten Datenmodelle. Neue Instanzen von ContentType werden automatisch erstellt, wenn neue Datenmodelle installiert werden.

Instanzen von ContentType haben Methoden zur Rückgabe der Datenmodell-Klassen, die sie repräsentieren, sowie zur Abfrage von Objekten der Datenmodelle. ContentType hat auch einen custom manager, der Methoden hinzufügt, um mit ContentType zu arbeiten und Instanzen von ContentType für ein bestimmtes Datenmodell einzuholen.

Die Beziehungen zwischen deinen Datenmodellen und ContentType können auch benutzt werden, um “generische” Beziehungen zwischen einer Instanz eines deiner Datenmodelle und irgendeinem anderen installierten Datenmodell zu ermöglichen.

Installation des contenttypes-Framework

Das contenttypes-Framework ist schon in der standardmäßigen INSTALLED_APPS-Liste enthalten, die von django-admin.py startproject erstellt wurde. Wenn du es aber entfernt hast oder deine INSTALLED_APPS-Liste manuell erstellen willst, kannst du es aktivieren, indem du 'django.contrib.contenttypes' zu deiner INSTALLED_APPS-Einstellung hinzufügst.

Es ist generell eine gute Idee, das contenttypes-Framework installiert zu haben, da es von vielen der anderen mitgelieferten Anwendungen benötigt wird:

  • Die Admin-Anwendung benutzt es, um die Geschichte von jedem Objekt zu protokollieren, das durch das Admin-Interface hinzugefügt oder geändert wurde.
  • Djangos Authentifizierungs-Framework benutzt es, um die Berechtigungen für Benutzer an spezifische Datenmodelle zu knüpfen.
  • Djangos Kommentar-System (django.comments.comments) benutzt es, um Kommentare an beliebige Datenmodelle “anzuhängen”.

Das ContentType-Datenmodell

Jede Instanz von ContentType hat drei Felder, welche zusammengenommen das installierte Datenmodell eindeutig beschreiben:

app_label
Der Name der Anwendung, von der das Datenmodell Teil ist. Dies wird mit dem app_label-Attribut des Datenmodells erstellt. Es beinhaltet nur den letzten Teil des Python-Importpfades der Anwendung; “django.contrib.contenttypes” wird z. B. ein app_label von “contenttypes”.
model
Der Name der Datenmodell-Klasse.
name
Der lesbare Teil des Datenmodells. Dieser wird mit dem verbose_name-Attribut des Datenmodells erstellt.

Lass uns jetzt ein Beispiel ansehen, um zu verstehen, wie dieses funktioniert. Wenn du die contenttypes-Anwendung installiert hast, die sites-Anwendung in den Einstellungen unter INSTALLED_APPS hinzugefügt hast, sowie manage.py syncdb aufgerufen hast, dann ist das Datenmodell django.contrib.sites.model.Site bereits in deine Datenbank installiert worden. Zusätzlich dazu wurde eine neue Instanz von ContentType mit den folgenden Werten erzeugt:

  • app_label wird auf 'sites' (den letzten Teil des Python-Pfades “django.contrib.sites”) gesetzt.
  • model wird auf 'site' gesetzt.
  • name wird auf 'site' gesetzt.

Methoden von ContentType-Instanzen

Jede Instanz von ContentType hat Methoden, mit der du von einer ContentType-Instanz auf das Datenmodell gelangen kannst, die es repräsentiert, oder um Objekte vom Datenmodell abzurufen:

get_object_for_this_type(**kwargs)
Nimmt einen Satz mit, für das – vom ContentType repräsentierte – Datenmodell zulässigen, Abfrage-Parametern entgegen, und führt eine get()-Abfrage auf das Datenmodell aus, die das entsprechende Objekt zurückgibt.
model_class()
Gibt die Datenmodell-Klasse zurück, die von dieser ContentType-Klasse repräsentiert wird.

So können wir z. B. den ContentType für das User-Datenmodell nachschlagen:

>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label="auth", model="user")
>>> user_type
<ContentType: user>

Danach können wir es für einen bestimmten User benutzen, oder um Zugriff auf die User-Datenmodell-Klasse zu bekommen:

>>> user_type.model_class()
<class 'django.contrib.auth.models.User'>
>>> user_type.get_object_for_this_type(username='Guido')
<User: Guido>

Zusammengenommen ermöglichen get_object_for_this_type und model_class zwei extrem wichtige Anwendungsfälle:

  1. Wenn du diese Methoden benutzt, kannst du höheren, generischen Code schreiben, der Abfragen an ein beliebliges, installiertes Datenmodell schickt. Statt eine einzelne, spezifische Datenmodell-Klasse zu importieren und zu benutzen, kannst du zur Laufzeit ein app_label und model in eine ContentType Suchanfrage leiten. Dann kannst du mit der Datenmodell-Klasse arbeiten oder Objekte daraus beziehen.
  2. Indem du ein anderes Datenmodell mit ContentType verknüpfst, bindest du dessen Instanzen an eine bestimmte Datenmodell-Klasse und kannst diese Methoden benutzen, um Zugang zu den Datenmodell-Klassen zu erhalten.

Viele von Djangos mitgelieferten Anwendungen benutzen die letztere Technik. Das Berechtigungssystem in Djangos Authentifizierungs-Framework benutzt z. B. ein Permission-Datenmodell mit einem Fremdschlüssel auf ContentType. Dieses lässt Permission Konzepte wie “darf einen Blogbeitrag hinzufügen” oder “darf einen Nachrichten-Artikel löschen” repräsentieren.

Der ContentTypeManager

ContentType hat auch einen benutzerdefinierten Manager, den ContentTypeManager, der folgende Methoden hinzufügt:

clear_cache()
Leert einen internen Cache, der von ContentType benutzt wird, um nachzuverfolgen, von welchen Datenmodellen für welche es ContentType-Instanzen erzeugt hat. Du wirst diese Methode wahrscheinlich niemals selber aufrufen müssen. Django ruft sie automatisch auf, wenn es nötig ist.
get_for_model(model)
Nimmt entweder eine Datenmodell-Klasse oder eine Instanz eines Datenmodells entgegen und gibt eine ContentType-Instanz, die das Datenmodell repräsentiert, zurück.

Die Methode get_for_model ist besonders nützlich, wenn du weißt, dass du mit einem ContentType arbeiten musst, aber nicht auf das Problem treffen willst, die Meta-Daten des Datenmodells für eine manuelle Suchanfrage einholen zu müssen:

>>> from django.contrib.auth.models import User
>>> user_type = ContentType.objects.get_for_model(User)
>>> user_type
<ContentType: user>

Generische Verbindungen

Wenn du einen Fremdschlüssel von einem deiner eigenen Datenmodelle zu ContentType hinzufügst, erlaubt dir das, dein Datenmodell effektiv an eine andere Datenmodell-Klasse anzuknüpfen, wie im Beispiel des Permission-Datenmodells oben zu sehen. Aber es ist möglich, noch einen Schritt weiterzugehen und ContentType zu benutzen, um wirklich generische (manchmal “polymorph” genannte) Verbindungen zwischen Datenmodellen zu erhalten.

Ein einfaches Beispiel wäre ein Schlagwortsystem, das so aussehen könnte:

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class TaggedItem(models.Model):
    tag = models.SlugField()
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    def __unicode__(self):
        return self.tag

Ein normaler ForeignKey kann nur auf ein anderes Datenmodell “zeigen”, was bedeutet, dass wenn das TaggedItem-Datenmodell einen ForeignKey benutzen würde, müsste es ein – und zwar nur ein – Datenmodell zur Speicherung der Schlagworte wählen. Die Anwendung contenttypes bietet einen speziellen Feldtyp – django.contrib.contenttypes.generic.GenericForeignKey – an, der dieses umgeht und es zulässt, dass die Verbindung mit jedem Datenmodell funktioniert. Einen GenericForeignKey einzurichten erfordert drei Schritte:

  1. Gib deinem Datenmodell einen ForeignKey auf ContentType.
  2. Gib deinem Datenmodell ein Feld, das einen Primärschlüssel-Wert von dem Datenmodell enthält, auf das du dich beziehst (für die meisten Datenmodelle bedeutet das ein IntegerField oder ein` `PositiveIntegerField).
  3. Gib deinem Datenmodell einen GenericForeignKey und liefere ihm die Namen der zwei oben beschriebenen Felder. Wenn diese Felder “content_type” und “object_id” genannt werden, kannst du dies auslassen, da dies die standardmäßigen Feldnamen sind, nach denen GenericForeignKey suchen wird.

Dieses aktiviert eine API, die ähnlich zu der ist, die für den normalen ForeignKey verwendet wird. Jedes TaggedItem wird ein Feld content_object haben, das das Objekt zurückgibt, auf das es sich bezieht. Du kannst ebenfalls auf das Feld zuordnen oder es benutzen, wenn du ein TaggedItem erstellst:

>>> from django.contrib.models.auth import User
>>> guido = User.objects.get(username='Guido')
>>> t = TaggedItem(content_object=guido, tag='bdfl')
>>> t.save()
>>> t.content_object
<User: Guido>

Umgekehrte Generische Verbindungen

Wenn du weißt, welche Datenmodelle von dir am meisten benutzt werden, kannst auch eine “umgekehrte” generische Verbindung hinzufügen, um eine zusätzliche API zu erhalten. Zum Beispiel:

class Bookmark(models.Model):
    url = models.URLField()
    tags = generic.GenericRelation(TaggedItem)

Bookmark-Instanzen werden dann jeweils ein tags-Attribut haben, das benutzt werden kann, um das zugeordnete TaggedItems abzufragen:

>>> b = Bookmark('http://www.djangoproject.com/')
>>> b.save()
>>> t1 = TaggedItem(content_object=b, tag='django')
>>> t1.save()
>>> t2 = TaggedItem(content_object=b, tag='python')
>>> t2.save()
>>> b.tags.all()
[<TaggedItem: django>, <TaggedItem: python>]

Wenn du keine umgekehrte Verbindung hinzufügst, kannst du die Suchanfrage manuell ausführen:

>>> b = Bookmark.objects.get(url='http://www.djangoproject.com/)
>>> bookmark_type = ContentType.objects.get_for_model(b)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id,
...                           object_id=b.id)
[<TaggedItem: django>, <TaggedItem: python>]

Beachte, dass, wenn du ein Objekt mit einer GenericRelation löschst, auch alle Objekte gelöscht werden, auf die ein GenericForeignKey zeigt. Im Beispiel oben, bedeutet das, wenn ein Bookmark-Objekt gelöscht wird, werden auch alle TaggedItem-Objekte, die darauf zeigen zur gleichen Zeit gelöscht.