Ramverket för innehållstyper¶
Django innehåller en contenttypes
-applikation som kan spåra alla modeller som är installerade i ditt Django-drivna projekt, vilket ger ett generiskt gränssnitt på hög nivå för att arbeta med dina modeller.
Översikt¶
Kärnan i contenttypes-applikationen är modellen ContentType
, som finns i django.contrib.contenttypes.models.ContentType
. Instanser av ContentType
representerar och lagrar information om de modeller som är installerade i ditt projekt, och nya instanser av ContentType
skapas automatiskt när nya modeller installeras.
Instanser av ContentType
har metoder för att returnera de modellklasser de representerar och för att fråga objekt från dessa modeller. ContentType
har också en :ref:``custom manager <custom-managers>` som lägger till metoder för att arbeta med ContentType
och för att erhålla instanser av ContentType
för en viss modell.
Relationer mellan dina modeller och ContentType
kan också användas för att aktivera ”generiska” relationer mellan en instans av en av dina modeller och instanser av alla modeller du har installerat.
Installera ramverket för contenttypes¶
Contenttypes-ramverket ingår i standard INSTALLED_APPS
-listan som skapats av django-admin startproject
, men om du har tagit bort det eller om du manuellt ställer in din INSTALLED_APPS
-lista kan du aktivera det genom att lägga till 'django.contrib.contenttypes'
till din INSTALLED_APPS
-inställning.
Det är i allmänhet en bra idé att ha contenttypes-ramverket installerat; flera av Djangos andra medföljande applikationer kräver det:
Admin-programmet använder den för att logga historiken för varje objekt som läggs till eller ändras via admin-gränssnittet.
Djangos
autentiseringsramverk
använder det för att knyta användarrättigheter till specifika modeller.
Modellen ContentType
(innehållstyp)¶
- class ContentType[source]¶
Varje instans av
ContentType
har två fält som tillsammans ger en unik beskrivning av en installerad modell:- app_label¶
Namnet på den applikation som modellen är en del av. Detta tas från
app_label
-attributet för modellen och inkluderar endast den sista delen av applikationens Python-importväg;django.contrib.contenttypes
, till exempel, blir enapp_label
avcontenttypes
.
- model¶
Namnet på modellklassen.
Dessutom är följande egendom tillgänglig:
- name[source]¶
Det mänskliga läsbara namnet på innehållstypen. Detta hämtas från attributet
verbose_name
i modellen.
Låt oss titta på ett exempel för att se hur detta fungerar. Om du redan har contenttypes
-programmet installerat, och sedan lägger till the sites application
till din INSTALLED_APPS
-inställning och kör manage.py migrate
för att installera det, kommer modellen django.contrib.sites.models.Site
att installeras i din databas. Tillsammans med det kommer en ny instans av ContentType
att skapas med följande värden:
Metoder för instanser av ContentType
¶
Varje ContentType
-instans har metoder som gör att du kan komma från en ContentType
-instans till den modell den representerar, eller för att hämta objekt från den modellen:
- ContentType.get_object_for_this_type(using=None, **kwargs)[source]¶
Tar en uppsättning giltiga uppslagsargument för den modell som
ContentType
representerar, och gören get()-uppslagning
på den modellen och returnerar motsvarande objekt. Argumentetusing
kan användas för att ange en annan databas än standarddatabasen.Changed in Django 5.1:Argumentet
using
lades till.
- ContentType.model_class()[source]¶
Returnerar den modellklass som representeras av denna
ContentType
-instans.
Vi kan till exempel leta upp ContentType
för modellen User
:
>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label="auth", model="user")
>>> user_type
<ContentType: user>
Och sedan använda den för att fråga efter en viss User
, eller för att få tillgång till modellklassen User
:
>>> user_type.model_class()
<class 'django.contrib.auth.models.User'>
>>> user_type.get_object_for_this_type(username="Guido")
<User: Guido>
Tillsammans möjliggör get_object_for_this_type()
och model_class()
två extremt viktiga användningsfall:
Med hjälp av dessa metoder kan du skriva generisk kod på hög nivå som utför frågor på alla installerade modeller - istället för att importera och använda en enda specifik modellklass kan du skicka en
app_label
ochmodel
till enContentType
-uppslagning vid körning och sedan arbeta med modellklassen eller hämta objekt från den.Du kan relatera en annan modell till
ContentType
som ett sätt att binda instanser av den till särskilda modellklasser, och använda dessa metoder för att få åtkomst till dessa modellklasser.
Flera av Djangos medföljande applikationer använder sig av den senare tekniken. Till exempel: använder behörighetssystemet
i Djangos autentiseringsramverk en Permission
-modell med en främmande nyckel till ContentType
; detta låter Permission
representera koncept som ”kan lägga till blogginlägg” eller ”kan ta bort nyhetsartikel”.
Funktionen ”ContentTypeManager¶
- class ContentTypeManager[source]¶
ContentType
har också en anpassad manager,ContentTypeManager
, som lägger till följande metoder:- clear_cache()[source]¶
Rensar en intern cache som används av
ContentType
för att hålla reda på modeller för vilka den har skapatContentType
-instanser. Du kommer förmodligen aldrig att behöva anropa den här metoden själv; Django kommer att anropa den automatiskt när den behövs.
- get_for_id(id)[source]¶
Leta upp en :klass:`~django.contrib.contenttypes.models.ContentType` efter ID. Eftersom denna metod använder samma delade cache som
get_for_model()
, är det att föredra att använda denna metod framför den vanligaContentType.objects.get(pk=id)
- get_for_model(model, for_concrete_model=True)[source]¶
Tar antingen en modellklass eller en instans av en modell och returnerar
ContentType
-instansen som representerar den modellen.for_concrete_model=False
tillåter hämtning avContentType
för en proxymodell.
- get_for_models(*models, for_concrete_models=True)[source]¶
Tar ett variabelt antal modellklasser och returnerar en ordbok som mappar modellklasserna till
ContentType
-instanser som representerar dem.for_concrete_models=False
tillåter hämtning avContentType
för proxymodeller.
- get_by_natural_key(app_label, model)[source]¶
Returnerar
ContentType
-instansen som identifieras unikt av den angivna applikationsetiketten och modellnamnet. Det primära syftet med denna metod är att tillåta attContentType
-objekt refereras via en :ref:naturlig nyckel<topics-serialization-natural-keys>
under deserialisering.
Metoden get_for_model()
är särskilt användbar när du vet att du behöver arbeta med en ContentType
men inte vill göra dig besväret att hämta modellens metadata för att utföra en manuell sökning:
>>> from django.contrib.auth.models import User
>>> ContentType.objects.get_for_model(User)
<ContentType: user>
Generiska relationer¶
Genom att lägga till en främmande nyckel från en av dina egna modeller till ContentType
kan din modell effektivt binda sig till en annan modellklass, som i exemplet med modellen Permission
ovan. Men det är möjligt att gå ett steg längre och använda ContentType
för att möjliggöra verkligt generiska (ibland kallade ”polymorfa”) relationer mellan modeller.
Det kan till exempel användas för ett taggningssystem på följande sätt:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveBigIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
def __str__(self):
return self.tag
class Meta:
indexes = [
models.Index(fields=["content_type", "object_id"]),
]
En normal ForeignKey
kan bara ”peka på” en annan modell, vilket innebär att om modellen TaggedItem
använde en ForeignKey
skulle den behöva välja en och endast en modell att lagra taggar för. Contenttypes-applikationen tillhandahåller en speciell fälttyp (GenericForeignKey
) som kringgår detta och gör att relationen kan vara med vilken modell som helst:
- class GenericForeignKey[source]¶
Det finns tre delar för att konfigurera en
GenericForeignKey
:Ge din modell en
ForeignKey
tillContentType
. Det vanliga namnet för detta fält är ”content_type”.Ge din modell ett fält som kan lagra primära nyckelvärden från de modeller du kommer att relatera till. För de flesta modeller innebär detta en
PositiveBigIntegerField
. Det vanliga namnet för detta fält är ”object_id”.Ge din modell en
GenericForeignKey
, och skicka den namnen på de två fälten som beskrivs ovan. Om dessa fält heter ”content_type” och ”object_id” kan du utelämna detta - det är standardfältens namn somGenericForeignKey
kommer att leta efter.
Till skillnad från
ForeignKey
, skapas inte automatiskt ett databasindex förGenericForeignKey
, så det rekommenderas att du använderMeta.indexes
för att lägga till ditt eget index med flera kolumner. Detta beteende kan komma att ändras i framtiden.- for_concrete_model¶
Om
False
, kommer fältet att kunna referera till proxy-modeller. Standard ärTrue
. Detta speglar argumentetfor_concrete_model
tillget_for_model()
.
Kompatibilitet med primärnyckeltyp
Fältet ”object_id” behöver inte vara av samma typ som primärnyckelfälten i de relaterade modellerna, men deras primärnyckelvärden måste kunna kopplas till samma typ som fältet ”object_id” genom dess get_db_prep_value()
-metod.
Om du t.ex. vill tillåta generiska relationer till modeller med antingen IntegerField
eller CharField
primärnyckelfält, kan du använda CharField
för fältet ”object_id” i din modell eftersom heltal kan omvandlas till strängar med get_db_prep_value()
.
För maximal flexibilitet kan du använda en TextField
som inte har en maximal längd definierad, men detta kan medföra betydande prestandaförluster beroende på din databasbackend.
Det finns ingen universallösning för vilken fälttyp som är bäst. Du bör utvärdera de modeller som du förväntar dig att peka på och avgöra vilken lösning som är mest effektiv för ditt användningsfall.
Serialisering av referenser till ContentType
-objekt
Om du serialiserar data (t.ex. när du genererar fixtures
) från en modell som implementerar generiska relationer, bör du förmodligen använda en naturlig nyckel för att unikt identifiera relaterade ContentType
-objekt. Se natural keys och dumpdata --natural-foreign
för mer information.
Detta kommer att möjliggöra ett API som liknar det som används för en vanlig ForeignKey
; varje TaggedItem
kommer att ha ett content_object
-fält som returnerar det objekt som det är relaterat till, och du kan också tilldela det fältet eller använda det när du skapar en TaggedItem
:
>>> from django.contrib.auth.models import User
>>> guido = User.objects.get(username="Guido")
>>> t = TaggedItem(content_object=guido, tag="bdfl")
>>> t.save()
>>> t.content_object
<User: Guido>
Om det relaterade objektet tas bort, förblir fälten content_type
och object_id
inställda på sina ursprungliga värden och GenericForeignKey
returnerar None
:
>>> guido.delete()
>>> t.content_object # returns None
På grund av hur GenericForeignKey
implementeras kan du inte använda sådana fält direkt med filter (filter()
och exclude()
, till exempel) via databas-API:et. Eftersom en GenericForeignKey
inte är ett normalt fältobjekt, kommer dessa exempel inte att fungera:
# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
På samma sätt visas inte GenericForeignKey
s i ModelForm
s.
Omvända generiska relationer¶
- class GenericRelation[source]¶
Relationen från det relaterade objektet tillbaka till detta objekt existerar inte som standard. Om du ställer in
related_query_name
skapas en relation från det relaterade objektet tillbaka till detta. Detta gör det möjligt att ställa frågor och filtrera från det relaterade objektet.
Om du vet vilka modeller du kommer att använda oftast kan du också lägga till en ”omvänd” generisk relation för att aktivera ytterligare ett API. Till exempel:
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
class Bookmark(models.Model):
url = models.URLField()
tags = GenericRelation(TaggedItem)
”Bokmärkes”-instanser har alla ett ”tags”-attribut, som kan användas för att hämta deras associerade ”tagged items”:
>>> b = Bookmark(url="https://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()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
Du kan också använda add()
, create()
eller set()
för att skapa relationer:
>>> t3 = TaggedItem(tag="Web development")
>>> b.tags.add(t3, bulk=False)
>>> b.tags.create(tag="Web framework")
<TaggedItem: Web framework>
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>, <TaggedItem: Web development>, <TaggedItem: Web framework>]>
>>> b.tags.set([t1, t3])
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: Web development>]>
Anropet remove()
raderar de angivna modellobjekten i bulk:
>>> b.tags.remove(t3)
>>> b.tags.all()
<QuerySet [<TaggedItem: django>]>
>>> TaggedItem.objects.all()
<QuerySet [<TaggedItem: django>]>
Metoden clear()
kan användas för att massradera alla relaterade objekt för en instans:
>>> b.tags.clear()
>>> b.tags.all()
<QuerySet []>
>>> TaggedItem.objects.all()
<QuerySet []>
Genom att definiera GenericRelation
med related_query_name
kan man ställa frågor från det relaterade objektet:
tags = GenericRelation(TaggedItem, related_query_name="bookmark")
Detta möjliggör filtrering, ordning och andra frågeoperationer på Bookmark
från TaggedItem
:
>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmark__url__contains="django")
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
Om du inte lägger till related_query_name
kan du göra samma typer av uppslagningar manuellt:
>>> bookmarks = Bookmark.objects.filter(url__contains="django")
>>> bookmark_type = ContentType.objects.get_for_model(Bookmark)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks)
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
Precis som GenericForeignKey
accepterar namnen på fälten content-type och object-ID som argument, så gör även GenericRelation
det; om modellen som har den generiska främmande nyckeln använder icke-standardnamn för dessa fält, måste du skicka namnen på fälten när du ställer in en GenericRelation
till den. Om t.ex. modellen TaggedItem
som nämns ovan använder fält med namnen content_type_fk
och object_primary_key
för att skapa sin generiska främmande nyckel, måste en GenericRelation
tillbaka till den definieras på följande sätt:
tags = GenericRelation(
TaggedItem,
content_type_field="content_type_fk",
object_id_field="object_primary_key",
)
Observera också att om du tar bort ett objekt som har en GenericRelation
, kommer alla objekt som har en GenericForeignKey
som pekar på det också att tas bort. I exemplet ovan innebär detta att om ett Bookmark
-objekt raderas, kommer alla TaggedItem
-objekt som pekar på det att raderas samtidigt.
Till skillnad från ForeignKey
accepterar GenericForeignKey
inte ett on_delete
-argument för att anpassa detta beteende; om så önskas kan du undvika kaskadradering genom att inte använda GenericRelation
, och alternativt beteende kan tillhandahållas via pre_delete
-signalen.
Generiska relationer och aggregering¶
Djangos API för databasaggregering fungerar med en GenericRelation
. Du kan till exempel ta reda på hur många taggar alla bokmärken har:
>>> Bookmark.objects.aggregate(Count("tags"))
{'tags__count': 3}
Generisk relation i formulär¶
Modulen django.contrib.contenttypes.forms
tillhandahåller:
En formulärfabrik,
generic_inlineformset_factory()
, för användning medGenericForeignKey
.
- generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field='content_type', fk_field='object_id', fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True, min_num=None, validate_min=False, absolute_max=None, can_delete_extra=True)[source]¶
Returnerar en
GenericInlineFormSet
medmodelformset_factory()
.Du måste ange
ct_field
ochfk_field
om de skiljer sig från standardvärdena,content_type
respektiveobject_id
. Andra parametrar liknar de som dokumenteras imodelformset_factory()
ochinlineformset_factory()
.Argumentet
for_concrete_model
motsvarar argumentetfor_concrete_model
påGenericForeignKey
.
Generiska relationer inom administration¶
Modulen django.contrib.contenttypes.admin
tillhandahåller GenericTabularInline
och GenericStackedInline
(underklasser till GenericInlineModelAdmin
)
Dessa klasser och funktioner gör det möjligt att använda generiska relationer i formulär och i admin. Se dokumentationen model formset och admin för mer information.
- class GenericInlineModelAdmin[source]¶
Klassen
GenericInlineModelAdmin
ärver alla egenskaper från klassenInlineModelAdmin
. Den lägger dock till ett par egna för att arbeta med den generiska relationen:- ct_field¶
Namnet på
ContentType
främmande nyckelfält på modellen. Standardvärdet ärcontent_type
.
- ct_fk_field¶
Namnet på det heltalsfält som representerar ID:t för det relaterade objektet. Standardvärdet är
object_id
.
- class GenericStackedInline[source]¶
Underklasser till
GenericInlineModelAdmin
med staplad respektive tabulär layout.
GeneriskPrefetch()
¶
Denna sökning liknar Prefetch()
och den bör endast användas på GenericForeignKey
. Argumentet querysets
accepterar en lista med querysets, var och en för en annan ContentType
. Detta är användbart för GenericForeignKey
med icke-homogena uppsättningar resultat.
>>> from django.contrib.contenttypes.prefetch import GenericPrefetch
>>> bookmark = Bookmark.objects.create(url="https://www.djangoproject.com/")
>>> animal = Animal.objects.create(name="lion", weight=100)
>>> TaggedItem.objects.create(tag="great", content_object=bookmark)
>>> TaggedItem.objects.create(tag="awesome", content_object=animal)
>>> prefetch = GenericPrefetch(
... "content_object", [Bookmark.objects.all(), Animal.objects.only("name")]
... )
>>> TaggedItem.objects.prefetch_related(prefetch).all()
<QuerySet [<TaggedItem: Great>, <TaggedItem: Awesome>]>