Django : contexte multiple passé a un template bug

Le problème exposé dans ce sujet a été résolu.

Bonjour,

J’ai recemment voulu me mettre a Django pour essayer de developper une petite app pour aider la boite d’un pote et je bloque sur un truc tout con.

J’ai bien défini mes modeles, mes vues et mes templates.

Cependant sur une des vues le template doit agréger les données de deux modèles (les collaborateurs et les missions)

Je me retrouve donc avec ce code :

Pour les vues

from django.http import HttpResponse
from django.template import loader

from .models import Collaborateurs, Expe

def index(request):
    liste_collab = Collaborateurs.objects.all()
    template = loader.get_template('dc/index.html')
    context = {
        'liste_collab' : liste_collab
    }
    return HttpResponse(template.render(context, request))
    
def detail_collab(request, collaborateurs_id ):
    detail_collaborateur = Collaborateurs.objects.filter(id=collaborateurs_id)
    liste_expe = Expe.objects.filter(collab=collaborateurs_id)
    template = loader.get_template('dc/cv.html')
    context = {
        'detail_collaborateur' : detail_collaborateur,
        'liste_expe' : liste_expe,
    }
    return HttpResponse(template.render(context, request))

Pour les modèles :

from django.db import models


class Collaborateurs(models.Model):
    nom = models.CharField(max_length=100)
    prenom = models.CharField(max_length=100)
    titre = models.CharField(max_length=200)
    texte_introductif = models.CharField(max_length=700)
    nb_annees_exp = models.IntegerField(default = 0)
    comp_1 = models.CharField(max_length=200)
    comp_2 = models.CharField(max_length=200)
    comp_3 = models.CharField(max_length=200)
    comp_4 = models.CharField(max_length=200)
    def __str__(self):
        return self.nom

class Client (models.Model):
    nom_client = models.CharField(max_length=200)
    secteur = models.CharField(max_length=100)
    def __str__(self):
        return self.nom_client

class Expe(models.Model):
    collab = models.ForeignKey(Collaborateurs, on_delete=models.CASCADE)
    poste = models.CharField(max_length=200)
    client = models.ForeignKey(Client, on_delete=models.CASCADE)
    intro = models.CharField(max_length=700)
    descript = models.CharField(max_length=2000)
    env_tech = models.CharField(max_length=700)
    date_debut = models.DateField('date_debut')
    date_fin = models.DateField('date_fin')
    def __str__(self):
        return self.poste

Et pour le template qui me pose problème :

{% if detail_collaborateur %} 
    <p> Nom : {{ detail_collaborateur.nom }}</br>
    Prenom : {{ detail_collaborateur.prenom }}</br>
    Titre : {{ detail_collaborateur.titre }}</br>
    Texte introduction : </br>
    Nombre d'annees d'experiences :   </br>
    Competences 1 :  </br>
    Competences 2 :  </br>
    Competences 3 :  </br>
    Competences 4 :  </br>
    {% if liste_expe %} 
        {% for expe in liste_expe %}
        <p> ==============================================</br> 
        Mission : {{ expe.poste }} </br>
        Client : {{expe.client}} </br>
        Texte introductif : {{ expe.intro }}</br>
        Description de la mission : {{expe.descript}}</br>
        Environnement technique : {{expe.env_tech}}</br>
        Date début : {{expe.date_debut}} </br>
        Date de fin : {{expe.date_fin}} </br>
        ==============================================</p>
        {% endfor %}
        
    {% else %}
        <p>Pas de mission.</p>
    {% endif %}
{% else %}
    <p>Erreur</p>
{% endif %}

Quand je me rend sur ma page toutes les données des missions s’affichent parfaitement mais les données du collaborateur ne s’affiche pas (mais aucune erreur n’est remontée par Django) :

image.png
image.png

J’ai beau chercher je ne vois pas ou j’ai fait une erreur :o (enfin vu mon niveau je me doute bien que j’ai fait une erreur :) )

Ou-me suis-je trompé ?

merci de votre aide :)

Salut !

Je pense que le problème vient du fait qu’en faisant detail_collaborateur = Collaborateurs.objects.filter(id=collaborateurs_id), tu récupère en fait un Queryset, qui équivaut à une liste de résultats.

Le moteur de templates de Django ne renvoie pas d’erreurs lorsqu’un attribut est manquant, en fait il essaie successivement plusieurs choses pour le récupérer (il essaye notamment collaborateur.nom, collaborateur["nom"], etc), et si rien ne marche, il renvoie une chaine vide.

Quand tu doit récupérer un élément dont tu sait qu’il n’existe qu’en un seul exemplaire (en l’occurrence, il n’y a qu’un seul collaborateur qui possède l’ID collaborateurs_id (d’ailleurs, le S est de trop s’il n’y en a qu’un)), il vaut mieux utiliser la méthode get, qui renvoie directement un résultat au lieu d’un Queryset. Ce qui donne donc ceci.

detail_collaborateur = Collaborateurs.objects.get(id=collaborateurs_id)

Tu peux aussi jeter un oeil du côté de la fonction raccourci get_object_or_404. ;)

+3 -0

J’en profite pour quelques conseils sur le code (sinon la réponse de @rezemika est bonne).

Tu charges à la main le gabari, sache qu’il existe une méthode raccourcie pour faire ça d’un coup plus simplement :

from django.shortcuts import render

def index(request):
    return render(request, "dc/index.html", {
        "liste_collab": Collaborateurs.objects.all()
    })

Aussi pour ce genre de codes très répétitifs de listes et de détails, Django propose des classes d’affichage spécialisées permettant de réduire les vues à :

from django.views.generic import ListView, DetailView

class IndexView(ListView):
    model = Collaborateurs
    template_name = "dc/index.html"
    context_object_name = "liste_collab"

class DetailCollabView(DetailView):
    model = Collaborateurs
    template_name = "dc/cv.html"
    context_object_name = "detail_collaborateur"

    # Seul détail pour ajouter du contexte faut passer par cette méthode
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['liste_expe'] = Expe.objects.filter(collab=self.object.pk)
        return context

C’est pas forcément plus court mais c’est (quasi) que de la configuration (quel modèle, quel nom d’objet dans le contexte, etc.) ce qui évite les erreurs et permet de gagner du temps de réflexion pour ce qui est important. Autre effet kiss-cool, ces vues gèrent automatiquement tous les trucs casse pieds courants, par exemple la pagination, si on lui demande gentiment. Par contre dans la définition des URLs, il faut spécifier IndexViex.as_view() au lieu de juste index comme avec les vues basées sur des fonctions.

Si tu suis le tutoriel Django, tu vas de toute façon voir ces vues basées sur les classes assez rapidement ^^ C’est la bonne façon de faire dans ces cas là.

Et enfin, plutôt que collab.id, il est recommandé sous Django d’utiliser collab.pk (primary key), l’attribut pk étant présent dans tous les modèles et pointant systématiquement vers la clef primaire du modèle à utiliser (souvent le champ id automatiquement généré, mais pas toujours, et c’est une convention Django)

+2 -0

Merci des tips :)

J’avoue j’ai fait des infidélités au tuto de Zds pour suivre celui officiel de Django (que j’ai trouvé plus adapté à ce que je cherchais pour revenir ensuite sur celui de Zds pour approfondir ) mais je suppose qu’ils abordent aussi ce point plus tard dans le cours

Je garde ton truc en tête pour quand j’en arriverai la :)

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte