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'authentificationde Django l’utilise pour associer les permissions des utilisateurs à des modèles spécifiques.
Le modèle ContentType¶
-
class
ContentType¶ Chaque instance de
ContentTypea 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_labeldu modèle et ne comprend que la dernière partie du chemin d’importation Python de l’application ; par exemple, l’attributapp_labeldedjango.contrib.contenttypesdevientcontenttypes.
-
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_namedu 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
ContentTyperepré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_labeletmodelpour chercher un objetContentTypeau 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
ContentTypeafin 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¶ ContentTypepossède aussi un gestionnaire personnalisé,ContentTypeManager, qui ajoute les méthodes suivantes :-
clear_cache()¶ Efface un cache interne utilisé par
ContentTypepour 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
ContentTypepar 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
ContentTypereprésentant ce modèle.for_concrete_model=Falsepermet de récupérer l’instanceContentTyped’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
ContentTypequi les représentent.for_concrete_model=Falsepermet de récupérer les instancesContentTypede modèles mandataires.
-
get_by_natural_key(app_label, model)¶ Renvoie l’instance
ContentTypeidentifié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 objetsContentTyped’ê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.
Par exemple, on pourrait l’utiliser pour un système d’étiquetage comme 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é
ForeignKeyversContentType. 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é
GenericForeignKeyen 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 queGenericForeignKeyva 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_modeldeget_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>]>
Il est aussi possible d’utiliser add(), create() ou set() pour créer des relations :
>>> 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>]>
L’appel à remove() va supprimer en bloc les objets de modèle ciblés
>>> b.tags.remove(t3)
>>> b.tags.all()
<QuerySet [<TaggedItem: django>]>
>>> TaggedItem.objects.all()
<QuerySet [<TaggedItem: django>]>
La méthode clear() peut être utilisée pour supprimer en bloc tous les objets liés à une instance
>>> b.tags.clear()
>>> b.tags.all()
<QuerySet []>
>>> TaggedItem.objects.all()
<QuerySet []>
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='bookmark')
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(bookmark__url__contains='django')
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
Si vous n’ajoutez pas la relation inverse related_query_name, vous pouvez faire les mêmes types de recherches manuellement :
>>> 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>]>
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 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, absolute_max=None, can_delete_extra=True)¶ Renvoie un
GenericInlineFormSetà l’aide demodelformset_factory().Vous devez fournir
ct_fieldetfk_fields’ils sont différents des valeurs par défaut, respectivementcontent_typeetobject_id. Les autres paramètres sont similaires à ceux documentés dansmodelformset_factory()etinlineformset_factory().Le paramètre
for_concrete_modelcorrespond au paramètrefor_concrete_modeldeGenericForeignKey.Changed in Django 3.2:Les paramètres
absolute_maxetcan_delete_extraont été ajoutés.
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
GenericInlineModelAdminhé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
ContentTypesur 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
GenericInlineModelAdminavec mises en page empilées et tabulaires, respectivement.