Il dettaglio del singolo corso

Dopo aver fatto la vista per listare i nostri corsi creiamo una vista per vedere il dettaglio di ogni singolo corso. Aggiungiamo la nuova vista in corsi/views.py.

from django.views.generic import DetailView, ListView

from corsi.models import Corso


class CorsoListView(ListView):
    model = Corso


class CorsoDetailView(DetailView):
    context_object_name = 'corso'
    queryset = Corso.objects.all()

Questa nuova vista CorsoDetailView estende la vista generica DetailView e sovrascrive due attributi:

  • context_object_name per usare corso come nome di variabile contente l'istanza di Corso passata al template, altrimenti sarebbe stata object.
  • queryset per specificare il QuerySet dal quale prendere l'istanza, utile per filtrare a monte i modelli che vogliamo poter richiamare da questa vista.

L'uso di questi attributi è abbastanza arbitrario, avremmo potuto definire model come abbiamo fatto per CorsoListView ma avremmo visto due attributi utili in meno.

Fatta la nostra vista dobbiamo aggiungerla in corsi/urls.py aggiungendola in urlpatterns:

urlpatterns = [
    path("corsi/", views.CorsoListView.as_view(), name="corsi-list"),
    path("corsi/<int:pk>/", views.CorsoDetailView.as_view(), name="corsi-detail"),
]

Qui possiamo fare attenzione a due cose: la prima è che i path sono considerati chiusi, nel senso che una vista risponderà ad un path solo se viene trovata una corrispondenza esatta; se non fosse stato così il secondo path non sarebbe stato raggiungibile.

L'altra cosa da notare è la cattura dei parametri nell'url, la sintassi è formata da due parti separate da ::

  • la prima indica il tipo, in questo esempio int per catturare solo le cifre; non è obbligatoria e se non fornita cattura il contenuto fino al prossimo /.
  • La seconda parte è il nome che diamo alla variabile passata alla vista, pk è dettata da DetailView , in questo caso la variabile è configurabile tramite l'attributo pk_url_kwarg. Le viste fatte a classi appaiono come veramente magiche quando le si usano, bisogna farci la mano.

Collegata la vista al routing possiamo creare il template che si aspetta corsi/templates/corsi/corso_detail.html:

{% extends "corsi/base.html" %}

{% block content %}
<h2>Corso: {{ corso }}</h2>
<p>Categoria:  {{ corso.categoria }}</p>
<p>Docenti: {% for docente in corso.docenti.all %}{{ docente.username }}{% endfor %}</p>
<p>Descrizione: {{ corso.descrizione }}</p>
{% endblock %}

Dovrebbe essere quasi tutto familiare, facciamo attenzione solo alla variabile che usiamo per il for, essenzialmente è lo stesso codice che avremmo scritto in una vista o nella shell ma senza le parentesi.

Abbiamo tutti i pezzi per poter chiamare la nostra vista dal browser, il fatto che dobbiamo sapere l'id del nostro modello però è un po' scomodo. Per ovviare a questo possiamo estendere il nostro modello Corso, aggiungendo un metodo get_absolute_url che tramite la funzione reverse recupera il path assoluto della vista per il singolo corso.

Usare reverse ci permette di poter cambiare in futuro i path delle nostre url senza dover aggiornarlo in tante occorrenze sparse nel nostro codice.

from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse


class Categoria(models.Model):
    titolo = models.CharField(max_length=100)

    creato = models.DateTimeField(auto_now_add=True)
    aggiornato = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.titolo

    class Meta:
        verbose_name_plural = "Categorie"


class Corso(models.Model):
    titolo = models.CharField(max_length=100)
    descrizione = models.TextField()
    categoria = models.ForeignKey(Categoria, null=True, blank=True, on_delete=models.PROTECT)
    docenti = models.ManyToManyField(User)

    creato = models.DateTimeField(auto_now_add=True)
    aggiornato = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.titolo

    def get_absolute_url(self):
        return reverse("corsi-detail", args=[self.pk])

    class Meta:
        verbose_name_plural = "Corsi"

Possiamo aggiornare il template corsi/templates/corsi/corso_list.html per trasformare le occorrenze dei corsi in un link alla relativa pagina di dettaglio:

{% extends "corsi/base.html" %}

{% block content %}
    <h2>Corsi</h2>
    <ul>
        {% for corso in object_list %}
        <li><a href="{{ corso.get_absolute_url }}">{{ corso }}</a></li>
        {% endfor %}
    </ul>
{% endblock %}

Puntiamo il browser all'indirizzo http://127.0.0.1:8000/corsi/corsi/ per verificare che funzioni.

Infine ricordiamoci sempre di aggiornare il nostro codice su git:

git add corsi
git commit -m "Aggiungiamo vista per dettaglio corso"
git push origin main

Esercizi

Leggi la documentazione di DetailView.

Leggi la documentazione delle urls.

Leggi la documentazione di get_absolute_url.

Leggi la documentazione di reverse