Création de requêtes

Une fois les modèles de données créés, Django offre automatiquement une API d’abstraction de base de données qui permet de créer, obtenir, mettre à jour et supprimer des objets. Ce document explique comment utiliser cette API. Consultez la référence des modèles de données pour des détails complets sur toutes les options d’interrogation des modèles.

À travers ce guide (et dans la référence), nous nous référons aux modèles suivants, qui forment une application de blog :

from datetime import date

from django.db import models


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name


class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name


class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField(default=date.today)
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField(default=0)
    number_of_pingbacks = models.IntegerField(default=0)
    rating = models.IntegerField(default=5)

    def __str__(self):
        return self.headline

Création d’objets

Pour représenter des données d’une table de base de données en objets Python, Django utilise un système intuitif : une classe de modèle représente une table de base de données, et une instance de cette classe représente un enregistrement particulier dans la table de base de données.

Pour créer un objet, créez une instance de la classe de modèle en utilisant des paramètres nommés, puis appelez save() pour l’enregistrer dans la base de données.

En supposant que les modèles se trouvent dans un fichier mysite/blog/models.py, voici un exemple :

>>> from blog.models import Blog
>>> b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
>>> b.save()

Ceci exécute une commande SQL INSERT en arrière-plan. Django ne sollicite pas la base de données tant que vous n’appelez pas explicitement save().

La méthode save() ne renvoie aucune valeur.

Voir aussi

save() accepte un certain nombre d’options avancées décrites ailleurs. Consultez la documentation de save() pour les détails complets.

Pour créer et enregistrer un objet en une seule étape, utilisez la méthode create().

Enregistrement des modifications d’objets

Pour enregistrer les modifications d’un objet existant dans la base de données, utilisez save().

Considérant une instance b5 d’un Blog ayant déjà été enregistrée dans la base de données, cet exemple modifie son nom et met à jour son enregistrement dans la base de données :

>>> b5.name = "New name"
>>> b5.save()

Ceci génère une commande SQL UPDATE en arrière-plan. Django ne sollicite pas la base de données tant que vous n’appelez pas explicitement save().

Enregistrement des champs ForeignKey et ManyToManyField

La mise à jour d’un champ ForeignKey fonctionne exactement de la même façon que l’enregistrement d’un champ normal, attribuez un objet du bon type au champ en question. Cet exemple met à jour l’attribut blog d’une instance Entry nommée entry, en supposant que les instances appropriées de Entry et de Blog sont déjà enregistrées dans la base de données (afin de pouvoir être récupérées ci-dessous) :

>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

La mise à jour d’un champ ManyToManyField fonctionne un peu différemment ; utilisez la méthode add() du champ pour ajouter un enregistrement à la relation. Cet exemple ajoute l’instance Author joe à l’objet entry:

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

Pour ajouter plusieurs enregistrements à un champ ManyToManyField d’un seul coup, indiquez plusieurs paramètres dans l’appel à add(), comme ceci :

>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)

Django se plaint si vous essayez d’assigner ou d’ajouter un objet de type inapproprié.

Sélection d’objets

Pour extraire des objets de la base de données, construisez un QuerySet via un Manager de la classe de votre modèle.

Un QuerySet représente une collection d’objets de la base de données. Il peut comporter zéro, un ou plusieurs filtres. Les filtres réduisent les résultats de requêtes en fonction des paramètres donnés. En termes SQL, un QuerySet équivaut à une commande SELECT et un filtre correspond à une clause restrictive telle que WHERE ou LIMIT.

On obtient un QuerySet en utilisant le Manager du modèle. Chaque modèle a au moins un Manager ; il s’appelle objects par défaut. On y accède directement via la classe du modèle, comme ceci :

>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name="Foo", tagline="Bar")
>>> b.objects
Traceback:
    ...
AttributeError: "Manager isn't accessible via Blog instances."

Note

Les Managers ne sont accessibles que par les classes de modèle et non par les instances de modèle, afin d’accentuer la séparation entre les opérations sur les « tables » et les opérations sur les « enregistrements ».

Le Manager est la source principale des QuerySets d’un modèle. Par exemple, Blog.objects.all() renvoie un QuerySet contenant tous les objets Blog de la base de données.

Sélection de tous les objets

La façon la plus simple d’obtenir des objets d’une table est de tous les sélectionner. Pour ce faire, utilisez la méthode all() d’un Manager:

>>> all_entries = Entry.objects.all()

La méthode all() renvoie un QuerySet de tous les objets dans la base de données.

Sélection d’objets spécifiques avec les filtres

Le QuerySet renvoyé par all() contient tous les objets de la table de la base de données. Mais le plus souvent, seul un sous-ensemble de tous les objets devra être sélectionné.

Pour créer un tel sous-ensemble, vous affinez le QuerySet initial en y ajoutant des filtres de conditions. Les deux façons les plus utilisées pour affiner un QuerySet sont :

filter(**kwargs)
Renvoie un nouveau QuerySet contenant les objets qui répondent aux paramètres de recherche donnés.
exclude(**kwargs)
Renvoie un nouveau QuerySet contenant les objets qui ne répondent pas aux paramètres de recherche donnés.

Les paramètres de recherche (**kwargs dans les définitions de fonction ci-dessus) doivent être dans le format décrit dans Recherches dans les champs ci-dessous.

Par exemple, pour obtenir un QuerySet des articles de blog de l’année 2006, utilisez filter() comme ceci :

Entry.objects.filter(pub_date__year=2006)

Avec la classe de gestionnaire par défaut, c’est équivalent à :

Entry.objects.all().filter(pub_date__year=2006)

Enchaînement des filtres

Le résultat de l’affinage d’un QuerySet est lui-même un QuerySet, il est donc possible d’enchaîner les filtrages successifs. Par exemple :

>>> Entry.objects.filter(headline__startswith="What").exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(pub_date__gte=datetime.date(2005, 1, 30))

À partir du QuerySet initial de toutes les lignes dans la base de données, le code ci-dessus ajoute un filtre, puis une exclusion, puis un autre filtre. Le résultat final est un QuerySet contenant tous les enregistrements ayant un titre (headline) commençant par « What », ayant été publiés entre le 30 janvier 2005 et aujourd’hui.

Les QuerySet filtrés sont uniques

À chaque fois que vous affinez un QuerySet, vous obtenez un QuerySet tout neuf qui n’est lié d’aucune manière au QuerySet précédent. Chaque affinage crée un QuerySet séparé et distinct qui peut être stocké, utilisé et réutilisé.

Exemple :

>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

Ces trois QuerySet sont séparés. Le premier est un QuerySet de base contenant tous les enregistrements qui contiennent un titre commençant par « What ». Le second est un sous-ensemble du premier, avec un critère supplémentaire qui exclut les enregistrements dont le champ pub_date est aujourd’hui ou dans le futur. Le troisième est un sous-ensemble du premier, avec un critère supplémentaire qui sélectionne uniquement les enregistrements dont le champ pub_date est aujourd’hui ou dans le futur. Le QuerySet initial (q1) n’est pas affecté par le processus d’affinage.

Les objects QuerySet sont différés

Les QuerySets sont différés (« lazy ») ; la création d’un QuerySet ne génère aucune activité au niveau de la base de données. Vous pouvez empiler les filtres toute la journée, Django ne lance aucune requête tant que le QuerySet n’est pas évalué. Regardez cet exemple :

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)

Malgré le fait que ceci ressemble à trois interrogations de la base de données, en réalité une seule interrogation est faite, à la dernière ligne (print(q)). En général, les résultats d’un QuerySet ne sont récupérés de la base de données que lorsque vous les « demandez ». Lorsque cela arrive, le QuerySet est évalué en accédant à la base de données. Pour plus de détails sur le moment exact où l’évaluation a lieu, consultez Quand les objets QuerySet sont évalués.

Sélection d’un objet unique avec get()

filter() renvoie toujours un QuerySet, même si la requête ne renvoie qu’un seul objet ; dans ce cas, le QuerySet ne contiendra simplement qu’un seul élément.

Si vous savez qu’un seul objet correspondra à votre requête, vous pouvez utiliser la méthode get() d’un Manager qui renvoie directement l’objet :

>>> one_entry = Entry.objects.get(pk=1)

N’importe quelle expression de recherche peut être utilisée avec get(), comme pour filter(). Voir Recherches dans les champs ci-dessous.

Notez qu’il y a une différence entre l’utilisation de get() et celle de filter() avec le segment [0]. Si aucun résultat ne correspond à la requête, get() génère une exception DoesNotExist. Cette exception est un attribut de la classe de modèle sur laquelle la requête est appliquée ; ainsi, dans le code ci-dessus, s’il n’existe pas d’objet Entry avec une clé primaire de 1, Django génère l’exception Entry.DoesNotExist.

De même, Django réagit si plus d’un élément correspond à la requête get(). Dans ce cas, il lève l’exception MultipleObjectsReturned qui est également un attribut de la classe de modèle elle-même.

Autres méthodes de QuerySet

La plupart du temps, vous utiliserez all(), get(), filter() et exclude() lorsque vous aurez besoin de sélectionner des objets dans la base de données. Cependant, c’est loin d’être tout ; consultez la référence de l’API QuerySet pour une liste complète des diverses méthodes de QuerySet.

Limitation des QuerySet

Utilisez un sous-ensemble de la syntaxe Python de segmentation des listes pour restreindre un QuerySet à un certain nombre de résultats. C’est l’équivalent des clauses SQL LIMIT et OFFSET.

Par exemple, ceci renvoie les 5 premiers objets (LIMIT 5) :

>>> Entry.objects.all()[:5]

Ceci renvoie du sixième au dixième objet (OFFSET 5 LIMIT 5) :

>>> Entry.objects.all()[5:10]

Les index négatifs (ex. : Entry.objects.all()[-1]) ne sont pas pris en charge.

De manière générale, la segmentation d’un QuerySet renvoie un nouveau QuerySet ; la requête n’est pas évaluée, sauf si vous utilisez le paramètre step de la syntaxe de segmentation de Python. Par exemple, ceci exécuterait effectivement la requête pour renvoyer une liste d’un objet sur 2 parmi les 10 premiers :

>>> Entry.objects.all()[:10:2]

Tout filtre ou tri subséquent d’un jeu de requête segmenté est interdit en raison de la nature ambiguë de son éventuel fonctionnement.

Pour obtenir un seul objet plutôt qu’une liste (ex. : SELECT foo FROM bar LIMIT 1), utilisez un index au lieu d’un segment. Par exemple, ceci renvoie le premier objet Entry de la base de données, après avoir trié les objets alphabétiquement par titre (headline) :

>>> Entry.objects.order_by("headline")[0]

C’est grossièrement équivalent à :

>>> Entry.objects.order_by("headline")[0:1].get()

Notez toutefois que si aucun objet ne correspond à la requête, une exception IndexError est générée dans le premier cas, tandis que dans le second cas, c’est l’exception DoesNotExist qui sera générée. Voir get() pour plus de détails.

Recherches dans les champs

La recherche dans les champs est ce qui constitue le cœur des clauses SQL WHERE. La syntaxe s’exprime par des paramètres nommés dans les méthodes filter(), exclude() et get() de QuerySet.

Les paramètres nommés de base de ces requêtes prennent la forme champ__typerequete=valeur (il s’agit d’un double soulignement). Par exemple :

>>> Entry.objects.filter(pub_date__lte="2006-01-01")

peut être grossièrement traduit en code SQL comme :

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

Comment est-ce possible ?

Python permet de définir des fonctions acceptant des paramètres nom-valeur arbitraires dont les noms et les valeurs sont évalués au moment de l’exécution. Pour plus d’informations, consultez Keyword Arguments dans le tutoriel Python officiel.

Le champ indiqué dans une recherche doit correspondre au nom d’un champ de modèle. Il existe cependant une exception ; dans le cas d’une clé étrangère ForeignKey, vous pouvez indiquer le nom du champ additionné du suffixe _id. Dans ce cas, le paramètre valeur doit contenir la valeur brute de la clé primaire du modèle étranger. Par exemple :

>>> Entry.objects.filter(blog_id=4)

Si vous fournissez un paramètre nommé non valide, une fonction de recherche signalera une exception TypeError.

L’API de base de données gère une vingtaine de types de recherche ; une référence complète se trouve dans la référence des recherches de champs. Pour vous donner une idée de ce qui est possible, voici quelques recherches parmi les plus fréquemment utilisées :

exact

Une correspondance « exacte ». Par exemple :

>>> Entry.objects.get(headline__exact="Cat bites dog")

Produit le code SQL ressemblant à :

SELECT ... WHERE headline = 'Cat bites dog';

Si vous n’indiquez pas de type de recherche, c’est-à-dire si votre paramètre nommé ne contient pas de double soulignement, Django considère que le type de recherche est exact.

Par exemple, les deux lignes suivantes sont équivalentes :

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)  # __exact is implied

Il s’agit d’un raccourci commode, puisque les recherches exact sont les cas les plus courants.

iexact

Une recherche insensible à la casse. Ainsi, la requête :

>>> Blog.objects.get(name__iexact="beatles blog")

Aurait pour occurrence un Blog intitulé "Beatles Blog", "beatles blog" ou même "BeAtlES blOG".

contains

Test d’inclusion sensible à la casse. Par exemple :

Entry.objects.get(headline__contains="Lennon")

Pourrait être traduit grosso modo par ce code SQL :

SELECT ... WHERE headline LIKE '%Lennon%';

Notez que le titre (headline) 'Today Lennon honored' correspondrait à cette recherche, mais pas 'today lennon honored'.

Il existe aussi une version insensible à la casse, icontains.

startswith, endswith
Recherche « commençant par » et « finissant par », respectivement. Il en existe aussi des versions insensibles à la casse : istartswith et iendswith.

Encore une fois, ce n’est qu’un survol. Une référence complète est disponible dans la référence des recherches de champ.

Recherches traversant les relations

Django offre une approche puissante et intuitive pour « suivre » les relations dans les recherches, se chargeant automatiquement des JOIN SQL en arrière-plan. Pour atteindre une relation, utilisez les noms de champ qui servent de relation vers d’autres modèles, séparés par des doubles soulignements, jusqu’à atteindre le champ souhaité.

Cet exemple sélectionne tous les objets Entry d’un Blog ayant pour name la valeur 'Beatles Blog':

>>> Entry.objects.filter(blog__name="Beatles Blog")

Ce mécanisme de traversée de relations peut être aussi profond que vous le souhaitez.

Ceci fonctionne également dans le sens inverse. Même s’il peut être personnalisé, la manière par défaut de se référer à une relation « inverse » dans une expression de requête est d’utiliser le nom du modèle en minuscules.

Cet exemple sélectionne tous les objets Blog ayant au moins une Entry ayant un headline contenant 'Lennon':

>>> Blog.objects.filter(entry__headline__contains="Lennon")

Si vous filtrez à travers plusieurs relations et qu’un des modèles intermédiaires n’a pas de valeur répondant à la condition du filtre, Django le traite comme s’il y avait là un objet vide (toutes les valeurs sont NULL), mais valide. C’est-à-dire qu’aucune erreur ne sera signalée. Par exemple, dans ce filtre :

Blog.objects.filter(entry__authors__name="Lennon")

(partant du principe qu’il existe un modèle Author lié), s’il n’y a pas de author associé à une entry, ce sera traité comme s’il n’y avait pas non plus de name, plutôt que de signaler une erreur à cause de l”author manquant. Normalement, c’est bien le comportement souhaité. Le seul cas où ça pourrait porter à confusion, c’est quand vous utilisez isnull. Ainsi :

Blog.objects.filter(entry__authors__name__isnull=True)

renvoie les objets Blog ayant un author avec le name vide ainsi que tous ceux ayant un author vide dans l”entry. Si vous ne voulez pas de cette deuxième catégorie d’objets, vous pourriez écrire :

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

Traversée des relations multivaluées

Lors de la traversée d’un champ ManyToManyField ou d’un clé ForeignKey inverse (comme de Blog à Entry), le filtrage sur plusieurs attributs pose la question de savoir si chaque attribut doit correspondre pour chaque objet lié. On pourrait rechercher des blogs qui ont un article en 2008 avec « “Lennon” » dans son titre, ou plus simplement des blogs qui ont un article en 2008 de même que des articles plus vieux ou plus récentes qui contiennent « “Lennon” » dans leur titre.

Pour sélectionner tous les blogs contenant au moins un article en 2008 ayant « Lennon » dans son titre (le même article devant satisfaire les deux conditions), il faudrait écrire

Blog.objects.filter(entry__headline__contains="Lennon", entry__pub_date__year=2008)

Sinon, pour effectuer une requête plus permissive sélectionnant tous les blogs ayant un ou plusieurs articles avec « Lennon » dans leur titre et un ou plusieurs articles en 2008, on écrirait

Blog.objects.filter(entry__headline__contains="Lennon").filter(
    entry__pub_date__year=2008
)

Supposons qu’il n’y ait qu’un seul blog ayant à la fois des articles contenant « Lennon » et des articles de 2008, mais qu’aucun des articles de 2008 ne contienne « Lennon ». La première requête ne renverrait aucun blog, mais la seconde renverrait ce blog (et cela parce que les lignes sélectionnées par le second filtre ne sont pas forcément les mêmes que celles du premier filtre ; chaque instruction de filtrage agit sur les éléments Blog, pas sur les éléments Entry ). En résumé, si toutes les conditions doivent s’appliquer sur les mêmes objets liés, alors chaque filtre doit être contenu dans un même appel à filter().

Note

Comme la seconde requête (plus permissive) enchaîne plusieurs filtres, elle effectue plusieurs jointures vers le modèle principal, ce qui peut provoquer des doublons.

>>> from datetime import date
>>> beatles = Blog.objects.create(name="Beatles Blog")
>>> pop = Blog.objects.create(name="Pop Music Blog")
>>> Entry.objects.create(
...     blog=beatles,
...     headline="New Lennon Biography",
...     pub_date=date(2008, 6, 1),
... )
<Entry: New Lennon Biography>
>>> Entry.objects.create(
...     blog=beatles,
...     headline="New Lennon Biography in Paperback",
...     pub_date=date(2009, 6, 1),
... )
<Entry: New Lennon Biography in Paperback>
>>> Entry.objects.create(
...     blog=pop,
...     headline="Best Albums of 2008",
...     pub_date=date(2008, 12, 15),
... )
<Entry: Best Albums of 2008>
>>> Entry.objects.create(
...     blog=pop,
...     headline="Lennon Would Have Loved Hip Hop",
...     pub_date=date(2020, 4, 1),
... )
<Entry: Lennon Would Have Loved Hip Hop>
>>> Blog.objects.filter(
...     entry__headline__contains="Lennon",
...     entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>]>
>>> Blog.objects.filter(
...     entry__headline__contains="Lennon",
... ).filter(
...     entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>

Note

Le comportement de filter() pour les requêtes qui couvrent des relations multi-valeurs” tel que décrit ci-dessus, n’est pas implémenté de la même manière pour exclude(). Au contraire, les conditions de restriction indiquées lors qu’un appel à exclude() ne doivent pas nécessairement s’appliquer au même élément.

Par exemple, la requête ci-dessous exclurait les blogues qui contiennent à la fois « Lennon » dans le titre et des entrées publiées en 2008

Blog.objects.exclude(
    entry__headline__contains="Lennon",
    entry__pub_date__year=2008,
)

Cependant, contrairement au comportement lors de l’utilisation de filter(), cela ne sélectionnera pas les blogs basés sur des entrées qui satisfont les deux conditions en même temps. Pour faire cela, c’est à dire pour sélectionner tous les blogs qui ne contiennent pas d’entrées publiées avec « Lennon » et qui ont été publiées en 2008, vous devez faire deux requêtes

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains="Lennon",
        pub_date__year=2008,
    ),
)

Les filtres peuvent référencer les champs d’un modèle

Dans les exemples donnés jusqu’ici, nous avons construit des filtres comparant la valeur d’un champ de modèle avec une constante. Mais qu’en est-il si vous souhaitez comparer la valeur d’un champ de modèle avec un autre champ du même modèle ?

Django propose les expressions F pour autoriser de telles comparaisons. Les instances de F() agissent comme des références vers un champ de modèle à l’intérieur d’une requête. Ces références peuvent ensuite être utilisées dans des filtres de requêtes pour comparer les valeurs de deux champs différents d’une même instance de modèle.

Par exemple, pour obtenir la liste de tous les articles de blogue ayant reçus plus de commentaires que de « pings », nous construisons un objet F() pour référencer le nombre de pings afin d’utiliser cet objet F() dans la requête :

>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks"))

Django prend en charge l’utilisation des opérateurs arithmétiques addition, soustraction, multiplication, division, modulo et puissance avec les objets F(), aussi bien avec des constantes qu’avec d’autres objets F(). Pour obtenir tous les articles de blogue avec plus de deux fois de commentaires que de pings, nous modifions ainsi la requête :

>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2)

Pour trouver tous les articles où la notation de l’article est plus petite que la somme des pings et des commentaires, nous pourrions effectuer cette requête :

>>> Entry.objects.filter(rating__lt=F("number_of_comments") + F("number_of_pingbacks"))

Vous pouvez aussi employer la notation en double soulignement pour traverser les relations dans un objet F(). Un objet F() contenant un double soulignement produira toute jointure nécessaire pour accéder aux objets liés. Par exemple, pour obtenir tous les articles dont le nom de l’auteur est égal au nom du blogue, nous pourrions effectuer la requête :

>>> Entry.objects.filter(authors__name=F("blog__name"))

Pour les champs date et date/heure, il est possible d’ajouter ou de soustraire un objet timedelta. L’exemple suivant renvoie tous les articles modifiés plus de 3 jours après avoir été publiés :

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F("pub_date") + timedelta(days=3))

Les objets F() prennent en charge les opérations bit à bit via .bitand(), .bitor(), .bitxor(), .bitrightshift() et .bitleftshift(). Par exemple :

>>> F("somefield").bitand(16)

Oracle

Oracle ne prend pas en charge l’opération bit à bit XOR.

Les expressions peuvent référencer des transformations

Django prend en charge l’appel à des transformations dans les expressions.

Par exemple, pour trouver tous les objets Entry publiés la même année que leur date de dernière modification :

>>> from django.db.models import F
>>> Entry.objects.filter(pub_date__year=F("mod_date__year"))

Pour trouver l’année de publication la plus ancienne d’un élément, on peut lancer la requête :

>>> from django.db.models import Min
>>> Entry.objects.aggregate(first_published_year=Min("pub_date__year"))

Cette exemple cherche la valeur de l’élément le mieux noté et le nombre total de commentaires de tous les éléments pour chaque année :

>>> from django.db.models import OuterRef, Subquery, Sum
>>> Entry.objects.values("pub_date__year").annotate(
...     top_rating=Subquery(
...         Entry.objects.filter(
...             pub_date__year=OuterRef("pub_date__year"),
...         )
...         .order_by("-rating")
...         .values("rating")[:1]
...     ),
...     total_comments=Sum("number_of_comments"),
... )

Le raccourci de recherche pk

Pour des raisons pratiques, Django fournit un raccourci de recherche pk, signifiant « primary key » (clé primaire).

Dans le modèle d’exemple Blog, la clé primaire est le champ id. Ainsi, ces trois lignes sont équivalentes :

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)  # __exact is implied
>>> Blog.objects.get(pk=14)  # pk implies id__exact

L’emploi de pk n’est pas limité aux requêtes __exact; toute expression de requête peut être combinée avec pk pour effectuer une requête sur la clé primaire d’un modèle :

# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1, 4, 7])

# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)

Les recherches sur pk fonctionnent également en traversant les relations. Par exemple, ces trois lignes sont équivalentes :

>>> Entry.objects.filter(blog__id__exact=3)  # Explicit form
>>> Entry.objects.filter(blog__id=3)  # __exact is implied
>>> Entry.objects.filter(blog__pk=3)  # __pk implies __id__exact

Échappement des signes pour cent et des soulignements dans les instructions LIKE

Les recherches de champs produisant des instructions SQL LIKE (iexact, contains, icontains, startswith, istartswith, endswith et iendswith) échappent automatiquement les deux caractères à signification spéciale des instructions LIKE: le signe pour cent et le soulignement (dans une instruction LIKE, le signe pour cent est utilisé comme caractère de remplacement multiple alors qu’un soulignement est utilisé comme caractère de remplacement unique).

Cela signifie que les choses devraient fonctionner intuitivement, sans faille d’abstraction. Par exemple, pour obtenir la liste de tous les articles contenant un signe pour cent, indiquez le signe pour cent comme pour n’importe quel autre caractère :

>>> Entry.objects.filter(headline__contains="%")

Django se charge de l’échappement à votre place ; le code SQL résultant ressemblera à quelque chose comme :

SELECT ... WHERE headline LIKE '%\%%';

Même chose pour les soulignements. Aussi bien les signes pour cent que les soulignements sont gérés pour vous de manière transparente.

Mise en cache et objets QuerySet

Chaque QuerySet contient un cache pour minimiser l’accès à la base de données. La compréhension de son fonctionnement vous permet d’écrire le code le plus efficace possible.

Pour un nouvel objet QuerySet, son cache est vide. À la première évaluation d’un QuerySet (et donc qu’une requête de base de données est effectuée), Django enregistre les résultats de la requête dans le cache de l’objet QuerySet et renvoie les résultats qui ont été explicitement demandés (par ex. l’élément suivant dans le cas d’une itération du QuerySet). Les évaluations suivantes de l’objet QuerySet réutilisent les résultats mis en cache.

Gardez à l’esprit ce comportement de mise en cache, car vous pourriez vous faire avoir si vous n’utilisez pas correctement l’objet QuerySet. Par exemple, le code suivant crée deux objets QuerySet, les évalue et les abandonne :

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

Dans ce cas, la même requête de base de données est exécutée deux fois, ce qui double en pratique la charge sur la base de données. Il est également imaginable que les deux listes ne contiennent pas exactement les mêmes enregistrements de base de données, car un objet Entry pourrait avoir été ajouté ou supprimé dans la fraction de seconde entre les deux requêtes.

Pour éviter ce problème, stockez l’objet QuerySet et réutilisez-le :

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset])  # Evaluate the query set.
>>> print([p.pub_date for p in queryset])  # Reuse the cache from the evaluation.

Quand les objets QuerySet ne sont pas mis en cache

Les objets QuerySet ne mettent pas toujours leurs résultats en cache. Lorsque seule une partie d’un queryset est évaluée, le cache est consulté mais s’il n’est pas rempli, les éléments renvoyés par la requête suivante ne sont pas mis en cache. Spécifiquement, cela signifie que la restriction d’un jeu de requêtes en employant une segmentation ou un index de liste ne remplira par le cache.

Par exemple, l’interrogation répétée d’un objet QuerySet à l’aide d’un index effectue une requête dans la base de données à chaque fois :

>>> queryset = Entry.objects.all()
>>> print(queryset[5])  # Queries the database
>>> print(queryset[5])  # Queries the database again

Cependant, si le jeu de requête complet a déjà été évalué, le cache sera tout de même mis à contribution :

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset]  # Queries the database
>>> print(queryset[5])  # Uses cache
>>> print(queryset[5])  # Uses cache

Voici quelques exemples d’autres actions qui déclenchent l’évaluation de tout l’objet QuerySet et qui par conséquent remplissent le cache :

>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

Note

L’affichage simple d’un QuerySet ne remplit pas le cache. Ceci parce que l’appel à __repr__() ne renvoie qu’un segment du jeu de requête complet.

Requêtes asynchrones

Si vous écrivez des vues ou du code asynchrone, vous ne pouvez pas utiliser l’ORM pour des requêtes de la façon qui est décrite ci-dessus, car il n’est pas possible d’appeler du code synchrone bloquant à partir de code asynchrone, cela bloquerait la boucle événementielle (ou plus probablement Django le remarquera et générera l’exception SynchronousOnlyOperation pour que ce blocage ne se produise pas).

Heureusement, il est possible de faire de nombreuses requêtes en utilisant les API de requêtes asynchrones de Django. Toute méthode susceptible de bloquer, telle que get() ou delete(), possède une variante asynchrone (aget() ou adelete()), et quand vous passez en boucle sur les résultats, vous pouvez alors utiliser l’itération asynchrone (async for).

Requêtes et itération

La manière par défaut de passer en boucle sur une requête - avec for - aboutit à une requête de base de données bloquante en arrière-plan du moment que Django charge les résultats au moment de l’itération. Pour corriger cela, vous pouvez exploiter async for:

async for entry in Authors.objects.filter(name__startswith="A"):
    ...

Soyez bien conscient que vous ne pouvez pas non plus exécuter d’autres opérations qui aboutiraient à une itération du jeu de requête, comme par exemple le placer dans un appel à list() pour forcer son évaluation (vous pouvez cependant utiliser async for dans une compréhension de liste si vous le souhaitez).

Comme les méthodes de QuerySet telles que``filter()`` ou exclude() n’exécutent pas réellement de requête (elles préparent le jeu de requête à s’exécuter au moment de l’itération), vous pouvez les utiliser sans crainte dans du code asynchrone. Pour savoir quelles méthodes peuvent continuer à être utilisées telles quelles, et quelles sont celles qui possèdent une version asynchrone, lisez la section suivante.

QuerySet et méthodes de gestionnaire

Certaines méthodes de gestionnaires et de jeux de requêtes, comme get() et first(), forcent l’exécution immédiate de la requête et sont bloquantes. D’autres, comme filter() et exclude(), ne forcent pas d’exécution et peuvent donc être utilisées à partir de code asynchrone sans problème. Mais comment pouvez-vous faire cette distinction ?

Bien qu’il soit possible de faire des recherches pour savoir s’il existe une version préfixée par a de ces méthodes (par exemple, aget existe, mais pas afilter()), il existe une façon plus logique : examinez de quelle sorte de méthode il s’agit dans la référence de QuerySet.

Dans cette référence, vous trouverez les méthodes de QuerySet classées en deux catégories :

  • Les méthodes qui renvoient de nouveaux jeux de requête: celles-ci sont non bloquantes et ne possèdent pas de version asynchrone. Vous pouvez les utiliser librement dans toute situation, bien que pour defer() et only(), il est recommandé de lire les notes à leur sujet avant de les utiliser.
  • Les méthodes qui ne renvoient pas de nouveaux jeux de requête: celles-ci sont bloquantes et possèdent une version asynchrone. Le nom de la version asynchrone est indiqué dans leur documentation, même si le modèle standard est d’ajouter un préfixe a.

Avec cette distinction, vous pouvez savoir quand vous devez utiliser les versions asynchrones et quand ce n’est pas indiqué. Par exemple, voici une requête asynchrone valable

user = await User.objects.filter(username=my_input).afirst()

filter() renvoie un jeu de requête et peut donc bien être utilisée dans un environnement asynchrone, alors que first() est immédiatement évaluée et renvoie une instance de modèle. Ainsi, nous la modifions en afirst() et utilisons await en préambule de toute l’expression afin de l’appeler d’une manière adéquate pour l’asynchrone.

Note

Si vous oubliez le mot-clé await, vous pourriez voir des erreurs du genre « coroutine object has no attribute x » ou « <coroutine …> » au lieu de vos instances de modèle. Lorsque vous voyez ces erreurs, c’est qu’il manque un await quelque part pour transformer la coroutine en une valeur réelle.

Transactions

Les transactions ne sont actuellement pas prises en charge pour les requêtes et mises à jour asynchrones. Si vous tentez de les utiliser quand même, vous obtiendrez des exceptions SynchronousOnlyOperation.

Si vous souhaitez utiliser une transaction, nous suggérons d’écrire le code ORM à l’intérieur d’une fonction synchrone séparée, puis de l’appeler en utilisant sync_to_async - voir Gestion du code asynchrone pour plus de détails.

Interrogation de JSONField

L’implémentation des interrogations est différente pour JSONField, principalement en raison de l’existence des transformations de clés. Pour le montrer, nous allons utiliser le modèle d’exemple suivant

from django.db import models


class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = models.JSONField(null=True)

    def __str__(self):
        return self.name

Stockage et requête de None

Comme pour les autres champs, le stockage de None comme valeur de champ enregistre une valeur SQL NULL. Même si ce n’est pas recommandé, il est possible de stocker la valeur scalaire null de JSON au lieu du NULL SQL en utilisant Value(None, JSONField()).

Quelle que soit la valeur stockée, lorsqu’elle est lue de la base de données, la représentation Python de la valeur scalaire null de JSON est la même que pour la valeur SQL NULL, c’est-à-dire None. Il peut donc être difficile de faire la distinction entre les deux.

Ceci ne s’applique qu’à None comme valeur de premier niveau du champ. Si None se trouve à l’intérieur d’une structure list ou dict, elle sera toujours interprétée comme un null de JSON.

Lors des requêtes, la valeur None sera toujours interprétée comme la valeur null de JSON. Pour savoir si un champ possède la valeur SQL NULL, utilisez isnull:

>>> Dog.objects.create(name="Max", data=None)  # SQL NULL.
<Dog: Max>
>>> Dog.objects.create(name="Archie", data=Value(None, JSONField()))  # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value(None, JSONField()))
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>
>>> Dog.objects.filter(data__isnull=False)
<QuerySet [<Dog: Archie>]>

Sauf dans le cas où vous souhaitez vraiment travailler avec des valeurs SQL NULL, préférez le paramètre null=False et définissez une valeur par défaut convenable pour les valeurs vides, telle que default=dict.

Note

Le stockage de la valeur scalaire JSON null ne viole pas null=False.

Changed in Django 4.2:

La possibilité d’exprimer la valeur JSON null avec Value(None, JSONField()) a été ajoutée.

Obsolète depuis la version 4.2: La transmission de Value("null") pour exprimer la valeur JSON null a été rendue obsolète.

Transformations de clé, d’indice et de chemin

Pour rechercher en fonction d’une clé de dictionnaire donnée, utilisez cette clé comme nom de requête :

>>> Dog.objects.create(
...     name="Rufus",
...     data={
...         "breed": "labrador",
...         "owner": {
...             "name": "Bob",
...             "other_pets": [
...                 {
...                     "name": "Fishy",
...                 }
...             ],
...         },
...     },
... )
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed="collie")
<QuerySet [<Dog: Meg>]>

Il est possible d’enchaîner plusieurs clés pour former une recherche de chemin :

>>> Dog.objects.filter(data__owner__name="Bob")
<QuerySet [<Dog: Rufus>]>

Si la clé est un entier, elle sera interprétée comme une transformation d’indice de tableau :

>>> Dog.objects.filter(data__owner__other_pets__0__name="Fishy")
<QuerySet [<Dog: Rufus>]>

Si la clé avec laquelle vous souhaitez rechercher entre en conflit avec le nom d’une recherche existante, utilisez la recherche contains à la place.

Pour rechercher des clés manquantes, utilisez l’expression de recherche isnull:

>>> Dog.objects.create(name="Shep", data={"breed": "collie"})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>

Note

Les exemples de requêtes ci-dessus utilisent implicitement la recherche exact. Les transformations de clé, d’indice et de chemin peuvent aussi être chaînées avec : icontains, endswith, iendswith, iexact, regex, iregex, startswith, istartswith, lt, lte, gt et gte, tout comme avec Requêtes de contenance et de clé.

Expressions KT()

New in Django 4.2.
class KT(lookup)

Représente la valeur textuelle d’une clé, d’un index ou d’une transformation de chemin de JSONField. On peut utiliser la notation du double soulignement dans lookup pour enchaîner des transformations de clés de dictionnaire ou d’index.

Par exemple :

>>> from django.db.models.fields.json import KT
>>> Dog.objects.create(
...     name="Shep",
...     data={
...         "owner": {"name": "Bob"},
...         "breed": ["collie", "lhasa apso"],
...     },
... )
<Dog: Shep>
>>> Dogs.objects.annotate(
...     first_breed=KT("data__breed__1"), owner_name=KT("data__owner__name")
... ).filter(first_breed__startswith="lhasa", owner_name="Bob")
<QuerySet [<Dog: Shep>]>

Note

En raison de la façon dont les requêtes clé-chemin fonctionnent, exclude() et filter() ne garantissent pas la production d’ensembles exhaustifs. Si vous souhaitez inclure les objets pour lesquels le chemin n’existe pas, ajoutez l’expression isnull.

Avertissement

Comme n’importe quelle chaîne peut être une clé d’un objet JSON, toute recherche autre que celles mentionnées ci-dessous sera interprétée comme une recherche par clé. Aucune erreur n’est produite. Faites spécialement attention aux erreurs de frappe et vérifiez systématiquement que vos requêtes fonctionnent correctement.

Utilisateurs MariaDB et Oracle

Quand on utilise order_by() sur une transformation de clé, d’indice ou de chemin, le tri des objets se fait sur la représentation textuelle des valeurs. Ceci se produit parce que les bases de données MariaDB et Oracle ne fournissent pas de fonction pour convertir les valeurs JSON en leur valeur SQL équivalente.

Utilisateurs Oracle

Pour les bases de données Oracle, l’utilisation de None comme valeur de requête dans une requête exclude() renvoie des objets qui n’ont pas null comme valeur sur le chemin indiqué, incluant aussi les objets qui n’ont pas le chemin du tout. Avec les autres moteurs de base de données, la requête renvoie les objets où le chemin indiqué existe et la valeur n’est pas null.

Utilisateurs de PostgreSQL

Avec PostgreSQL, si une seule clé ou indice est donnée, l’opérateur SQL -> est utilisé. Si plusieurs opérateurs sont donnés, alors c’est l’opérateur #> qui est utilisé.

Utilisateurs de SQLite

Avec SQLite, les valeurs de chaîne "true", "false", and "null" sont toujours interprétées respectivement comme True, False, et le null de JSON.

Requêtes de contenance et de clé

contains

La recherche contains est surchargée pour les champs JSONField. Les objets renvoyés sont ceux pour qui le dictionnaire de paires clé-valeur est totalement contenu au premier niveau du champ. Par exemple :

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={"owner": "Bob"})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={"breed": "collie"})
<QuerySet [<Dog: Meg>]>

Oracle et SQLite

contains n’est pas pris en charge avec Oracle et SQLite.

contained_by

Il s’agit de l’inverse de la recherche contains, les objets renvoyés sont ceux pour qui les paires de clé-valeur forment un sous-ensemble des valeurs transmises. Par exemple :

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={"breed": "collie", "owner": "Bob"})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={"breed": "collie"})
<QuerySet [<Dog: Fred>]>

Oracle et SQLite

contained_by n’est pas pris en charge avec Oracle et SQLite.

has_key

Renvoie les objets où la clé donnée se trouve au premier niveau des données. Par exemple :

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_key="owner")
<QuerySet [<Dog: Meg>]>

has_keys

Renvoie les objets où toutes les clés données se trouvent au premier niveau des données. Par exemple :

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_keys=["breed", "owner"])
<QuerySet [<Dog: Meg>]>

has_any_keys

Renvoie les objets où au moins une des clés données se trouve au premier niveau des données. Par exemple :

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_any_keys=["owner", "breed"])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

Requêtes complexes avec des objets Q

Les requêtes à paramètres nommés (dans filter(), etc.) sont combinées par AND (ET). Si vous avez besoin d’exécuter des requêtes plus complexes (par exemple des requêtes contenant des instruction avec OR (OU)), vous pouvez utiliser des objets Q.

Un objet Q (django.db.models.Q) est un objet utilisé pour englober plusieurs paramètre nommés. Ces paramètre nommés sont indiqués comme pour les « Recherches dans les champs » ci-dessus.

Par exemple, cet objet Q représente une seule requête LIKE:

from django.db.models import Q

Q(question__startswith="What")

Les objets Q peuvent être combinés à l’aide des opérateurs &, | et ^. Lorsqu’un opérateur est utilisé avec deux objets Q, cela produit un nouvel objet Q.

Par exemple, cette ligne produit un seul objet Q représentant la combinaison par « OR » de deux requêtes "question__startswith":

Q(question__startswith="Who") | Q(question__startswith="What")

C’est équivalent à la clause SQL WHERE ci-dessous :

WHERE question LIKE 'Who%' OR question LIKE 'What%'

Vous pouvez composer des instructions de complexité arbitraire en combinant des objets Q avec les opérateurs &, | et ^ et en les groupant par des parenthèses. De même, les objets Q peuvent être inversés par l’opérateur de négation ~, permettant à des requêtes combinées d’utiliser à la fois des requêtes normales et des requêtes inversées (NOT) :

Q(question__startswith="Who") | ~Q(pub_date__year=2005)

Chaque fonction de recherche acceptant des paramètres nommés (par ex. filter(), exclude(), get()) peut aussi recevoir un ou plusieurs objets Q comme paramètre positionnel (non nommé). Si vous indiquez plusieurs objets Q comme paramètres d’une fonction de recherche, les paramètres seront combinés avec « AND ». Par exemple :

Poll.objects.get(
    Q(question__startswith="Who"),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)

… se traduit grossièrement en SQL par :

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

Les fonctions de recherche peuvent mélanger l’utilisation d’objets Q et de paramètres nommés. Tous les paramètres fournis à une fonction de recherche (que ce soit des paramètres nommés ou des objets Q) sont combinés par l’opérateur « AND ». Cependant, si un objet Q est fourni, il doit précéder toute définition de paramètre nommé. Par exemple :

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith="Who",
)

… correspond à une requête valable, équivalente à l’exemple précédent ; mais :

# INVALID QUERY
Poll.objects.get(
    question__startswith="Who",
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)

… n’est pas correct.

Voir aussi

Les exemples de requêtes OR dans les tests unitaires de Django montrent quelques utilisations possibles de Q.

Comparaison d’objets

Pour comparer deux instances de modèle, utilisez l’opérateur de comparaison standard de Python, le double signe égal : ==. En arrière-plan, ce sont les valeurs clés primaires des deux modèles qui sont comparées.

En utilisant l’exemple Entry ci-dessus, les deux instructions suivantes sont équivalentes :

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

Si la clé primaire d’un modèle ne s’appelle pas id, aucun problème. Les comparaisons utilisent toujours la clé primaire, quel que soit son nom. Par exemple, si un champ clé primaire d’un modèle s’appelle name, ces deux lignes sont équivalentes :

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

Suppression d’objets

La méthode de suppression se nomme delete(). Cette méthode supprime immédiatement l’objet et renvoie le nombre d’objets supprimés ainsi qu’un dictionnaire avec le nombre de suppressions par type d’objet. Exemple :

>>> e.delete()
(1, {'blog.Entry': 1})

Il est aussi possible de supprimer des objets groupés. Chaque QuerySet comporte une méthode delete() qui supprime tous les objets contenus dans l’objet QuerySet.

Par exemple, cette commande supprime tous les objets Entry dont l’année de pub_date est 2005 :

>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

Sachez toutefois que cette opération sera autant que possible exécutée purement au niveau SQL, ce qui signifie que les méthodes delete() des instances individuelles ne seront pas forcément appelées durant le processus. Si vous avez écrit une méthode delete() personnalisée dans une classe de modèle et que vous voulez être certain qu’elle soit appelée, vous devrez supprimer « manuellement » les instances de ce modèle (par ex. en itérant sur un objet QuerySet et en appelant explicitement delete() sur chaque instance) plutôt que d’employer la méthode de suppression groupée delete() de l’objet QuerySet.

Lorsque Django supprime un objet, il émule par défaut le comportement de la contrainte SQL ON DELETE CASCADE. En d’autres termes, tout objet possédant des clés étrangères vers l’objet en cours de suppression seront également supprimés. Par exemple :

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

Ce comportement en cascade peut être personnalisé au moyen du paramètre on_delete de la classe ForeignKey.

Notez que delete() est la seule méthode de QuerySet qui n’est pas exposée sur un objet Manager. Il s’agit d’un mécanisme de sécurité pour vous empêcher d’exécuter accidentellement Entry.objects.delete() ce qui supprimerait toutes les lignes. Si vous souhaitez vraiment supprimer tous les objets, vous devez alors indiquer explicitement une requête contenant tous les objets :

Entry.objects.all().delete()

Copie des instances de modèles

Même s’il n’existe pas de méthode intégrée pour la copie d’instances de modèles, il est possible de créer facilement de nouvelles instances en copiant toutes les valeurs des champs d’une autre instance. Dans le cas le plus simple, on peut définir pk à None et _state.adding to True. En utilisant notre exemple de blogue :

blog = Blog(name="My blog", tagline="Blogging is easy")
blog.save()  # blog.pk == 1

blog.pk = None
blog._state.adding = True
blog.save()  # blog.pk == 2

Les choses se compliquent lorsqu’il y a de l’héritage. Considérons une sous-classe de Blog:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)


django_blog = ThemeBlog(name="Django", tagline="Django is easy", theme="python")
django_blog.save()  # django_blog.pk == 3

En raison du fonctionnement de l’héritage, vous devez définir à None à la fois pk et id, ainsi que _state.adding à True:

django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
django_blog.save()  # django_blog.pk == 4

Ce processus ne copie pas les relations ne faisant pas partie de la table de base de données du modèle. Par exemple, Entry possède un champ ManyToManyField vers Author. Après avoir dupliqué un objet « entry », vous devez définir les relations plusieurs-à-plusieurs vers le nouvel objet :

entry = Entry.objects.all()[0]  # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
entry.save()
entry.authors.set(old_authors)

Pour un champ OneToOneField, vous devez dupliquer l’objet lié et l’attribuer au champ correspondant du nouvel objet pour éviter la rupture de la contrainte unique du champ un-à-un. Par exemple, en supposant que entry a déjà été dupliqué comme ci-dessus :

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail._state.adding = True
detail.entry = entry
detail.save()

Mise à jour simultanée de plusieurs objets

Il peut arriver que vous souhaitiez définir la valeur d’un certain champ pour tous les objets d’un QuerySet. C’est faisable à l’aide de la méthode update(). Par exemple :

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline="Everything is the same")

Seuls les champs qui ne sont pas des relations et les champs ForeignKey peuvent être mis à jour avec cette méthode. Pour mettre à jour un champ qui ne représente pas une relation, indiquez la nouvelle valeur sous forme de constante. Pour mettre à jour un champ ForeignKey, indiquez la nouvelle valeur sous forme d’une instance de modèle vers laquelle le champ devra pointer. Par exemple :

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.update(blog=b)

La méthode update() est appliquée immédiatement et renvoie le nombre de lignes correspondant à la requête (ce qui ne correspond pas toujours au nombre de lignes mises à jour si certaines lignes ont déjà la bonne valeur). La seule restriction sur l’objet QuerySet mis à jour est qu’il ne peut accéder qu’à une seule table de base de données, la table principale du modèle. Vous pouvez filtrer selon des champs liés, mais vous ne pouvez mettre à jour des colonnes que dans la table principale du modèle. Exemple :

>>> b = Blog.objects.get(pk=1)

# Update all the headlines belonging to this Blog.
>>> Entry.objects.filter(blog=b).update(headline="Everything is the same")

Soyez conscient que la méthode update() est directement convertie en instruction SQL. Il s’agit d’une opération groupée pour des mises à jour directes. Elle ne fait appel à aucune méthode save() des modèles, n’émet aucun signal pre_save ou post_save (qui sont des conséquences de l’appel à save()) et ne respecte pas l’option de champ auto_now. Si vous souhaitez enregistrer chaque élément d’un objet QuerySet tout en garantissant que la méthode save() de chaque instance est appelée, vous n’avez pas besoin d’une fonction particulière pour cela. Effectuez une boucle et appelez save() pour chaque objet :

for item in my_queryset:
    item.save()

Les appels à update peuvent aussi utiliser les expressions F pour mettre à jour un champ en fonction de la valeur d’un autre champ du modèle. C’est particulièrement utile pour incrémenter des compteurs en fonction de leur valeur actuelle. Par exemple, pour incrémenter le nombre de pings de chaque article d’un blogue :

>>> Entry.objects.update(number_of_pingbacks=F("number_of_pingbacks") + 1)

Cependant, au contraire des objets F() dans les clauses filter() et exclude(), il n’est pas autorisé d’introduire des jointures lors de l’utilisation de F() dans des mises à jour ; seuls des champs du modèle en cours de mise à jour peuvent être référencés. Si vous essayez d’introduire une jointure avec un objet F(), une erreur FieldError sera générée :

# This will raise a FieldError
>>> Entry.objects.update(headline=F("blog__name"))

Recours au code SQL brut

Si vous rencontrez le besoin de devoir écrire une requête SQL trop complexe pour l’abstraction de base de données de Django, il peut être nécessaire d’écrire le code SQL à la main. Django offre quelques options pour l’écriture de requêtes SQL brutes ; voir Lancement de requêtes SQL brutes.

Finalement, il est important de noter que la couche de base de données de Django n’est qu’une interface vers votre base de données. Vous pouvez accéder à votre base de données par d’autres outils, langages de programmation ou environnements de base de données ; il n’y a rien de spécifique à Django en ce qui concerne la base de données.

Back to Top