如何将ForeignKey的值移到两个表之外?

时间:2019-10-04 00:22:47

标签: django django-models django-queryset

我有以下型号:

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关系。

例如:

共有三种工具:  小提琴,  大提琴,和  钢琴

使用这三种乐器进行演奏的实例称为“钢琴三重奏”。

称为“ Beaux Arts Trio”的合奏与乐器“ Piano Trio”链接。

“ Menahem Pressler”是“ Beaux Arts Trio”的乐团成员和钢琴家。

我想将此乐器链接到“钢琴”。钢琴是允许链接的乐器,因为它在链接到乐团的乐器中。如何在EnsembleMember模型中设置最后一个连接?

在集成和演奏之前,我希望具有以下连接。

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 :(得分:0)

我试图查看是否可以帮助您。我遇到的问题主要有两个。

  1. 理想情况下,一个工作示例可以剪切并粘贴到django中并运行,例如什么是Group和MPPTModel?
  2. 复杂性是使用新术语/概念(例如合奏,乐器,乐器)添加的,我对此一无所知,因此首先我必须了解它是什么以及它们如何相互联系。

下面是我的尝试,以及我如何解释您的问题,您写的是-”“我希望能够将EnsembleMembers仅链接到Ensemble下的仪器中可用的仪器。我将如何创建此ForeignKey关系。 “

即您想将EnsembleMember(例如Mannahem Pressler)链接到您在Ensemble中预定义的乐器之一。也就是说,如果合奏是钢琴三重奏,则您有钢琴,现在您想将音乐家链接到这架钢琴。

开箱即用,我没有在Django Admin中获得编辑按钮来更改钢琴上的音乐家名称,但是您可以激活此按钮,请参阅此参考文献Django admin: how to enable add/edit buttons for many-to-many model relationships

如果有误,请发表评论:

  • 乐团成员Menahem Pressler在Beaux Arts Trio中弹钢琴
  • 合奏Beaux Arts Trio有乐器Piano Trio
  • 可用的乐器是钢琴三重奏,其中包括钢琴,大提琴,小提琴
  • 音乐家/人,名单上的是Menahem Pressler
  • 对于钢琴,目前有一位音乐家是Menahem Pressler

从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}'

答案 1 :(得分:0)

怎么样?

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

答案 2 :(得分:0)

听起来您想使用数据库约束来确保您不会意外地将无效的演奏者乐器添加到合奏中。

我看不到使用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
        )