Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Serializer not showing up

I am trying to update my serializers in my drf project to be shown in a nested way. The two models in question are Image and Gallery, images are related to Galleries.

I tried following https://www.django-rest-framework.org/api-guide/relations/#nested-relationships, but i am not entirely sure why it is not working.

Below is models.py

class Gallery(models.Model):
    title = models.CharField(max_length=30)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_on = models.DateTimeField(auto_now_add=True, blank=True)
    modified_on = models.DateTimeField(auto_now=True, blank=True)

    def __str__(self):
        return self.title


class Image(models.Model):
    gallery_id = models.ForeignKey(Gallery, on_delete=models.CASCADE)
    img = models.ImageField(upload_to='images/')
    created_on = models.DateTimeField(auto_now_add=True, blank=True)

serializers.py

class ImageSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Image
        fields = ["gallery_id", "img", "created_on", "id"]


class GallerySerializer(serializers.HyperlinkedModelSerializer):
    image = ImageSerializer(many=True, read_only=True)

    def validate(self, data):
        # Check if user id is equal object id before creation or if SuperUser
        request = self.context.get("request")
        if request.user.id != data["author"].id and request.user.is_superuser is not True:
            raise ValidationError("Unauthorized User Post")
        return data

    class Meta:
        model = Gallery
        fields = ["title", "author", "created_on", "modified_on", "image", "id"]

Expecting outcome would be

[
  {
    "title": "test_1",
    "author": "http://127.0.0.1:8000/api/users/2/",
    "created_on": "2019-08-19T09:13:45.107658Z",
    "modified_on": "2019-08-19T09:13:45.107731Z",
    "image": [
      {
        "gallery_id": "http://127.0.0.1:8000/api/galleries/24/",
        "img": "http://127.0.0.1:8000/media/images/angga-tantama-background-art-minimalism.jpg",
        "created_on": "2019-08-20T09:17:31.790901Z",
        "id": 6
      },
      {
        "gallery_id": "http://127.0.0.1:8000/api/galleries/24/",
        "img": "http://127.0.0.1:8000/media/images/art-vector-background-illustration-minimalism-angga-tantam-2.jpg",
        "created_on": "2019-08-20T09:31:40.505035Z",
        "id": 7
      }
    ]
    "id": 24
  },
  {
    "title": "test_2",
    "author": "http://127.0.0.1:8000/api/users/2/",
    "created_on": "2019-08-20T09:42:09.448974Z",
    "modified_on": "2019-08-20T09:42:09.449042Z",
    "id": 27
  }
]
like image 791
IKAdmin Avatar asked Aug 20 '19 09:08

IKAdmin


2 Answers

image = ImageSerializer(many=True, read_only=True, source='image_set')

or

image_set = ImageSerializer(many=True, read_only=True)  # use image_set in fields list too.

Let's say you have a Gallery object similar to this:

g = Gallery.objects.get(pk=1)

Now the queryset for all the images related the given Galley object will be:

Image.objects.filter(gallery_id=g)  # g is the gallery object

In Django we can simplify it as:

g.image_set  # same as Image.objects.filter(gallery_id=g)

Now the thing is where does this magical image_set comes from. In Django ORM if you can use related_name in model's ForeignKey to query related objects, like this:

gallery_id = models.ForeignKey(Gallery, on_delete=models.CASCADE, related_name='something')
# if you do this in your models.py then to get all images of a gallery you will now do:
g.something

But since you didn't specified related_name in the ForeginKey it defaults to model name all lowercase + _set, therefore in this case: image_set.
Here is a link to docs.

If you specify two ForeignKey to same model from a model django will ask you to add related_name too (when making migrations), as it can default related name for one fields only.

This is called reverse_relationship. forward_relationship will be when you do something like this:

img = Image.objects.get(pk=1)
img.gallery_id  # get the gallery object related to the image

This is quite straightforward as gallery_id is a field in your model.


Also side note don't name your ForeignKey fields with a trailing _id, it's misleading. img.gallery_id is not the id of the Gallery it's the whole Gallery object. Django saves Related fields with a trailing id in the database, so in your case the column name in your database will be gallery_id_id (most likely, might have been changed in newer version).
When querying if your field name is gallery_id:
img.gallery_id  # the gallery object
img.gallery_id.id  # actual id of the gallery
img.gallery_id_id  # actual id of the gallery

But if you name your field just gallery:

img.gallery  # the gallery object
img.gallery.id  # actual id of the gallery
img.gallery_id  # actual id of the gallery

Which is a lot more clear.


So now you know why image_set is an attribute of your model class. And drf looks for model attributes in field names, so you either have your field name same the attribute(image_set) or specify the attribute using the source argument of the serializer.
like image 110
Vaibhav Vishal Avatar answered Nov 12 '22 03:11

Vaibhav Vishal


For things to work, you need to specify where Serializer should take data, using source keyword

In your case, this should do the trick.

class GallerySerializer(serializers.HyperlinkedModelSerializer):
    image = ImageSerializer(source="image_set", many=True, read_only=True)

    def validate(self, data):
        # Check if user id is equal object id before creation or if SuperUser
        request = self.context.get("request")
        if request.user.id != data["author"].id and request.user.is_superuser is not True:
            raise ValidationError("Unauthorized User Post")
        return data

    class Meta:
        model = Gallery
        fields = ["title", "author", "created_on", "modified_on", "image", "id"]

In the case where you used "related_name" in your FK declaration, you should use this reverse related name.

As you expected many Image instance, I strongly suggest you name your field images instead of image

I also suggest you have a look on permission instead of using getting your logic into your Serializers.

like image 20
Julien Kieffer Avatar answered Nov 12 '22 03:11

Julien Kieffer