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 django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self): # __unicode__ on Python 2
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.
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, il suffit d’attribuer 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 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(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]
Pour obtenir un seul objet plutôt qu’une liste (ex. : SELECT foo FROM bar LIMIT 1
), utilisez un simple 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
etiendswith
.
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 simplement 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. Pour faire référence à une relation « inverse », utilisez simplement 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¶
Quand vous filtrez un objet basé sur un champ ManyToManyField
ou un champ ForeignKey
inversé, deux types de filtres peuvent être intéressants. Considérez la relation Blog
/Entry
(Blog
vers Entry
est une relation un-à-plusieurs). Nous pourrions être intéressés à trouver des blogues qui ont une entrée contenant “Lennon” dans le titre et ayant été publiée en 2008. Ou nous pourrions vouloir trouver des blogues qui contiennent une entrée avec “Lennon” dans le titre et une entrée qui a été publiée en 2008. Comme il y a plusieurs entrées associées à un seul Blog
, ces deux requêtes sont possibles et ont du sens dans certaines situations.
Le même genre de situation survient avec un champ ManyToManyField
. Par exemple, si une Entry
avait un ManyToManyField
nommé tags
, nous pourrions vouloir trouver des entrées liées aux tags
nommés “music” et “bands”, ou nous pourrions vouloir une entrée qui contient un tag
avec le nom “music” et un statut “public”.
Pour gérer ces deux situations, Django a une façon cohérente de traiter les appels de filter()
. Tous les paramètres d’un appel à filter()
sont appliqués simultanément pour filtrer les éléments qui correspondent à tous ces critères. Les appels subséquents à filter()
restreignent ensuite davantage les éléments renvoyés, mais pour les relations multivaluées ces appels s’appliquent à tous les objets liés au modèle principal, pas nécessairement aux objets (dépendants du modèle principal) qui ont été sélectionnés par un appel précédent à filter()
.
Ceci peut paraître un peu déroutant ; nous espérons qu’un exemple viendra clarifier les choses. Pour sélectionner tous les blogues qui contiennent des entrées qui ont à la fois “Lennon” dans le titre et qui ont été publiées en 2008 (la même entrée qui satisfait les deux conditions), nous pourrions écrire :
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
Pour sélectionner tous les blogues qui contiennent une entrée avec “Lennon” dans le titre et d’autre part une entrée qui a été publiée en 2008, nous pourrions écrire :
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
En supposant qu’un seul blogue contienne à la fois des articles contenant “Lennon” et des articles de 2008, mais qu’aucun des articles de 2008 ne contient “Lennon”, la première requête ne renverrait aucun blogue alors que la seconde requête renverrait ce blogue.
Dans le deuxième exemple, le premier filtre restreint le queryset à tous les blogues avec un lien vers des articles contentant “Lennon” dans le titre. Le second filtre restreint davantage les blogues résultants à ceux qui ont aussi un lien vers les articles publiés en 2008. Les articles sélectionnés par le second filtre peuvent être ou non les mêmes que ceux sélectionnés par le premier filtre. Nous filtrons les éléments Blog
avec chaque appel de filtre, pas les éléments Entry
.
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(n_comments__gt=F('n_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(n_comments__gt=F('n_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('n_comments') + F('n_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 aussi en charge les opérations bit à bit via .bitand()
et .bitor()
, par exemple :
>>> F('somefield').bitand(16)
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, il suffit d’indiquer 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, il suffit de stocker l’objet QuerySet
et de le réutiliser :
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use 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 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, il suffit d’utiliser 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, {'weblog.Entry': 1})
La valeur renvoyée contenant le nombre d’objets supprimés a été ajoutée.
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
.
La valeur renvoyée contenant le nombre d’objets supprimés a été ajoutée.
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, il suffit de définir pk
à None
. En utilisant notre exemple de blogue :
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
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
:
django_blog.pk = None
django_blog.id = None
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.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.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.all().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.select_related().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. Il suffit d’effectuer une boucle et d’appeler 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.all().update(n_pingbacks=F('n_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.