Il y a déjà une excellente réponse de la part de dani herrera , mais je souhaite en dire plus.
Comme expliqué dans la deuxième option, la solution requise par l'OP consiste à modifier la conception et à mettre en œuvre deux contraintes uniques par paire. L'analogie avec les matchs de basket-ball illustre le problème d'une manière très pratique.
Au lieu d'un match de basket, j'utilise l'exemple avec des matchs de football (ou de football). Un match de football (que j'appelle cela Event
) est joué par deux équipes (dans mes modèles, une équipe est Competitor
). Il s'agit d'une relation plusieurs-à-plusieurs ( m:n
), n
limitée à deux dans ce cas particulier, le principe convient à un nombre illimité.
Voici à quoi ressemblent nos modèles:
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 événement pourrait être:
- titre: Coupe Carabao, 4e tour,
- lieu: Anfield
- Heure: 30. octobre 2019, 19:30 GMT
- participants:
- nom: Liverpool, ville: Liverpool
- nom: Arsenal, ville: London
Maintenant, nous devons résoudre le problème à partir de la question. Django crée automatiquement une table intermédiaire entre les modèles avec une relation plusieurs-à-plusieurs, mais nous pouvons utiliser un modèle personnalisé et ajouter d'autres champs. J'appelle ce modèle Participant
:
Participant à la classe (models.Model):
RÔLES = (
(«H», «Domicile»),
(«V», «Visiteur»),
)
event = models.ForeignKey (Event, on_delete = models.CASCADE)
concurrent = models.ForeignKey (concurrent, on_delete = models.CASCADE)
role = models.CharField (max_length = 1, choix = ROLES)
classe Meta:
unique_together = (
(«événement», «rôle»),
(«événement», «concurrent»),
)
def __str __ (auto):
retourne '{} - {}'. format (self.event, self.get_role_display ())
Le ManyToManyField
possède une option through
qui nous permet de spécifier le modèle intermédiaire. Changeons cela dans le modèle 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
Les contraintes uniques limiteront désormais automatiquement le nombre de concurrents par événement à deux (car il n'y a que deux rôles: Domicile et visiteur ).
Dans un événement particulier (match de football), il ne peut y avoir qu'une seule équipe à domicile et une seule équipe de visiteurs. Un club (Competitor
) peut apparaître comme équipe à domicile ou comme équipe de visiteurs.
Comment gérons-nous maintenant toutes ces choses dans l'administrateur? Comme ça:
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)
Nous avons ajouté le Participant
as inline dans le EventAdmin
. Lorsque nous créons de nouveaux, Event
nous pouvons choisir l'équipe d'accueil et l'équipe de visiteurs. L'optionmax_num
limite le nombre d'entrées à 2, donc pas plus de 2 équipes peuvent être ajoutées par événement.
Cela peut être refactorisé pour différents cas d'utilisation. Disons que nos événements sont des compétitions de natation et au lieu de la maison et du visiteur, nous avons les couloirs 1 à 8. Nous refactorisons simplement 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())
Avec cette modification, nous pouvons avoir cet événement:
Un nageur ne peut apparaître qu'une seule fois dans une manche et un couloir ne peut être occupé qu'une seule fois dans une manche.
J'ai mis le code sur GitHub: https://github.com/cezar77/competition .
Encore une fois, tous les crédits reviennent à dani herrera. J'espère que cette réponse apporte une valeur ajoutée aux lecteurs.