如何创建具有关系的 Django 模型,允许集合中的项目在该集合的容器中仅使用一次

How to create a Django Model with relationships allowing items from a set to be used just once within a container for that set

我想为 Django ORM 创建一个关系,我可以在其中添加一个集合中的对象,以及在该关系中关联的数据,但每个项目只添加一次到任何给定的容器。我的意思是使用术语集,定义如下:

A set is a well defined collection of distinct objects.

集合中的每个项目,SetItem,在集合中都是唯一的。我确保它们在这种情况下是唯一的,方法是在 class 定义中使用 unique=True kwarg 定义其字段。这些项目的容器 SetItemContainer 与 SetItem 有关系,后者允许容器将某些数据与 SetItemRelationship 相关联。

这是一个多对多关系,但有一个扭曲,每个 SetItem 只能有一个 ABCD个人 SetContainer。使用披萨和浇头类比,每个披萨可以有任意数量的浇头,每个浇头可以有任意数量的披萨,但在这种情况下,任何一个披萨都不能多次添加浇头,即你不能添加两个"Anchovies" 任何单个披萨的配料(这就是 SetItemRelationship 中的 "data" 的用途)。

您可以在此应用的 models.py

中看到建议的模式
from django.db import models

class SetItem(models.Model):
    text = models.CharField(max_length=32, unique=True)

    def __unicode__(self):
        return self.text

class SetContainer(models.Model):
    label = models.CharField(max_length=64)
    set_items = models.ManyToManyField(SetItem, through='SetItemRelationship')

    def __unicode__(self):
        return self.label

class SetItemRelationship(models.Model):
    container = models.ForeignKey(SetContainer)
    item = models.ForeignKey(SetItem)
    data = models.PositiveSmallIntegerField()

    def __unicode__(self):
        return "{:s} contains {:s}".format(self.container.label, self.item.text)

这种模型关系允许我创建多个 SetContainer 对象,每个对象都有自己的 SetItem 对象实例以及与之关联的数据。但是,我想仅限于为每个关系添加一个实例。我试图使用以下 admin.py:

来解决这个问题
from django.contrib import admin
from .models import SetItem, SetContainer, SetItemRelationship

class SetInline(admin.TabularInline):
    model = SetItemRelationship

class SetContainerAdmin(admin.ModelAdmin):
    inlines = [SetInline]

admin.site.register(SetContainer, SetContainerAdmin)
admin.site.register(SetItem)

如您在下面的屏幕截图中所见,我能够将两个 SetItemRelationships 添加到具有 SetItem.text == A

的 Unique SetItem

如何设置关系以防止添加,理想情况下,在添加新的 SetItemRelationship 时防止下拉列表包含任何以前使用过的 SetItems。

编辑:我添加了一个解释性段落,包括比萨饼和配料的类比,并进行了修改以帮助解释这种关系。

在我看来,您需要的是使 containeritem 成为 "unique together",这样相同的 container 就不会与相同的 [=12] 相关联=]不止一次。您可以使用 Meta class:

class SetItemRelationship(models.Model):
    container = models.ForeignKey(SetContainer)
    item = models.ForeignKey(SetItem)
    data = models.PositiveSmallIntegerField()

    class Meta(object):
        unique_together = (("container", "item"), )

    def __unicode__(self):
        return "{:s} contains {:s}".format(self.container.label, self.item.text)

一些想法:

# signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver

from app.models import SetItemRelationship

@receiver(pre_save, sender=SetItemRelationship)
def update_relationship_if_exists(sender, **kwargs):
    new_relationship = kwargs['instance']
    old_relationship = (SetItemRelationship.objects
                        .filter(container=instance.container, item=instance.item)
                        .first())
    if old_relationship:
        # force update
        new_relationship.id = old_relationship.id

放置signals.py的位置:link