5.6 KiB
Querysets & 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 aux objets persistants -
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 propose un attribut objects
, qui correspond
à un manager (ou un gestionnaire, si vous préférez).
Ce gestionnaire constitue l’interface par laquelle vous accéderez à la base de données. Mais pour cela, vous aurez aussi besoin d’appliquer certains requêtes ou filtres. Et pour cela, vous aurez besoin des querysets
, qui consistent en des … ensembles de requêtes :-).
Si on veut connaître la requête SQL sous-jacente à l’exécution du queryset, il suffit d’appeler la fonction str() sur la propriété query
:
queryset = Wishlist.objects.all()
print(queryset.query)
Conditions AND et OR sur un queryset
Pour un `AND`, il suffit de chaîner les conditions. ** trouver un exemple ici ** :-)
Mais en gros : bidule.objects.filter(condition1, condition2)
Il existe deux autres options : combiner deux querysets avec l'opérateur `&` ou combiner des Q objects avec ce même opérateur.
Soit encore combiner des filtres:
from core.models import Wish
Wish.objects (1)
Wish.objects.filter(name__icontains="test").filter(name__icontains="too") (2)
-
Ca, c’est notre manager.
-
Et là, on chaîne les requêtes pour composer une recherche sur tous les souhaits dont le nom contient (avec une casse insensible) la chaîne "test" et dont le nom contient la chaîne "too".
Pour un 'OR', on a deux options :
-
Soit passer par deux querysets, typiuqment
queryset1 | queryset2
-
Soit passer par des
Q objects
, que l’on trouve dans le namespacedjango.db.models
.
from django.db.models import Q
condition1 = Q(...)
condition2 = Q(...)
bidule.objects.filter(condition1 | condition2)
L’opérateur inverse (NOT)
Idem que ci-dessus : soit on utilise la méthode exclude
sur le queryset, soit l’opérateur ~
sur un Q object;
Ajouter les sujets suivants :
-
Prefetch
-
select_related
Gestionnaire de models (managers) et opérations
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()
.
-
Instanciation: MyClass()
-
Récupération: MyClass.objects.get(pk=…)
-
Sauvegarde : MyClass().save()
-
Création: MyClass.objects.create(…)
-
Liste des enregistrements: MyClass.objects.all()
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.
Requêtes
DANGER: Les requêtes sont sensibles à la casse, même si le moteur de base de données ne l’est pas. C’est notamment le cas pour Microsoft SQL Server; faire une recherche directement via les outils de Microsoft ne retournera pas obligatoirement les mêmes résultats que les managers, qui seront beaucoup plus tatillons sur la qualité des recherches par rapport aux filtres paramétrés en entrée.
Jointures
Pour appliquer une jointure sur un modèle, nous pouvons passer par les méthodes select_related
et prefetch_related
.
Il faut cependant faire très attention au prefetch related, qui fonctionne en fait comme une grosse requête dans laquelle
nous trouvons un IN (…)
.
Càd que Django va récupérer tous les objets demandés initialement par le queryset, pour ensuite prendre toutes les clés primaires,
pour finalement faire une deuxième requête et récupérer les relations externes.
Au final, si votre premier queryset est relativement grand (nous parlons de 1000 à 2000 éléments, en fonction du moteur de base de données),
la seconde requête va planter et vous obtiendrez une exception de type django.db.utils.OperationalError: too many SQL variables
.
Nous pourrions penser qu’utiliser un itérateur permettrait de combiner les deux, mais ce n’est pas le cas…
Comme l’indique la documentation:
Note that if you use iterator() to run the query, prefetch_related() calls will be ignored since these two optimizations do not make sense together.
Ajouter un itérateur va en fait forcer le code à parcourir chaque élément de la liste, pour l’évaluer. Il y aura donc (à nouveau) autant de requêtes qu’il y a d’éléments, ce que nous cherchons à éviter.
informations = (
<MyObject>.objects.filter(<my_criteria>)
.select_related(<related_field>)
.prefetch_related(<related_field>)
.iterator(chunk_size=1000)
)