Search code examples
pythondjangodjango-rest-frameworkdjango-serializer

Django Rest Framework - Filter fields of nested relationships


I want to return a nested representation of Customer with Image objects, where a Customer can have many Images.

Currently, the GET request returns a list of ALL images from the Image queryset for each Customer object as shown below. How can I show only related Image(s) for each Customer object in a list instead?

# The 'images' field currently returns all images rather than related images to the customer id.
[
   {
       'id': 1,
       'name': 'John Doe',
       'images': [
           {'id': 1, 'name': 'foo.jpg', 'customer': 1},
           {'id': 2, 'name': 'bar.jpg', 'customer': 2},
           {'id': 3, 'name': 'foobar.jpg', 'customer': 3},
           ...
       ]
   },
   {
       'id': 2,
       'name': 'Jane Doe',
       'images': [
           {'id': 1, 'name': 'foo.jpg', 'customer': 1},
           {'id': 2, 'name': 'bar.jpg', 'customer': 2},
           {'id': 3, 'name': 'foobar.jpg', 'customer': 3},
           ...
       ]
   },
   ...
]

This is my current setup

Example Models

class Customer(models.Model):
      name = models.CharField()

class Image(models.Model):
      name = models.CharField()
      customer = models.ForeignKey(
        Customer, on_delete=models.CASCADE)

Example Serializers

class CustomerSerializer(serializers.ModelSerializer):
    # My Customer nested relationship is expressed using ImageSerializer as a field
    images = ImageSerializer(many=True)

    class Meta:
        model = Customer
        fields = ('id', 'name', 'images')
        read_only_fields = ('id',)

class ImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Image
        fields = '__all__'
        read_only_fields = ('id',)

Please let me know if my question is unclear and I will update my question. Thank you.


Solution

  • You need to use the related name as the field for the reverse foreign key.

    In your case this would be;

    class CustomerSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = Customer
            fields = ('id', 'name', 'image_set')
            read_only_fields = ('id',)
    

    Consider it like a reverse FK query - Customer.objects.first().image_set.all()


    DRF Docs on this are here; Reverse Relations

    Django docs on reversing relationships are here


    Using the _set reverse lookup like this is the django default. It's possible that you may add your own related_name to a relationship.

    An example from the django docs;

    # Declare the ForeignKey with related_query_name
    class Tag(models.Model):
        article = models.ForeignKey(
            Article,
            on_delete=models.CASCADE,
            related_name="tags",
            related_query_name="tag",
        )
        name = models.CharField(max_length=255)
    
    # That's now the name of the reverse filter
    Article.objects.filter(tag__name="important")
    

    For your example you might add something like;

    class Image(models.Model):
        name = models.CharField()
        customer = models.ForeignKey(
            Customer,
            on_delete=models.CASCADE,
            related_name="images"
        )
    

    The django default of Customer.image_set will still work, but you can then do Customer.images.all() because you've defined that related_name for the relationship.