C'è già un'ottima risposta da parte di dani herrera , tuttavia desidero approfondirla ulteriormente.
Come spiegato nella seconda opzione, la soluzione richiesta dall'OP è quella di modificare il design e implementare due vincoli univoci a coppie. L'analogia con le partite di basket illustra il problema in modo molto pratico.
Invece di una partita di basket, uso esempi con partite di calcio (o di calcio). Una partita di calcio (che io la chiamo Event
) è giocata da due squadre (nei miei modelli una squadra è Competitor
). Questa è una relazione molti-a-molti ( m:n
), con un n
limite a due in questo caso particolare, il principio è adatto per un numero illimitato.
Ecco come appaiono i nostri modelli:
class Competitor(models.Model):
name = models.CharField(max_length=100)
city = models.CharField(max_length=100)
def __str__(self):
return self.name
class Event(models.Model):
title = models.CharField(max_length=200)
venue = models.CharField(max_length=100)
time = models.DateTimeField()
participants = models.ManyToManyField(Competitor)
def __str__(self):
return self.title
Un evento potrebbe essere:
- titolo: Coppa Carabao, 4 ° turno,
- sede: Anfield
- orario: 30. ottobre 2019, 19:30 GMT
- partecipanti:
- nome: Liverpool, città: Liverpool
- nome: Arsenal, città: Londra
Ora dobbiamo risolvere il problema dalla domanda. Django crea automaticamente una tabella intermedia tra i modelli con una relazione molti-a-molti, ma possiamo usare un modello personalizzato e aggiungere altri campi. Chiamo quel modello Participant
:
partecipante alla classe (models.Model):
RUOLI = (
('H', 'Home'),
("V", "Visitatore"),
)
event = models.ForeignKey (Event, on_delete = models.CASCADE)
competitor = models.ForeignKey (Competitor, on_delete = models.CASCADE)
ruolo = models.CharField (max_length = 1, scelte = ROLES)
classe Meta:
unique_together = (
("evento", "ruolo"),
("evento", "concorrente"),
)
def __str __ (self):
return '{} - {}'. format (self.event, self.get_role_display ())
L' ManyToManyField
ha un'opzione through
che ci permette di specificare il modello intermedio. Cambiamo quello nel modello Event
:
class Event(models.Model):
title = models.CharField(max_length=200)
venue = models.CharField(max_length=100)
time = models.DateTimeField()
participants = models.ManyToManyField(
Competitor,
related_name='events', # if we want to retrieve events for a competitor
through='Participant'
)
def __str__(self):
return self.title
I vincoli univoci ora limitano automaticamente il numero di concorrenti per evento a due (perché ci sono solo due ruoli: Casa e Visitatore ).
In un particolare evento (partita di calcio) può esserci solo una squadra di casa e una sola squadra ospite. Un club ( Competitor
) può apparire come squadra di casa o come squadra ospite.
Come gestiamo ora tutte queste cose nell'amministratore? Come questo:
from django.contrib import admin
from .models import Competitor, Event, Participant
class ParticipantInline(admin.StackedInline): # or admin.TabularInline
model = Participant
max_num = 2
class CompetitorAdmin(admin.ModelAdmin):
fields = ('name', 'city',)
class EventAdmin(admin.ModelAdmin):
fields = ('title', 'venue', 'time',)
inlines = [ParticipantInline]
admin.site.register(Competitor, CompetitorAdmin)
admin.site.register(Event, EventAdmin)
Abbiamo aggiunto Participant
come inline in EventAdmin
. Quando ne creiamo di nuovi Event
possiamo scegliere la squadra di casa e la squadra di visitatori. L'opzione max_num
limita il numero di voci a 2, quindi non è possibile aggiungere più di 2 squadre per evento.
Questo può essere refactored per diversi casi d'uso. Supponiamo che i nostri eventi siano gare di nuoto e invece di casa e visitatori, abbiamo corsie da 1 a 8. Rifattorizziamo semplicemente Participant
:
class Participant(models.Model):
ROLES = (
('L1', 'lane 1'),
('L2', 'lane 2'),
# ... L3 to L8
)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
competitor = models.ForeignKey(Competitor, on_delete=models.CASCADE)
role = models.CharField(max_length=1, choices=ROLES)
class Meta:
unique_together = (
('event', 'role'),
('event', 'competitor'),
)
def __str__(self):
return '{} - {}'.format(self.event, self.get_role_display())
Con questa modifica possiamo avere questo evento:
Un nuotatore può apparire solo una volta in un caldo e una corsia può essere occupata solo una volta in un caldo.
Ho inserito il codice su GitHub: https://github.com/cezar77/competition .
Ancora una volta, tutti i crediti vanno a dani herrera. Spero che questa risposta offra un valore aggiunto ai lettori.