Fare le query
Ora che abbiamo creato i nostri modelli possiamo usare l'interfaccia di Django per creare, modificare ed eliminare istanze dei nostri modelli.
Questo è il contenuto del file corsi/models.py
:
from django.db import models
from django.contrib.auth.models import User
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
class Meta:
verbose_name_plural = "Corsi"
Creare oggetti
Come abbiamo visto in precedenza Django implementa il pattern Active record e quindi per creare i nostri dati dobbiamo interagire con le classi che abbiamo definito.
Per eseguire il codice qua sotto useremo la shell Django, richiamabile con il comando:
python3 manage.py shell
La shell si presenterà come una REPL simile a quella di Python:
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
Da questa shell possiamo eseguire tutti gli esempi successivi.
Per cominciare creiamo una Categoria
:
from corsi.models import Categoria
categoria = Categoria(titolo="Sviluppo software")
categoria.save()
Creare una istanza della classe non è sufficiente per persisterla nel database, bisogna chiamare il
metodo save()
. save()
si occupa sia dell'inserimento che dell'aggiornamento in database
automaticamente, chiamandolo più di una volta sulla stessa istanza non ne creerà di nuove.
Esiste un helper create()
che raggruppa le due operazioni che useremo negli esempi successivi:
Categoria.objects.create(titolo="Sviluppo software")
L'attributo objects
è una istanza di un Manager, viene creata per default in ogni modello ed è
l'interfaccia che ci permette di fare le query al database attraverso il nostro modello.
Relazioni uno a molti
Ora procediamo creando il Corso
:
from corsi.models import Corso
corso = Corso.objects.create(
titolo="Corso Django", descrizione="Un corso su Django", categoria=categoria)
Come potete vedere i campi di relazione si assegnano usando una istanza salvata in database del modello a cui fanno riferimento.
Ogni ForeignKey
crea nel modello a cui punta un Manager di relazione, analogo ad objects
.
Questo Manager si presenta sottoforma di attributo con nome <nome modello con fk>_set
, nel nostro
caso Categoria
avrà un attributo corso_set
. Tramite questo Manager sarà possibile eseguire
tutte le query che vedremo successivamente.
Relazioni molti a molti
Per comporre una relazione molti a molti abbiamo bisogno che entrambi i modelli siano già salvati in database. Quindi procediamo con la creazione di un utente che fungerà da docente per il nostro corso:
from django.contrib.auth.models import User
docente = User.objects.create_user("docente")
Il modello User
implementa un metodo specifico create_user
come interfaccia per creare una istanza,
questo è un pattern abbastanza diffuso per nascondere un po' di complessità per gli utilizzatori.
Per aggiungere il docente al corso si usa la seguente sintassi:
corso.docenti.add(docente)
L'interfaccia per aggiornare i ManyToManyField
ha delle somiglianze con quella dei set
del linguaggio
Python.
Leggere dal database
Il metodo per recuperare una singola istanza è .get()
, può essere chiamato senza parametri se esiste
una sola istanza in database per un dato modello oppure possiamo passargli dei parametri:
categoria = Categoria.objects.get()
categoria_con_parametri = Categoria.objects.get(titolo="Sviluppo software")
categoria == categoria_con_parametri
La nostra comparazione restituisce True
perché l'istanza è la stessa.
Per recuperare tutte le istanze si usa .all()
:
Categoria.objects.all()
Per prendere tutti i corsi di una istanza di categoria possiamo usare il Manager di relazione
corso_set
:
categoria.corso_set.all()
Il nome del manager è per default il nome del modello che ha la ForeignKey
unito alla stringa _set
.
Per recuperare invece più di una istanza filtrandola per qualche parametro si usa .filter()
:
Categoria.objects.filter(titolo="Sviluppo software")
I campi ForeignKey
e ManyToManyField
si possono filtrare usando una istanza di un altro modello o
un suo attributo:
Corso.objects.filter(categoria=categoria)
Corso.objects.filter(categoria__titolo="Sviluppo software")
Corso.objects.filter(docenti=docente)
Corso.objects.filter(docenti__username="docente")
Sia all()
che filter()
restituiscono dei QuerySet
. I QuerySet
sono delle classi iterabili
contenenti istanze del modello su cui è stata fatta la query. Ad esempio possiamo stampare i singoli
modelli presenti:
qs = Categoria.objects.all()
for categoria in qs:
print(categoria)
I QuerySet
sono lazy nel senso che sono valutati solo quando ci vengono fatte sopra delle operazioni
come iterarci sopra o stamparli. Nei nostri esempi sono stati valutati perché nella shell viene stampata
la rappresentazione dell'output delle istruzioni date.
Ad esempio se assegniamo un QuerySet
ad una variabile, il QuerySet
non viene valutato e quindi la
query SQL sottostante non viene eseguita.
Possiamo filtrare le nostre istanze anche in modo negativo cioè specificando dei criteri per
l'esclusione usando il metodo .exclude()
:
Corso.objects.exclude(categoria=categoria)
Con la query precedente abbiamo escluso tutti i corsi facenti parte di una categoria specifica.
I metodi filter
ed exclude
possono essere usati contemporaneamente per costruire lo stesso QuerySet.
I QuerySet sono ordinabili usando il metodo order_by()
:
Corso.objects.order_by("titolo")
Se non ordiniamo esplicitamente i QuerySet non sono ordinati, anche se può sembrare lo siano. Possiamo ordinare i QuerySet per un numero arbitriario di campi.
I metodi che abbiamo visto finora restituiscono sempre un QuerySet contente istanze di modelli, questo comporta:
- fare una query SQL per recuperare tutte le colonne delle righe coinvolte
- per ognuna di queste righe creare una nuova istanza del nostro modello
In alcuni casi questo potrebbe comportare far fare alla nostra applicazione più lavoro di quello necessario. Esistono altri due metodi che ci permettono di restituire un sottoinsieme dei campi senza costruire le istanze dei modelli.
Il metodo values()
restituisce un QuerySet di dizionari con chiave il nome del campo e come valore
il valore del campo:
Corso.objects.values("titolo", "categoria")
Ogni elemento del QuerySet sarà un dizionario del tipo {"titolo": "titolo", "categoria": 1}
dove
1
è il valore della chiave primaria dell'istanza di Categoria
collegata al Corso
. Se non
specifichiamo alcun campo saranno restituiti tutti quelli del modello.
Il metodo values_list()
invece restituisce un QuerySet di tuple:
Corso.objects.values_list("titolo", "categoria")
Ogni elemento del QuerySet sarà una tupla del tipo ("titolo", 1)
. values_list()
dispone di un
parametro che permette di rendere flat l'output restituito:
Corso.objects.values_list("categoria", flat=True)
Ogni elemento del QuerySet sarà un numero che identifica il valore di una chiave primaria di Categoria
.
Questo metodo si sposa bene con distinct()
che elimina i duplicati dal nostro QuerySet.
Infine altri due metodi utili sono count()
per far contare al database quante istanze ci sono in un
QuerySet, mentre exists()
restituisce un valore boolean che indica la presenza o meno di istanze
nel QuerySet:
Corso.objects.filter(categoria=categoria).count()
Corso.objects.filter(categoria=categoria).exists()
È possibile visualizzare una rappresentazione, non sempre precisa, della query che ha popolato un
QuerySet stampandone l'attributo query
:
qs = Corso.objects.all()
print(qs.query)
SELECT "corsi_corso"."id", "corsi_corso"."titolo", "corsi_corso"."descrizione", "corsi_corso"."categoria_id", "corsi_corso"."brochure", "corsi_corso"."creato", "corsi_corso"."aggiornato" FROM "corsi_corso"
Aggiornare istanze modelli
Per aggiornare un QuerySet si usa il metodo update()
:
Corso.objects.filter(titolo="Sviluppo software").update(categoria=categoria)
Usare update()
su un QuerySet non implica chiamare il metodo save()
di ogni oggetto presente nel
QuerySet, viene generata una singola query SQL UPDATE
.
Eliminare istanze modelli
Per eliminare delle istanze di modelli possiamo usare il metodo delete()
sia sulla istanza che sui
QuerySet
:
Corso.objects.all().delete()
Categoria.objects.get().delete()
Abbiamo cancellato prima tutte le istanze di Corso
e successivamente la Categoria
perché abbiamo
configurato Django per proteggere la cancellazione di una Categoria
quando viene usata da un Corso
.
Esercizi
Apri una shell Django e prova a creare, modificare ed eliminare dei modelli.
Leggi l'introduzione della documentazione dei QuerySet.
Consulta la documentazione dei Manager.
Consulta la documentazione dei Manager di relazione.