Django Rest Framework - Nested Serializer - Access one attribute from a list - django-rest-framework

In Dango Rest Framework, how do I write a nested serializer that accesses a single attribute of one item from a related list of items?
For example, using the models from the documentation,
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
How would I write a serializer that would return the album name, the title of the first track, and the title of the last track?
A sample response might be:
{
"album_name": "Kid A",
"first_track_title": "Everything in its right place",
"last_track_title": "Motion picture soundtrack"
}

Related

Django Rest Framework - Updating a ForeignKey Field entry in the view

In my Django Rest Framework project, I have a ForeignKey relationship between two models:
class Book(models.Model):
...
category = models.ForeignKey(Category, on_delete=models.CASCADE, blank=True, null=True)
...
class Category(models.Model):
name = models.CharField(max_length=100, blank=False)
As you can see, a Book can belong to a Category but it does not have to. That means the 'category' field could be null.
So, in my views.py, any Book instance can be updated/patched if the user wants to assign a certain Book to a particular Category. That views.py update method looks like this:
class UpdateBooksCategory(generics.GenericAPIView):
'''
Class-based view to update the 'category' field of a Book instance.
'''
serializer_class = BookSerializer
permission_classes = [IsAuthenticated]
def patch(self, request,*args, **kwargs):
# get the Book instance first
book = Book.objects.get(pk=request.data.get('bookId'))
# if it is not assigned to a Category, then assign it
if book and not book.category:
book.category = Category.objects.get(name=request.data.get('categoryName'))
book.save()
serializer = self.get_serializer(book, context={"request": request})
return Response(serializer.data)
# otherwise, return a generic response
return Response({'response': "You have already put the selected Book in a Category."})
If you can see, first I get the Book instance that the user wants to update by using the Book's ID. If its Category field is not already filled, I get a Category instance using the given category name and assign it.
For the sake of completeness, here are my serializer classes:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name']
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', /*some other fields*/,..., 'category']
So, finally my question: I wanted to know if this is the preferred way of updating a ForeingKey field like this? I mean looking at the UpdateBooksCategory class-based view, is this the right way of doing it? The code works ( I tested it with PostMan) but since I am new to DRF I wanted to know if such an updating process is correct.
You can change your BookSerializer:
class BookSerializer(serializers.ModelSerializer):
category_id = serializers.IntegerField(write_only=True)
category = CategorySerializer(read_only=True)
class Meta:
model = Book
fields = [
'id',
# some other fields,
'category',
'category_id',
]
category will be a nested data that is read only, then setting the category will be by including the category_id in your requests.

Django Rest - get related data in M2M relation

I have 2 models:
class Artist(models.Model):
name = models.CharField(max_length=255)
music_type = models.CharField(max_length=50)
class Event(models.Model):
event_name = models.CharField(max_length=255)
description = models.TextField()
place = models.CharField(max_length=50)
longitude = models.DecimalField(max_digits=9, decimal_places=6)
latitude = models.DecimalField(max_digits=9, decimal_places=6)
date = models.DateField()
artists = models.ManyToManyField(Artist)
For every artist I would like to get list of other artists if they have ever been in any event together.
I was able to create close solution, but only for specific artist:
def get_related_artists(request):
artist_id = 2
related_events = Artist.objects.filter(id=artist_id).first().event_set.all()
related_artists_ids = []
for event in related_events:
related_artists_ids = related_artists_ids + list(event.artists
.all()
.values_list('id', flat=True)
.all())
related_artists = Artist.objects\
.filter(id__in=set(related_artists_ids))\
.exclude(id=artist_id)
serializer = ArtistRelatedSerializer(related_artists, many=True)
return JsonResponse(serializer.data, safe=False)
So firstly I get all event where specific artist took part. Later I iterate over this events and get other artist's ids. Another step is to remove duplicated ids and specific artist id. At the end I use serializer to return data.
Serializer looks like:
class ArtistRelatedSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = '__all__'
Unfortunately I think it isn't optimal solution and works only for hardcoded artist's id. I would like to get all artists and for ech list of other artists.
I was thinking about creating loop and iterate over Artist.objects.count() but I couldn't find a solid solution to maintain all this queries.
Is there any other, maybe easier way to solve this solution?
You can solve this issue from Serializer by using SerializerMethodField. Get all events in each Artist, the example below:
class ArtistSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = ('id', 'name', 'music_type', 'events')
events = serializers.SerializerMethodField()
def get_events(self, obj):
events_qs = Event.objects.filter(artists__in=[obj.id])
events = EventSerializer(
events_qs, many=True, context=self.context).data
return events
To avoid the error by the circle imports, you should use import in the function

Parameters for Django REST Model Model.objects.create function

Need to know the parameters for Model.objects.create function. This example is from this link: https://www.django-rest-framework.org/api-guide/relations/#nested-relationships. Here in this line Track.objects.create(album=album, **track_data)
What is album=album? I think right side assigned value of album is Album.objects.create(**validated_data) but what is the left side album. As per the documentation it is the Model field. But neither Model Track and Album contains fieldalbum`.
Is this the default model name for Album?
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
def create(self, validated_data):
tracks_data = validated_data.pop('tracks')
album = Album.objects.create(**validated_data)
for track_data in tracks_data:
Track.objects.create(album=album, **track_data)
return album
Not explicitly said in the tutorial, but it's assumed that there is a ForeignKey field in Track called album, that refers to the Album model.
Notice the following in the second code snippet within the Example section:
>>> album = Album.objects.create(album_name="The Grey Album", artist='Danger Mouse')
>>> Track.objects.create(album=album, order=1, title='Public Service Announcement', duration=245)
<Track: Track object>
That hints what I'm saying. The fact that there is no field albumon TrackSerializer would have raise an error if it was simply Track.objects.create(**track_data) because the field album would be missing. Thus, you need to include it explicitly.

Django Rest Framework get many-to-many relation showing in API

My little podcast backend has some models, one of which is Episode. Now there are two new ones for guests (Guest) and topics (Topic) - both of which have a ManyToManyField to Episode. This basically works as intended in the admin. Now I need to bring that to the API.
The detailed Serializer/View for each episode already has some other related data (no many-to-many so far). I also checked this post but still can't get it it work and think I might miss something.
Here's the essence just for the guest example to keep it dense, topic is literally identical:
Episode Model:
class Episode(models.Model):
number = models.CharField(max_length=10, blank=False)
title = models.CharField(max_length=100, blank=False)
show = models.ForeignKey(Show, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now=False)
published_at = models.DateTimeField(auto_now=False)
updated_at = models.DateTimeField(auto_now=False)
cover_image = models.URLField(null=True)
def __str__(self):
return self.title
Guest Model:
class Guest(models.Model):
episodes = models.ManyToManyField(Episode)
name = models.CharField(max_length=100, blank=False)
twitter = models.CharField(max_length=20, null=True)
def __str__(self):
return self.name
Serializer:
class GuestSerializer(serializers.ModelSerializer):
class Meta:
model = Guest
fields = ('name',)
Episode Serializer where it should appear:
class EpisodeDetailSerializer(serializers.ModelSerializer):
...
guest = GuestSerializer(many=True, read_only=True)
topic = TopicSerializer(many=True, read_only=True)
class Meta:
model = Episode
fields = (..., 'guest', 'topic')
depth = 1
I have put some data on for guests and topics but I can't get them showing up on the API. I've also tried 'Guest.episodes.through' like I do have it on the admin for Inline Admin Classes but that didn't change anything.
Your Episode model does not have any forward relationships to your Guest or Topic models. For that reason, your Episode serializer should seek to serialize guest_set and topic_set instead of guest and topic.
If you specify a related name for those M2M field, you can customize the naming convention here. Right now you have this:
class Guest(models.Model):
episodes = models.ManyToManyField(Episode)
...
Which means that to get to all the Guests associated with a given Episode called ep, the reverse lookup would be ep.guest_set.all(). Define a related name and you can call the middle term whatever you want:
class Guest(models.Model):
episodes = models.ManyToManyField(Episode, related_name='guests')
...
With this you could use ep.guests.all(), and you could update your Episode serializer to look for guests as opposed to guest_set.

Confused to Serializer relations in Django Rest

In the Serializer relations part on Django official website, it provides a music albums example to represent and explain some relationship.
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks')
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ('album', 'order')
ordering = ['order']
def __unicode__(self):
return '%d: %s' % (self.order, self.title)
Later, it interacts model with Serializer.
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
The result is:
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
]
}
I am confused variable tracks in the AlbumSerializer. It seems that the variable tracks is irrelevant to Album and Track model.How it is related to the Album and Track models? How it calls and invokes unicode method of Track model in the AlbumSerializer?
It is related via related_name
album = models.ForeignKey(Album, related_name='tracks')
The related_name attribute specifies the name of the reverse relation from Album to Track.
Read more https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.ForeignKey.related_name

Resources