Relazioni tra Modelli
Le relazioni tra modelli permettono di collegare tabelle diverse nel database. Django supporta tre tipi di relazione: uno-a-molti (ForeignKey), uno-a-uno (OneToOneField) e molti-a-molti (ManyToManyField).
ForeignKey (Uno-a-Molti)
Una ForeignKey crea una relazione uno-a-molti. Ogni articolo appartiene a una sola categoria, ma una categoria puo avere molti articoli:
from django.db import models
class Categoria(models.Model):
nome = models.CharField(max_length=100)
def __str__(self):
return self.nome
class Articolo(models.Model):
titolo = models.CharField(max_length=200)
categoria = models.ForeignKey(
Categoria,
on_delete=models.CASCADE,
related_name='articoli',
)
Opzioni on_delete
Il parametro on_delete definisce il comportamento quando lâoggetto referenziato viene eliminato:
# CASCADE: elimina anche gli articoli collegati
categoria = models.ForeignKey(Categoria, on_delete=models.CASCADE)
# PROTECT: impedisce l'eliminazione se ci sono oggetti collegati
categoria = models.ForeignKey(Categoria, on_delete=models.PROTECT)
# SET_NULL: imposta il campo a NULL (richiede null=True)
categoria = models.ForeignKey(Categoria, on_delete=models.SET_NULL, null=True)
# SET_DEFAULT: imposta il campo al valore predefinito
categoria = models.ForeignKey(Categoria, on_delete=models.SET_DEFAULT, default=1)
# DO_NOTHING: non fa nulla (puo causare errori di integrita)
categoria = models.ForeignKey(Categoria, on_delete=models.DO_NOTHING)
OneToOneField (Uno-a-Uno)
Crea una relazione uno-a-uno, utile per estendere un modello esistente:
from django.contrib.auth.models import User
class Profilo(models.Model):
utente = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name='profilo',
)
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True)
# Utilizzo
utente = User.objects.get(username='mario')
print(utente.profilo.bio) # accesso tramite related_name
ManyToManyField (Molti-a-Molti)
Crea una relazione molti-a-molti. Django genera automaticamente una tabella intermedia:
class Tag(models.Model):
nome = models.CharField(max_length=50, unique=True)
class Articolo(models.Model):
titolo = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag, related_name='articoli', blank=True)
# Gestire la relazione
articolo = Articolo.objects.get(pk=1)
tag_python = Tag.objects.get(nome='python')
articolo.tags.add(tag_python) # aggiungere
articolo.tags.remove(tag_python) # rimuovere
articolo.tags.set([tag1, tag2, tag3]) # impostare la lista completa
articolo.tags.clear() # rimuovere tutti
articolo.tags.all() # tutti i tag dell'articolo
Modello Through (Tabella Intermedia Personalizzata)
Per aggiungere campi extra alla relazione molti-a-molti, usa un modello through:
class Studente(models.Model):
nome = models.CharField(max_length=100)
corsi = models.ManyToManyField('Corso', through='Iscrizione')
class Corso(models.Model):
nome = models.CharField(max_length=200)
class Iscrizione(models.Model):
studente = models.ForeignKey(Studente, on_delete=models.CASCADE)
corso = models.ForeignKey(Corso, on_delete=models.CASCADE)
data_iscrizione = models.DateField(auto_now_add=True)
voto_finale = models.DecimalField(max_digits=4, decimal_places=2, null=True)
class Meta:
unique_together = ['studente', 'corso']
# Con through, usa il modello intermedio per creare relazioni
Iscrizione.objects.create(studente=mario, corso=django_corso, voto_finale=28.5)
Relazioni Inverse (related_name)
Il parametro related_name definisce il nome per accedere alla relazione inversa:
# Dal lato "uno" verso "molti"
categoria = Categoria.objects.get(pk=1)
articoli_categoria = categoria.articoli.all() # usa il related_name
# Filtrare tramite relazione inversa
categorie_con_articoli = Categoria.objects.filter(articoli__pubblicato=True)
Ottimizzazione con select_related e prefetch_related
Le relazioni possono causare il problema N+1 query. Django offre due metodi per risolverlo:
# select_related: per ForeignKey e OneToOneField (usa JOIN SQL)
articoli = Articolo.objects.select_related('categoria', 'autore').all()
for articolo in articoli:
print(articolo.categoria.nome) # nessuna query aggiuntiva
# prefetch_related: per ManyToManyField e relazioni inverse (usa query separate)
articoli = Articolo.objects.prefetch_related('tags').all()
for articolo in articoli:
print(articolo.tags.all()) # nessuna query aggiuntiva
# Combinazione di entrambi
articoli = Articolo.objects.select_related('categoria').prefetch_related('tags').all()
select_related esegue un JOIN SQL ed e ideale per relazioni singole. prefetch_related esegue query separate ed e ideale per relazioni molti-a-molti e inverse.
Conclusione
Le relazioni tra modelli sono fondamentali per progettare database relazionali in Django. ForeignKey, OneToOneField e ManyToManyField coprono tutti i tipi di relazione, mentre related_name, i modelli through e le ottimizzazioni con select_related e prefetch_related ti permettono di gestirle in modo efficiente e leggibile.