How to create a Django model with relationships allowing items from a set to be used only once in a container for that set

I would like to create a relationship for the Django ORM where I can add objects from a set, with the data associated with that relationship, but only add each item to any particular container every time. I mean to use the term Set, defined like this:

A set is a well-defined collection of different objects.

Each item in the SetItem is unique in the set. I guarantee they are unique in this case if the fields are defined with unique=True

kwarg in the class definition. The container for these SetItemContainer items is related to SetItem, which allows the container to bind some data to the SetItemRelationship .

This ratio ManyToMany, but with a twist can be only one of each SetItem A

, B

, C

or D

in any one SetContainer. Using the analogy between Pizza and Toppings, each pizza can have any number of Toppings, Every Topping can have as many pizzas as possible, but in this case, No Topping can be added to any individual pizza more than once, meaning you cannot add two "Anchovies" Toppings on any single pizza (which is what the "data" in SetItemRelationship is for).

You can see the suggested template in this models.py app

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 allows me to create multiple SetContainer objects, each with its own instance of SetItem objects with data associated with them. However, I would like to limit myself to just adding one instance of each relationship. I tried to figure it 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 screenshot, I can add two SetItemRelationships to a unique SetItem that has SetItem.text == A

How can I set up the relationship to prevent this addition, and ideally to prevent it from falling out of any previously used SetItems when a new SetItemRelationship is added.

django-admin showing multiple instances of a SetItem in a SetContainer

Edit . I have added an explanatory paragraph to include the analogy between Pizza and Toppings, modified to help explain this relationship.

+3


source to share


2 answers


It seems to me that you need to make container

both item

"unique together" so that one and the same container

cannot be associated with the same item

more than once. You do it with a class Meta

:



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)

      

+3


source


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 the link signals.py

:.

+1


source







All Articles