Від Dani Herrera вже є чудова відповідь , проте я хотів би детальніше розглянути її.
Як пояснено у другому варіанті, рішенням, яке вимагає ОП, є зміна конструкції та реалізація двох унікальних обмежень попарно. Аналогія з баскетбольними матчами ілюструє проблему дуже практичним чином.
Замість баскетбольного матчу я використовую приклад з футбольними (або футбольними) іграми. У футбольну гру (яку я так називаю 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 за GMT
- учасники:
- назва: Ліверпуль, місто: Ліверпуль
- назва: Арсенал, місто: Лондон
Тепер ми повинні вирішити питання з питання. Django автоматично створює проміжну таблицю між моделями, що мають відношення «багато до багатьох», але ми можемо використовувати власну модель і додавати додаткові поля. Я називаю цю модель Participant
:
Учасник класу (моделі. Модель):
РОЛИ = (
("Н", "Головна"),
("V", "Відвідувач"),
)
event = models.ForeignKey (подія, on_delete = моделі.CASCADE)
конкурент = моделі.ForeignKey (Конкурент, on_delete = моделі.CASCADE)
role = models.CharField (max_length = 1, вибір = ROLES)
Мета мета:
unique_together = (
("подія", "роль"),
("подія", "конкурент"),
)
def __str __ (само):
повернути формат "{} - {}" (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/comcharge .
Знову ж таки, всі кредити йдуть на дані герреру. Я сподіваюся, що ця відповідь надасть читачам деяку додаткову цінність.