Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Accéder à une Foreign Key dans la classe d'un modèle

+5 votes

Je cherche à compter le nombre d'objets lié à une Foreign Key dans mon modèle, c'est un cas assez classique et malgré les différentes recherches je n'y parviens pas.

donc je fais :

class Author(models.Model):
    name = models.Charfield(max_length=200)
    nb_books = Author.books.count()

class Book(models.Model):
    author = models.ForeignKey(Author, related_name='books')
    title = models.CharField(max_length=400)

Là j'ai une belle erreur : 'NameError: name 'Author' is not defined'. De ce que je comprend, je ne peux pas utiliser Author car je n'ai pas instancié la classe ?

J'ai résolu le problème en faisant une boucle dans ma vue Django qui va compter le nombre de livre et ajoute un champs au dictionnaire retourner par Author.objects.all(), résultat ça marche mais ça me semble un peu moche comme méthode. De plus j'aimerai appliquer un tri sur le nombre de livres et donc utiliser une class Meta avec un 'ordering' plutôt que de faire le tri dans ma vue.

demandé 23-Jul-2015 par Stiquemou (206 points)

2 Réponses

+3 votes
 
Meilleure réponse

De ce que je comprend, je ne peux pas utiliser Author car je n'ai pas
instancié la classe ?

En plus de la reponse de @jc sur ce point, j'ajoute que les classes definies ds le fichier models.py sont des classes qui ne seront jamais instanciées nulle part. On parle ici de programmation declarative, dans le sens où, ces classes ne sont utilisées par django que pour definir (declarer), quels sont les champs/attributs de la table/classe.

La preuve, lorsque tu veux creer un nouvel objet dans ta console:

(w) >>> un_autheur = Author(name='Pitt')
(w) >>> type(un_autheur.name)
<class 'str'>
(w) >>> 

Pour avoir le nombre de livre dans chaque objet de type autheur, ayechoyouzeucode:

(w) >>> from django.db.models import Count
(w) >>> all_authors = Author.objects.all().annotate(number_of_books=Count('books__id'))
(w) >>> for i in all_authors:
...   print(vars(i))
... 
{'number_of_books': 3, '_state': <django.db.models.base.ModelState object at 0x7fb0e323d320>, 'id': 1, 'name': 'foo'}
{'number_of_books': 2, '_state': <django.db.models.base.ModelState object at 0x7fb0e323d630>, 'id': 2, 'name': 'bar'}
{'number_of_books': 1, '_state': <django.db.models.base.ModelState object at 0x7fb0e323d6d8>, 'id': 3, 'name': 'baz'}
(w) >>> 

Pour avoir le nombre de livres en partant d'un objet de type Author:

(w) >>> a1 = Author.objects.first()
(w) >>> a1.books.count()
3
(w) >>> a1.name
'foo'
(w) >>> 
répondu 23-Jul-2015 par Nsukami_ (1,986 points)
sélectionné 28-Jul-2015 par Stiquemou

Du coup, est-ce qu'un order by sur la colonne générée par l'annotate fonctionne ?

   Author.objects.all().annotate(number_of_books=Count('books__id').order_by('number_of_books',)

Ma console dit que oui :\

(w) >>> from django.db.models import Count
(w) >>> x = Author.objects.all().annotate(number_of_books=Count('books__id')).order_by('number_of_books')
(w) >>> for i in x: print(i.number_of_books)
... 
1
2
3
(w) >>> 

Bon, pas trop de surprise ici (j’avais en tête ce genre de petites surprises avec l'ORM django :) )

+2 votes

Pour la question originale : tu ne peux pas le faire comme çà.

Tout comme tu ne peux pas faire

In [1]: class A(object):
   ...:     a = "a"
   ...:     b = A.a
   ...:     
NameError: name 'A' is not defined

J'ai résolu le problème en faisant une boucle dans ma vue Django

C'est une mauvaise idée : utilise plutot la méthode count, ca générera une seule requete sql du style select count(...) par auteur, avec la bonne jointure, et qui te renvois direect un entier. Ca t'évitera une itération sur tes n livres et çà fait bosser le moteur sql qui est là pour çà :)
Dans l'idée : fait le maximum de filtrage/tri/etc au niveau de ta base de données.

Point bonus a chaque fois que tu peux le faire en une seule requete. Mais des fois un truc qui se fait bien en SQL va etre compliqué a faire passer dans l'ORM django.

Dans ton cas ca te donnera un truc du genre un_auteur.books.count(), avec un_auteur ton instance d'auteur.

Si t'as besoin de plus de controle, regarde du coté des managers.

Pour sortir les auteur par nombre de livre, un début de solution ici avec la méthode annotate.

Par contre si tu veux rammener les colonnes des auteurs et des livres, le tout ordonnés par le nombre de livre, ca risque de se compliquer.
Pas sur que ce soit possible en une seule requete, mais j'espere me tromper.

répondu 23-Jul-2015 par jc (2,674 points)
...