Jest już świetna odpowiedź od Dani Herrera , jednak chciałbym ją dalej rozwinąć.
Jak wyjaśniono w drugiej opcji, rozwiązaniem wymaganym przez PO jest zmiana projektu i wdrożenie dwóch unikalnych ograniczeń parami. Analogia z meczami koszykówki ilustruje problem w bardzo praktyczny sposób.
Zamiast meczu koszykówki używam przykładu z grami piłkarskimi (lub piłkarskimi). Mecz piłki nożnej (który nazywam Event
) jest rozgrywany przez dwie drużyny (w moich modelach jest drużyna Competitor
). Jest to relacja wiele do wielu ( m:n
), z n
ograniczeniem do dwóch w tym konkretnym przypadku, zasada jest odpowiednia dla nieograniczonej liczby.
Oto jak wyglądają nasze modele:
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
Wydarzeniem może być:
- tytuł: Puchar Carabao, 4. runda,
- miejsce: Anfield
- czas: 30. października 2019, 19:30 GMT
- Uczestnicy:
- nazwa: Liverpool, miasto: Liverpool
- nazwa: Arsenał, miasto: Londyn
Teraz musimy rozwiązać problem z pytania. Django automatycznie tworzy tabelę pośrednią między modelami z relacją wiele do wielu, ale możemy użyć niestandardowego modelu i dodać kolejne pola. Nazywam ten model Participant
:
Uczestnik klasy (modele. model):
ROLE = (
(„H”, „Home”),
(„V”, „Visitor”),
)
event = models.ForeignKey (Event, on_delete = models.CASCADE)
konkurent = modele.ForeignKey (konkurent, on_delete = modele.CASCADE)
rola = modele. CharField (max_length = 1, wybory = ROLE)
klasa Meta:
unikalny_together = (
(„wydarzenie”, „rola”),
(„wydarzenie”, „zawodnik”),
)
def __str __ (self):
zwróć format „{} - {}”. (self.event, self.get_role_display ())
ManyToManyField
Posiada opcję through
, która pozwala nam określić pośredni model. Zmieńmy to w modelu 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
Unikatowe ograniczenia teraz automatycznie ograniczyć liczbę konkurentów na razie do dwóch (bo tam są tylko dwie role: Start i Visitor ).
W konkretnym wydarzeniu (meczu piłki nożnej) może być tylko jedna drużyna gospodarzy i tylko jedna drużyna gości. Club ( Competitor
) może występować jako drużyna gospodarzy lub drużyna gości.
Jak zarządzamy teraz tymi wszystkimi rzeczami w adminie? Lubię to:
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)
Dodaliśmy Participant
jako wbudowany w EventAdmin
. Kiedy tworzymy nowy Event
, możemy wybrać zespół gospodarzy i zespół gości. Opcja max_num
ogranicza liczbę zgłoszeń do 2, dlatego nie można dodać więcej niż 2 drużyn na wydarzenie.
Można to zmienić dla różnych przypadków użycia. Powiedzmy, że nasze imprezy to zawody pływackie, a zamiast domu i gościa mamy ścieżki od 1 do 8. Po prostu zmieniliśmy 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())
Dzięki tej modyfikacji możemy mieć to wydarzenie:
Pływak może pojawić się tylko raz w upale, a tor może być zajęty tylko raz w upale.
Umieszczam kod w GitHub: https://github.com/cezar77/competition .
Ponownie wszystkie kredyty trafiają do Dani Herrera. Mam nadzieję, że ta odpowiedź zapewni czytelnikom pewną wartość dodaną.