Похоже, вы хотели бы использовать ограничение базы данных, чтобы гарантировать, что вы случайно не добавите инструмент недопустимого игрока в ансамбль.
Я не вижу способа сделать это в базе данныхуровень с использованием Django. Вы можете посмотреть на ограничения в Django 2.2+, если хотите продолжить этот подход: https://docs.djangoproject.com/en/2.2/ref/models/constraints/
Лично я бы настроил менеджер модели для модели EnsembleMember, чтобы вместо этого проверить ограничение на уровне кода приложения.
В вашем коде представления вместо вызова ...
EnsembleMember.objects.create(...)
... вызов метода произвольного менеджера моделей (см. Конкретный пример в тестовом примере)
EnsembleMember.objects.add_member(...)
Ваш файл models.py выглядит следующим образом (я заменил ваши подклассы models.Model, чтобы мои тесты работали):
class Person(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
class Instrumentation(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
class Instrument(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
category = models.CharField(max_length=100, null=True, blank=True)
instrumentation = models.ManyToManyField(
Instrumentation,
verbose_name='instrumentation',
related_name='instrument',
blank=True
)
class Ensemble(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
instrumentation = models.ForeignKey(
Instrumentation,
verbose_name='part',
related_name='ensemble_member',
null=True, blank=True,
on_delete=models.PROTECT
)
# Catch this error in the view
class InvalidInstrumentError(Exception):
pass
class EnsembleMemberManager(models.Manager):
# This custom manager enforces your business logic
def add_member(
self,
ensemble: 'Ensemble',
member: 'Person',
instrument: 'Instrument'
):
# This clause ensures that the new ensemble member plays a valid
# instrument for the ensemble's instrumentation
if not instrument in ensemble.instrumentation.instrument.all():
raise InvalidInstrumentError
# Just like EnsembleMember.objects.create
# use **kwargs if you have more properties
return self.create(
person=member,
instrument=instrument,
ensemble=ensemble
)
class EnsembleMember(models.Model):
person = models.ForeignKey(
Person,
verbose_name='member',
on_delete=models.PROTECT
)
# You want the ForeignKey to reference Instrument, not Instrumentation
# because the person plays a specific instrument and the Ensemble
# already references Instrumentation.
# Also updated the related_name so there is no duplication
instrument = models.ForeignKey(
Instrument,
verbose_name='part',
related_name='ensemble_member_instrument',
null=True,
blank=True,
on_delete=models.PROTECT
)
ensemble = models.ForeignKey(
Ensemble,
verbose_name='ensemble',
related_name='ensemble_member',
on_delete=models.PROTECT
)
# Add this line to use the custom manager.
objects = EnsembleMemberManager()
Вот тестовый пример, который демонстрирует ваш вариант использования.
from django.test import TestCase
from model_mommy import mommy
from instruments.models import (
Person,
Instrumentation,
Instrument,
Ensemble,
EnsembleMember,
InvalidInstrumentError,
)
class TestMrPressler(TestCase):
def test_mgr_constraint(self):
# Valid instruments
valid_instrumentation = mommy.make(Instrumentation)
piano = mommy.make(Instrument)
violin = mommy.make(Instrument)
cello = mommy.make(Instrument)
piano.instrumentation.add(valid_instrumentation)
violin.instrumentation.add(valid_instrumentation)
cello.instrumentation.add(valid_instrumentation)
# Invalid instrument
kazoo = mommy.make(Instrument)
beaux_arts_trio = mommy.make(
Ensemble,
instrumentation=valid_instrumentation
)
menahem_pressler = mommy.make(Person)
# Mr.Pressler does NOT play kazoo
with self.assertRaises(InvalidInstrumentError):
EnsembleMember.objects.add_member(
ensemble=beaux_arts_trio,
member=menahem_pressler,
instrument=kazoo
)
# But he fiddles like a beast
presslers_role = EnsembleMember.objects.add_member(
ensemble=beaux_arts_trio,
member=menahem_pressler,
instrument=violin
)
self.assertEqual(
presslers_role.instrument.name,
violin.name
)