How can I restrict the list of objects in API view once an object has been added to a relationship?

Question:

I am working in django-rest-framework and I have three models: Event, Performer, and Link. I have many-to-many relationships established on the Event and Performer models as ‘links’ pointing to the Link model. In the API view, when I am creating or updating an event or performer, I am given a list of all links. I would like them to be removed as options once they’ve been associated with another object, but I can’t seem to figure out how to. Below is my code:

class Link(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    address = models.URLField()

    def __str__(self):
        return f"{self.address}"

    class Meta:
        ordering = ['created']


class Performer(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    first_name = models.CharField(max_length=20)
    last_name = models.CharField(max_length=20)
    links = models.ManyToManyField(Link)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

    class Meta:
        ordering = ['created']


class Event(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    sale_date = models.DateTimeField()
    event_date = models.DateTimeField()
    performer = models.ForeignKey(Performer, on_delete=models.CASCADE)
    links = models.ManyToManyField(Link)

    class Meta:
        ordering = ['event_date']

and I’m using this for serializers:

class LinkSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Link
        fields = ['url', 'address']


class PerformerSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Performer
        fields = ['url', 'first_name', 'last_name', 'links']


class EventSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Event
        fields = ['url', 'performer', 'sale_date', 'event_date', 'links']

I thought about using

ManyToManyField.limit_choices_to

but I don’t know what my selector would look like. I also thought I could use

Link.objects.exclude(...)
 or 
Link.objects.filter(...)

call somewhere but I just don’t know where. Thanks to anyone who can help!

Edit: thought I’d add that what I thought would work is to use ‘limit_choices_to’ to filter out any links that are included in a relationship, but I couldn’t figure out how to test if an object was in a relationship (and since there’s multiple relationships only testing for one isn’t perfect either)

Asked By: Ryan Hall

||

Answers:

You should make use of the Serializer class’ get_queryset method:

class LinkSerializer(serializers.HyperlinkedModelSerializer):
    def get_queryset(self):
        return super().get_queryset().filter(performer=None, event=None)

    class Meta:
        model = Link
        fields = ['url', 'address']
Answered By: anthony2261

I figured out what I was trying to accomplish with this: I needed to restrict the choices for the field at the model level, which I was able to do by passing a predetermined restriction to the ‘limit_choices_to=’ parameter. See code below and thank you to @anthony2261 for the suggestion, your filter section helped me to understand how to filter even though it wasn’t the type of filtering I needed!

# create a dict of filter conditions(?)
restrict_choices = {'performer': None, 'event': None}

class Performer(...):
    ...
    # refer to the restriction defined previously 
    # when defining the links relationship.
    links = models.ManyToManyField(Link, limit_choices_to=restrict_choices)
Answered By: Ryan Hall