<< précédentsuivant >>

Annexe C: Référence de l’API de base de données

L’API de base de données sous Django est la seconde moitié du modèle d’API abordée dans l’annexe B. Lorsque vous aurez défini un modèle, vous vous utiliserez cette API à chaque fois que vous aurez besoin d’accéder à la base de données. Vous avez vu des exemples de l’usage de cette API tout au long du livre; cette annexe explique toutes les options en détail.

Tout comme pour les APIs de modèle abordées dans l’annexe B, et malgré que ces APIs soient considérées comme très stables, les développeurs de Django ajoutent constamment de nouveaux raccourcis et de nouvelles commodités. C’est donc une bonne idée de toujours vérifer la documentation à jour disponible en ligne, à l’adresse http://www.djangoproject.com/documentation/0.96/db-api/.

Tout au long de cette référence, nous prendrons pour base le modèle suivant, qui pourrait constituer une application de weblog simple:

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateTimeField()
    authors = models.ManyToManyField(Author)

    def __str__(self):
        return self.headline

Créer des objets

Pour créer un objet, instantiez le en fournissant des arguments mot-clef au modèle de classe, puis appelez ensuite save() pour l’enregistrer dans la base de données:

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

Ceci effectue une instruction SQL INSERT en coulisse. Django n’affecte pas la base de données tant que vous n’appelez pas explicitement save().

La méthode save() n’a pas de valeur de retour.

Pour créer un objet et le sauvegarder en une seule étape, consultez la méthode create du manager, que nous aborderons sous peu.

Que se passe-t’il lorsque vous enregistrez ?

Lorsque vous sauvegardez un objet, Django effectue les étapes suivantes:

  1. Emission d’un signal pre_save. Ceci notifie qu’un objet est sur le point d’être enregistré. Vous pouvez enregistrer un «listener» qui sera invoqué à chaque fois que ce signal est émis. Ces signaux sont toujours en développement et n’étaient pas documentés lorsque ce livre a été mlis sous presse; vérifiez la documentation en ligne pour les dernières évolutions.

  2. Prétraitement des données. Chaque champ de l’objet est appelé à faire toute modification automatique des données dont le champ pourrait avoir besoin d’effectuer. La plupart des champs ne font pas de pré traitement - le champ de donnée est laissé en l’état. Le pré traitement est utilisé seulement sur les champs qui ont un comportement spécial, comme par exemple les champs de fichier.

  3. Preparation des données pour la base de données. Chaque champs est interrogé pour fournit sa valeur courante dans un type de donnée qui peut être écrit dans la base de données.

    La plupart des champs ne nécessitent pas de préparation des données. Les types de données simples, comme les entiers ou les chaînes, sont «prêts à l’emploi» en tant qu’objets Python. Cependant, les types de données plus complexes demandent souvent quelque modification. Par exemple, DateFields utilise un objet Python datetime pour stocker les données. Les bases de données ne stockant pas les objets datetime, la valeur du champ doit être converti en une chaîne de date respectueuse de la norme ISO avant insertion 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 composent ensuite une instruction SQL pour une insertion dans la base de données.

  5. Émission d’un signal post_save. Comme pour le signal pre_save, ceci est utilisé pour notifier qu’un objet a été suavegardé avec succès. Ces signaux ne sont eux aussi pas encore documentés.

Auto incrémentation des clefs primaires

Pour des raisons pratiques, à chaque modèle est affecté un champ de clef primaire auto incrémenté appelé id à moins que vous ne précisiez explicitement primary_key=True sur un champ. (consultez la section intitulée «Autofield» de l’annexe B).

Si votre modèle possède un AutoField, cette valeur auto incrémentée sera calculée et enregistrée en tant qu’attribut de votre objet lors du premier appel à save():

>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b2.id     # Returns None, because b doesn't have an ID yet.
None

>>> b2.save()
>>> b2.id     # Returns the ID of your new object.
14

Il n’y a aucun moyen de dire quelle sera la valeur d’un ID avant d’appeler save(), parce que cette valeur est calculée par votre base de données, et non pas par Django.

Si un modèle présente un AutoField mais que vous voulez définir explicitement un nouvel ID pour l’objet lors de l’enregistrement, il suffit simplement de le définir explicitement avant la sauvegarde, plutot que de compter sur l’assignation automatique de l’ID:

>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b3.id
3
>>> b3.save()
>>> b3.id
3

Si vous assignez manuellement les valeurs des clefs primaires automatique, assurez vous de ne pas utiliser des valeurs de clef primaires déjà existantes ! Si vous créez un nouvel objet avec une valeur de clef primaire explicite déjà existante en base de données, Django considérera que vous être en train de modifier l’enregistrement existant plutôt que d’en créer un nouveau.

Considérant 'Cheddar Talk', l’exemple précédent du blog, cet exemple écrasera l’enregistrement précédent dans la base de données:

>>> b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
>>> b4.save()  # Overrides the previous blog with ID=3!

Spécifier explicitement les valeurs de clef primaire automatique est surtout utile pour la sauvegarde massive d’objet, lorsque vous serez en confiance vous n’aurez pas de collision de clef primaire.

Enregistrer les modifications des objets

Pour enregistrer les changements apportés à un objet qui est déjà présent dans la base de données, utilisez save().

Considérant l’instance b5 d’un Blog qui a déjà été sauvé dans la base de données, cet exemple modifie son nom et met à jour son enregistrement dans la base:

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

Ceci effectue une instruction SQL UPDATE en coulisse. Ici aussi, Django n’affecte pas la base de données tant que vous n’appelez pas explicitement save().

Comment Django sait quand faire un UPDATE et quand faire faire un INSERT ?

Vous avez peut être remarqué que les objets de la base de données Django utilisent la même méthode save() pour créer et modifier les objets. Django fait abstraction du besoin d’utiliser les instructions SQL INSERT ou UPDATE. Plus spécifiquement, lorsque vous appelez save(), DJango suit cet algorithme:

FIXME

  • Si l’attribut clef-primaire de l’objet est fixé à une valeur évaluée à True (c’est à dire une valeur autre qu’une chaîne vide ou que None), Django execute une requête SELECT pour déterminer si un enregistrement avec la clef-primaire donnée existe déjà.
  • Si l’enregistrement avec la clef primaire existe déjà, Django execute une requête UPDATE.
  • Si l’attribut clef-primaire de l’objet n’est pas fixé, ou s’il est fixé mais qu’un enregistrement n’existe pas, Django execute un INSERT.

À cause de cela, vous devez être attentif à ne pas préciser une valeur de clef primaire explicitement lorsque vous sauvegardez de nouveaux objets si vous ne pouvez pas garantir que la valeur de la clef primaire n’est pas utilisée.

Mettre à jour des champs ForeignKey focntionne exactement de la même façon; assignez simplement un objet du type adéquat au champ en question:

>>> joe = Author.objects.create(name="Joe")
>>> entry.author = joe
>>> entry.save()

Django se plaindra si vous tentez d’assigner un objet du mauvais type.

Récuperer les objets

Tout au long du livre vous avez pu voir des objet récupérés en utilisant du comme semblable à celui-ci:

>>> blogs = Blog.objects.filter(author__name__contains="Joe")

FIXME FUZZY Il y a quelques «parties mouvantes» à l’arrière plan: lorsque vous récupérez des objets depuis la base de données , vous construisez en fait un QuerySet en utilisant le Manager du modèle. Ce QuerySet sait comment exécuter du SQL et retourner les objets demandés.

There are quite a few ?moving parts? behind the scenes here: when you retrieve objects from the database, you?re actually constructing a QuerySet using the model?s Manager. This QuerySet knows how to execute SQL and return the requested objects.

L’annexe B examine ces deux objets du point de vue de la définition de modèle; nous l’examinons à présent la façon dont ils opèrent.

Un QuerySet represente une collection d’objets dans votre base de données. Il peut y avoir aucun, un ou plusieurs filtres - des critères qui diminuent la collection selon des paramètres donnés. En termes SQL, un QuerySet équivaut à une instruction SELECT, et un filtre est un facteur limitant identique à WHERE ou LIMIT.

FIXME FUZZY Vous obtenez un QuerySet en utilisant le Manager de votre modèle. Chaque modèle possède au moin un Manager, appelé objects par défaut. Accédez y directement via le modèle de classe, comme ceci:

>>> Blog.objects
<django.db.models.manager.Manager object at 0x137d00d>

Les Manager s sont accessibles uniquement via les modèles de classes, plutôt que depuis les modèles d’instances, pour augmenter la séparation entre les opérations «au niveau des tables» et les opérations «au niveau de enregistrements»:

>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Manager isn't accessible via Blog instances.

Le Manager est la source principale des QuerySets pour un modèle. Il agit comme un QuerySet «parent» qui décrit tous les objets dans la table de la base de données du modèle. Par exemple, Blog.objects est le QuerySet initial qui contient tous les objets Blog dans la base de données.

Mise en cache et QuerySets

Chaque QuerySet contient un cache, afin de minimiser les accès à la base de données. Il est important de comprendre comment cela fonctionne, de façon à écrire du code le plus efficace possible.

Dans un QuerySet nouvellement créé, le cache est vide. La première fois qu’un QuerySet est évalué - et par conséquent, qu’une requête se produit dans la base de données - Django enregistre les résutlats de la requête dans le cache du QuerySet et retourne les résultats qui ont été explicitement demandés (par exemple, l’élément suivant, si l’on itère sur le QuerySet). Les évalutions ultérieures du QuerySet réutilisent les résultats mis en cache.

Gardez à l’esprit ces fonctionnalités de cache, parce qu’il peut vous jouer des tours si vous n’utilisez pas vos QuerySet s correctement. Par exemple, l’exemple suivant va créer deux QuerySet s, les évaluera, puis les rejetera:

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

Cela signifie que la même requete dans la base de données sera exécuté deux fois, doublant la charge effective de votre base? De plus , il est possible que deux listes n’incluent pas les mêmes enregistrements, parcequ’une Entry peut avoir été ajoutée ou supprimée dans la fractio de seconde entre les deux requêtes.

Pour éviter ce probèle, enregistrez simplement le QuerySet et réutilisez le:

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

Filtrer les objets

La manière la plus simple de récupérer des objets depuis une table est de les prendre tous. Pour ce faire, utilisez laméthode all() sur un Manager:

>>> Entry.objects.all()

La méthode all() retourne un QuerySet de tous les objets en base de données.

Habituellement, cependant, vous aurez besoin de selectionner uniquement une partie du jeu complet d’objets. Pour créer une telle selection, vous affinez le QuerySet initial, en ajoutant des conditions filtrantes. Vous ferez cela généralement en utilisant les méthodes filter() ou exclude():

>>> y2006 = Entry.objects.filter(pub_date__year=2006)
>>> not2006 = Entry.objects.exclude(pub_date__year=2006)

filter() et exclude() prennent toutes deux des arguments de recherche de champ, qui seront abordés en d’ici peu.

Chaîner les filtres

Le résutlat de l’affinage d’un QuerySet est lui même un QuerySet, il est donc possible de les chaîner, par exemple:

>>> qs = Entry.objects.filter(headline__startswith='What')
>>> qs = qs..exclude(pub_date__gte=datetime.datetime.now())
>>> qs = qs.filter(pub_date__gte=datetime.datetime(2005, 1, 1))

Ce code prends pour QuerySet initial toutes les entrées de la base de données, ajoute un filtre, puis une exclusion, et enfin un autre filtre. Le résultat final est un QuerySet contenant toutes les entrées ayant une en-tête commençant par «What» qui ont été publiées entre le premier janvier 2005 et la date actuelle.

Il est important de remarquer que les QuerySets sont fainéant - le fait de créer un QueySet n’implqie pas un activité de la base de données. En fait, les trois ligne de code précédentes ne font aucun appels en base de données; vous pouvez chaîner des filtres ensembles toute la journée sans que Django ne lance de requête jusqu’à ce que le QuerySet ne soit évalué.

Vous pouvez évaluer un QuerySet selon n’importe laquelle des méthodes suivantes:

  • En l’itérant: Un QuerySet est itérable, et execute ses requêtes en base de données lors de la première itération. Par exemple, le QuerySet qui suit n’est évalué que lorsqu’il est itéré dans la boucle for:

    qs = Entry.objects.filter(pub_date__year=2006)
    qs = qs.filter(headline__icontains="bill")
    for e in qs:
        print e.headline
    

    Cela affiche toutes les en-têtes de l’année 2006 qui contiennent «bill» mais n’effectue qu’une opération en base de données.

  • En l’affichant: Un QuerySet est évalué lorsque vous appelez repr(). Ceci est une commodité de l’interpréteur interactif Python, vous pouvez donc voir immédiatement vos résultats lorsque vous utilisez l’API interactivement.

  • En le segmentant: Comme cela est expliqué dans la section «Limiter les QuerySets» à venir, un QuerySet peut être segmenté en utilisant la syntaxe de segmentation de tableau en Python. Habituellement, segmenter un QuerySet renvoie un autre QuerySet (non évalué), mais Django executera la requête en base de données si vous utilisez le paramètre «step» de la syntaxe de segmentation.

  • En le convertissant en liste: Vous pouvez forcer l’évaluation d’un QuerySet en appelant list(), par exemple:

    >>> entry_list = list(Entry.objects.all())
    

    Prenez averti, cependant, que cela peut engendrer une grosse surcharge de la mémoire, parce que Django chargera chaque élément de la liste en mémoire. À l’inverse, itérer un QuerySet ménagera votre base de données en ne chargeantles données et en n’instanciant les objets au fur et à mesure de vos besoins.

Les QuerySets filtrés sont uniques

Chaque fois que vous affinez un QuerySet, vous obtenez un nouveau QuerySet qui n’est absolument pas lié au précédent QuerySet. Chaque affinage créer un QuerySet distinct et cloisonné qui peut être stocké, utilisé et réutilisé:

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

Ces trois QuerySets sont séparer. Le premier est un QuerySet basique contenant toutes les entrées qui contiennent une en-tête commençant par «What». Le second est une partie du premier, avec un critère supplémentaire qui exclu les enregistrements dont la date de publication pub_date est postérieure à la date courante. Le troisième est une partie du premier, avec un critère additionnel qui selectionne uniquement les enregistrements dont la pub_date est supérieur à la date courante. Le QuerySet initial (q1) n’est pas affecté par le processus d’affinage.

Limiter les QuerySets

Utilisez la syntaxe Python de segmentation de tableau pour limiter votre QuerySet à un certain nombre de résultats. Ceci est équivalent aux clauses LIMIT et OFFSET du SQL.

Par exemple, ceci renvoie les cinq premières entrées (LIMIT 5):

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

Ceci renvoie la sixième entrée et suivantes, jusqu’à la dixième (OFFSET 5 LIMIT 5):

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

Généralement, la segmentation d’un QuerySet renvoie un nouveau QuerySet - cela n’évalue pas la requête. Une exception à la règle se présente si vous utilisez le paramètre «step» de la syntaxe Pytohn de segmentation. Par exemple, ce code executera en fait la requête de façon à retourner une liste du deuxième objet parmis les dix premiers:

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

Pour récupérer un unique objet plutot qu’une liste (par exemple, SELECT foo FROM bar LIMIT 1), utilisez un simple index au lieu d’un segment. Par exemple, ce code retourne la première Entry de la base de données, après avoir ordonné les entrées alphabétiquement selon l’en-tête:

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

Ceci est à peu près équivalent au code suivant:

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

Notez, cependant, que le premier lévera une IndexError alors que le second lévera un DoesNotExist si aucun objet correspond au critère donné.

Les méthodes de requête qui retournent de nouveaux QuerySets

Django fournit tout une gamme de méthode d’affinage d’un QuerySet qui modifie à la fois les types de résultats renvoyés par le QuerySet ou la manière dont sa requête SQL est executé. Ces méthodes sont décrites dans les sections qui suivent. Certaines de ces méthodes acceptent des arguments de recharche de champ, qui sont abordés en détail un peu plus tard.

filter(**lookup)

Renvoie un nouveau QuerySet contenant les objets correspondant aux paramètres de recherche indiqués.

exclude(**kwargs)

Renvoie un nouveau QuerySet contenant les objets ne correspondant pas aux paramètres de recherche indiqués.

order_by(*fields)

Par défaut, les resutlats retournés par un QuerySet sont ordonnés selon l’ordre du tuple précisé dans l’option ordering des métadonnées du modèle (voir l’annexe B). Vous pouvez outrepasser cela pour une requête particulière en utilisant la méthode order_by():

>> Entry.objects.filter(pub_date__year=2005).order_by('-pub_date','headline')

Ce résultat sera ordonné par pub_date décroissantes, puis par headline croissant. Le signe moins au début de "-pub_date" indique l’ordre décroissant. L’ordre ascendant est sous entendu si le - est absent. Pour ordonner aléatoirement, utilisez "?", comme ceci:

>>> Entry.objects.order_by('?')
distinct()

Renvoie un nouveau QuerySet utilisant SELECT DISTINCT dans sa requête SQL. Cela élimine la duplication des lignes dans les résultats de la requête.

Par défaut, un QuerySet n’éliminera pas les lignes dupliquées. En pratique, cela est rarement un problème, parce que les requêtes simples telles que Blog.objects.all() n’intrduisent pas la possibilité de lignes résultantes dupliquées.

Cependantn si votre requête porte sur de multiples tables, il est possible d’obtenir des doublons lorsqu’un QuerySet est évalué. However, if your query spans multiple tables, it?s possible to get duplicate results when a QuerySet is evaluated. C’est alors que vous utiliserez distinct().

values(*fields)

Renvoie un QuerySet spécial qui évalue une liste de dictionnaires à la place de l’instance du modèle des objets. Chacun de ces dictionnaires représente un objet, avec les clef correspondantes aux noms des attributs de modèle des objets:

# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith='Beatles')
[Beatles Blog]

# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith='Beatles').values()
[{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles
news.'}]

Les values() prennent des arguments positionnels optionnels, les *fields, qui précisent les noms de champ auquels le SELECT doit se limiter. Si vous précisez les champs, chaque dictionnaire contiendra uniquement le champ clefs/valeurs du champ que vous précisez. Si vous ne spécifiez pas les champs, chaque dictionnaire contiendra un clef et une valeur pour chaque champ de la table en base de données:

>>> Blog.objects.values()
[{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles
news.'}],
>>> Blog.objects.values('id', 'name')
[{'id': 1, 'name': 'Beatles Blog'}]

Cette méthode est utile lorsque vous savez que vous allez avoir besoin des valeurs d’un petit nombre de champs parmis ceux disponibles et que vous n’aurez pas besoin des fonctionnalités d’une instance du modèle de l’objet. Il est plus efficace de ne selectionner que les champs dont vous avez besoin d’utiliser.

dates(field, kind, order)

Renvoie un QuerySet spécial qui évalue une liste d’objets datetime.datetime représentant toutes les dates disponibles d’un type particulier parmi celles contenues dans le QuerySet.

L’argument field doit être le nom d’un DateField ou DateTimeField de votre modèle. Le type d’argument doit être soit "year", "month", ou "day". Chaque objet datetime.datetime dans la liste résultante est «tronquée» selon le type donné:

  • "year" retourne une liste de toutes les valeurs distinctes de l’année pour le champ.
  • "month" retourne une liste de toutes les valeurs distinctes de l’année/mois pour le champ.
  • "day" retourne une liste de toutes les valeurs distinctes de l’année/mois/jour pour le champ.

order, par défaut à 'ASC', peut être soit 'ASC' ou 'DESC'. Cela précise la façon d’ordonner les résultats.

Voici quelques exemples:

>>> Entry.objects.dates('pub_date', 'year')
[datetime.datetime(2005, 1, 1)]

>>> Entry.objects.dates('pub_date', 'month')
[datetime.datetime(2005, 2, 1), datetime.datetime(2005, 3, 1)]

>>> Entry.objects.dates('pub_date', 'day')
[datetime.datetime(2005, 2, 20), datetime.datetime(2005, 3, 20)]

>>> Entry.objects.dates('pub_date', 'day', order='DESC')
[datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)]

>>>
Entry.objects.filter(headline__contains='Lennon').dates('pub_date',
'day')
[datetime.datetime(2005, 3, 20)]
select_related()

Renvoie un QuerySet qui «suivra» automatiquement les relations de clef étrangère, sélectionnant ces données additionnelles liées à l’objet lors de l’execution de sa requête. Ceci est un accélérateur de performance dont le résultat se traduit par de (parfois beaucoup) plus grandes requêtes mais signifie aussi que l’usage ultérieur des relations de clef étrangère ne nécessitera pas de requêtes dans la base.

les exemples suivants illustrent la différence entre des recherches pleines et des recharches select_related(). Voici une recherche standard:

# Hits the database.
>>> e = Entry.objects.get(id=5)

# Hits the database again to get the related Blog object.
>>> b = e.blog

Et voici une recherche select_related:

# Hits the database.
>>> e = Entry.objects.select_related().get(id=5)

# Doesn't hit the database, because e.blog has been prepopulated
# in the previous query.
>>> b = e.blog

select_related() explore les clefs étrangères aussi loin que possible. Si vous avez les modèles suivant:

class City(models.Model):
    # ...

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(City)

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person)

alors un appel à Book.objects.select_related().get(id=4) placera en cache la Person liée et la City liée:

>>> b = Book.objects.select_related().get(id=4)
>>> p = b.author         # Doesn't hit the database.
>>> c = p.hometown       # Doesn't hit the database.

>>> b = Book.objects.get(id=4) # No select_related() in this example.
>>> p = b.author         # Hits the database.
>>> c = p.hometown       # Hits the database.

Notez que select_related() ne suit pas les clefs étrangères qui ont null=True.

Habituellement, utiliser select_related() peut largement améliorer les performances puisque votre application peut éviter beaucoup d’appels à la base de données. Cependant, dans les situations avec des jeux de relations profondemment imbriquées, select_related() peut parfois aboutir au suivi de «beaucoup trop» de relations et peut générer des requêtes si grandes qu’elles finissent par être lentes.

extra()

Parfois, la syntaxe de requête sous Django ne peut exprimer d’elle même une clause WHERE complexe. Pour ces cas extremes, Django fournit le modificateur extra() QuerySet - une accroche pour injecter des clauses spécifiques dans le SQL généré par un QuerySet.

Par définition, ces recherches exceptionnelles peuvent ne pas être portable vers divers moteurs de base de données (parce que vous écrivez explicitement du code SQL) et peuvent violer le principe DRY. Vous devez donc les éviter autant que possible.

Précisez un ou plusieurs params, select, where, ou tables. Aucun des arguments n’est requis, mais vous devriez utiliser au moin l’un d’entre eux.

L’argument select vous permet de placer des champs supplémentaires dans la clause SELECT. Ce doit être un dictionnaire faisant correspondre les noms d’attribut au clauses SQL à utiliser pour calculer cet attribut:

>>> Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})

En retour, chaque objet Entry aura un attribut supplémentaire, is_recent, un Booléen représentant le fait que la pub_date de l’entrée est postérieure au premier janvier 2006.

L’exemple suivant est plus abouti; il effectue une sous requête pour donner à chaque objet Blog résultant un attribut entry_count, calcul entier des objets Entry associés:

>>> subq = 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id'
>>> Blog.objects.extra(select={'entry_count': subq})

(Dans ce cas particulier, nous exploitons le fait que la requête contiendra déjà la table blog_blog dans sa clause FROM).

Vous pouvez définir des clauses SQL WHERE explicites - probablement pour effectuer des jointures non-explicites» en utilisant where. Vous pouvez manuellement ajouter des tables à la clause SQL FROM en utilisant tables.

where et tables prennent toutes une liste de chaînes de caractères. Tous les paramètres where sont cumulés aux autres critères de recherche: FIXME FUZZY

>>> Entry.objects.extra(where=['id IN (3, 4, 5, 20)'])

Les paramètres select et where décrits auparavant peuvent utiliser les substituts de chaînes standards en Python: '%s' pour indiquer les paramètres que le moteur de base de données doit automatiquement rapporter. L’argument params est une liste de tous les paramètres supplémentaires qui doivent être substitué:

>>> Entry.objects.extra(where=['headline=%s'], params=['Lennon'])

Utilisez toujours params à la place d’embarquer les valeurs directement dans un select ou un where parce que params s’assurera que les valeurs sont correctement rapportés conformément à votre base de données particulière.

Voici un exemple de la marche à ne pas suivre:

Entry.objects.extra(where=["headline='%s'" % name])

Et voici un exemple de la bonne façon de faire:

Entry.objects.extra(where=['headline=%s'], params=[name])

Les méthodes de QuerySet qui ne renvoient pas de QuerySets

Les méthodes de QuerySet suivantes évaluent le QuerySet et renvoient quelque chose d’autre qu’ un QuerySet - un objet unique, une valeur et ainsi de suite.

get(**lookup)

Renvoie l’objet correspondant aux paramètres de recherche donnés, qui doivent être au format décrit dans la section «recherches de champ ». Elle lève une AssertionError si plus d’un objet a été trouvé.

get() lève une exception DoesNotExist si un objet n’a pas été trouvé d’après les paramètres données. L’exception DoesNotExist est un attribut du modèle de classe, par exemple:

>>> Entry.objects.get(id='foo') # raises Entry.DoesNotExist

L’exception DoesNotExist hérite de django.core.exceptions.ObjectDoesNotExist, vous pouvez donc viser de multiples exceptions DoesNotExist:

>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
...     e = Entry.objects.get(id=3)
...     b = Blog.objects.get(id=1)
... except ObjectDoesNotExist:
...     print "Either the entry or blog doesn't exist."
create(**kwargs)

C’est une méthode pratique pour créer un objet et le sauvegarder en une seule étape. Elle vous permet de cumuler deux étapes courantes:

>>> p = Person(first_name="Bruce", last_name="Springsteen")
>>> p.save()

en une seule ligne:

>>> p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
get_or_create(**kwargs)

C’est une méthode pratique pour rechercher un objet et en créer un s’il n’existe pas. Elle retourne un tuple de (object, created), où object est celui récupéré ou créé et created est un Booléen précisant si un nouvel objet a été créé.

Cette méthode est conçue comme un raccourcis vers le code courant et est surtout utilisé pour les scripts d’importation de données, par exemple:

try:
    obj = Person.objects.get(first_name='John',
    last_name='Lennon')
except Person.DoesNotExist:
    obj = Person(first_name='John', last_name='Lennon',
    birthday=date(1940, 10, 9))
    obj.save()

Ce modèle s’alourdit à mesure que le nombre de champs dans un modèle augmente. L’exemple précédent peut être réécrit en utilisant get_or_create() comme suit:

obj, created = Person.objects.get_or_create(
    first_name = 'John',
    last_name  = 'Lennon',
    defaults   = {'birthday': date(1940, 10, 9)}
)

Tout arguments mot-clef transmis à get_or_create() - à l’exception d’un argument optionnel appelé defaults - seront utilisés dans un appel get(). Si un objet est trouvé, get_or_create() renvoie un tuple de cet objet et False. Si un objet n’est pas trouvé, get_or_create()``instenciera et enregistrera un nouvel objet, renvoyant un tuple de celui-ci et ``True. Le nouvel objet sera créé selon cet algorithme:

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, cela signifie qu’il commence par tout argument mot-clef autre que defaults qui ne contient pas un double soulignement (qui indiquerait une recherche inexacte). Ensuite il ajoute le contenu de defaults, écrasant toutes clef si nécessaire, et utilise le résultat en tant qu’arguments mot-clef pour le modèle de classe.

Si vous avez un champ nommé defaults et que vous vouliez l’utiliser comme une recherche excate dans get_or_create(), utilisez simplement 'defaults__exact' comme ceci:

Foo.objects.get_or_create(
    defaults__exact = 'bar',
    defaults={'defaults': 'baz'}
)

Note

Comme mentionné auparavant, get_or_create() est principalement utile dans les scripts qui nécessitent de parcourir les données et de créer de nouveaux enregistrements si ceux existants ne sont pas disponibles. Mais si vous avez besoin d’utiliser get_or_create() dans une vue, assurez vous de l’utiliser uniquement dans les requêtes POST à moins que vous ayez une bonne raison de ne pas le faire. Les requêtes GET ne doivent avoir aucun effet sur les données; utilisez POST lorsque une requête sur une page a un effet de bord sur vos données.

count()

Renvoie un entier représentant le nombre d’objets dans la base de données correspondants au QuerySet. count() ne lève jamais d’exceptions. Voici un exemple:

# Returns the total number of entries in the database.
>>> Entry.objects.count()
4

# Returns the number of entries whose headline contains 'Lennon'
>>> Entry.objects.filter(headline__contains='Lennon').count()
1

count() effectue un SELECT COUNT(*) en coulisses, vous devez donc toujours utiliser count() plutôt que charger la totalité des enregistrements en objet Python et d’appeler len() sur le résultat.

Selon la base de données que vous utilisez (par exemple, PostgreSQL ou MySQL), count() peut retourner un entier long à la place d’un entier Python normal. C’est une bizarrerie sous-tendue par la mise en œuvre qui ne devrait pas poser problèmes dans le monde réel.

in_bulk(id_list)

Prends une liste de valeurs de clef primaires et renvoie un dictionnaire mettant en correspondance chaque valeur de clef primaire avec une instance de l’objet selon l’ID donné, par exemple:

>>> Blog.objects.in_bulk([1])
{1: Beatles Blog}
>>> Blog.objects.in_bulk([1, 2])
{1: Beatles Blog, 2: Cheddar Talk}
>>> Blog.objects.in_bulk([])
{}

Les IDs des objets qui n’existent pas sont silencieusement eliminés du dictionnaire résultant. Si vous passez in_bulk() (en vrac) une liste vide, vous obtiendrez un dictionnaire vide.

latest(field_name=None)

Renvoie le dernier objet de la table, par date, en utilisant le field_name fournit en tant que champ de date. Cet exemple renvoie la dernière Entry dans la table, conformement au champ pub_date:

>>> Entry.objects.latest('pub_date')

Si votre Meta de modèle précise un get_latest_by, vous pouvez réduire l’argument field_name à latest(). Django utilisera le champ précisé par défaut dans get_latest_by.

Tout comme get(), latest() lève un DoesNotExist si un objet n’existe pas selon les paramètres fournis.

Field Lookups

Les recherches de champ représentent la manière dont vous indiquez la garniture d’une clause SQL WHERE Elles sont fournies comme arguments mot-clef aux méthodes filter(), exclude(), et get() du QuerySet

Les arguments mot-clef de recherche basiques prennent la forme field__lookuptype=value (notez le double soulignement). Par exemple:

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

se traduit (à peu près) en SQL:

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

Si vous transmettez un argument mot-clef invalide, une fonction de recherche lévera une TypeError.

Les types de recherches supportés sont les suivant.

exact

Exécute une correspondance exacte:

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

Ce code retrouve tout objet dont le titre est exactement «Man bites dog». Si vous ne fournissez pas un type de recherche - autrement dit, si votre argument mot-clef ne contient pas de double soulignement - le type de recherche est considéré comme étant exact.

Par exemple, les deux instructions suivantes sont équivalentes:

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

Cela par commodité, puisque les recherches exact sont les plus courantes.

iexact

Exécute une coorespondance exacte insensible à la casse:

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

Elle retrouvera 'Beatles Blog', 'beatles blog', 'BeAtLes BLoG', et ainsi de suite.

contains

Test un contenu sensible à la casse:

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

Elle remontera le titre 'Today Lennon honored' mais pas 'today lennon honored'.

SQLite ne supporte pas les instructions LIKE sensibles à la casse; lorsque vous utilisez SQLite, contains agit comme icontains.

Échapper les caractères pourcent et soulignement dans les instruciton LIKE

Les recherches de champ assimilables aux instructions SQL LIKE (iexact, contains, icontains, startswith, istartswith, endswith, et iendswith) échapperons automatiquement les deux caractères spéciaux utilisés dans les instructions LIKE - les caractères pourcent et soulignement. (Dans une instruction LIKE, le caractère pourcent signifie «plusieurs caractères joker» alors que le soulignement signifie «un seul caractère joker»).

FIXME FUZZY Cela signifie que les choses devraient fonctionner de manière intuitive, de sorte que la capture ne présente pas de fuite. Par exemple, pour récupérer toutes les entrées qui contiennent un caractère pourcent, utilisez simplement le caractère pourcent comme tout autre caractère:

Entry.objects.filter(headline__contains='%')

Django prends soin de renseigner le reste pour vous. Le SQL résultant ressemblera à quelque chose comme ceci:

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

Le fonctionnement est identique pour les soulignements. Les caractères pourcent et soulignement sont gérés pour vous de façon transparente.

icontains

Test un contenu insensible à la casse:

>>> Entry.objects.get(headline__icontains='Lennon')

Contrairement à contains, icontains remontera 'today lennon honored'.

gt, gte, lt, et lte

Ils représentent les caractères supérieur à, supérieur ou égal à, inférieur, et inférieur ou égal à:

>>> Entry.objects.filter(id__gt=4)
>>> Entry.objects.filter(id__lt=15)
>>> Entry.objects.filter(id__gte=0)

Ces requêtes renvoient tout objet dont l’ID est supérieur à 4, dont l’ID est inférieur à 15, et dont l’ID est supérieur ou égal à 1, respectivement.

Vous les utiliserez généralement sur des champs numériques. Soyez attentif avec les champs de caractère puisque l’ordre des caractères n’est pas toujours celui que vous pourriez attendre (c’est à dire que la chaîne «4» sort après la chaîne «10»).

in

Filtre selon les valeurs d’une liste donnée:

Entry.objects.filter(id__in=[1, 3, 4])

Ce code retourne tous les objets ayant l’ID 1, 3, ou 4.

startswith

Exécute un «commence par» insensible à la casse:

>>> Entry.objects.filter(headline__startswith='Will')

Ceci retournera les titres «Will he run ?» et «Willbur named judge», mais pas «Who is Will ?» ou «will found in crypt».

istartswith

Exécute un «commence par» insensible à la casse:

>>> Entry.objects.filter(headline__istartswith='will')

Ceci retournera les titres «Will he run ?», «Willbur named judge», et «will found in crypt», mais pas «Who is Will ?».

endswith et iendswith

Exécute un «se termine par» sensible et insensible à la casse:

>>> Entry.objects.filter(headline__endswith='cats')
>>> Entry.objects.filter(headline__iendswith='cats')

range

Exécute un contrôle de gamme inclusif:

::
>>> start_date = datetime.date(2005, 1, 1)
>>> end_date = datetime.date(2005, 3, 31)
>>> Entry.objects.filter(pub_date__range=(start_date, end_date))

Vous pouvez utilisez range là ou vous pouvez utiliser BETWEEN en SQL - pour les dates, les nombres, et même les caractères.

année, mois et jour

Pour les champs date/datetime, exécute une correspodance exacte sur l’année, le mois ou le jour:

# Recherche sur l'année
>>>Entry.objects.filter(pub_date__year=2005)

# Recherche sur le mois -- accepte les entiers
>>> Entry.objects.filter(pub_date__month=12)

# Recherche sur le jour
>>> Entry.objects.filter(pub_date__day=3)

# Combinaison: renvoie toutes les entrées de Noël pour n'importe quelle année
>>> Entry.objects.filter(pub_date__month=12, pub_date_day=25)

isnull

Accepte soit True ou False, ce qui correspond aux requêtes SQL de IS NULL et IS NOT NULL, respectivement:

>>> Entry.objects.filter(pub_date__isnull=True)

__isnull=True contre __exact=None

Il y a une différence importante entre __isnull=True et __exact=None. __exact=None retournera toujours un jeu de résultat vide, parce que le SQL nécessite qu’aucune valeur soit égale à NULL. __isnull détermine si le champ porte actuellement la valeur de NULL sans faire de comparaison.

search

Une recherche Booléenne plein text qui tire partie de l’indexation . C’est identique à contains mais reste significativement plus rapide grâce à l’indexation plein texte.

Notez qui ceci est disponible uniquement pour MySQL et nécessite une manipulation directe de la base de donnée pour ajouter l’indexation plein texte.

Le raccourcis de recherche pk

Par commodité, Django fournit un type de recherche pk, pk correspondant à «primary_key» (clef primaire).

Dans l’exemple de modèle Blog, la clef primaire est le champ id, ces trois instructions sont donc équivalentes:

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

L’usage de pk n’est pas limité 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 d’un modèle:

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

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

les recherches pk fonctionnent aussi sur les jointures. Par exemple, ces trois instrctions sont équivalentes:

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

Recherches complexesavec les objets Q

Les requêtes arguement mot-clef - dans filter() et consort - sont cumulées. Si vous avez besoin d’exécuter des requêtes plus complexes (par exemple, des requêtes avec des instructions OR), vous pouvez utiliser des objets Q.

Un objet Q (django.db.models.Q) est un objet utilisé pour encapsuler une collection d’arguments mot-clef. Ces arguments mot-clef sont spécifiques tout comme dans la section «recherches de champ».

Par exemple, cet ojet 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 génère un nouvel objt Q. Par exemple, cette instruction génère un unique objet Q qui représente le OR de deux requêtes "question__startswith":

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

Ceci est équivalent à la clause SQL WHERE suivante:

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

Vous pouvez composer des instructions de complexité arbitraire en combinant des objets Q avec les opérateurs & et |. Vous pouvez aussi utiliser des groupements entre parenthèses.

Chaque fonction de recherche qui prends des arguments mot-clef (par exemple, filter(), exclude(), get()) peut aussi accepter un ou plusieurs objets Q en arguments positionnels (pas en arguments nommés). Si vous fournissez de multiples arguments d’objet Q à une fonction de recherche, les arguments seront cumulés, par exemple:

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

se traduit à peu près en SQL:

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

Les fonctions de recherche peuvent combiner l’utilisation d’objets Q et d’arguments mot-clef. Tous les arguments fournis à une fonction de recherche (qu’il s’agissent d’arguments mot-clef ou d’objet Q) sont cumulés. Cependant, si un objet Q est fournit, il doit précédé la définition de tout arguments mot-clef. Par exemple, le code suivant:

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

est une requête valide, équivalente à l’exemple précédent, mais celui-ci:

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

ne sera pas valide.

Vous pouvez trouver des exemples en ligne à l’adresse: http://www.djangoproject.com/documentation/0.96/models/or_lookups/.

Objets liés

Lorsque vous définissez une relation dans un modèle (c’est à dire une ForeignKey, une OneToOneField, ou une ManyToManyField), les instances de ce modèle présenterons une API fort utile pour accéder aux objets liés.

Par exemple, un objet Entry e peut obtenir son objet Blog associé en accédant à l’attribut de blog, e.blog.

Django crée aussi les accessseurs d’API pour les «autres» constituants de la relation - le lien du modèle lié vers le modèle définissant cette relation. Par exemple, un objet Blog b a acces à une liste de tous les objets Entry liés via l’attribut entry_set: b.entry_set.all().

Tous les exemples dans cette section utilisent les modèles Blog, Author, et Entry définis au début de cette page.

Recherches étendue aux relations

Django offre une manière puissante et intuitive de «suivre» les relations au sein des recherches, en prenant automatiquement soin en coulisses du JOIN SQL à votre place. Pour étendre une relation, utilisez simplement le nom des champs liés parmis les modèles, séparés par des doubles soulignements, jusqu’à ce que vous obteniez le champ que vous désirez.

Cet exemple récupère tous les objets Entry du Blog dont le nom est Beatles Blog:

>>> Entry.objects.filter(blog__name__exact='Beatles Blog')

Cette étendue peut être aussi profonde que vous le désirez.

Cela fonctionne aussi à rebour. Pour «remonter» dans une relation, utilisez simplement le nom du modèle en bas de casse.

Cet exemple récupère tous les objets Blog qui ont au moin une Entry dont le titre contient 'Lennon':

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

Relations entre clefs étrangères

Si un modèle possède une ForeignKey, les instances de ce modèles auront accès au objets (étrangers) liés via un simple attribut de modèle, par exemple:

e = Entry.objects.get(id=2)
e.blog # Returns the related Blog object.

Vous pouvez utiliser «get» et «set» via un attribut de clef étrangère . Comme vous pouvez vous y attendre, les modifications apportées à la clef étrangère ne sont pas enregistré dans la base de données avant que vous n’appeliez save(), par exemple:

e = Entry.objects.get(id=2)
e.blog = some_blog
e.save()

Si un champ ForeignKey a un null=True initialisé (c’est à dire qu’il autorise les valeurs NULL), vous pouvez lui assigner None:

e = Entry.objects.get(id=2)
e.blog = None
e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

L’accès descendant aux relations un-à-plusieurs est mis en cache lors du premier accès à l’objet lié. Les accès ultérieurs à la clef étrangère d’une même instance d’objet sont mis en cache, par exemple:

e = Entry.objects.get(id=2)
print e.blog  # Hits the database to retrieve the associated Blog.
print e.blog  # Doesn't hit the database; uses cached version.

Notez que la méthode select_related du QuerySet garnit récursivement le cache de toutes les relations un-à-plusieurs à l’avance:

e = Entry.objects.select_related().get(id=2)
print e.blog  # Doesn't hit the database; uses cached version.
print e.blog  # Doesn't hit the database; uses cached version.

select_related() est documentée dans la section «Méthodes du QuerySet qui retournent un nouveau QuerySet»

Relations inverses des clefs étrangères

Les relations de clefs étrangères sont automatiquement symétriques - une relation inverse est déduite de la présence d’une ForeignKey pointant sur un autre modèle.

Si un modèle possède une ForeignKey, les instances de clef étrangère du modèle aurons accès à un Manager qui retournera toutes les instances du premier modèle. Par défaut, ce Manager est appelé FOO_set, où FOO est le nom du modèle source, converti en bas de casse. Ce Manager retourne les QuerySets, qui peuvent filtrés et manipulés comme décrit dans la section «Récupérer des objets».

Voici un exemple:

b = Blog.objects.get(id=1)
b.entry_set.all() # Returns all Entry objects related to Blog.

# b.entry_set is a Manager that returns QuerySets.
b.entry_set.filter(headline__contains='Lennon')
b.entry_set.count()

Vous pouvez outrepasser le nom du FOO_set en précisant le paramètre related_name dans la définition de ForeignKey(). Par exemple, si le modèle Entry à été modifié au profit de blog = ForeignKey(Blog, related_name='entries'), l’exemple de code précédent ressemblera à ceci:

b = Blog.objects.get(id=1)
b.entries.all() # Returns all Entry objects related to Blog.

# b.entries is a Manager that returns QuerySets.
b.entries.filter(headline__contains='Lennon')
b.entries.count()

Vous ne pouvez pas avoir accès à un Manager de ForeignKey inverse depuis la classe; il doit être accédé depuis une instance:

Blog.entry_set # Raises AttributeError: "Manager must be accessed via instance".

En complément des méthodes QuerySet définies dans la section «Récupérer des objets», le Manager de ForeignKey propose ces méthodes supplémentaires:

  • add(obj1, obj2, ...): ajoute les objets du modèle spécifié au jeu de modèle lié, par exemple:

    b = Blog.objects.get(id=1)
    e = Entry.objects.get(id=234)
    b.entry_set.add(e) # Associates Entry e with Blog b.
    
  • create(**kwargs): créé un nouvel objet, l’enregistre, et le place dans le jeu d’objet lié. Elle renvoie l’objet nouvellement créé:

    b = Blog.objects.get(id=1)
    e = b.entry_set.create(headline='Hello', body_text='Hi',
    pub_date=datetime.date(2005, 1, 1))
    # No need to call e.save() at this point -- it's already been
    saved.
    

    Cela équivaut (en beaucoup plus simple) au code suivant:

    b = Blog.objects.get(id=1)
    e = Entry(blog=b, headline='Hello', body_text='Hi',
    pub_date=datetime.date(2005, 1, 1))
    e.save()
    

    Notez qu’il n’y a pas besoin de préciser l’argument mot-clef du modèle qui définit la relation. Dans l’exemple précédent, nous ne transmettons pas le paramètre blog à create(). Django comprend que le nouveau champ Entry de l’objet blog doit prendre la valeur b.

  • remove(obj1, obj2, ...): supprime les objets du modèle spécifié du jeu d’objet lié:

    b = Blog.objects.get(id=1)
    e = Entry.objects.get(id=234)
    b.entry_set.remove(e) # Disassociates Entry e from Blog b.
    

    De façon à prévenir des incohérence dans la base de données, cette méthode n’existe que pour les objets ForeignKeynull=True. Si le champ lié ne peut être fixé None (NULL), alors un objet ne peut être supprimé d’une relation sans avoir été ajouté à une autre. Dans l’exemple précédent, supprimer e de b.entry_set() équivaut à faire un e.blog = None, et puisque la ForeignKey du blog n’a pas de null=True, cela n’est pas valide.

  • clear(): supprime tous les objets du jeu d’objet lié:

    b = Blog.objects.get(id=1)
    b.entry_set.clear()
    

    Notez que ceci n’efface pas les objets liés - il s’agit juste d’une dissociation.

    Tout comme remove(), clear() est disponible uniquement sur les ForeignKeynull=True.

Pour assigner les membres d’un jeu lié en une seule fois, assignez leurs simplement n’importe quel objet itérable, par exemple:

b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]

Si la méthode clear() est diponible, tous les objets pré-existants seront supprimés de l’entry_set avant que tous les objets dans l’itérable (dans ce cas, une liste) soient ajoutés au jeu. Si la méthode clear() n’est pas disponible, tous les objets dans l’itérable seront ajouté sans supprimer 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.

Relations plusieurs-à-plusieurs

Chaque terminaison d’une relation plusieurs-à-plusieurs ont automatiquement une API d’accès à l’autre. L’API fonctionne simplement comme une relation un-à-plusieur «inverse» (décrite dans la section précédente).

La seule différence se situe dans le nommage des attributs: le modèle qui défini le ManyToManyField utilise lui même le nom d’attribut de ce champ, alors que le modèle «inverse» utilise le nom du modèle converti en bas de casse du modèle d’origine, augmenté d’un '_set' (tout comme les relations un-à-plusieurs inverses).

Un exemple rends ce concept plus facile à comprendre:

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

Tout comme ForeignKey, ManyToManyField peut préciser un related_name. Dans l’exemple précédent, si le ManyToManyField dans Entry avait spécifiéa related_name='entries', alors chaque instance d’Author aurait eu un attribut entries à la place de entry_set.

Comment les relations ascendantes sont-elles possibles ?

Les autres mappeurs relationnels objets imposent que vous définissiez les deux terminaisons d’une relation. Les développeurs de Django pensent qu’il s’agit là d’une violation du principe DRY (Don’t Repeat Yourself, ne vous répétez pas), Django vous impose donc de définir seulement une terminaison de la relation. Mais comment cela est-il possible, sachant qu’un modèle de classe ne sait pas quel est l’autre modèle de classe qui lui est associé avant que ces autres modèles de classe soient chargés ?

La réponse réside dans le paramètre INSTALLED_APPS. La première fois qu’un modèle est chargé, Django parcours chaque modèle situé dans INSTALLED_APPS et crée au besoin les relation ascendantes en mémoire. Essentiellement, l’une des fonctions de INSTALLED_APPS est d’indiquer à Django le domaine de modèle complet.

Requêtes sur les objets liés

Les requêtes impliquant des objets liés suivent les mêmes règles que les requêtes impliquant des champs à valeur normale. En spécifiant la valeur auquel doit correspondre une requête, vous pouvez soit utiliser l’instance d’objet elle même ou la valeur de la clef primaire pour cet objet.

Par exemple, si vous avez un objet b de Blog avec un id=5, les trois requêtes suivantes seront identiques:

Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly

Supprimer des objets

La méthode de suppression se nomme delete(). Cette méthode supprime immédiatement les objets et n’a pas de valeur de retour:

e.delete()

Vous pouvez aussi supprimer des objets par lot. Chaque QuerySet a une méthode delete(), qui supprime tous les membres de ce QuerySet. Par exemple, ce code supprime tous les objets Entry dont l’année de pub_date est 2005:

Entry.objects.filter(pub_date__year=2005).delete()

Lorsque Django supprime un objet, il émule la fonctionnalité de contrainte SQL ON DELETE CASCADE - en d’autres termes, tous les objets qui ont des clefs étrangères pointant sur l’objet à effacer sera effacé avec lui, par exemple:

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

Notez que delete() est la seule méthode de QuerySet qui ne soit pas exposée sur le Manager lui même. C’est un mécanisme de sécurité pour vous éviter d’accidentellement faire un Entry.objects.delete() et effacer ainsi toutes les entrées. Si vous voulez effacer tous les objets, alors vous devez explicitement demander un jeu de requête complet:

Entry.objects.all().delete()

Méthodes d’instance additionnelles

En complément de save() et delete(), un modèle d’objet peut prendre tout ou partie des méthodes suivantes.

get_FOO_display()

Pour chaque champ qui proposant des choices, l’objet aura une méthode get_FOO_display(), où FOO est le nom du champ. Cette méthode renvoie la valeur «humainement lisible» du champ. Par exemple, dans le modèle suivant:

GENDER_CHOICES = (
    ('M', 'Male'),
    ('F', 'Female'),
)
class Person(models.Model):
    name = models.CharField(max_length=20)
    gender = models.CharField(max_length=1,
    choices=GENDER_CHOICES)

Chaque instance Person aura une méthode get_gender_display():

>>> p = Person(name='John', gender='M')
>>> p.save()
>>> p.gender
'M'
>>> p.get_gender_display()
'Male'

get_next_by_FOO(**kwargs) and get_previous_by_FOO(**kwargs)

Pour chaque DateField et chaque DateTimeField qui n’ont pas null=True, l’objet proposera les méthodes get_next_by_FOO() et get_previous_by_FOO(), où FOO est le nom du champ. Cela retourne l’objet précédent et l’objet suivant selon le champ de date, levant l’exception DoesNotExist appropriée si nécessaire.

Les deux méthodes acceptent des arguments mot-clef optionnels, qui doivent être au format décrit dans la section «Recherche de champ».

Notez que dans le cas de valeurs de date identiques, ces méthodes utiliserons en dernier recour l’ID pour contrôle. Ceci garantie qu’aucun enregistrement sera oublié ou dupliqué. Pour un exemple complet, consultez les exemples de l’API de recherche à l’adresse: http://www.djangoproject.com/documentation/0.96/models/lookup/.

get_FOO_filename()

Pour chaque FileField, l’objet aura une méthode get_FOO_filename()FOO est le nom du champ. Ceci renvoie le chemin complet du système de fichier vers le fichier, conformément à votre paramètre MEDIA_ROOT.

Notez que ImageField est techniquement une sous classe de FileField, et qu’ainsi chaque modèle avec un ImageField aura aussi cette méthode.

get_FOO_url()

Pour chaque FileField, l’objet aura une méthode get_FOO_url(), où FOO est le nom du champ. Elle renvoie l’url complet du fichier, conformement à votre paramètre MEDIA_URL. Si la valeur est vide, cette méthode retourne une chaîne vide.

get_FOO_size()

Pour chaque FileField, l’objet aura une méthode get_FOO_size(), où FOO est le nom du champ. Elle renvoie la taille du fichier, en octets. (En coulisses, elle utilise os.path.getsize).

save_FOO_file(filename, raw_contents)

Pour chaque FileField, l’objet aura une méthode save_FOO_file(), où FOO est le nom du champ. Cela enregistre le fichier donné sur le système de fichier, en utilisant le nom de fichier précisé. Si un fichier du même nom existe déjà, Django ajoute un soulignement à la fin du nom de fichier (mais avant l’extension) jusqu’à ce que le nom de fichier soit disponible.

get_FOO_height() et get_FOO_width()

Pour chaque ImageField, l’objet aura les méthodes get_FOO_height() et get_FOO_width(), où FOO est le nom du champ. Elles retournent la largeur (ou la hauteur) de l’image, sous forme d’entier, en pixels.

Raccourcis

En développant des vues, vous découvrirez de nombreux idiomes sur votre parcours d’utilisateur d’API de base de données. Django encode certain de ces idiomes sous forme de raccourcis qui peuvent être utilisé pour simplifier le processus d’écriture des vues. ces fonctions sont dans le module django.shortcuts.

get_object_or_404()

Un idiome courant est d’utiliser get() et de lever un Http404 si l’objet n’existe pas. Cette idiome est résumé par get_object_or_404(). Cette fonction prends un modèle Django pour premier argument et un nombre arbitraire d’arguments mot-clef, qui sont transmis à la fonction get() du manager par défaut. Il lève un Http404 si l’objet n’existe pas, par exemple:

# Get the Entry with a primary key of 3
e = get_object_or_404(Entry, pk=3)

Lorsque vous fournissez un modèle à cette fonction faisant office de raccourci, le manager par défaut est utilisé pour exécuter la requête get() sous jacente. Si vous ne voulez pas utiliser le manager par défaut, ou si vous voulez rechercher une liste d’objets liés, vous pouvez proposer get_objet_or_404() avec un objet Manager à la place:

# Get the author of blog instance e with a name of 'Fred'
a = get_object_or_404(e.authors, name='Fred')

# Use a custom manager 'recent_entries' in the search for an
# entry with a primary key of 3
e = get_object_or_404(Entry.recent_entries, pk=3)

get_list_or_404()

get_list_or_404 agit de la même façon que get_object_or_404(), sauf qu’elle utilise filter() à la place de get(). Elle lève un Http404 si la liste est vide.

Recour au SQL brut

Si vous avez besoin d’écrire une requête SQL trop complexe pour être prise en charge par le mappeur de base de données Django, vous pouvez retourner dans le mode d’instruction SQL brut.

La méthode préférée pour faire cela est de donner des méthodes personnalisées à votre modèle ou au manager qui exécute les requêtes. Bien que rien n’oblige les requêtes en base de données à être au niveau de la couche du modèle sous Django, cette approche conserve toute la logique d’accès aux données à un seul endroit, ce qui est intelligent du point de vue de l’organisation du code. Pour les instructions, consultez l’annexe B.

Finallement, il est important de noter que la couche de base de données sous Django est simplement une interface vers votre base. Vous pouvez accéder à votre base de données via d’autres outils, d’autres langages de programmation, ou d’autres frameworks de base de données - il n’y a rien de spécifique à Django en matière de base de données.

<< précédentsuivant >>

Dernière modification: 2008-08-04 13:38:17.319493