======================================== Référence de l'API de la base de données ======================================== Après avoir créé vos modèles de données (`data models`_), Django met automatiquement à votre disposition une API d'abstraction de la base de données qui vous permet de créer, récupérer, mettre à jour ou effacer des objets. Ce document explique comment. .. _`data models`: ../model-api/ Dans l'ensemble de ce document, nous ferons référence aux modèles suivants, qui représentent une application de blog:: class Blog(models.Model): nom = models.CharField(max_length=100) titre = models.TextField() def __unicode__(self): return self.nom class Auteur(models.Model): nom = models.CharField(max_length=50) email = models.EmailField() def __unicode__(self): return self.nom class Billet(models.Model): blog = models.ForeignKey(Blog) titre = models.CharField(max_length=255) texte = models.TextField() date_publication = models.DateTimeField() auteurs = models.ManyToManyField(Author) def __unicode__(self): return self.titre Création d'objets ================= Pour représenter les données d'une table de la base de données en objets Python, Django utilise un système intuitif: la classe d'un modèle représente une table de la base de données, et une instance de cette classe correspond à un enregistrement précis dans cette table. Pour créer un objet, instanciez le en utilisant comme paramètres les noms des champs définis dans la classe modèle, puis appelez ``save()`` pour l'enregistrer dans la base de données. On importe la classe du modèle d'où qu'il soit dans le path Python, comme vous pouvez l'imaginez. (Cette remarque parce que dans les versions précédentes de Django, l'importation de modèles était plus complexe.) En supposant que les modèles se trouvent dans ``mysite/blog/models.py``, voilà un exemple:: from mysite.blog.models import Blog b = Blog(nom='Le blog des Beatles', titre='Toutes les dernières nouvelles des Beatles.') b.save() Cela crée, en coulisse, une instruction SQL ``INSERT``. Django ne l'exécute pas tant que vous n'appelez pas explicitement ``save()``. La méthode ``save()`` n'a pas de valeur de retour. Pour créer et sauver un objet en une seule fois, voir la méthode `create`__. __ `create(**kwargs)`_ Incrémentation automatique des clefs primaires ---------------------------------------------- Si un modèle a un champ ``AutoField`` (une clef primaire qui s'incrémente automatiquement) cette valeur incrémentée automatiquement sera calculée et enregistrée comme une propriété de votre objet la première fois que vous appellerez ``save()``. Exemple:: b2 = Blog(nom='Parlons du Camembert', titre='Réflexions sur les fromages.') b2.id # Renvoie None, car b n'a pas encore d'ID. b2.save() b2.id # Renvoie l'ID de notre nouvel objet. Il n'y a aucun moyen de connaître la valeur d'un champ ID avant d'avoir appelé ``save()``, parce que cette valeur est calculée par la base de données, pas par Django. (Pour des raisons pratiques, tous les modèles ont, par défaut, un champ ``AutoField`` nommé ``id`` à moins qu'on ne spécifie explicitement ``primary_key=True`` sur un autre champ. voir `AutoField documentation`_) .. _AutoField documentation: ../model-api/#autofield Définir explicitement les valeurs d'un champ de clef primaire automatique ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Si un modèle a un champ ``AutoField`` mais que vous souhaitez définir un autre ID pour cet objet, attribuez le explicitement avant de sauver l'objet, plutôt que de compter sur l'attribution automatique de l'ID. Exemple:: b3 = Blog(id=3, nom='Parlons du Camembert', titre='Réflexions sur les fromages.') b3.id # Renvoie 3. b3.save() b3.id # Renvoie 3. Si vous affectez manuellement la valeur d'un champ de clef primaire automatique, faites attention de ne pas utiliser une valeur déjà existante. Si vous utilisez une valeur qui se trouve déjà dans la base de données, Django agira comme si vous mettiez à jour l'objet existant au lieu d'en créer un nouveau. En considérant l'exemple précédent (``'Parlons du Camembert'``), le code ci-dessous remplacera l'entrée dans la base de donnée:: b4 = Blog(id=3, nom='Parlons du Roquefort', titre='Tout sauf du fromage.') b4.save() # Ecrase le blog précédent avec ID=3! Voir `Comment Django sait s'il doit faire un UPDATE ou un INSERT`_, ci-dessous, pour comprendre ce qui se passe. Spécifier explicitement la valeur d'une clef primaire est surtout utile pour enregistrer de gros volumes d'objets, quand on est certain qu'il n'y aura pas de conflit au niveau des clefs primaires. Que se passe-t-il lorsque vous effectuer un enregistrement ? ------------------------------------------------------------ Lorsque que l'on enregistre un objet, Django effectue les étapes suivantes: 1. **Émission d'un signal de pré-enregistrement (``pre_save``).** Indique que l'objet va bientôt être enregistré. On peut mentionner un listener qui sera appelé à chaque émission d'un signal. (Il n'existe pas encore de documents sur ces signaux.) 2. **Prétraitement des données.** Chaque champ de l'objet reçoit l'ordre d'effectuer une modification automatique des données si nécessaire. La plupart des champs ne font *aucun* prétraitement. Le champ de données est conservé tel quel. Le prétraitement est réservé aux champs ayant un comportement spécifique. Par exemple, si votre modèle à un champ ``DateField`` avec l'attribut ``auto_now=True``, la phase de préparation à la sauvegarde modifiera les données de l'objet pour s'assurer que le champ de date contient la date du jour. (Notre documentation n'inclut pas encore une liste de tous les champs ayant un "comportement spécifique".) 3. **Préparation des données pour la base de données.** Chaque champ reçoit l'ordre de fournir sa valeur actuelle dans un type de données qui puisse être écrit dans la base de données. La plupart des champs *ne* nécessitent *aucune* préparation. Les types de données simples, comme les entiers ou les chaînes de caractères, sont 'prêts pour l'écriture' en tant qu'objet Python. Cependant, les types de données plus complexes nécessitent souvent quelques modifications. Par exemple, les champs ``DateField`` utilisent un objet Python ``datetime`` pour stocker les données. Les bases de données ne pouvant pas stocker des objets ``datetime``, la valeur du champ doit être convertie en une chaîne de caractères conforme à l'ISO pour pouvoir être insérée dans la base de données. 4. **Insertion des données dans la base de données.** Les données prétraitées et préparées sont ensuite transformées en une requête SQL d'insertion dans la base de données. 5. **Émission d'un signal de post-enregistrement (``post_save``).** Tout comme le signal de pré-enregistrement, il sert à indiquer que l'objet a été enregistré correctement. Enregistrer les modifications des objets ======================================== Pour enregistrer les changements d'un objet qui existe déjà dans la base de données, utilisez ``save()``. Soit une instance ``b5`` de la classe ``Blog`` qui existe déjà dans la base de données, dans cet exemple on change son nom et on met à jour l'objet dans la base de données:: b5.nom = 'Nouveau nom' b5.save() Cela crée, en coulisse, une instruction SQL ``UPDATE``. Django ne l'exécute pas tant que vous n'appelez pas explicitement ``save()``. La méthode ``save()`` n'a pas de valeur de retour. Enregistrement des champs ForeignKey et ManyToManyField ------------------------------------------------------- La mise à jour d'un champ ``ForeignKey`` fonctionne exactement de la même manière que l'enregistrement d'un champ simple. Il suffit d'associer un objet du bon type au champ en question:: blog_fromage = Blog.objects.get(nom="Parlons du Camembert") billet.blog = blog_fromage billet.save() La mise à jour d'un champ ``ManyToManyField`` fonctionne un peu différemment. On utilise la méthode ``add()`` sur le champ pour ajouter un enregistrement à la relation:: joe = Author.objects.create(nom="Joe") Billet.authors.add(joe) Django fera la grimace si vous essayez d'affecter ou d'ajouter un objet du mauvais type. Comment Django sait s'il doit faire un UPDATE ou un INSERT ---------------------------------------------------------- Vous avez peut-être remarqué qu'on utilise la méthode ``save()`` que ce soit pour créer ou pour modifier des objets. Avec Django, il n'est pas nécessaire d'utiliser une instruction SQL ``INSERT`` ou ``UPDATE``. Pour être précis, lorsqu'on appelle la méthode ``save()``, il utilise l'algorithme suivant: * Si la clef primaire de l'objet possède une valeur que l'on peut estimer à ``True`` (c'est-à-dire qui ne soit ni ``None`` ni une chaîne vide), Django exécute une requête ``SELECT`` pour vérifier s'il existe un enregistrement avec la même clef primaire. * S'il en trouve un, Django exécute alors un ``UPDATE`` pour le mettre à jour. * Si la clef primaire de l'objet n'est *pas* spécifiée, ou qu'elle l'est mais qu'elle n'existe pas dans la table, Django exécute un ``INSERT``. Le point à retenir est qu'il ne faut pas définir explicitement une valeur à la clef primaire quand on enregistre de nouveaux objets si on n'est pas certain que cette valeur n'est pas déjà utilisée. Pour plus de détails à ce propos, reportez-vous au paragraphe "Définir explicitement la valeur d'un champ de clef primaire automatique" ci-dessus. Récupération des objets ======================= Pour récupérer des objets de votre base de données, il vous faut construire un ``QuerySet`` via un ``Manager`` sur la classe de votre modèle. Un ``QuerySet`` représente un groupe d'objets de votre base de données. Il peut être constitué d'aucun, d'un seul ou de plusieurs *filtres* qui affinent la sélection d'objets selon les paramètres transmis. En SQL, un ``QuerySet`` équivaut à un ``SELECT``, et un filtre à une clause comme ``WHERE`` ou ``LIMIT``. On obtient un ``QuerySet`` en utilisant le ``Manager`` du modèle. Chaque modèle a au moins un ``Manager`` nommé, par défaut, ``objects``. On y accède directement via la classe du modèle comme suit:: Blog.objects # b = Blog(nom='Foo', titre='Bar') b.objects # AttributeError: "Manager isn't accessible via Blog instances." (Les ``Managers`` ne sont accessibles que par la classe du modèle (méthode de classe), et non pas par une de ses instances. Et cela pour bien signifier la séparation entre les opérations au niveau table et les opérations au niveau enregistrement) Le ``Manager`` est la source principale de ``QuerySets`` d'un modèle. Il fonctionne comme un ``QuerySet`` racine qui décrit tous les objets d'un modèle qui sont dans la base de données. Par exemple, ``Blog.objects`` est le ``QuerySet`` de base qui contient tous les objets ``Blog`` qui sont dans la base de données. Récupération tous les objets ---------------------------- La manière la plus simple de récupérer les objets d'une table est de les prendre tous. Pour ce faire on utilise la méthode ``all()`` sur un ``Manager``. Exemple:: tout_les_billets = Billet.objects.all() La méthode ``all()`` renvoie un ``QuerySet`` de tous les objets de la base. (Si ``Billet.objects`` est un ``QuerySet``, pourquoi ne peut-on pas simplement utiliser ``Billet.objects`` ? Parce que ``Billet.objects`` est le ``QuerySet`` racine; c'est un cas particulier, il ne peut pas être évalué. La méthode ``all()``, elle, renvoie un ``QuerySet`` qui *peut* être évalué.) Filtrage des objets ------------------- Le ``QuerySet`` racine fourni par le ``Manager`` décrit tous les objets de la base. Mais en général on a plutôt besoin d'accéder à une sélection particulière d'objets plutôt qu'à tous. Pour créer une sélection, on affine le ``QuerySet`` de base en lui ajoutant des filtres. Les deux façons les plus simples sont les suivantes: ``filter(**kwargs)`` Renvoie un nouveau ``QuerySet`` contenant les objets qui correspondent aux critères de recherche fournis. ``exclude(**kwargs)`` Renvoie un nouveau ``QuerySet`` contenant les objets qui *ne* correspondent *pas* aux critères de recherche. Les paramètres de recherche, (``**kwargs`` dans l'exemple précédent) doivent être dans le format `Recherche sur un champ`_ décrit ci-dessous. Par exemple pour obtenir un ``QuerySet`` de tous les billets de l'année 2006, on utilise ``filter()`` de cette façon:: Billet.objects.filter(date_publication__year=2006) (Notez que l'on a pas à utiliser ``all()`` -- ``Billet.objects.all().filter(...)``. Cela fonctionnerait quand même, mais c'est inutile. On utilise ``all()`` seulement quand on veut tous les objets.) Chaînage des filtres ~~~~~~~~~~~~~~~~~~~~ Le résultat de l'affinage d'un ``QuerySet`` est un autre ``QuerySet``. Il est donc possible d'enchaîner les filtres. Par exemple:: Billet.objects.filter( titre__startswith='Quel').exclude( date_publication__gte=datetime.now()).filter( date_publication__gte=datetime(2005, 1, 1)) ...utilise le ``QuerySet`` de base de tous les enregistrements de la table, y ajoute un filtre, puis une exclusion et enfin un autre filtre. Le résultat est un ``QuerySet`` contenant tous les billets dont le titre commence par "Quel", publiés entre le 1er janvier 2005 et aujourd'hui. Chaque ``QuerySet`` filtré est unique ------------------------------------- A chaque fois qu'on affine un ``QuerySet``, on obtient un ``QuerySet`` tout neuf aucunement lié au précédent. Chaque affinage crée donc un ``QuerySet`` bien distinct, qui peut être stocké, utilisé et réutilisé. Exemple:: q1 = Billet.objects.filter(titre__startswith="Quel") q2 = q1.exclude(date_publication__gte=datetime.now()) q3 = q1.filter(date_publication__gte=datetime.now()) Ces trois ``QuerySets`` sont distincts. Le premier est un ``QuerySet`` de base contenant tous les billets dont le titre commence par "Quel". Le second est un sous groupe du premier. Le critère supplémentaire exclut les billets dont le champ ``date_publication`` est supérieur à la date d'aujourd'hui. Le troisième est lui aussi un sous groupe du premier, qui contient uniquement les billets dont le champ ``date_publication`` est supérieur à la date d'aujourd'hui. Le ``QuerySet`` de base (``q1``) n'est aucunement affecté par ce processus de filtrage. Les ``QuerySets`` sont passifs ------------------------------ Les ``QuerySets`` sont passifs, parce que leur création n'implique aucun appel à la base de données. On peut empiler des filtres toute la journée, Django n'exécutera vraiment la requête que lorsque le ``QuerySet`` sera *évalué*. Quand les ``QuerySets`` sont-ils évalués ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ On peut évaluer un ``QuerySet`` par les façons suivants: * **Itération.** Un ``QuerySet`` est un itérateur, et il exécute sa requête sur la base de données la première fois qu'on lance une itération dessus. Par exemple, pour afficher les titres de toutes les billets de la base de données, on aura:: for b in Billet.objects.all(): print b.titre * **Découpage.** Comme on l'explique ci-dessous dans le paragraphe `Limiter un QuerySet`_, un ``QuerySet`` peut être découpé en utilisant la syntaxe Python pour le découpage de liste. En général découper un ``QuerySet`` renvoie un autre ``QuerySet`` (non évalué), mais Django exécutera une requête sur la base de données si vous utilisez le paramètre "pas" de la syntaxe de découpage. * **repr().** Un ``QuerySet`` est évalué quand on le passe à la fonction ``repr()``. Ceci est pratique quand on utilise l'API dans l'interpréteur interactif car on voit tout de suite les résultats. * **len().** Un ``QuerySet`` est évalué quand on le passe à la fonction len(). Comme on peut s'en douter, cela renvoie la longueur du ``QuerySet``. Note: *N'utilisez pas* len() si vous voulez juste savoir le nombre d'éléments contenus dans un ``QuerySet``. Il est beaucoup plus efficace de demander à la base de données de les compter, en utilisant une instruction SQL ``SELECT COUNT(*)``. Django propose précisément une méthode ``count()`` pour ça. Voir ``count()`` plus bas. * **list().** On peut forcer l'évaluation d'un ``QuerySet`` en utilisant la fonction ``list()``. Par exemple:: liste_billet = list(Billet.objects.all()) Attention quand même car cela peut entraîner une très grosse consommation de mémoire parce que Django chargera chaque élément de la liste en mémoire. A l'opposé, lorsque que l'on parcourt un ``QuerySet`` par itération, on profite de la capacité des bases de données à charger les données et générer les objets au fur et à mesure qu'on en a besoin. Sérialisation de ``QuerySet`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Si on sérialise_ un ``QuerySet``, cela forcera le chargement de tous les résultats en mémoire avant la sérialisation. La sérialisation est généralement utilisée en tant que précurseur pour la sauvegarde et lorsque le ``QuerySet`` sauvé est restauré, on désire que les résultats soient aussi présents. Cela signifie que lorsque l'on désérialise un ``QuerySet``, cela contient les résultats au moment de la sérialisation, et non les résultats actuels de la base de données. Si l'on veut uniquement sérialiser les informations nécessaires pour récréer le ``QuerySet`` ultérieurement à partir de la base de données, il suffit de sérialiser l'attribut ``query`` du ``QuerySet``. On peut alors récréer le ``QuerySet`` original (sans aucun résultat chargé) en utilisant un code comme celui-ci:: >>> import pickle >>> requete = pickle.loads(s) # Supposons que 's' est la chaîne de sérialisation. >>> qs = MonModele.objects.all() >>> qs.query = requete # Restaure la requête original. .. _sérialise: http://docs.python.org/lib/module-pickle.html Limiter un QuerySet ------------------- On utilise la syntaxe de découpage de liste de Python pour limiter un ``QuerySet`` à un certain nombre de résultats. C'est l'équivalent des clauses ``LIMIT`` et ``OFFSET`` en SQL. Par exemple, pour renvoyer les 5 premiers objets (``LIMIT 5``), on aura:: Billet.objects.all()[:5] Pour renvoyer cinq objets, du sixième au dixième (``OFFSET 5 LIMIT 5``), on aura:: Billet.objects.all()[5:10] On peut aussi découper à partir de l'élément 'N' jusqu'à la fin du ``QuerySet``. Par exemple, pour renvoyer tous les éléments à partir du sixième, on aura:: Billet.objects.all()[5:] L'implémentation de l'exemple précédent en SQL varie selon la base de données utilisée, mais dans tous les cas, cette syntaxe est supportée. En général, le découpage d'un ``QuerySet`` renvoie un nouveau ``QuerySet`` sans évaluation de la requête. Ça n'est pas le cas lorsqu'on utilise le paramètre "step" de la syntaxe de découpage de Python. Dans l'exemple suivant, la requête sera exécutée pour obtenir un objet sur deux dans l'intervalle allant du 1er au 10ème:: Billet.objects.all()[:10:2] Pour récupérer un seul objet, plutôt qu'une liste (``SELECT foo FROM bar LIMIT 1``), utilisez un simple index plutôt qu'un intervalle. L'exemple ci-dessous renvoie le premier objet, après les avoir mis dans l'ordre alphabétique en fonction de leur titre:: Billet.objects.order_by('titre')[0] Le résultat sera à peu près le même avec l'instruction suivante:: Billet.objects.order_by('titre')[0:1].get() A noter cependant que la première syntaxe lèvera une exception ``IndexError`` si aucun objet ne correspond aux critères, alors que la seconde lèvera une exception ``DoesNotExist``. Les méthodes des ``QuerySets`` qui renvoient d'autres ``QuerySets`` ------------------------------------------------------------------- Django fournit une série de méthodes d'affinage des ``QuerySets``, qui modifient soit le type de résultat renvoyé soit la manière dont la requête SQL est exécutée. ``filter(**kwargs)`` ~~~~~~~~~~~~~~~~~~~~ Renvoie un nouveau ``QuerySet`` contenant les objets qui correspondent aux critères de recherche. Les paramètres de recherche (``**kwargs``) doivent être dans le format décrit dans la rubrique `Recherche sur un champ`_ plus bas. Si il y a plusieurs paramètres, ils sont ajoutés en utilisant ``AND`` dans l'instruction SQL sous-jacente. ``exclude(**kwargs)`` ~~~~~~~~~~~~~~~~~~~~~ Renvoie un nouveau ``QuerySet`` contenant tous les objets qui ne correspondent *pas* aux critères de recherche. Les paramètres de recherche (``**kwargs``) doivent être dans le format décrit dans la rubrique `Recherche sur un champ`_. Si il y a plusieurs paramètres, ils sont joints dans la requête SQL par un ``AND`` et contenus dans un ``NOT()``. Dans cet exemple, tous les enregistrements dont ``date_publication`` est postérieur au 3/1/2005 ET ceux dont le champ ``titre`` est "bonjour":: Billet.objects.exclude(date_publication__gt=datetime.date(2005, 1, 3), titre='bonjour') La requête SQL correspondante est:: SELECT ... WHERE NOT (date_publication > '2005-1-3' AND titre = 'Bonjour') Dans ce deuxième exemple, tous les enregistrements dont ``date_publication`` est postérieur au 3/1/2005 OU ceux dont le champ ``titre`` est "bonjour":: Billet.objects.exclude(date_publication__gt=datetime.date(2005, 1, 3)).exclude(titre='bonjour') La requête SQL correspondante est:: SELECT ... WHERE NOT date_publication > '2005-1-3' AND NOT titre = 'bonjour' Ce dernier exemple est donc plus restrictif. ``order_by(*fields)`` ~~~~~~~~~~~~~~~~~~~~~ Par défaut, les résultats renvoyés par un ``QuerySet`` sont classés d'après le tuple de classement du paramètre ``ordering`` de la classe ``Meta`` du modèle. On peut redéfinir cet ordre au niveau de chaque ``QuerySet`` en utilisant sa méthode ``order_by``. Exemple:: Billet.objects.filter(date_publication__year=2005).order_by('-date_publication', 'titre') Le résultat de ce ``QuerySet`` sera classé en ordre décroissant du champ ``date_publication``, puis en ordre croissant du champ ``titre``. Le signe "moins" dans ``"-date_publication"`` indique l'ordre décroissant. L'ordre croissant est implicite. Pour obtenir un ordre aléatoire, utilisez ``"?"``:: Billet.objects.order_by('?') Note: Les requêtes ``order_by('?')`` peuvent être coûteuses et lentes selon la base de données utilisée. Pour classer sur un champ se trouvant dans une autre table, on utilise la même syntaxe qu'avec les requêtes sur les relations des modèles. C'est-à-dire, le nom du champ, suivi par double soulignement (``__``), suivi par le nom du champ dans le nouveau modèle, et ainsi de suite jusqu'au modèle désiré. Par exemple:: Billet.objects.order_by('blog__nom', 'titre') Si on essaye de classer par un champ qui est une relation vers un autre modèle, Django utilisera l'ordre par défaut défini dans le modèle relié (ou trier par la clef primaire du modèle relié s'il n'y a pas de ``Meta.ordering`` spécifié). Par exemple:: Billet.objects.order_by('blog') ...est identique à:: Billet.objects.order_by('blog__id') ...puisque le modèle ``Blog`` n'a pas d'ordre prédéfini. Il faut faire attention à l'utilisation du tri par un champ sur un objet relié lorsqu'on utilise aussi ``distinct()``. Voir la note dans la section `distinct()`_ pour comprendre comment le tri par objet relié peut modifié les résultats attendus. Il est toléré de spécifier un champ multi-valué pour trier les résultats (par exemple, un champ ``ManyToMany``). Normalement, cela n'est pas une chose sensé et cela est vraiment une fonctionnalité pour un usage avancé. Cependant, si vous savez que les données filtrées de votre ``QuerySet`` ou les données disponibles implique qu'il n'y aura qu'un tri possible pour chaque élément sélectionné, ce tri peut-être exactement ce que vous cherchiez. Utilisez le tri sur champ multi-valué avec précaution et vérifié que les résultats obtenus sont ceux désirés. **Nouveau dans la version de développement:** Si on ne souhaite qu'aucun ordre ne soit appliqué à la requête, pas même l'ordre par défaut, on appelle ``order_by()`` sans paramètres. **Nouveau dans la version de développement:** La syntaxe pour trier grâce aux objets reliés à changer. Voir la `documentation Django 0.96`_ pour l'ancienne syntaxe. .. _documentation Django 0.96: http://www.djangoproject.com/documentation/0.96/model-api/#floatfield On ne peut pas préciser si le tri doit être sensible à la casse, Django renverra le résultat tel qu'il est renvoyé par votre base de données. ``reverse()`` ~~~~~~~~~~~~~ **Nouveau dans la version de développement** On utilise la méthode ``reverse()`` pour inverser l'ordre dans lequel ont été renvoyé les éléments du ``QuerySet``. L'appel à ``reverse()`` une seconde fois, restaure l'ordre initial. Pour retrouver les cinq ''derniers'' éléments d'un ``QuerySet``, on peut utiliser ceci:: mon_queryset.reverse()[:5] Notez que cela n'est pas exactement la même chose que le découpage à partir de la fin d'une séquence en Python. L'exemple précédent retournera le dernier élément en premier, puis le pénultième et ainsi de suite. S'il s'agissait d'une séquence Python et que nous regardions ``seq[:-5]``, le premier élément serait le cinquième en partant de la fin. Django ne supporte pas ce mode d'accès (découpage à partir de la fin), car il est impossible de la faire efficacement en SQL. ``distinct()`` ~~~~~~~~~~~~~~ Renvoie un nouveau ``QuerySet`` dont la requête SQL utilise ``SELECT DISTINCT``. Cela permet d'éliminer les doublons. Par défaut, un ``QuerySet`` n'élimine pas les doublons. En pratique ça n'est généralement pas un problème parce que, avec des requêtes simple comme ``Blog.objects.all()`` , il n'y a pas possibilité de doublons. Par contre, lorsqu'on fait une requête sur plusieurs tables, cela peut arriver, rendant utile l'usage de la méthode ``distinct()``. .. note:: Chaque champ utilisé lors d'un appel à ``ordre_by()`` est inclut dans la colonne ``SELECT``. Cela peut parfois mener à des résultats inattendus lorsqu'on l'utilise en conjonction avec ``distinct()``. Si vous trié à partir d'un champ sur un objet relié, ces champs seront ajoutés aux colonnes sélectionnées et créeront peut-être des duplicatas. Puisque les colonnes supplémentaires n'apparaissent pas dans le résultat renvoyé (elles ne sont présentes que pour le tri), cela peut parfois renvoyer des résultats non distincts. Pareillement, si vous utilisez une requête ``values()`` pour réduire les colonnes sélectionnées, ces dernières, utilisée dans n'importe quel ``order_by()`` (ou l'ordre par défaut du modèle) seront toujours impliqué et affecteront peut-être l'unicité des résultats. La morale, ici, est que si on utilise ``distinct()``, il faut faire attention à l'ordre par objet relié. Pareillement, lorsque l'on utilise ``distinct()`` avec ``values()``. ``values(*fields)`` ~~~~~~~~~~~~~~~~~~~ Renvoie un ``ValuesQuerySet`` -- Il s'agit d'un ``QuerySet`` représenté sous forme d'une liste de dictionnaires, plutôt que d'une liste d'instances du modèle. Chacun de ces dictionnaires représente un objet. Chaque clef correspond à une propriété du modèle. L'exemple suivant illustre la différence de représentation entre les dictionnaires de ``values()`` et les listes d'instances habituelles:: # Cette liste contient une instance de l'objet Blog. >>> Blog.objects.filter(nom__startswith='Beatles') [Beatles Blog] # Cette liste contient un dictionnaire. >>> Blog.objects.filter(nom__startswith='Beatles').values() [{'id': 1, 'nom': 'Beatles Blog', 'titre': 'Toutes les dernières nouvelles des Beatles.'}] ``values()`` peut recevoir une liste d'arguments, ``*fields``, qui précisent à quels champs le ``SELECT`` doit être limité. Si ces champs sont spécifiés, les dictionnaires ne contiendront que les clefs/valeurs de champ pour les champs spécifiés. A défaut, ils contiendront les clefs et les valeurs de tous les champs de la table de la base de données. Exemple:: >>> Blog.objects.values() [{'id': 1, 'nom': 'Beatles Blog', 'titre': 'Toutes les dernières nouvelles des Beatles.'}], >>> Blog.objects.values('id', 'nom') [{'id': 1, 'nom': 'Beatles Blog'}] Il est aussi possible de récupérer les valeurs à partir d'une relation ``ForeignKey`` en utilisant le double soulignement pour séparer le nom des champs, tout comme pour l'appel à la commande ``filter()``. Par exemple:: >>> Billet.objects.values('blog__nom').distinct() [{'nom': 'Beatles Blog'}] Il faut préciser plusieurs subtilités: * La méthode ``values()`` ne renvoie rien pour les attributs ``ManyToManyField`` et lèvera une erreur si l'on essaye de lui passer ce type de champ. * Si l'on a un champ appelé ``foo`` de type ``ForeignKey``, l'appel par défaut à ``values()`` retournera une clé de dictionnaire appelée ``foo_id``, puisque c'est le nom de l'attribut du modèle caché qui enregistre la valeur effective (l'attribut ``foo`` renvoie au modèle relié). Lorsqu'on appelle ``values()`` avec comme paramètre des noms de champ, on peut aussi passer aussi bien ``foo`` ou ``foo_id`` pour avoir le même résultat (la clé du dictionnaire correspondera le nom de champ passé en paramètre). Par exemple:: >>> Billet.objects.values() [{'blog_id: 1, 'titre': u'Premier billet', ...}, ...] >>> Billet.objects.values('blog') [{'blog': 1}, ...] >>> Billet.objects.values('blog_id') [{'blog_id': 1}, ...] * Lorsqu'on utilise ``values()`` conjointement à ``distinct()``, il faut être conscient que l'ordre des résultats peut être modifié. Voir la note dans la section `distinct()`_, ci-dessus, pour plus de details. **Nouveau dans la version de développement::** Auparavant, il était impossible de passer ``blog_id`` à la méthode ``values()`` dans l'exemple précédent, était accepté uniquement ``blog``. Un ``ValuesQuerySet`` est utile lorsque l'on sait qu'on ne va avoir besoin que d'un petit nombre des champs disponibles et qu'on n'aura pas besoin de la fonctionnalité globale d'un objet. C'est plus pratique de sélectionner seulement les champs dont on a besoin. Pour finir, notons qu'un ``ValuesQuerySet`` est un descendant de ``QuerySet`` et qu'il hérite donc de ses méthodes. On peut utiliser ``filter()`` ou ``order_by()``... Oui, ces 2 exemples renvoient bien le même résultat:: Blog.objects.values().order_by('id') Blog.objects.order_by('id').values() Les créateurs de Django ont préféré que les méthodes qui affectent le SQL soient devant les méthodes (optionnelles) qui affectent la représentation (comme ``values()``). Mais ça n'a pas d'importance. ``values_list(*fields)`` ~~~~~~~~~~~~~~~~~~~~~~~~ **Nouveau dans la version de développement** Ceci est une méthode similaire à ``values()`` excepté qu'au lieu de retourner une liste de dictionnaires, elle retourne une liste de tuples. Chaque tuple contient la valeur du champ respectif passé en paramètre lors de l'appel à ``values_list()``. Le premier élément est le premier champ, etc. Par exemple:: >>> Billet.objects.values_list('id', 'titre') [(1, u'Premier billet'), ...] Si l'on passe en argument uniquement un seul champ, il est possible de passer aussi le paramètre ``flat``. Si celui-ci est vrai (``True``), les résultats retournés seront des valeurs simples, et non des 1-uplets. Un exemple devrait éclaircir la différence :: >>> Billet.objects.values_list('id').order_by('id') [(1,), (2,), (3,), ...] >>> Billet.objects.values_list('id', flat=True).order_by('id') [1, 2, 3, ...] C'est une erreur de passer l'argument ``flat`` lorsque plusieurs champs sont présents. Si l'on ne passe aucune valeur à ``values_list()``, cela retournera tous les champs du modèle, dans l'ordre de déclaration. ``dates(field, kind, order='ASC')`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Renvoie un ``DateQuerySet`` -- il s'agit d'un ``QuerySet`` d'objets ``datetime.datime``. Il contient autant d'éléments qu'il y a de valeurs distinctes pour le paramètre ``kind`` (catégorie) fourni. Le paramètre ``field`` doit être le nom d'un des champs de type ``DateField`` ou ``DateTimeField`` de votre modèle. Le paramètre ``kind`` doit être une chaîne ayant une des valeurs ``"year"``, ``"month"`` ou ``"day"``. Dans le cas d'un kind ``"month"`` ou ``"day"``, les dates renvoyées sont tronquées. La précision des dates renvoyées est au niveau du paramètre ``kind``. * ``"year"`` renvoie une liste de toutes les années distinctes pour ce champ. * ``"month"`` renvoie une liste de tous les paires mois/année distinctes pour ce champ. * ``"day"`` renvoie une liste de toutes les dates (jour/mois/année) distinctes pour ce champ Le paramètre ``order`` définit dans quel ordre sont classés les résultats. L'ordre croissant ``'ASC'`` est implicite, sinon on utilise ``'DESC'``. Exemples:: >>> Billet.objects.dates('date_publication', 'year') [datetime.datetime(2005, 1, 1)] >>> Billet.objects.dates('date_publication', 'month') [datetime.datetime(2005, 2, 1), datetime.datetime(2005, 3, 1)] >>> Billet.objects.dates('date_publication', 'day') [datetime.datetime(2005, 2, 20), datetime.datetime(2005, 3, 20)] >>> Billet.objects.dates('date_publication', 'day', order='DESC') [datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)] >>> Billet.objects.filter(titre__contains='Lennon').dates('date_publication', 'day') [datetime.datetime(2005, 3, 20)] ``none()`` ~~~~~~~~~~ **Nouveau dans la version de développement** Renvoie un ``EmptyQuerySet`` -- un ``QuerySet`` vide. Ca sert lorsque l'on sait qu'on doit renvoyer une liste vide, mais que la fonction appelante attend un résultat de type ``QuerySet``. Exemples:: >>> Billet.objects.none() [] ``all()`` ~~~~~~~~~~ **Nouveau dans la version de développement** Renvoie une ''copie'' du ``QuerySet`` actuel (ou une classe dérivée du ``QuerySet`` passé en paramètre). Cela peut être utile dans certaines situations où l'on désire passer en paramètre soit un manager de modèle, soit un ``QuerySet`` et pratiquer d'autres filtrages sur le résultat. On peut appeler sûrement ``all()`` sur un objet et on aura alors un ``QuerySet`` pour d'autres opérations. ``select_related()`` ~~~~~~~~~~~~~~~~~~~~ Renvoie un ``QuerySet`` qui aura automatiquement "suivi" les relations de clefs étrangères (foreign-key). Les objets reliés par ces relations seront mis en cache pour une utilisation ultérieure. Ceci permet d'obtenir des résultats (parfois beaucoup) plus importants en volume, mais le nombre de requêtes nécessaires sera réduit lorsqu'on utilisera les relations de clefs étrangères ultérieurement. Cet exemple illustre la différence entre une recherche normale et une recherche qui utilise ``select_related()``. Voici une recherche normale:: # Une requête est exécutée e = Billet.objects.get(id=5) # Une autre requête est exécutée pour récupérer l'objet relié par # la relation. b = e.blog Avec ``select_related``:: # Une requête est exécutée. e = Billet.objects.select_related().get(id=5) # Aucune requête n'est exécutée, l'objet e.blog a été instancié # et mis en cache lors de la requête précédente. b = e.blog ``select_related()`` suit autant de relations que possible. Soit le modèle suivant:: class Ville(models.Model): # ... class Personne(models.Model): # ... ville_naissance = models.ForeignKey(Ville) class Livre(models.Model): # ... auteur = models.ForeignKey(Personne) L'appel de ``Livre.objects.select_related().get(id=4)`` mettra en cache l'objet ``Personne`` relié à ``Livre``, puis l'objet ``Ville`` relié à ``Personne``:: b = Livre.objects.select_related().get(id=4) p = b.auteur # Pas de requête exécutée. c = p.ville_naissance # Pas de requête exécutée. sv = Livre.objects.get(id=4) # On n'utilise pas ``select_related()`` p = b.auteur # Une requête est exécutée. c = p.ville_naissance # Une requête est exécutée. Notons que ``select_related()`` n'exploite pas les relations de clefs étrangères qui ont ``null=True``. En général l'emploi de ``select_related()`` améliore grandement les performances en réduisant le nombre de requêtes SQL. Par contre, dans le cas où un grand nombre de relations s'enchaînent ``select_related()`` peut parfois finir par suivre "beaucoup trop" de relations, et peut générer des requêtes si importantes que cela finit par ralentir l'application. Dans ce cas, on peut utiliser le paramètre ``depth`` pour contrôler combien de niveaux de relations ``select_related()`` suivra effectivement:: b = Livre.objects.select_related(depth=1).get(id=4) p = b.auteur # Pas de requête exécuté. c = p.ville_naissance # Une requête est exécutée. L'argument ``depth`` est nouveau dans la version de développement. **Nouveau dans la version de développement:** Parfois on desire accéder qu'à un modèle spécifique relié à notre modèle racine, non à tous. Dans ces cas, on peut passer en argument le nom du champ relié à la méthode ``select_related()`` et cela ne suivra que cette relation. On peut aussi faire cela pour les modèles ayant plus d'une relation en séparant les noms des champs avec un double soulignement, comme pour les filtres. Par exemple, si on a le modèle suivant:: class Salle(models.Model): # ... immeuble = models.ForeignKey(...) class Classe(models.Model): # ... professeur = models.ForeignKey(...) salle = models.ForeignKey(Salle) sujet = models.ForeignKey(...) ...et que l'on désire travailler uniquement les attribut ``salle`` et ``sujet``, on peut juste écrire:: g = Classe.objects.select_related('salle', 'sujet') Ceci est aussi valide:: g = Classe.objects.select_related('salle__immeuble', 'sujet') ...et chargera la relation ``immeuble``. On peut uniquement se référer à une relation ``ForeignKey`` appartenant à la liste des champs passés en argument à ``select_related``. On *peut* faire référence à des champ ``ForeignKey`` ayant ``null=True`` (contrairement à l'appel par défaut à ``select_related()``). C'est une erreur d'utiliser à la fois une liste de champ et le paramètre ``depth`` dans le même appel à ``select_related()``, puisque ces options rentrent en conflit. ``extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Parfois la syntaxe de requête de Django ne parvient pas exprimer des clauses ``WHERE`` complexes. Pour ces cas particuliers, Django fournis le modificateur de ``QuerySet`` ``extra()`` qui permet d'injecter des clauses spécifiques dans le SQL généré par un ``QuerySet``. Par définition, ces critères supplémentaires peuvent ne pas être portables d'un moteur de base de données à l'autre, puisqu'il s'agit en fait d'écrire du code SQL. Par ailleurs, cela viole le concept DRY (Don't Repeat Yourself). Il faut donc l'utiliser le moins possible. ``extra()`` prend en paramètre au moins un argument parmi ``params``, ``select``, ``where`` ou ``tables``. (ils ne sont pas obligatoires mais il faut en utiliser au moins un). ``select`` L'argument ``select`` permet d'ajouter des champs supplémentaires à la clause ``SELECT``. Cela sera sous la forme d'un dictionnaire, dont la clef est le nom du champ calculé, et la valeur, la formule permettant de le calculer. Exemple:: Billet.objects.extra(select={'is_recent': "date_publication > '2006-01-01'"}) Le résultat sera donc une liste d'objets ``Billet`` ayant une propriété supplémentaire : un booléen nommé ``is_recent`` à True si le champ ``date_publication`` est une date supérieure au 1er janvier 2006. Django injecte le petit bout de SQL directement dans l'instruction ``SELECT``. La requête ressemblera à ça:: SELECT blog_billet.*, (date_publication > '2006-01-01') FROM blog_billet; L'exemple suivant est plus compliqué; il exécute une sous requête en donnant à chaque objet ``Blog`` du résultat de la requête principale, une propriété ``compteur_billet``, qui est le nombre d'objets ``Billet`` associés:: Blog.objects.extra( select={ 'compteur_billet': 'SELECT COUNT(*) FROM blog_Billet WHERE blog_billet.blog_id = blog_blog.id' }, ) (Dans ce cas précis, on exploite le fait que la requête fera déjà référence à la table ``blog_blog`` dans sa clause ``FROM``) La requête SQL générée ressemblera à ça:: SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_Billet WHERE blog_Billet.blog_id = blog_blog.id) FROM blog_blog; Notons que les parenthèses nécessaires à la syntaxe des sous requêtes pour la plupart des moteurs de base de données ne le sont pas pour les clauses ``select`` de Django. Par ailleurs certains moteurs de base de données ne supportent pas les sous requêtes (certaines anciennes versions de MySQL par exemple). **Nouveau dans la version de développement** Dans quelques rares cas, on désire passer des paramètres aux fragments SQL dans ``extra(select=...)``. Pour cela, on utilise le paramètre ``select_params``. Puisque ``select_params`` est une séquence et l'attribut ``select`` un dictionnaire, il faut faire attention à ce que les paramètres correspondent avec les parties supplémentaire sélectionnées. Dans cette situation, il faut utiliser un dictionnaire trié (``django.utils.datastructures.SortedDict``) pour la valeur sélectionnée, et non pas un dictionnaire normal de Python. Cela fonctionnera, par exemple:: Blog.objects.extra( select=SortedDict(('a', '%s'), ('b', '%s')), select_params=('un', 'deux')) ``where`` / ``tables`` On peut ajouter du code SQL à la clause ``WHERE`` en utilisant le paramètre ``where``, par exemple pour créer une jointure particulière. On peut ajouter manuellement des tables à la clause ``FROM`` en utilisant le paramètre ``tables``. ``where`` et ``tables`` acceptent l'un et l'autre une liste de chaînes. Tous les paramètres de ``where`` sont ajoutés aux éventuelles autres conditions de la clause ``WHERE`` avec un ``AND``. Exemple:: Billet.objects.extra(where=['id IN (3, 4, 5, 20)']) ...donnera, en gros, en SQL:: SELECT * FROM blog_Billet WHERE id IN (3, 4, 5, 20); Attention lorsqu'on utilise le paramètre ``tables``, si l'on spécifie des tables déjà utilisées dans la requête. Lorsqu'on ajoute des tables supplémentaires via le paramètre ``tables``, Django suppose que l'on que désire que cette table soit incluse une fois supplémentaire, si elle est déjà incluse. Ceci pose problème, puisque le nom de la table aura un alias. Si une table apparaît plusieurs fois dans une requête SQL, les occurrences suivantes doivent utiliser un alias pour que la base de données puisse les différencier. Si l'on fait référence à la table supplémentaire, que l'on a ajouté dans le paramètre ``where``, cela provoquera des erreurs. Normalement, on ajoute seulement des tables supplémentaires qui n'apparaissent pas dans la requête. Cependant, si le cas décrit précédemment apparaît, il existe quelques solutions. Premièrement, essayez de ne pas utiliser la table supplémentaire mais celle appartenant déjà à la requête. Si cela n'est pas possible, ajoutez l'appel ``extra()`` au début de la construction du ``QuerySet`` pour que votre table soit la première utilisation de cette table. Finalement, en dernier recours, regardez la requête produite et réécrivez votre ``where`` avec un alias pour la table supplémentaire. Cet alias sera le même à chaque fois que vous construirez un ``QuerySet`` de la même façon. ``order_by`` Si l'on désire classer le ``QuerySet`` renvoyé en utilisant des champs ou tables inclus via ``extra()``, on peut utiliser le paramètre ``order_by`` à ``extra()`` et lui passer une séquence de chaîne de caractères. Ces chaînes de caractères doivent être soit des champs de modèle (comme dans la méthode ``order_by()`` normal des ``QuerySet``), soit de la forme ``nom_table.nom_colonne`` ou un alias pour la colonne spécifiée grâce au paramètre ``select`` dans ``extra()``. Par exemple:: q = Billet.objects.extra(select={'est_recent': "date_publication > '2006-01-01'"}) q = q.extra(order_by = ['-est_recent']) Cela classera tous les éléments, pour lesquels ``est_recent`` est vrai, au début de l'ensemble résultant (``True`` est classé avant ``False`` dans l'ordre décroissant). Cela montre, par ailleurs, que vous pouvez faire plusieurs appels à ``extra()`` et que cela se comportera comme vous le souhaitez (ajouter des nouvelles contraintes à chaque fois). ``params`` Les paramètres ``select`` et ``where`` décrits ci-dessus peuvent utiliser la syntaxe Python de remplacement de chaîne -- `'%s'`` -- pour indiquer quels paramètres le moteur de base de données doit mettre entre guillemets. L'argument ``params`` est la liste des éventuels paramètres supplémentaires à substituer. Exemple:: Billet.objects.extra(where=['titre=%s'], params=['Lennon']) Utilisez toujours ``params`` plutôt que d'imbriquer directement les valeurs dans ``select`` ou ``where``. Avec ``params`` on est sûr que les guillemets utilisés correspondent au moteur de base de données, ou que d'éventuels guillemets seront correctement échappés. Ne pas faire:: Billet.objects.extra(where=["titre='Lennon'"]) Mais faire:: Billet.objects.extra(where=['titre=%s'], params=['Lennon']) **Nouveau dans la version de développement:** L'argument ``select_params`` d'``extra()`` est nouveau. Avant, on pouvait essayer de passer des paramètres pour ``select`` dans l'argument ``params``, mais cela était très instable. Les méthodes de ``QuerySets`` qui ne renvoient pas de ``QuerySet`` ------------------------------------------------------------------ Les méthodes suivantes évaluent le ``QuerySet`` duquel elles sont appelées et renvoient *autre chose* qu'un ``QuerySet``. Ces méthodes n'utilisent pas le cache (voir plus bas `Les QuerySets et leurs caches`_). Elles exécutent une requête SQL à chaque fois qu'elles sont appelées. ``get(**kwargs)`` ~~~~~~~~~~~~~~~~~ Renvoie l'objet correspondant au paramètre de recherche donné. Ce paramètre doit être fourni au format décrit dans `Recherche sur un champ`_. ``get()`` lève une exception ``MultipleObjetcsReturned`` si plusieurs objets sont trouvés. L'exception ``MultipleObjetcsReturned`` est un attribut de la classe modèle. Par exemple, ce qui suit lèvera une exception ``MultipleObjetcsReturned`` s'il y a plus d'un auteur ayant pour nom 'John':: Auteur.objects.get(nom='John') # lève Auteur.MultipleObjectsReturned ``get()`` lève une exception ``DoesNotExist`` si aucun objet n'est trouvé. L'exception ``DoesNotExist`` est une propriété de la classe sur modèle. Exemple:: Billet.objects.get(id='foo') # lève l'exception Billet.DoesNotExist L'exception ``DoesNotExist`` hérite de ``django.core.exceptions.ObjectDoesNotExist``, on peut donc intercepter plusieurs exceptions ``DoesNotExist`` dans le même bloc ``except``. Exemple:: from django.core.exceptions import ObjectDoesNotExist try: e = Billet.objects.get(id=3) b = Blog.objects.get(id=1) except ObjectDoesNotExist: print "L'objet Billet ou l'objet Blog n'existe pas." ``create(**kwargs)`` ~~~~~~~~~~~~~~~~~~~~ Méthode pratique pour la création d'un objet et son enregistrement en une seule ligne. Ainsi:: p = Personne.objects.create(prenom="Bruce", nom="Springsteen") et:: p = Personne(prenom="Bruce", nom="Springsteen") p.save() sont équivalents. ``get_or_create(**kwargs)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Méthode pratique pour rechercher un objet selon certains critères de recherche, ou le créer si il n'existe pas. Cela renvoie un tuple ``(object, created)``, ou ``object`` est l'objet récupéré ou créé, et ``created`` un booléen indiquant si l'objet a été créé ou pas. Ca permet d'éviter un paragraphe standard de code, et c'est surtout pratique pour les scripts d'importations de données. Par exemple:: try: obj = Personne.objects.get(prenom='John', nom='Lennon') except Personne.DoesNotExist: obj = Personne(prenom='John', nom='Lennon', date_naissance=date(1940, 10, 9)) obj.save() Grâce à ``get_or_create()``, on peut abréger le code ci-dessus, surtout dans le cas d'un modèle complexe où le nombre de champs augmente:: obj, created = Personne.objects.get_or_create(prenom='John', nom='Lennon', defaults={'date_naissance': date(1940, 10, 9)}) Tous les arguments passés à ``get_or_create()`` -- à part ``defaults`` qui est optionnel -- seront utilisés lors du ``get()``. Si un objet est trouvé, ``get_or_create()`` renvoie un tuple constitué de cet objet et du booléen ``False``. Si *aucun* objet n'est trouvé, ``get_or_create()`` instanciera puis enregistrera un nouvel objet, puis renverra un tuple constitué de ce nouvel objet et du booléen ``True``. Ce nouvel objet sera créé selon l'algorithme suivant:: defaults = kwargs.pop('defaults', {}) params = dict([(k, v) for k, v in kwargs.items() if '__' not in k]) params.update(defaults) obj = self.model(**params) obj.save() En français, ça signifie : qui commence par n'importe lequel des arguments de recherche qui ne soient pas dans ``'defaults'`` et qui ne contient pas un double soulignement (ce qui indiquerait une recherche non-exacte). Puis ajoutez le contenu de ``defaults``, en écrasant une clef si nécessaire, et utilisez le résultat en tant qu'argument mot-clef pour la classe du modèle. Si vous aviez un champ ``defaults`` et vouliez faire une recherche exacte dessus, il faut utiliser ``'defaults__exact'``, comme suit:: Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'}) Un dernier mot à propos de ``get_or_create()`` dans les vues. Comme mentionné plus haut, ``get_or_create()`` est surtout utile dans des scripts pour analyser les données. Si vous êtes amené à utiliser ``get_or_create()`` dans une vue, il faut vraiment le faire que dans une requête ``POST`` à moins d'avoir une bonne raison. Pour plus d'information, voyez `Safe methods`_ dans la RFC de HTTP. .. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 ``count()`` ~~~~~~~~~~~ Renvoie un entier représentant le nombre d'objets de la base correspondant au ``QuerySet``. ``count()`` ne lève jamais d'exception. Exemple:: # Retourne le nombre total d'objets billet dans la base Billet.objects.count() # Retourne le nombre total d'objets billet dont la propriété titre # contient 'Lennon' Billet.objects.filter(titre__contains='Lennon').count() Au niveau SQL, ``count()`` exécute un ``SELECT COUNT(*)``. Il faut toujours utiliser ``count()`` plutôt que d'appeler la fonction ``len()`` sur un objet Python contenant tous les objets d'une table. Selon le moteur de base de données utilisé, (PostgreSQL ou MySQL), ``count()`` peut renvoyer un entier long plutôt qu'un entier Python. C'est une petite bizarrerie de l'implémentation sous-jacente qui ne devrait pas poser de vrai problème. ``in_bulk(id_list)`` ~~~~~~~~~~~~~~~~~~~~ Accepte une liste de champs de clef primaire et renvoie un dictionnaire établissant la correspondance entre chaque champ de clef primaire et une instance de l'objet pour l'ID donné. Exemple:: >>> Blog.objects.in_bulk([1]) {1: Le Blog des Beatles} >>> Blog.objects.in_bulk([1, 2]) {1: Le Blog des Beatles, 2: Parlons du Camembert} >>> Blog.objects.in_bulk([]) {} Si l'on fournit une liste vide à ``in_bulk()``, on obtient un dictionnaire vide. ``iterator()`` ~~~~~~~~~~~~~~ Evalue le ``QuerySet`` (en effectuant un requête) et renvoie un `itérateur`_ vers les résultats. Un ``QuerySet`` lira tous ces résultats et instanciera tous les objets correspondants dès le premier accès. A l'inverse, ``iterator()`` lira les résultats et instanciera les objets par paquet, au fur et à mesure. Pour un ``QuerySet`` qui retourne un nombre important d'objets, ceci permet d'obtenir de meilleures performances et de réduire considérablement la mémoire utilisée. Remarquez que l'utilisation d'``iterator()`` sur un ``QuerySet`` ayant déjà été évalué forcera une nouvelle évaluation de celui-ci, dupliquant ainsi la requête. .. _itérateur: http://www.python.org/dev/peps/pep-0234/ ``latest(field_name=None)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Renvoie le dernier objet entré dans la table, par rapport à la date, en utilisant comme champ de date le champ ``field_name`` fourni. Dans cet exemple, le dernier ``Billet`` de la table est renvoyé, selon le champ ``date_publication`` :: Billet.objects.latest('date_publication') Si la classe ``Meta`` du modèle fournit l'attribut ``get_latest_by``, on peut laisser vide l'argument ``field_name`` de ``latest()``. Django utilisera le champ spécifié dans ``get_latest_by`` par défaut. Tout comme ``get()``, ``latest()`` lèvera l'exception ``DoesNotExist`` si aucun objet n'existe pour les paramètres donnés. Notez que ``latest()`` existe simplement pour des raisons pratiques et de lisibilité. Recherche sur un champ ---------------------- La recherche sur un champ vous permet de spécifier le contenu de la clause ``WHERE`` d'une requête SQL. Elle est spécifiée en tant qu'argument mot-clef des méthodes de ``QuerySet`` ``filter()``, ``exclude()`` et ``get()``. Une recherche de base sur l'argument mot-clef prend la forme ``champ__typederecherche=valeur``. (Il s'agit d'un double soulignement). Par exemple:: Billet.objects.filter(date_publication__lte='2006-01-01') peut être traduit (grossièrement) par la requête SQL suivante:: SELECT * FROM blog_billet WHERE date_publication <= '2006-01-01'; .. admonition:: Comment cela est possible ? Python a la capacité de définir des fonctions acceptant des arguments nom-valeur arbitraires dont les noms et valeurs seront évalués à l'exécution. Pour plus d'informations, voir `Arguments mot-clef`_ dans le tutoriel Python officiel. .. _`Arguments mot-clef`: http://docs.python.org/tut/node6.html#SECTION006720000000000000000 Si on passe en argument un mot-clef invalide, la fonction de recherche lèvera l'exception ``TypeError``. L'API de la base de données accepte les types de recherche suivants: exact ~~~~~ Correspondance exacte. Si la valeur fournie pour la comparaison est ``None``, elle sera interprétée en tant que SQL ``NULL`` (Voir isnull_ pour plus de détails). Exemples:: Billet.objects.get(id__exact=14) Billet.objects.get(id__exact=None) Equivalents SQL :: SELECT ... WHERE id = 14; SELECT ... WHERE id = IS NULL; **Nouveau dans la version de développement:** La sémantique de ``id__exact=None`` a changé dans la version de développement. Auparavant, elle était (intentionnellement) convertie en ``WHERE id = NULL`` au niveau SQL, ce qui ne correspondait avec aucune données enregistrée. Suite à une modification, elle se comporte maintenant comme ``id__isnull=True``. iexact ~~~~~~ Correspondance exacte insensible à la casse. Exemple:: Blog.objects.get(nom__iexact='beatles blog') Equivalent SQL :: SELECT ... WHERE nom ILIKE 'beatles blog'; Notez que cela correspondera à ``'Beatles Blog'``, ``'beatles blog'``, ``'BeAtLes BLoG'``, etc. contains ~~~~~~~~ Test de contenu sensible à la casse. Exemple:: Billet.objects.get(titre__contains='Lennon') Equivalent SQL :: SELECT ... WHERE titre LIKE '%Lennon%'; Notez que cela correspondera au titre ``'On rend hommage à Lennon '`` mais pas à ``'on rend hommage à lennon'``. SQLite ne supporte pas les déclarations ``LIKE`` sensibles à la casse. ``contains`` se comportera comme ``icontains`` pour SQLite. icontains ~~~~~~~~~ Test de contenu insensible à la casse. Exemple:: Billet.objects.get(titre__icontains='Lennon') Equivalent SQL:: SELECT ... WHERE titre ILIKE '%Lennon%'; gt ~~ Supérieur à. Exemple:: Billet.objects.filter(id__gt=4) Equivalent SQL:: SELECT ... WHERE id > 4; gte ~~~ Supérieur ou égal à. lt ~~ Inférieur à. lte ~~~ Inférieur ou égal à. in ~~ Appartient à la liste donnée. Exemple:: Billet.objects.filter(id__in=[1, 3, 4]) Equivalent SQL:: SELECT ... WHERE id IN (1, 3, 4); Il est aussi possible d'utiliser un ``QuerySet`` pour évaluer dynamiquement la liste de valeurs au lieu de fournir une liste de valeurs littérales. Le ``QuerySet`` doit être réduit en une liste de valeurs individuelles en utilisant la méthode ``values()``, et puis convertit en une requête en utilisant l'attribut ``query``:: Billet.objects.filter(blog__in=Blog.objects.filter(nom__contains='Fromage').values('pk').query) Ce ``QuerySet`` sera évalué en tant que sous-sélection:: SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Fromage%') startswith ~~~~~~~~~~ Commence par (sensible à la casse). Exemple:: Billet.objects.filter(titre__startswith='Will') Equivalent SQL:: SELECT ... WHERE titre LIKE 'Will%'; SQLite ne supporte pas les déclarations ``LIKE`` sensibles à la casse. ``startswith`` se comportera comme ``istartswith`` pour SQLite. istartswith ~~~~~~~~~~~ Commence par (insensible à la casse). Exemple:: Billet.objects.filter(titre__istartswith='will') Equivalent SQL:: SELECT ... WHERE titre ILIKE 'Will%'; endswith ~~~~~~~~ Termine par (sensible à la casse). Exemple:: Billet.objects.filter(titre__endswith='cats') Equivalent SQL:: SELECT ... WHERE titre LIKE '%cats'; SQLite ne supporte pas les déclarations ``LIKE`` sensibles à la casse. ``endswith`` se comportera comme ``iendswith`` pour SQLite. iendswith ~~~~~~~~~ Termine par (insensible à la casse). Exemple:: Billet.objects.filter(titre__iendswith='will') Equivalent SQL:: SELECT ... WHERE titre ILIKE '%will' range ~~~~~ Test de plage (inclusif). Exemple:: date_debut = datetime.date(2005, 1, 1) date_fin = datetime.date(2005, 3, 31) Billet.objects.filter(date_publication__range=(date_debut, date_fin)) Equivalent SQL:: SELECT ... WHERE date_publication BETWEEN '2005-01-01' and '2005-03-31'; On peut utiliser ``range`` partout où on peut utiliser ``BETWEEN`` en SQL (pour les dates, les nombres et même les caractères). year ~~~~ Pour les champs de date ou de date/heure, correspondance exacte de l'année. Accepte une année sous la forme de quatre chiffres. Exemple:: Billet.objects.filter(date_publication__year=2005) Equivalent SQL:: SELECT ... WHERE EXTRACT('year' FROM date_publication) = '2005'; (La syntaxe SQL exacte est différente pour chaque moteur de base de données.) month ~~~~~ Pour les champs de date ou de date/heure, correspondance exacte du mois. Accepte un entier de 1 (Janvier) à 12 (Décembre). Exemple:: Billet.objects.filter(date_publication__month=12) Equivalent SQL:: SELECT ... WHERE EXTRACT('month' FROM date_publication) = '12'; (La syntaxe SQL exacte est différente pour chaque moteur de base de données.) day ~~~ Pour les champs de date ou de date/heure, correspondance exacte du jour. Exemple:: Billet.objects.filter(date_publication__day=3) Equivalent SQL:: SELECT ... WHERE EXTRACT('day' FROM date_publication) = '3'; (La syntaxe SQL exacte est différente pour chaque moteur de base de données.) Notez que cela correspondera à toute données enregistrée ayant le troisième jour du mois comme date_publication, comme le 3 janvier, le 3 juillet, etc. isnull ~~~~~~ Accepte soit ``True`` soit ``False``, qui correspond aux requêtes SQL, respectivement, ``IS NULL`` et ``IS NOT NULL``. Exemple:: Billet.objects.filter(date_publication__isnull=True) Equivalent SQL:: SELECT ... WHERE date_publication IS NULL; search ~~~~~~ Une recherche booléenne de texte intégral (full-text), ce qui permet de profiter de l'indexation en texte intégral. Cela fonctionne de manière similaire à ``contains`` mais beaucoup plus rapidement à cause de l'indexation texte intégral. Notez que cela n'est disponible que sous MySQL et qu'une manipulation directe de la base de données est nécessaire pour ajouter l'index en texte intégral. regex ~~~~~ **Nouveau dans la version en développement de Django** Correspondance d'expression régulière sensible à la casse. La syntaxe des expressions régulières est celle utilisée par la base de données. Dans le cas SQLite, qui ne supporte pas nativement une recherche par expression régulière, la syntaxe est celle du module Python ``re``. Exemple:: Billet.objects.get(title__regex=r'^(An?|The) +') Equivalents SQL:: SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; -- MySQL SELECT ... WHERE REGEXP_LIKE(title, '^(an?|the) +', 'c'); -- Oracle SELECT ... WHERE title ~ '^(An?|The) +'; -- PostgreSQL SELECT ... WHERE title REGEXP '^(An?|The) +'; -- SQLite L'utilisation de chaîne de caractères brutes (par exemple, ``r'foo'`` à la place de ``'foo'``) pour la syntaxe des expressions régulières est recommandée. iregex ~~~~~~ **Nouveau dans la version en développement de Django** Correspondance d'expression régulière insensible à la casse. Exemple:: Billet.objects.get(title__iregex=r'^(an?|the) +') Equivalents SQL:: SELECT ... WHERE title REGEXP '^(an?|the) +'; -- MySQL SELECT ... WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i'); -- Oracle SELECT ... WHERE title ~* '^(an?|the) +'; -- PostgreSQL SELECT ... WHERE title REGEXP '(?i)^(an?|the) +'; -- SQLite Les recherches par défaut sont exactes -------------------------------------- Si vous ne fournissez pas un type de recherche, c'est-à-dire que votre mot-clef argument ne contient pas de double soulignement, la recherche est présumée être une recherche de type ``exact``. Par exemple, les deux déclarations suivantes sont équivalents:: Blog.objects.get(id__exact=14) # Forme explicite Blog.objects.get(id=14) # __exact est implicite Ceci dans un but pratique, car les recherches ``exact`` sont les plus courantes. Le raccourci de recherche pk ---------------------------- Dans un but pratique, Django fourni un type de recherche ``pk``, qui signifie "clef primaire" (primary_key). Dans l'exemple du modèle ``Blog``, la clef primaire est le champ ``id``. Ces trois déclarations sont donc équivalentes:: Blog.objects.get(id__exact=14) # Forme explicite Blog.objects.get(id=14) # __exact est implicite Blog.objects.get(pk=14) # pk implique id__exact L'utilisation de ``pk`` n'est pas limitée aux requêtes ``__exact``. N'importe quel terme de requête peut être combiné avec ``pk`` pour effectuer une requête sur la clef primaire du modèle:: # Récupère les entrées de blog ayant pour id 1, 4 et 7 Blog.objects.filter(pk__in=[1,4,7]) # Récupère toutes les entrées ayant un id > 14 Blog.objects.filter(pk__gt=14) Les recherches ``pk`` fonctionnent aussi lors de jointures croisées. Par exemple, ces trois déclarations sont équivalentes:: Billet.objects.filter(blog__id__exact=3) # Forme explicite Billet.objects.filter(blog__id=3) # __exact est implicite Billet.objects.filter(blog__pk=3) # __pk implique __id__exact .. note:: A cause de ce raccourci, vous ne pouvez pas avoir de champ nommé ``pk`` qui ne soit pas la clef primaire du modèle. Cela sera toujours remplacé par le nom de la clef primaire du modèle dans les requêtes. Les recherches étendues aux relations de liaison ------------------------------------------------ Django offre un moyen puissant et intuitif de "suivre" les relations de liaison dans une recherche, en prenant soin des ``JOIN``\s SQL pour vous automatiquement, en coulisse. Pour étendre une relation de liaison, utilisez simplement le nom des champs apparentés à travers le modèle, séparés par un double soulignement, jusqu'à ce que vous arriviez au champ désiré. Cet exemple récupère tout les objets ``Billet`` ayant un champ ``Blog`` dont le champ ``nom`` est ``'Beatles Blog'``:: Billet.objects.filter(blog__nom__exact='Beatles Blog') Cette étendue peut être aussi profonde que nécessaire. Cela fonctionne aussi en arrière. Pour référencer une relation de liaison "inversée", utilisez juste le nom du modèle en minuscule. Cette exemple récupère tous les objets ``Blog`` qui ont au moins un champ ``Billet`` dont le champ ``titre`` contient ``'Lennon'``:: Blog.objects.filter(billet__titre__contains='Lennon') Parcours des relations multivaluées ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Nouveau dans la version en développement de Django** Lorsque l'on filtre un objet en se basant sur un champ ``ManyToManyField`` ou sur un champ ``ForeignKeyField`` inversé, il y a deux manières différentes de trier qui peuvent vous intéresser. Prenons le cas de la relation ``Blog``/``Billet`` (``Blog`` vers ``Billet`` est une relation un-vers-plusieurs). Il peut être intéressant de retrouver les blogs qui ont un billet ayant pour titre *"Lennon"* et ayant été publié aujourd'hui. Ou alors, on désire retrouver les blogs qui ont un billet ayant pour titre *"Lennon"* et un autre billet ayant été publié aujourd'hui. Puisque plusieurs billets sont associés à un seul ``Blog``, ces deux requêtes sont possibles et ont un sens dans certaines situations. Le même type de situation se présente avec un champ ``ManyToManyField``. Par exemple, si un ``Billet`` a un champ ``ManyToManyField`` nommé ``etiquettes`` et que l'on désire trouver les billets ayant une étiquette nommé *"musique"* ou *"groupe"*, ou que l'on désire utiliser un billet qui contient une étiquette avec le nom *"musique"* et le statut de *"publique"*. Pour faire face à ces deux situations, Django utilise une manière cohérente de traitement pour les appels ``filter()`` et ``exclude()``. Tout ce qui appartient à un unique appel ``filter()`` est appliqué simultanément pour filtrer les éléments correspondants à ces exigences. Les appels successifs à ``filter()`` restreindront l'ensemble des objets, mais pour une relation multivaluée, ils s'appliqueront à n'importe quel objet lié au modèle de base, pas nécessairement aux objets sélectionnés par un appel précédent à ``filter()``. Cela peut paraître confus, donc voici un exemple pour clarifier. Pour sélectionner tous les blogs qui ont un billet ayant pour titre *"Lennon"* et ayant été publié aujourd'hui, on peut écrire:: Blog.objects.filter(billet__titre__contains='Lennon', billet__date_publication=datetime.date.today()) Pour sélectionner les blogs qui ont un billet ayant pour titre *"Lennon"* et, *en même temps*, un autre billet ayant été publié aujourd'hui, on peut écrire:: Blog.objects.filter(billet__titre__contains='Lennon').filter( billet__date_publication=datetime.date.today()) Dans ce second exemple, le premier filtre restreint le ``QuerySet`` à tous les blogs liés à ce type de billet. Le second filtre restreint l'ensemble des blogs *supplémentaires* à ceux qui sont liés au second type de billet. Les billets sélectionnés par le second filtre peuvent être ou non les mêmes que ceux du premier filtre. Ici, on filtre les éléments ``Blog`` avec chaque filtre, et non les éléments ``Billet``. Tout ce comportement s'applique aussi à ``exclude()`` : toutes les conditions d'une seule déclaration ``exclude()`` sont appliquées à une seule instance (si ces conditions concernent la même relation multivaluée). Les conditions dans les appels à ``filter()`` ou ``exclude()`` suivants, qui feront référence à la même relation, peuvent filtrer sur différents objets liés. Échapper les symboles pourcent et soulignement dans une déclaration LIKE ------------------------------------------------------------------------ Les recherches de champ équivalentes aux déclarations ``LIKE`` de SQL (``iexact``, ``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith`` and ``iendswith``) échapperont automatiquement les deux caractères spéciaux utilisés dans la déclaration ``LIKE``, le pourcent et le soulignement. (Dans une déclaration ``LIKE``, le symbole pourcent représente un joker pour plusieurs caractères et un soulignement représente un joker pour un unique caractère.) Cela signifie que les choses fonctionnent intuitivement, il n'y a pas de faille dans l'abstraction. Par exemple, pour récupérer toutes les entrées contenant un signe pourcent, utilisez simplement le signe pourcent comme tout autre caractère:: Billet.objects.filter(titre__contains='%') Django place les guillemets pour vous. La requête SQL résultante devrait ressembler à quelque chose comme ca:: SELECT ... WHERE titre LIKE '%\%%'; Les soulignements fonctionnent pareillement. Les symboles pourcent et soulignement sont gérés pour vous de manière transparante. Les QuerySets et leurs caches ----------------------------- Chaque ``QuerySet`` contient un cache afin de minimiser le nombre d'accès à la base de données. Il est important de comprendre leurs fonctionnement pour écrire le code le plus efficace. Dans tout nouveau ``QuerySet``, le cache est vide. A la première évaluation de celui-ci, une requête est exécutée sur la base, Django enregistre le résultat de la requête dans le cache du ``QuerySet`` et retourne le résultat explicitement demandé (par exemple, le prochain élément, si le ``QuerySet`` est itéré). Les évaluations futures du ``QuerySet`` réutiliseront les résultats contenus dans le cache. Il est important de se souvenir du comportement du cache, car cela peut vous jouer un mauvais tour si vous n'utilisez pas le ``QuerySet`` correctement. Par exemple, les lignes suivantes vont créer deux ``QuerySet``, les évaluer, et les jeter:: print [e.titre for e in Billet.objects.all()] print [e.date_publication for e in Billet.objects.all()] Cela signifie que la même requête sera exécutée deux fois, doublant ainsi la charge de la base de données. Aussi, il y a une possibilité que les deux listes n'incluent pas les mêmes enregistrements de la base, si un enregistrement ``Billet`` a été ajouté ou supprimé entre les deux requêtes. Pour éviter ce problème, enregistrez simplement le ``QuerySet`` et réutilisez le:: queryset = Billet.objects.all() print [p.titre for p in queryset] # Evalue le QuerySet. print [p.date_publication for p in queryset] # Réutilise le cache de l'évaluation précédente. Comparaison d'objets ==================== Pour comparer deux instances d'un modèle, utilisez simplement l'opérateur standard de comparaison de Python, le signe double égal: ``==``. En coulisse, ceci comparera les clefs primaires des deux instances. Appliqué à l'exemple ``Billet`` ci-dessus, les deux déclarations suivantes sont équivalentes:: some_Billet == other_Billet some_Billet.id == other_Billet.id Si la clef primaire du modèle n'est pas appelée ``id``, pas de problème. La comparaison s'effectuera toujours selon la clef primaire, quelque soit son nom. Par exemple, si la clef primaire d'un modèle est appelée ``nom``, ces deux déclarations sont équivalentes:: some_obj == other_obj some_obj.nom == other_obj.nom Recherche complexe avec les objets Q ==================================== Les requêtes avec argument mot-clef -- dans ``filter()``, etc. -- sont assemblées à l'aide d'un "ET" (AND). Si vous désirez exécuter des requêtes plus complexes (par exemple, des requêtes avec "OU" (OR)), vous pouvez utilisez les objets ``Q``. Un objet ``Q`` (``django.db.models.Q``) est un objet utilisé pour encapsuler une collection d'argument mot-clef. Ces arguments mot-clé sont spécifiés comme pour la recherche sur champs ci-dessus. Par exemple, cet objet ``Q`` encapsule une unique requête ``LIKE``:: Q(question__startswith='What') Les objets ``Q`` peuvent être combinés en utilisant les opérateurs ``&`` et ``|``. Lorsqu'un opérateur est utilisé sur deux objets ``Q``, il retourne un nouvel objet ``Q``. Par exemple, cette déclaration retourne un unique objet ``Q`` représentant l'union des deux requêtes ``"question__startswith"``:: Q(question__startswith='Qui') | Q(question__startswith='Quoi') Ce qui est équivalent à la clause ``WHERE`` SQL suivante:: WHERE question LIKE 'Qui%' OR question LIKE 'Quoi%' Vous pouvez composer les déclarations d'une complexité arbitraire en combinant les objets ``Q`` avec les opérateurs ``&`` et ``|``. Vous pouvez aussi utiliser des parenthèses de regroupement. **Nouveau dans la version en développement de Django:** Les objets``Q`` peuvent aussi être niés en utilisant l'opérateur ``~``, permettant ainsi aux recherches combinées d'être combinées avec des requêtes normales et niées (``NOT``):: Q(question__startswith='Who') | ~Q(date_publication__year=2005) Chaque fonction de recherche qui prends des arguments mots-clefs (par exemple, ``filter()``, ``exclude()``, ``get()``) peut aussi avoir un ou plusieurs objets ``Q`` en tant qu'argument positionnel (non-nommé). Si vous fournissez plusieurs arguments objet ``Q`` à la fonction de recherche, les arguments seront assemblés à l'aide d'un "ET" (AND). Par exemple:: Sondage.objects.get( Q(question__startswith='Qui'), Q(date_publication=date(2005, 5, 2)) | Q(date_publication=date(2005, 5, 6)) ) ... traduit grossièrement en requête SQL:: SELECT * from sondage WHERE LIKE 'Qui%' AND (date_publication = '2005-05-02' OR date_publication = '2005-05-06') Les fonctions de recherches peuvent mixer l'utilisation des objets ``Q`` et des arguments mots-clefs. Tous les arguments fournis à une fonction de recherche (qu'ils soient des arguments mots-clefs ou des objets ``Q``) sont assemblés par un "ET" ensemble. Cependant, si un objet ``Q`` est fourni, il doit précéder la définition de tout argument mot-clef. Par exemple:: Sondage.objects.get( Q(date_publication=date(2005, 5, 2)) | Q(date_publication=date(2005, 5, 6)), question__startswith='Qui') ... serait une requête valide, équivalente à l'exemple précédent. Mais:: # REQUETE INVALIDE Sondage.objects.get( question__startswith='Qui', Q(date_publication=date(2005, 5, 2)) | Q(date_publication=date(2005, 5, 6))) ... ne serait pas valide. Voir la `page d'exemples de recherche par union`_ pour plus d'exemples. .. _page d'exemples de recherche par union: ../models/or_lookups/ Objets apparentés ================= Lorsque vous définissez une relation de liaison dans un modèle (c-à-d, un champ ``ForeignKey``, ``OneToOneField``, ou ``ManyToManyField``), les instances de ce modèle auront une API pratique pour accéder aux objets apparentés. Appliqué aux modèles présents en haut de cette page, par exemple, un objet ``Billet`` nommé ``e`` peut accéder à son objet ``Blog`` associé en utilisant l'attribut ``blog``: ``e.blog``. (En coulisse, cette fonctionnalité est implémentée à l'aide des `descripteurs Python`_. Cela ne doit pas être très important pour vous, mais nous en parlons pour les curieux.) Django crée aussi des accesseurs d'API pour les "autres" côtés de la relation de liaison, comme le lien entre l'objet apparenté et le modèle définissant la relation de liaison. Par exemple, un objet ``Blog`` nommé ``b`` a accès à une liste de tous les objets ``Billet`` liés via l'attribut ``Billet_set``: ``b.Billet_set.all()``. Tous les exemples de cette section utilisent les modèles ``Blog``, ``Author`` et ``Billet`` définis en haut de cette page. .. _descripteurs Python: http://users.rcn.com/python/download/Descriptor.htm Les relations un-vers-plusieurs ------------------------------- En avant ~~~~~~~~ Si un modèle a un champ ``ForeignKey``, les instances de ce modèles auront accès à l'objet apparenté (étranger) via un simple attribut du modèle. Exemple:: e = Billet.objects.get(id=2) e.blog # Retourne l'objet Blog apparenté. On peut accéder et modifier des données via un attribut clef étrangère. Comme on peut s'y attendre, les changements sur la clé étrangère ne sont enregistrés que lors de l'appel à ``save()``. Exemple:: e = Billet.objects.get(id=2) e.blog = un_autre_blog e.save() Si un champ ``ForeignKey`` a l'option ``null=True`` (c-à-d, s'il accepte les valeurs ``NULL``), on peut lui assigner la valeur ``None``. Exemple:: e = Billet.objects.get(id=2) e.blog = None e.save() # "UPDATE blog_billet SET blog_id = NULL ...;" L'accès en avant à une relation un-vers-plusieurs met en cache l'objet concerné par la relation. Les accès ultérieurs à la clé étrangère sur le même objet utilisent le cache. Exemple:: e = Billet.objects.get(id=2) print e.blog # Interroge la base de données pour retrouver le Blog associé. print e.blog # N'interroge pas la base de données. Utilise la version en cache. On peut noter que la méthode ``select_related()`` du ``QuerySet`` remplit récursivement le cache de toutes les relations un-vers-plusieurs en avance. Exemple:: e = Billet.objects.select_related().get(id=2) print e.blog # N'interroge pas la base de données. Utilise la version en cache. print e.blog # N'interroge pas la base de données. Utilise la version en cache. La méthode ``select_related()`` est documentée dans la section `Les méthodes des QuerySets qui renvoient d'autres QuerySets`_ ci-dessus. En arrière ~~~~~~~~~~ Si un modèle possède un champ ``ForeignKey``, les instances du modèle de la clé étrangère auront accès à un ``Manager`` qui retourne toutes les instances du premier modèle. Par défaut, ce ``Manager`` est nommé ``FOO_set``, où ``FOO`` est le nom du modèle source, en minuscules. Ce ``Manager`` retourne des ``QuerySets``, qui peuvent être filtrés et manipulés comme décrit dans la section `Récupération des objets`_ ci-dessus. Exemple:: b = Blog.objects.get(id=1) b.billet_set.all() # Retourne tous les objets Billet reliés au Blog. # b.entry_set est un Manager qui retourne des QuerySets. b.billet_set.filter(titre__contains='Lennon') b.billet_set.count() Il est possible de redéfinir le nom de ``FOO_set`` en paramétrant l'attribut ``related_name`` dans la définition de ``ForeignKey()``. Par exemple, si le modèle ``Billet`` était modifié par ``blog = ForeignKey(Blog, related_name='billets')``, le code de l'exemple ci-dessus ressemblerait à ceci:: b = Blog.objects.get(id=1) b.billets.all() # retourne tous les objets Billet reliés au Blog. # b.entry_set est un Manager qui retourne des QuerySets. b.billets.filter(titre__contains='Lennon') b.billets.count() Il est impossible d'accéder au ``Manager`` d'une clé étrangère en arrière à partir de la classe. L'accès doit se faire à partir d'une instance. Exemple:: Blog.billet_set # lève l'exception AttributeError: "Manager must be accessed via instance". En plus des méthodes du ``QuerySet`` définies dans la section `Récupération des objets`_ ci-dessus, le ``Manager`` de ``ForeignKey`` possède ces méthodes additionnelles: * ``add(obj1, obj2, ...)``: Ajoute l'objet du modèle spécifié à l'ensemble des objets reliés. Exemple:: b = Blog.objects.get(id=1) e = Billet.objects.get(id=234) b.billet_set.add(e) # associe le Billet e avec le Blog b * ``create(**kwargs)``: Crée un nouvel objet, l'enregistre et l'ajoute à l'ensemble des objets reliés. Renvoie l'objet nouvellement créé. Exemple:: b = Blog.objects.get(id=1) e = b.billet_set.create(titre='Bonjour', texte='Coucou', date_publication=datetime.date(2005, 1, 1)) # Pas besoin d'appeler e.save() à cet endroit, le billet a déjà été enregistré. Ceci est équivalent à (mais plus simple que):: b = Blog.objects.get(id=1) e = Billet(blog=b, titre='Bonjour', texte='Coucou', date_publication=datetime.date(2005, 1, 1)) e.save() Notez qu'il n'est pas nécessaire de préciser l'argument mot-clef du modèle définissant la relation. Dans l'exemple précédent, on ne passe pas le paramètre ``blog`` à ``create()``. Django trouve lui-même que le champ ``blog`` du nouvel objet ``Billet`` doit prendre la valeur ``b``. * ``remove(obj1, obj2, ...)``: Retire les objets du modèle spécifié de l'ensemble des objets reliés. Exemple:: b = Blog.objects.get(id=1) e = Billet.objects.get(id=234) b.billet_set.remove(e) # Désassocie le Billet e du Blog b. Afin d'éviter l'inconsistance de la base de données, cette méthode existe seulement sur les objets ``ForeignKey`` où ``null=True``. Si le champ relié ne peut pas prendre la valeur ``None`` (``NULL``), alors un objet ne peut peut pas être retiré d'une relation sans être ajouté à une autre. Dans l'exemple ci-dessus, retirer ``e`` de ``b.billet_set`` est équivalent à écrire ``e.blog = None``, et puisque l'attribut ``ForeignKey`` ``blog`` n'a pas ``null=True``, cela est invalide. * ``clear()``: Retire tous les objets de l'ensemble des objets reliés. Exemple:: b = Blog.objects.get(id=1) b.billet_set.clear() Noter que cela ne supprime pas les objets reliés, cela les dissocie juste. Tout comme ``remove()``, ``clear()`` est uniquement disponible sur les champs ``ForeignKey`` où ``null=True``. Pour affecter les membres d'un ensemble relié d'un seul coup, il suffit de lui affecter un objet itérable. Exemple:: b = Blog.objects.get(id=1) b.billet_set = [e1, e2] Si la méthode ``clear()`` est disponible, tous les objets pré-existant seront retirés de ``billet_set`` avant d'ajouter tous les objets contenus dans l'itérable (dans ce cas, une liste). Si la méthode ``clear()`` n'est *pas* disponible, tous les objets contenus dans l'itérable seront ajoutés sans retirer les éléments existants. Chaque opération "inverse" décrite dans cette section a un effet immédiat sur la base de données. Chaque ajout, création et suppression est immédiatement et automatiquement enregistré dans la base de données. Les relations un-vers-un ------------------------ Les relations un-vers-un (one-to-one) sont vraiment similaires au relations un-vers-plusieurs. Si l'on définit un champ ``OneToOneField`` dans un modèle, les instances de ce modèle auront accès à l'objet relié via un simple attribut du modèle. Par exemple:: class BilletDetail(models.Model): billet = models.OneToOneField(Billet) details = models.TextField() bd = BilletDetail.objects.get(id=2) bd.entry # renvoie l'objet Billet relié. Les différences apparaissent dans les requêtes inversés. Le modèle relié dans une relation un-vers-un a aussi accès à un objet ``Manager``. Cependant, ce ``Manager`` représente un unique objet, au lieu d'une collection d'objets:: b = Billet.objects.get(id=2) b.billetdetail # renvoie l'objet BilletDetail relié. Si aucun objet a été affecté à cette relation, Django lèvera une exception ``DoesNotExist``. Les instances peuvent être affectées à la relation inverse de la même manière que l'affection dans une relation en avant:: b.entrydetail = bd Les relations plusieurs-vers-plusieurs -------------------------------------- Les deux extrémités d'une relation plusieurs-vers-plusieurs ont automatiquement accès à l'autre extrémités grâce à l'API. L'API fonctionne comme la relation un-vers-plusieurs en arrière. Voir `En arrière`_ ci-dessus. La seule différence réside dans le nommage de l'attribut: le modèle qui définit le champ ``ManyToManyField`` utilise le nom du champ de l'attribut lui-même, tandis que le modèle "inversé" utilise le nom du modèle d'origine en minuscule, suivi de ``'_set'`` (tout comme les relations un-vers-plusieurs inversées). Un exemple permet une meilleure compréhension:: b = Billet.objects.get(id=3) b.auteurs.all() # renvoie tous les objets Auteur associés à ce Billet b.auteurs.count() b.auteurs.filter(nom__contains='John') a = Auteur.objects.get(id=5) a.billet_set.all() # renvoie tous les objets Billet associés à cet Auteur Comme ``ForeignKey``, ``ManyToManyField`` accepte le paramètre ``related_name``. Dans l'exemple précédent, si le champ ``ManyToManyField`` de ``Billet`` avait spécifié ``related_name='billets'``, alors chaque instance du modèle ``Auteur`` aurait eu un attribut ``billets`` à la place de ``bilet_set``. Comment les relations en arrière sont-elles possible ? ------------------------------------------------------ Les autres ORM (Object-relational mapping) nécessite une définition de la relation au deux extrémités. Les développeurs Django pensent que ceci constitue une violation du principe DRY (Don't Repeat Yourself), donc Django ne nécessite qu'une définition à une extrémité. Mais comment cela est possible, étant donné que la classe modèle ne connaît pas les autres classes modèles reliés avant que celles-ci ne soient chargées ? La réponse se trouve dans le paramètre ``INSTALLED_APPS``. La première fois qu'un modèle est chargé, Django itère sur tous les modèles de ``INSTALLED_APPS`` et crée les relations en arrière nécessaires en mémoire. Essentiellement, une des fonctions de ``INSTALLED_APPS`` est d'indiquer à Django le domaine de modèle. Requêtes sur des objets reliés ------------------------------ Les requêtes impliquant des objets reliés suivent les même règles que les requêtes impliquant des champs à valeurs normals. Lorsqu'on spécifie la valeur de correspondance pour une requête, on utilise soit une instance de l'objet lui-même, soit la valeur de la clef primaire pour l'objet. Par exemple, s'il existait un objet ``b`` de type ``Blog`` avec ``id=5``, les trois requêtes suivantes seraient identiques:: Billet.objects.filter(blog=b) # requête utilisant l'instance de l'objet Billet.objects.filter(blog=b.id) # requête utilisant l'id de l'objet Billet.objects.filter(blog=5) # requête utilisant l'id directement Suppression d'objets ==================== La méthode de suppression est commodément nommé ``delete()``. Cette méthode supprime l'objet immédiatement et ne renvoie pas de valeur. Exemple:: b.delete() Il est aussi possible de supprimer des objets par lots. Chaque ``QuerySet`` possède une méthode ``delete()``, qui supprime tous les membres de ce ``QuerySet``. Par exemple, cela supprime tous les objets ``Billet`` ayant une ``date_publication`` datée de 2005:: Billet.objects.filter(date_publication__year=2005).delete() Lorsque Django supprime un objet, il simule le comportement de la contrainte SQL ``ON DELETE CASCADE``. En d'autres termes, chaque objets qui possède une clé étrangère pointant sur l'objet a supprimer, sera supprimé lui aussi. Par exemple:: b = Blog.objects.get(pk=1) # Cela supprime le Blog et tous les objets Billet associés b.delete() Notez que ``delete()`` est la seule méthode de ``QuerySet`` qui n'est pas apparente au ``Manager``. C'est un méchanisme de sécurité pour éviter de demander accidentellement ``Entry.objects.delete()`` et de supprimer *tous* les billets. Si l'on veut *absolument* supprimer tous les objets, il faut le faire explicitement sur tous l'ensemble:: Billet.objects.all().delete() Mettre à jour plusieurs objets d'un coup ======================================== **Nouveau dans la version de développement** Parfois on désire affecter à un champ une valeur particulière pour tous les objets d'un ``QuerySet``. Il est possible de le faire en utilisant la méthode ``update()``. Par exemple:: # mise à jour de tous les titres ayant une date de publication datée de 2007. Billet.objects.filter(date_publication__year=2007).update(titre='Toujours la même chose') Il est uniquement possible de modifier des champs sans relation et de champs ``ForeignKey`` avec cette méthode, et la valeur doit être une valeur écrite en dur (c-à-d, il est impossible d'affecter à un champ un autre champ à un moment particulier). Pour mettre à jour les champs ``ForeignKey``, on affecte à la nouvelle valeur le nouveau modèle vers lequel on désire pointer. Exemple:: b = Blog.objects.get(pk=1) # change chaque Billet afin qu'il appartienne à ce Blog. Billet.objects.all().update(blog=b) La méthode ``update()`` est appliquée instantanément et ne renvoie rien (tout comme ``delete()``). La seule restriction sur le ``QuerySet`` mis à jour est qu'il n'accède uniquement à une table de la base de données, la table principale du modèle. Inutile d'essayer de filtrer sur des champs reliés ou quelque chose dans le genre, cela ne marchera pas. Méthodes d'instance supplémentaires =================================== En plus des méthodes ``save()`` et ``delete()``, un objet modèle peut avoir une ou plusieurs des méthodes suivantes: get_FOO_display() ----------------- Pour chaque champ ayant un paramètre ``choices``, l'objet aura une méthode ``get_FOO_display()``, où ``FOO`` est le nom du champ. Cette méthode renvoie la valeur "lisible" du champ. Par exemple, dans le modèle suivant:: GENRE_CHOIX = ( ('H', 'Homme'), ('F', 'Femme'), ) class Personne(models.Model): nom = models.CharField(max_length=20) genre = models.CharField(max_length=1, choices=GENRE_CHOIX) ...chaque instance de ``Personne`` aura une méthode ``get_genre_display()``. Exemple:: >>> p = Personne(nom='John', genre='H') >>> p.save() >>> p.genre 'H' >>> p.get_genre_display() 'Homme' get_next_by_FOO(\**kwargs) and get_previous_by_FOO(\**kwargs) ------------------------------------------------------------- Pour chaque champ ``DateField`` et ``DateTimeField`` n'ayant pas ``null=True``, l'objet aura les méthodes ``get_next_by_FOO()`` et ``get_previous_by_FOO()``, où ``FOO`` est le nom du champ. Celles-ci renvoient l'objet suivant et précédent en accord avec le champ de date, levant l'exception appropriée ``DoesNotExist`` si nécessaire. Ces deux méthodes acceptent des arguments mot-clef facultatifs, qui doivent être dans le format décrit dans `Recherche sur un champ`_ ci-dessus. Notez qu'en cas de valeurs identiques, ces méthodes utiliseront l'ID en dernier recours. Cela garantie qu'aucun enregistrement ne sera sauté ou dupliqué. Pour un exemple complet, voir l'exemple d'`utilisation de l'API de recherche sur les modèles`_. .. _utilisation de l'API de recherche sur les modèles: ../models/lookup/ get_FOO_filename() ------------------ Pour chaque champ ``FileField``, l'objet aura une méthode ``get_FOO_filename()``, où ``FOO`` est le nom du champ. Cela renvoie le chemin absolu du système de fichier vers le fichier, selon le paramètre ``MEDIA_ROOT``. Notez que la classe ``ImageField`` est une sous-classe de ``FileField``, et par conséquent, chaque modèle avec un champ ``ImageField`` possédera cette méthode. get_FOO_url() ------------- Pour chaque champ ``FileField``, l'objet aura une méthode ``get_FOO_url()``, où ``FOO`` est le nom du champ. Cela renvoie le chemin URL complet vers le fichier, selon le paramètre ``MEDIA_URL``. Si la valeur est nulle, cette méthode renvoie une chaîne de caractères vide. get_FOO_size() -------------- Pour chaque champ ``FileField``, l'objet aura une méthode ``get_FOO_size()``, où ``FOO`` est le nom du champ. Cela renvoie la taille du fichier, en octets. (En coulisse, cela utilise `os.path.getsize``.) save_FOO_file(nom_fichier, contenus_bruts) ------------------------------------------ Pour chaque champ ``FileField``, l'objet aura une méthode ``save_FOO_file()``, où ``FOO`` est le nom du champ. Cela enregistre le fichier dans le système de fichier, en utilisant le nom passé en argument. Si un fichier avec le nom donné existe déjà, Django ajoute un caractère de soulignement à la fin du nom (mais avant l'extension) jusqu'à ce que le nom de fichier soit disponible. get_FOO_height() and get_FOO_width() ------------------------------------ Pour chaque champ ``ImageField``, l'objet aura les méthodes ``get_FOO_height()`` et ``get_FOO_width()``, où ``FOO`` est le nom du champ. Celles-ci renvoient la hauteur (ou largueur) de l'image, sous la forme d'un entier, en pixels. Raccourcis ========== Lorsque vous développerez des vues, vous découvrirez un certain nombre d'idiomes dans la manière dont vous utilisez l'API de la base de données. Django contient certains de ces idiomes en tant que raccourcis qui peuvent être utilisés pour simplifier l'écriture des vues. Ces fonctions sont dans le module ``django.shortcuts``. get_object_or_404() ------------------- Un idiome commun est d'utiliser ``get()`` et de lever ``Http404`` si l'objet n'existe pas. Cet idiome est retranscrit à travers ``get_object_or_404()``. Cette fonction prends un modèle Django en premier argument et un nombre arbitraire d'arguments mot-clef, qui seront passés à la fonction ``get()`` du ``Manager``. Cela lève ``Http404`` si l'objet n'existe pas. Par exemple:: # récupère le Billet avec une clef primaire de 3 b = get_object_or_404(Billet, pk=3) Lorsque l'on fournit un modèle à cette fonction raccourci, le ``Manager`` par défaut est utilisé pour exécuter la requête ``get()`` sous-jacente. Si l'on désire pas utiliser le ``Manager`` par défaut, ou si l'on veut chercher dans une liste d'objets reliés, on peut fournir à ``get_object_or_404()`` un objet ``Manager`` à la place. Par exemple:: # récupère l'auteur du blog e avec un nom de 'Fred' a = get_object_or_404(b.auteurs, nom='Fred') # utilise un manager personnalisé 'billets_recent' pour la # recherche d'un billet avec la clef primaire de 3 b = get_object_or_404(Billet.billets_recent, pk=3) **Nouveau dans la version de développement:** Le premier argument de ``get_object_or_404()`` peut aussi être un objet ``QuerySet``. Cela est utile dans les cas où l'on a définit une méthode de ``Manager`` personnalisée. Par exemple:: # utilise un QuerySet renvoyé par la méthode 'deja_publie' # du Manager personnalisé pour la recherche d'un billet avec #la clef primaire de 5 b = get_object_or_404(Billet.objects.deja_publie(), pk=5) get_list_or_404() ----------------- ``get_list_or_404`` se comporte de la même manière que ``get_object_or_404()``, excepté qu'elle utilise ``filter()`` à la place de ``get()``. Cela lève ``Http404`` si la liste est vide. Se rabattre sur SQL =================== Lorsque qu'il est nécessaire d'écrire une requête SQL qui est trop complexe pour que Django puisse s'en occuper, il est possible de se rabattre sur des requêtes SQL brutes. La manière conseillée de faire cela est de donner à votre modèle des méthodes personnalisées ou de personnaliser les méthodes du ``Manager`` qui exécutera les requêtes. Bien qu'il n'y ai rien dans Django qui exige que les requêtes doivent appartenir à la couche des modèles, cette approche conserve toute la logique métier en un endroit, ce qui est élégant du point de vue de l'organisation du code. Pour un mode d'emploi, voir `Exécuter du SQL personnalisé`_. Finalement, il est important de noter que la couche base de données de Django est simplement une interface vers votre base de données. Il est possible d'accéder à votre base de données via d'autres outils, langage de programmation ou framework de base de données. Il n'y a rien de spécifique à Django dans votre base de données. .. _Exécuter du SQL personnalisé: ../model-api/#ex-cuter-du-sql-personnalis