От Дани Эрреры уже есть отличный ответ , однако я хотел бы уточнить его.
Как объясняется во втором варианте, решение, требуемое OP, состоит в том, чтобы изменить конструкцию и реализовать два уникальных ограничения попарно. Аналогия с баскетбольными матчами очень наглядно иллюстрирует проблему.
Вместо баскетбольного матча я использую пример с футбольными (или футбольными) играми. В футбольную игру (которую я называю это Event
) играют две команды (в моих моделях это команда Competitor
). Это отношение «многие ко многим» ( m:n
), с n
ограничением до двух в данном конкретном случае принцип подходит для неограниченного числа.
Вот как выглядят наши модели:
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
Событие может быть:
- название: Кубок Карабао, 4-й тур,
- место проведения: Anfield
- время: 30. октябрь 2019, 19:30 по Гринвичу
- участники:
- название: Ливерпуль, город: Ливерпуль
- название: Арсенал, город: Лондон
Теперь мы должны решить вопрос из вопроса. Django автоматически создает промежуточную таблицу между моделями с отношением «многие ко многим», но мы можем использовать пользовательскую модель и добавлять дополнительные поля. Я называю эту модель Participant
:
Участник класса (модели. Модель):
РОЛИ = (
(«Н», «Дом»),
(«V», «Посетитель»),
)
event = models.ForeignKey (Event, on_delete = models.CASCADE)
Competitor = models.ForeignKey (Competitor, on_delete = models.CASCADE)
role = models.CharField (max_length = 1, выбор = РОЛИ)
Класс Meta:
unique_together = (
(«событие», «роль»),
(«событие», «конкурент»),
)
def __str __ (self):
return '{} - {}'. format (self.event, self.get_role_display ())
ManyToManyField
Имеет опцию , through
которая позволяет нам указать промежуточную модель. Давайте изменим это в модели 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
Уникальные ограничения теперь будут автоматически ограничивать число участников на событие до двух (потому что есть только две роли: Дом и Посетитель ).
В конкретном событии (футбольный матч) может быть только одна домашняя команда и только одна команда гостей. Клуб ( Competitor
) может выступать в качестве домашней команды или команды гостей.
Как нам теперь управлять всеми этими вещами в админе? Нравится:
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)
Мы добавили Participant
как встроенный в EventAdmin
. Когда мы создаем новое, Event
мы можем выбрать домашнюю команду и команду посетителей. Опция max_num
ограничивает количество записей до 2, поэтому на одно событие можно добавить не более 2 команд.
Это может быть реорганизовано для других случаев использования. Допустим, наши соревнования - это соревнования по плаванию, и вместо дома и посетителя у нас есть дорожки с 1 по 8. Мы просто рефакторинг 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())
С помощью этой модификации мы можем получить это событие:
Пловец может появиться только один раз в жару, а дорожка может быть занята только один раз в жару.
Я поместил код в GitHub: https://github.com/cezar77/competition .
Опять же, все кредиты идут к Дани Эррера. Я надеюсь, что этот ответ обеспечит читателям дополнительную ценность.