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
}
]
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.
_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).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.
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.
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.
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