Filtrez des QuerySets selon des critères avancés
Nous avons maintenant toutes les fonctionnalités nécessaires pour que les créateurs publient et partagent leurs billets de blog et photos. Construisons à présent un flux plus avancé.
Le site aura deux flux :
un flux blog et photo que nous allons construire ensemble,
un flux exclusivement photo que vous allez construire vous-même.
Si vous avez déjà travaillé avec les modèles Django, vous savez sûrement comment récupérer une instance unique d’un modèle avec get()
, et comment renvoyer des QuerySets filtrés grâce aux méthodes filter()
et exclude()
.
Examinons, en vidéo, des méthodes plus avancées de filtration et de combinaison de différents QuerySets.
Étape 1 : Créez des recherches complexes avec les recherches de champ
Construisons la vue qui constituera notre page de flux blog et photo. Nous le ferons sur la page d’accueil existante, donc ce sera la première chose que l’on verra en se connectant.
La première tâche est de récupérer les billets de blog des créateurs auxquels l’utilisateur connecté est abonné.
Vous pouvez trouver un QuerySet des objets
User
que quelqu’un suit avecUser.follows.all()
.Nous voulons récupérer toutes les instances de
Blog
liées à ces utilisateurs par son champcontributors
.
Pour ce faire, utilisez les recherches de champ.
Ici, nous pouvons utiliser la recherche de champin
. Pour l’utiliser, vous devez spécifier le champ,contributors
, suivi d’un double underscore,__
, et ensuite la recherche de champ,in
. Vous l’utilisez ensuite comme argument nommé lorsque vous filtrez.
Dans ce cas, vous obtenezcontributors__in
.
Voici à quoi cela ressemble dans la vue :
# blog/views.py
def home(request):
blogs = models.Blog.objects.filter(contributors__in=request.user.follows.all())
context = {
'blogs': blogs,
}
return render(request, 'blog/home.html', context=context)
Étape 2 : Utilisez les recherches de champ pour requêter des relations de modèle
Et maintenant, vous voulez obtenir des photos pour le flux. Voici vos deux conditions pour filtrer le QuerySet :
Le champ
Photo.uploader
doit être un utilisateur qui est suivi.Vous voulez exclure les photos déjà attachées aux instances de
Blog
que vous avez récupérées.
Pour répondre au premier point, utilisez un filtre similaire au premier :
photos = models.Photo.objects.filter(
uploader__in=request.user.follows.all())
Ensuite, excluez les photos qui sont déjà liées à des instances deBlog
.
Pour les exclure, empilez la méthodeexclude
sur le même QuerySet que la première.
Bien que le modèlePhoto
n’ait pas d’attribut blog
, vous pouvez quand même le soumettre à des requêtes, car le modèleBlog
a une relationForeignKey
àPhoto
. Spécifiez cette relation inverse en requêtant le nom du modèle en minuscules, ce qui vous donneblog
.
Pour exclure ces instances, vous pouvez utiliser :
blogs = models.Blog.objects.filter(contributors__in=request.user.follows.all())
photos = models.Photo.objects.filter(
uploader__in=request.user.follows.all()).exclude(
blog__in=blogs
)
Étape 3 : Construisez des recherches OR (« OU ») avec des objets Q
L’attribut starred
se trouve également dans le modèleBlog
. Les créateurs peuvent l’utiliser pour indiquer les posts de blog qu’ils veulent afficher sur le flux de tous les utilisateurs, que ceux-ci les suivent ou non.
Jusqu’à présent, vous n’avez étudié que les requêtes pour lesquelles les recherches peuvent être filtrées et ajoutées les unes aux autres avec « AND » (« ET »). Vous n’obtiendrez pas le résultat souhaité si vous essayez ça ici.
Vous obtiendriez ceci :
blogs = models.Blog.objects.filter(contributors__in=request.user.follows.all(), starred=True)
Ça ne renvoie que les billets pour lesquels au moins l’un des contributors
est dans user.follows
et où Blog.starred
est True
.
Cependant, nous voulons renvoyer les billets si l’un des contributors est dans user.follows
ou siBlog.starred
est True .
Comment nous y prendre ?
En utilisant les objets Q
!
Vous pouvez utiliser les objets Q
pour stocker certaines requêtes, que vous pouvez ensuite appliquer dans les filtres. Elles peuvent être combinées logiquement avec les opérations AND, &
; OR, |
; et NOT
, ~
Elles vous permettent de construire des requêtes de base de données complexes, au-delà de ce que filter()
et exclude()
peuvent accomplir seuls.
Pour requêter les billets dont l’un des contributors
est dans user.follows
ou dont starred
estTrue
, combinez deux objetsQ
contenant vos requêtes avec un opérateur OR,|
, comme ceci :
from django.db.models import Q
blogs = models.Blog.objects.filter(
Q(contributors__in=request.user.follows.all()) | Q(starred=True))
Vous pouvez également nier les objets Q
en utilisant l’opérateur NOT, ~
. La requête ci-dessous renverra un QuerySet identique au premier :
from django.db.models import Q
blogs = models.Blog.objects.filter(
Q(contributors__in=request.user.follows.all()) | ~Q(starred=False))
Ça fonctionne, mais notre première approche a l’air plus propre.
Étape 5 : Combinez toutes les requêtes dans la vue
Combinez toutes ces techniques dans la vue :
from django.db.models import Q
def home(request):
blogs = models.Blog.objects.filter(
Q(contributors__in=request.user.follows.all()) | Q(starred=True))
photos = models.Photo.objects.filter(
uploader__in=request.user.follows.all()).exclude(
blog__in=blogs
)
context = {
'blogs': blogs,
'photos': photos,
}
return render(request, 'blog/home.html', context=context)
Toutes les instances Blog
et Photo
sont maintenant dans la vue. Néanmoins, si vous voulez les afficher comme un seul flux, vous allez devoir les combiner et les trier d’une façon ou d’une autre. Faisons cela maintenant !
Assemblez et triez des QuerySets de différents types de modèle
Maintenant que nous avons récupéré les deux QuerySets pour les modèles Photo
etBlog
, nous devons les assembler dans une même liste pour les afficher sur notre flux. Voici comment en vidéo :
Si vous voulez classer un QuerySet d’un seul type de modèle, vous pouvez utiliser la méthode order_by()
, en la passant au champ que vous voulez utiliser pour organiser la séquence. Vous pouvez aussi ajouter un –
devant le champ pour inverser l’ordre des résultats.
Pour récupérer les instances deBlog
en commençant par la plus récente, vous pouvez exécuter :
blogs = blogs.order_by('-date_created')
Ce tri est exécuté en SQL, c’est donc plus rapide que de charger et trier des listes Python. Malheureusement, vous ne pouvez pas faire cela si vous combinez des QuerySets de différents types de modèle. Vous devez donc revenir au tri dans Python.
Vous pouvez faire différentes choses pour améliorer la performance, mais la principale sera d’utiliser itertools.chain pour assembler les QuerySets. Cette méthode retourne un itérateur qui itère sur tous les éléments itérables fournis, comme s’il s’agissait d’une seule séquence d’objets.
Vous pouvez ensuite fournir le résultat obtenu à la fonctionsorted
, pour renvoyer une liste triée.
Si vous combinez ces deux éléments avec une fonctionlambda
pour classer pardate_created
(date de création), vous obtiendrez :
from django.db.models import Q
def home(request):
blogs = models.Blog.objects.filter(
Q(author__in=request.user.follows) | Q(starred=True))
photos = models.Photo.objects.filter(
uploader__in=request.user.follows).exclude(
blog__in=blogs
)
blogs_and_photos = sorted(
chain(blogs, photos),
key=lambda instance: instance.date_created,
reverse=True
)
context = {
'blogs_and_photos': blogs_and_photos,
}
return render(request, 'blog/home.html', context=context)
Vous avez maintenant une liste des instances Photo
et Blog
pour le flux, triée avec les instances les plus récentes en premier. Tout est prêt pour construire un flux principal combiné comprenant des billets de blog et des photos ! Ils sont même combinés en une seule liste dans le contexte à passer au gabarit.
Vous allez maintenant créer le QuerySet pour le flux exclusivement photo.
C'est à vous ! Récupérez les photos pour le flux photo
La vue est construite pour le flux principal, mais nous voulons aussi avoir un autre flux, uniquement pour les photos.
Vous allez devoir construire une vue qui servira ce flux tout photo. Elle devra :
Être appelée
photo_feed
.Récupérer un QuerySet d’instances
Photo
, pour lesquelles leuploader
(la personne qui téléverse) est suivi par l’utilisateur, classées en commençant par la plus récente. Le tri doit être effectué avec l’API QuerySet.Inclure le QuerySet dans le
context
du gabarit, sous la cléphotos
.Renvoyer le rendu du gabarit
blog/photo_feed.html
— pas besoin de créer ce gabarit, nous le ferons plus tard.
Lorsque vous serez prêt, comparez votre travail avec la solution dans le dépôt GitHub.
En résumé
Vous pouvez utiliser un underscore double pour effectuer des recherches de champ sur des modèles séparés du modèle cible lorsque vous filtrez des QuerySets.
Vous pouvez utiliser des objets
Q
pour construire des requêtes de base de données avancées et récupérer des objets avec la logique booléenneAND
,OR
, etNOT
.Vous pouvez trier les QuerySets avec la méthode
order_by
.Vous pouvez créer un itérateur combinant les QuerySets de différents types de modèle en utilisant
itertools.chain
, et en faire une liste classée avecsorted
.Le pouvoir des QuerySets va beaucoup plus loin que ces quelques exemples. Consultez la documentation Django pour encore plus de filtres utiles.
Maintenant que vous comprenez les fonctionnalités avancées des QuerySets, vous pouvez afficher vos posts récupérés dans le flux !