129 lines
5.6 KiB
Plaintext
129 lines
5.6 KiB
Plaintext
### Querysets & managers
|
|
|
|
* http://stackoverflow.com/questions/12681653/when-to-use-or-not-use-iterator-in-the-django-orm
|
|
* https://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.query.QuerySet.iterator
|
|
* http://blog.etianen.com/blog/2013/06/08/django-querysets/
|
|
|
|
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`:
|
|
|
|
[source,python]
|
|
----
|
|
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:
|
|
|
|
[source,python]
|
|
----
|
|
from core.models import Wish
|
|
|
|
Wish.objects <1>
|
|
Wish.objects.filter(name__icontains="test").filter(name__icontains="too") <2>
|
|
----
|
|
<1> Ca, c'est notre manager.
|
|
<2> 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 namespace `django.db.models`.
|
|
|
|
[source,python]
|
|
----
|
|
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é:
|
|
|
|
1. 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`
|
|
2. 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.
|
|
|
|
[source,python]
|
|
----
|
|
informations = (
|
|
<MyObject>.objects.filter(<my_criteria>)
|
|
.select_related(<related_field>)
|
|
.prefetch_related(<related_field>)
|
|
.iterator(chunk_size=1000)
|
|
)
|
|
----
|
|
|
|
=== Aggregate vs. Annotate
|
|
|
|
https://docs.djangoproject.com/en/3.1/topics/db/aggregation/ |