Querysets et managers
L’ORM de Django (et donc, chacune des classes qui composent votre modèle) propose par défaut deux objets hyper importants:
- Les
managers
, qui consistent en un point d’entrée pour accéder à la persistance des données, - Les
querysets
, qui permettent de filtrer des ensembles ou sous-ensemble d’objets. Les querysets peuvent s’imbriquer, pour ajouter d’autres filtres à des filtres existants, et fonctionnent comme un super jeu d’abstraction pour accéder à nos données (persistentes).
Ces deux propriétés vont de paire; par défaut, chaque classe de votre modèle, de part sa dépendance avec la classe models.Model
, propose un attribut objects
, qui correspond au manager, à partir duquel nous pourrons déclarer des filtres ou opérations à exécuter sur la base de données..
Managers
Comme nous le verrons à la section Services, les managers
constituent une partie de la couche de “services” de notre application.
Ces services peuvent se trouver à trois endroits :
. Dans les modèles ou au niveau des managers . Dans les forms ou les sérializeurs . Dans une couche de services séparée du reste.
Chaque définition de modèle utilise un Manager
, afin d’accéder à la base de données et traiter nos demandes.
Indirectement, une instance de modèle ne connait pas la base de données: c’est son gestionnaire qui a cette tâche.
Il existe deux exceptions à cette règle: les méthodes save()
et update()
.
Type d’opération | Méthode ou fonction associée | |
---|---|---|
Instanciation | MyClass() | |
Create | Création | MyClass.objects.create(...) |
Read | Récupération | MyClass.objects.get(pk=...) |
Filtres et querysets | MyClass.objects.all() | |
Update | Sauvegarde | my_instance.save() |
Delete | Suppression | my_instance.delete() |
Par défaut, le gestionnaire est accessible au travers de la propriété objects
.
Cette propriété a une double utilité:
- Elle est facile à surcharger - il nous suffit de définir une nouvelle classe héritant de ModelManager, puis de définir, au niveau de la classe, une nouvelle assignation à la propriété
objects
- Il est tout aussi facile de définir d’autres propriétés présentant des filtres bien spécifiques.
Querysets
Les querysets permettent de réaliser des filtres complexes sur un ensemble structuré de données.
Nous en avons déjà vu une partie au chapitre précédent, grâce à l’application de la méthode .filter()
, à laquelle nous avions passé plusieurs paramètres permettant de rechercher des livres ou auteurs associés à notre modèle.
Littéralement, un queryset est un ensemble de requêtes ; il s’agit effectivement d’une ou plusieurs requêtes - appliquées séquentiellement - et permettant d’obtenir un résultat correspondant à cet ensemble.
Les querysets vont cependant plus loin, et permettent de réaliser des :
- Opérations logiques,
- Agrégations d’informations (Count, Max, …)
- Annotations exprimées au moyen de fonctions (également traduites en SQL …)
- … et plein d’autres sucreries syntaxiques (que nous n’aborderons pas ici 😴)
Opérations logiques
Les opérations logiques “de base” sont accessibles très facilement et peuvent être chaînées de manière totalement séquentielle :
"""Ceci nous donnera d'abord les livres qui contiennent le caractère "a"dans leur titre, puis appliquera un deuxième filtre sur ceuxqui contiennent *également* le caractère "b"."""Book.filter(title__contains="a").filter(title__contains="b") <1>
"Cette deuxième représentation est totalement identique à la première."""Book.filter(title__contains="a", title__contains="b") <2>
Une troisième option consiste à utiliser les Q objects
, que nous pouvons trouver dans l’espace de noms django.db
.
Ces éléments peuvent servir autant à appliquer un filtre AND
que OR
:
from django.db.models import Q
"""Nous cherchons les livres dont le titre contient "a" *ET* "b","""Book.objects.filter( Q(title__contains="a"), Q(title__contains="b"))
"""Nous cherchons ici les livres dont le titre contient "a" *OU* "b""""Book.objects.filter( Q(title__contains="a") | Q(title__contains="b")) <2>
De la même manière, la négation peut être représentée via un Q object
sur lequel nous appliquerons l’opérateur ~
:
from django.db.models import Q
"""Nous cherchons ici les livres dont le titre *ne contient pas* le caractère "a"."""Book.objects.filter(~Q(title__contains="a")) <1>
Agrégations
https://docs.djangoproject.com/en/3.1/topics/db/aggregation/
Anotations
https://docs.djangoproject.com/en/3.1/topics/db/aggregation/