L’infrastructure des types de contenus (contenttypes)¶
Django inclut une application contenttypes
qui permet de recenser l’ensemble des modèles installés dans un projet Django, fournissant une interface générique de haut niveau pour travailler avec les modèles.
Aperçu¶
Le cœur de l’application contenttypes est le modèle ContentType
, définir dans django.contrib.contenttypes.models.ContentType
. Les instances de ContentType
représentent et stockent des informations sur les modèles installés dans un projet ; de nouvelles instances de ContentType
sont automatiquement créées à chaque fois que de nouveaux modèles sont installés.
Les instances de ContentType
ont des méthodes pour renvoyer les classes de modèle qu’elles représentent et pour interroger les objets de ces modèles. ContentType
dispose également d’un gestionnaire personnalisé qui ajoute des méthodes pour travailler avec ContentType
et pour obtenir des instances de ContentType
pour un modèle particulier.
Les relations entre vos modèles et ContentType
peuvent également être utilisées pour établir des relations « génériques » entre une instance d’un de vos modèles et des instances de n’importe quel autre modèle que vous avez installé.
Installation du système contenttypes¶
Le système contenttypes est inclus par défaut dans la liste INSTALLED_APPS
créée par django-admin startproject
, mais si vous l’avez supprimé ou si vous définissez manuellement votre liste INSTALLED_APPS
, vous pouvez l’activer en ajoutant 'django.contrib.contenttypes'
à votre réglage INSTALLED_APPS
.
C’est généralement une bonne idée d’avoir l’application contenttypes installée ; plusieurs autres applications fournies avec Django ont en besoin :
- L’application d’administration l’utilise pour tracer l’historique de chaque objet ajouté ou modifié via l’interface d’administration.
- Le
système d'authentification
de Django l’utilise pour associer les permissions des utilisateurs à des modèles spécifiques.
Le modèle ContentType
¶
-
class
ContentType
¶ Chaque instance de
ContentType
a deux champs qui, pris ensemble, décrivent de manière unique un modèle installé :-
app_label
¶ Le nom de l’application dont le modèle fait partie. Il provient de l’attribut
app_label
du modèle et ne comprend que la dernière partie du chemin d’importation Python de l’application ; par exemple, l’attributapp_label
dedjango.contrib.contenttypes
devientcontenttypes
.
-
model
¶ Le nom de classe du modèle.
De plus, la propriété suivante est disponible :
-
name
¶ Le nom lisible du type de contenu. Il provient de l’attribut
verbose_name
du modèle.
-
Examinons un exemple pour voir comment cela fonctionne. Si l’application contenttypes
est déjà installée, que vous ajoutez ensuite l’application sites
à votre réglage INSTALLED_APPS
et que vous exécutez manage.py migrate
pour l’installer, le modèle django.contrib.sites.models.Site
sera installé dans votre base de données. Dans le même temps, une nouvelle instance de ContentType
sera créée avec les valeurs suivantes :
Méthodes des instances ContentType
¶
Chaque instance ContentType
possède des méthodes qui permettent de passer d’une instance ContentType
au modèle qu’elle représente, ou de récupérer des objets à partir de ce modèle :
-
ContentType.
get_object_for_this_type
(**kwargs)¶ Accepte un ensemble de paramètres de recherche valides pour le modèle que
ContentType
représente, et réaliseune recherche get()
sur ce modèle, renvoyant l’objet correspondant.
-
ContentType.
model_class
()¶ Renvoie la classe du modèle représenté par cette instance
ContentType
.
Par exemple, nous pourrions chercher le ContentType
du modèle User
:
>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label='auth', model='user')
>>> user_type
<ContentType: user>
Puis l’utiliser pour rechercher un User
particulier ou pour accéder à la classe du modèle User
:
>>> user_type.model_class()
<class 'django.contrib.auth.models.User'>
>>> user_type.get_object_for_this_type(username='Guido')
<User: Guido>
Ensemble, get_object_for_this_type()
et model_class()
permettent deux cas d’utilisation extrêmement importants :
- En utilisant ces méthodes, vous pouvez écrire du code générique de haut niveau qui effectue des requêtes sur n’importe quel modèle installé ; au lieu d’importer et d’utiliser une unique classe de modèle spécifique, vous pouvez passer des variables
app_label
etmodel
pour chercher un objetContentType
au moment de l’exécution, et travailler ensuite avec la classe de modèle ou récupérer des objets avec. - Vous pouvez associer un autre modèle à un objet
ContentType
afin de lier ses instances à certaines classes de modèle, et utiliser ces méthodes pour obtenir l’accès à ces classes de modèle.
Plusieurs des applications intégrées à Django font usage de cette dernière technique. Par exemple, le système de permissions
dans le système d’authentification de Django utilise un modèle Permission
avec une clé étrangère vers ContentType
; cela permet à Permission
de représenter des concepts tels que « peut ajouter une entrée de blog » ou « peut supprimer une actualité ».
Le gestionnaire ContentTypeManager
¶
-
class
ContentTypeManager
¶ ContentType
possède aussi un gestionnaire personnalisé,ContentTypeManager
, qui ajoute les méthodes suivantes :-
clear_cache
()¶ Efface un cache interne utilisé par
ContentType
pour garder une trace des modèles pour lesquels il a créé des instancesContentType
. Vous n’aurez probablement jamais besoin d’appeler cette méthode de vous-même ; Django l’appelle automatiquement lorsque c’est nécessaire.
-
get_for_id
(id)¶ Recherche un
ContentType
par son identifiant. Comme cette méthode utilise le même cache partagé queget_for_model()
, il est préférable d’utiliser cette méthode à l’habituelContentType.objects.get (pk=id)
.
-
get_for_model
(model, for_concrete_model=True)¶ Accepte soit une classe de modèle, soit une instance de modèle, et renvoie l’instance
ContentType
représentant ce modèle.for_concrete_model=False
permet de récupérer l’instanceContentType
d’un modèle mandataire.
-
get_for_models
(*models, for_concrete_models=True)¶ Accepte un nombre variable de classes de modèle et renvoie un dictionnaire associant les classes de modèle aux instances
ContentType
qui les représentent.for_concrete_model=False
permet de récupérer les instancesContentType
de modèles mandataires.
-
get_by_natural_key
(app_label, model)¶ Renvoie l’instance
ContentType
identifiée de façon unique par l’étiquette d’application et le nom du modèle donnés. L’objectif principal de cette méthode est de permettre aux objetsContentType
d’être référencés par une clé naturelle pendant la désérialisation.
-
La méthode get_for_model()
est particulièrement utile lorsque vous savez que vous devez travailler avec un ContentType
mais que vous ne voulez pas vous soucier d’obtenir les métadonnées du modèle pour effectuer une recherche manuelle :
>>> from django.contrib.auth.models import User
>>> ContentType.objects.get_for_model(User)
<ContentType: user>
Relations génériques¶
L’ajout d’une clé étrangère depuis l’un de vos propres modèles vers un ContentType
permet au modèle de se lier efficacement à une autre classe de modèle, comme dans l’exemple du modèle Permission
ci-dessus. Mais il est possible d’aller encore plus loin et d’utiliser ContentType
pour permettre de véritables relations génériques (parfois appelées « polymorphes ») entre les modèles.
Un exemple simple est un système d’étiquetage, qui pourrait ressembler à ceci :
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.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.tag
Une clé ForeignKey
normale ne peut « pointer » que vers un seul autre modèle, ce qui signifie que si le modèle TaggedItem
utilisait une clé ForeignKey
, il devrait choisir un et un seul modèle pour stocker les étiquettes associées. L’application contenttypes fournit un type de champ spécial (GenericForeignKey
) qui résout cette problématique et permet que la relation se fasse avec n’importe quel modèle :
-
class
GenericForeignKey
¶ Il y a trois étapes à suivre pour mettre en place une
GenericForeignKey
:- Ajoutez à votre modèle une clé
ForeignKey
versContentType
. Le nom usuel de ce champ est « content_type ». - Ajoutez à votre modèle un champ qui peut stocker des valeurs de clé primaire provenant des modèles qui feront l’objet du lien. Pour la plupart des modèles, cela correspond à un champ
PositiveIntegerField
. Le nom usuel de ce champ est « object_id ». - Ajoutez à votre modèle une clé
GenericForeignKey
en lui passant les noms des deux champs décrits ci-dessus. Si ces champs sont nommés « content_type » et « object_id », vous pouvez les omettre, car ce sont les noms de champs par défaut queGenericForeignKey
va chercher.
-
for_concrete_model
¶ Si cet attribut vaut
False
, le champ sera en mesure de référencer des modèles mandataires. La valeur par défaut estTrue
. Cela fait écho au paramètrefor_concrete_model
deget_for_model()
.
- Ajoutez à votre modèle une clé
Compatibilité de type de clé primaire
Le champ object_id
n’a pas besoin d’être du même type que les champs de clé primaire des modèles connexes, mais leurs valeurs de clé primaire doivent pouvoir être transformées dans le même type que le champ object_id
par leur méthode get_db_prep_value()
.
Par exemple, si vous souhaitez autoriser les relations génériques de modèles avec des champs de clé primaire IntegerField
ou CharField
, vous pouvez utiliser CharField
comme type de champ object_id
du modèle puisque les nombres entiers peuvent être forcés vers des chaînes de caractères par get_db_prep_value()
.
Pour une flexibilité maximale, vous pouvez utiliser un champ TextField
qui n’a pas de longueur maximale définie, mais cela pourrait entraîner des pénalités de performances significatives en fonction du moteur de base de données.
Il n’y a pas de solution passe-partout concernant le meilleur type de champ. Vous devez évaluer les modèles que vous prévoyez de lier et déterminer quelle solution sera la plus efficace pour votre cas d’utilisation.
Sérialisation des références aux objets ContentType
Si vous sérialisez des données (par exemple, lors de la génération de fixtures
) à partir d’un modèle qui met en œuvre les relations génériques, vous devriez probablement utiliser une clé naturelle pour identifier de manière unique les objets ContentType
associés. Voir clés naturelles et dumpdata --natural-foreign
pour plus d’informations.
Cela activera une API similaire à celle utilisée pour une clé ForeignKey
normale ; chaque TaggedItem
aura un champ content_object
qui renvoie l’objet auquel il est lié, et vous pouvez également attribuer une valeur à ce champ ou l’utiliser lors de la création d’un 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>
Si l’objet lié est supprimé, les champs content_type
et object_id
conservent leur valeur originale et le champ GenericForeignKey
renvoie None
:
>>> guido.delete()
>>> t.content_object # returns None
En raison de la façon dont GenericForeignKey
est implémentée, vous ne pouvez pas utiliser ces champs directement avec les filtres (filter()
et exclude()
, par exemple) par l’intermédiaire de l’API de base de données. Comme une clé GenericForeignKey
n’est pas un objet de champ normal, ces exemples ne fonctionneront pas :
# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
De même, les clés GenericForeignKey
n’apparaissent pas dans les formulaires ModelForm
.
Relations génériques inverses¶
-
class
GenericRelation
¶ La relation de l’objet lié vers cet objet n’existe pas par défaut. En définissant
related_query_name
, la relation est créée de l’objet lié vers celui-ci. Cela permet d’interroger et de filtrer à partir de l’objet lié.
Si vous savez quels seront les modèles que vous utiliserez le plus souvent, vous pouvez également ajouter une relation générique « inverse » pour activer une API supplémentaire. Par exemple :
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
class Bookmark(models.Model):
url = models.URLField()
tags = GenericRelation(TaggedItem)
Les instances Bookmark
auront chacune un attribut tags
, qui peut être utilisé pour récupérer leurs objets TaggedItems
associés :
>>> 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>]>
En définissant l’attribut related_query_name
de GenericRelation
, cela permet d’interroger à partir de l’objet lié :
tags = GenericRelation(TaggedItem, related_query_name='bookmarks')
Cela permet de filtrer, trier ou d’autres opérations d’interrogation sur Bookmark
à partir de TaggedItem
:
>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmarks__url__contains='django')
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
Bien sûr, si vous n’ajoutez pas la relation inverse, vous pouvez faire les mêmes types de recherches manuellement :
>>> b = Bookmark.objects.get(url='https://www.djangoproject.com/')
>>> bookmark_type = ContentType.objects.get_for_model(b)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id=b.id)
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
Tout comme GenericForeignKey
accepte les noms des champs type de contenu et identifiant d’object en paramètres, il en va de même pour GenericRelation
; si le modèle qui a la clé étrangère générique n’utilise pas des noms par défaut pour ces champs, vous devez lui passer les noms des champs lors de la création d’une relation GenericRelation
. Par exemple, si le modèle TaggedItem
mentionné ci-dessus utilisait des champs nommés content_type_fk
et object_primary_key
pour créer sa clé étrangère générique, alors une relation GenericRelation
inverse devrait être définie comme suit :
tags = GenericRelation(
TaggedItem,
content_type_field='content_type_fk',
object_id_field='object_primary_key',
)
Notez également que si vous supprimez un objet qui possède une relation GenericRelation
, tous les objets qui ont une clé GenericForeignKey
pointant vers lui seront aussi supprimés. Dans l’exemple ci-dessus, cela signifie que si un objet Bookmark
a été supprimé, tous les objets TaggedItem
pointant dessus seront supprimés en même temps.
Contrairement à ForeignKey
, GenericForeignKey
n’accepte pas de paramètre on_delete
pour personnaliser ce comportement ; si vous le souhaitez, vous pouvez éviter la cascade de suppression en évitant simplement de définir une relation GenericRelation
; un comportement différent peut être fourni par le signal pre_delete
.
Relations génériques et agrégation¶
L”API d’agrégation de base de données de Django fonctionne avec les relations GenericRelation
. Par exemple, vous pouvez savoir combien de « tags » sont définis pour tous les signets :
>>> Bookmark.objects.aggregate(Count('tags'))
{'tags__count': 3}
Relations génériques dans les formulaires¶
Le module django.contrib.contenttypes.forms
fournit :
BaseGenericInlineFormSet
- Une fabrique de groupe de formulaires,
generic_inlineformset_factory()
, pour être utilisée avecGenericForeignKey
.
-
class
BaseGenericInlineFormSet
¶
-
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)¶ Renvoie un
GenericInlineFormSet
à l’aide demodelformset_factory()
.Vous devez fournir
ct_field
etfk_field
s’ils sont différents des valeurs par défaut, respectivementcontent_type
etobject_id
. Les autres paramètres sont similaires à ceux documentés dansmodelformset_factory()
etinlineformset_factory()
.Le paramètre
for_concrete_model
correspond au paramètrefor_concrete_model
deGenericForeignKey
.
Relations génériques dans l’interface d’administration¶
Le module django.contrib.contenttypes.admin
fournit GenericTabularInline
et GenericStackedInline
(sous-classes de GenericInlineModelAdmin
)
Ces classes et fonctions permettent l’utilisation de relations génériques dans les formulaires et l’interface d’administration. Voir la documentation sur les formulaires groupés de modèle et l’interface d’administration pour plus d’informations.
-
class
GenericInlineModelAdmin
¶ La classe
GenericInlineModelAdmin
hérite de toutes les propriétés d’une classeInlineModelAdmin
. Cependant, elle en ajoute quelques autres de son cru pour travailler avec la relation générique :-
ct_field
¶ Le nom du champ de clé étrangère
ContentType
sur le modèle. Par défaut,content_type
.
-
ct_fk_field
¶ Le nom du champ nombre entier qui représente l’identifiant de l’objet associé. Par défaut,
object_id
.
-
-
class
GenericTabularInline
¶
-
class
GenericStackedInline
¶ Les sous-classes de
GenericInlineModelAdmin
avec mises en page empilées et tabulaires, respectivement.