Как ForeignKey к значению в двух таблицах? - PullRequest
3 голосов
/ 04 октября 2019

У меня есть следующие модели:

class Ensemble(Group):
    name = models.CharField(max_length=100, null=True, blank=True)
    instrumentation = models.ForeignKey(Instrumentation -> Instrument, verbose_name=_('part'), related_name='ensemble_member', null=True, blank=True, on_delete=models.PROTECT)

class EnsembleMember(models.Model):
    person = models.ForeignKey(Person, verbose_name=_('member'), on_delete=models.PROTECT)
    instrument ???? = models.ForeignKey(Instrumentation, verbose_name=_('part'), related_name='ensemble_member', null=True, blank=True, on_delete=models.PROTECT) //This is the line in question
    ensemble = models.ForeignKey(Ensemble, verbose_name=_('ensemble'), related_name='ensemble_member', on_delete=models.PROTECT)

class Instrumentation(models.Model):
    name = models.CharField(max_length=100, null=True, blank=True)

class Instrument(MPTTModel):
    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)

Я бы хотел иметь возможность связывать EnsembleMembers только с инструментами, доступными в инструментах Ensemble. Как бы я создал это отношение ForeignKey.

Например:

Есть три инструмента: скрипка, виолончель и фортепиано

Экземпляр инструментов с этими тремя инструментами называется«Фортепианное трио».

Ансамбль под названием «Трио изящных искусств» связан с инструментами «Фортепианное трио».

«Менахем Пресслер» является членом ансамбля и пианистом вТрио изящных искусств ».

Я хочу связать этот инструмент с« Пианино ». Пианино является допустимым инструментом для связи, потому что он находится в инструментах, связанных с ансамблем. Как настроить это последнее соединение в модели EnsembleMember?

Я хотел бы иметь следующие соединения перед Ensemble и Instrumentation.

Piano Trio (Instrumentation)  --> Beaux Arts Trio (Ensemble)
        ^                                    ^
        |                                    |
        |                                    |
Piano (Instrument)      --> Menahem Pressler (pianist, Ensemble Member)
Violin (Instrument)     --> Daniel Guilet (violinist, Ensemble Member)
Cello (Instrument)      --> Bernard Greenhouse (cellist, Ensemble Member)

Ответы [ 3 ]

0 голосов
/ 21 октября 2019

Как насчет этого?

class Ensemble(Group):
    name = models.CharField(max_length=100, null=True, blank=True)
    instrumentation = models.ForeignKey(Instrumentation -> Instrument, verbose_name=_('part'), related_name='ensemble_member', null=True, blank=True, on_delete=models.PROTECT)

class EnsembleMember(models.Model):
    person = models.ForeignKey(Person, verbose_name=_('member'), on_delete=models.PROTECT)
    ensemble = models.ForeignKey(Ensemble, verbose_name=_('ensemble'), related_name='ensemble_member', on_delete=models.PROTECT)


ensemble_member = EnsemberMember.objects.first()
instrumentation = ensemble_member.ensemble.instrumentation
0 голосов
/ 24 октября 2019

Похоже, вы хотели бы использовать ограничение базы данных, чтобы гарантировать, что вы случайно не добавите инструмент недопустимого игрока в ансамбль.

Я не вижу способа сделать это в базе данныхуровень с использованием 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
        )
0 голосов
/ 20 октября 2019

Я пытался понять, смогу ли я вам помочь. Проблем, с которыми я столкнулся, в основном две.

  1. В идеале рабочий пример, который можно вырезать и вставить в django и запустить, например, что такое Group и MPPTModel?
  2. Сложность добавляется с использованием новых терминов /Понятия, такие как ансамбль, инструмент, приборостроение, я ничего не знал об этом, поэтому сначала я должен был понять, что это такое и как это связано друг с другом.

Ниже моя попытка икак я интерпретирую ваш вопрос, вы пишете - «Я хотел бы иметь возможность связывать EnsembleMembers только с инструментами, доступными в инструментах в Ensemble. Как бы я создал эти отношения ForeignKey».

т.е. вы хотите связать EnsembleMember, такой как Mannahem Pressler, с одним из инструментов, которые вы предварительно определили в Ensemble. То есть, если ансамбль - это фортепианное трио, у вас есть пианино, и теперь вы хотите связать музыканта с этим пианино.

Из коробки у меня нет кнопки редактирования в Django Admin, чтобыизмените имя музыканта на пианино, однако вы можете активировать эту кнопку, см. эту ссылку Администратор Django: как включить кнопки добавления / редактирования для отношений модели «многие ко многим»

, еслинеправильно, пожалуйста, прокомментируйте:

  • член ансамбля Менахем Пресслер играет на пианино в Beox Arts Trio
  • ансамбль Beaux Arts Trio имеет инструментарий Piano Trio
  • доступные инструменты естьФортепианное трио, состоящее из фортепиано, виолончели, скрипки
  • музыкантов / персона, в списке есть Менахем Пресслер
  • Для фортепиано в данный момент в списке есть один музыкант, Менахем Пресслер

из импортных моделей django.db

class Person(models.Model):
    name = models.CharField(max_length=100, null=True, blank=True)
    def __str__(self):
        return f'{self.name}'

class Instrument(models.Model): #MPTTModel):
    name = models.CharField(max_length=100, null=True, blank=True)
    category = models.CharField(max_length=100, null=True, blank=True)
    person = models.ManyToManyField('Person', blank=True)

    def __str__(self):
        return f'{self.name}'

class Instrumentation(models.Model):
    name = models.CharField(max_length=100, null=True, blank=True)
    instrument = models.ManyToManyField('Instrument', blank=True)

    def __str__(self):
        return f'{self.name}'

class Ensemble(models.Model):  # Group):
    name = models.CharField(max_length=100, null=True, blank=True)
    instrumentation = models.ForeignKey(Instrumentation, null=True, blank=True, on_delete=models.PROTECT)

    def __str__(self):
        return f'{self.name}'

class EnsembleMember(models.Model):
    person = models.ForeignKey(Person, on_delete=models.PROTECT)
    instrument = models.ForeignKey(Instrument, null=True, blank=True, on_delete=models.PROTECT) # //This is the line in question
    ensemble = models.ForeignKey(Ensemble, on_delete=models.PROTECT)

    def __str__(self):
        return f'{self.person}'
...