I'd like to create a relationship for Django ORM in which I can add objects from a Set, with data associated in that relationship, but only add each item once to any given container. I mean to use the term Set, defined as follows:
A set is a well defined collection of distinct objects.
Each item in the set, SetItem, is unique within the set. I am ensuring that they are unique in this case by having its fields defined with the unique=True kwarg at the class definition. The container for these items, SetItemContainer, has a relationship to the SetItem which permit the container to associate some data with the SetItemRelationship.
It is a ManyToMany Relationship, but with a twist that there can only be one of each SetItem A, B, C, or D in any individual SetContainer. Using the Pizza and Toppings analogy, Each Pizza can have any amount of Toppings, Every Topping can as many Pizzas as want it, but in this case No Topping can be added to any individual Pizza more than once, i.e. You can't add two "Anchovies" Toppings on any individual Pizza (That's what the "data" in the SetItemRelationship is for).
You can see a proposed pattern in this app's 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)
This model relationship permits me to create multiple SetContainer objects, each of which have their own instances of the SetItem objects with data associated with them. However, I would like to be limited to only adding one instance of each relationship. I've attempted to figure this out, using the following 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)
As you can see in the following screen cap, I am capable of adding two SetItemRelationships to the Unique SetItem which has SetItem.text == A
How can I set up a relationship to prevent that addition, and ideally, to prevent the drop down from containing any of the previously used SetItems when adding a new SetItemRelationship.

Edit: I added an explanatory paragraph including the Pizza and Toppings analogy, modified to help explain this relationship.
It seems to me what you need is to make container and item be "unique together" so that the same container cannot be related to the same item more than once. You do this with a 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)
Some ideas:
# 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
Where to place signals.py: link.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With