Simple question:
I have next models:
class Artist(models.Model):
name = models.CharField(...)
surname = models.CharField(...)
age = models.IntegerField(...)
clas Album(models.Model):
artist = models.ForeignKey(Artist, ...)
title = models.CharField(...)
I wish to create an Album serializer that:
Shows Artist information on GET
Can link Artist using pk on POST
I can make the POST using pk using the following serializer, but I don't know how to GET artist information in the same serializer:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
model = Album
fields = '__all__'
Thanks.
You can override your AlbumSerializer's to_representation method:
class AlbumSerializer(serializers.ModelSerializer):
def to_representation(self, obj):
data = super().to_representation(obj)
data['artist'] = ArtistSerializer(obj.artist).data
return data
class Meta:
model = Album
fields = '__all__'
I believe what you're looking for is related to this API Reference and to StringRelatedField
It depends how you configure your __str__ method inside Artist class.
models.py
class Artist(models.Model):
name = models.CharField(...)
surname = models.CharField(...)
age = models.IntegerField(...)
def __str__(self):
return "{name} {surname} - {age} years old".format(
name=self.name, surname=self.surname, age=self.age)
serializers.py
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ['artist, ...']
I am not sure I fully understand your question, but if you want to link two serializers you have to do the following:
class ArtistSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = '__all__'
class AlbumSerializer(serializers.ModelSerializer):
artist = ArtistSerializer()
class Meta:
model = Album
exclude = '__all__'
However, if you just want to respond to a GET request, you should have the request object in the GET method, which essentially should contain the Album id and then you can link the two models together:
Album.objects.filter(pk=request['album_id']).first().artist
and get the artist associated with the particular album.
Related
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = "__all__"
class EmployeeSerializer(serializers.ModelSerializer):
bookings_st = BookingSerializer(many=True, read_only=True)
class Meta:
model = Employee
fields = "__all__"
class ProjectSerializer(serializers.ModelSerializer):
employees = EmployeeSerializer(read_only=True, many=True)
class Meta:
model = Project
fields = "__all__"
class Employee(models.Model):
name = models.CharField(max_length=127)
lastname = models.CharField(max_length=127)
class Project(models.Model):
title = models.CharField(max_length=127)
employees = models.ManyToManyField(Employee,
related_name='employees')
class Booking(models.Model):
start = models.DateField()
end = models.DateField()
employee = models.ForeignKey(Employee,
on_delete=models.CASCADE,
related_name='bookings_st')
project = models.ForeignKey(Project,
on_delete=models.CASCADE,
related_name='bookings_st')
I get nested object, but how to get in Emploee only related to both (project and employee) bookings? Now I just get all bookings that this employee has.
I mean that structure:
project_1:
emploee_1:
[bookings_that_belong_to_THIS_PROJECT]
A possibility here is to leverage a SerializerMethodField together with the serializer's context object:
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = "__all__"
class EmployeeSerializer(serializers.ModelSerializer):
bookings_st = serializers.SerializerMethodField()
def get_bookings_st(self, employee):
project = self.context.get("project")
return BookingSerializer(
employee.bookings_st.filter(project=project),
many=True,
).data
class Meta:
model = Employee
fields = "__all__"
class ProjectSerializer(serializers.ModelSerializer):
employees = serializers.SerializerMethodField()
def get_employees(self, project):
return EmployeeSerializer(
project.employees.all(),
many=True,
context={**self.context, "project": project}
).data
class Meta:
model = Project
fields = "__all__"
This wouldn't be super performant if you're using that when listing projects or when retrieving a specific project with many employees. Depending on your use case - if you're only using it to retrieve a specific project, for example, you could leverage prefetch_related and the Prefetch class to prefetch custom employees + bookings querysets by filtering out the specific project at hand (and you could then use the regular serializers).
The problem is I have a 'details' field which should render into a nested relationship with it's parent serializer. I have tried a bunch of stuff and nothing seems to be working.
Here's my models:
class BusinessOrderModel(OrderToModel):
reference = models.IntegerField()
business_num = models.ForeignKey('BusinessModel', on_delete=models.CASCADE)
def __str__(self):
return str(self.reference)
class BusinessModel(models.Model):
Business_num = models.IntegerField(primary_key=True)
def __str__(self):
return str(self.Business_num)
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', on_delete=models.CASCADE)
and here's my serializers which aren't working:
class DetailSerializer(serializers.ModelSerializer):
class Meta:
model = DetailModel
fields = ('id', 'detail')
class BusinessOrderSerializer(serializers.ModelSerializer):
details = DetailSerializer(many=True)
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
I've tried many different things but I get this error:
Got AttributeError when attempting to get a value for field details
on serializer BusinessOrderSerializer. The serializer field might be
named incorrectly and not match any attribute or key on the
BusinessOrderModel instance. Original exception text was:
'BusinessOrderModel' object has no attribute 'details'.
Any help is much appreciated.
Thank you very much.
Using details to lookup reverse relationships only works if you set it as the related_name. The default for BusinessOrderModel to DetailModel will be detailmodel_set.
To make it accessible by calling details you should make this change:
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', related_name="details", on_delete=models.CASCADE)
Now you can use DetailModel.objects.get(id=1).details.all()
You can also customize the query in your serializer:
class BusinessOrderSerializer(serializers.ModelSerializer):
details = SerializerMethodField()
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
def get_details(self, obj):
return DetailSerializer(obj.details.filter(), many=True).data
In my models.py there are two models:
class Genre(models.Model):
genre_id = models.CharField(max_length=10)
name = models.CharField(max_length=40)
information = models.CharField(max_length=120)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
genre = models.ForeignKey(Genre, on_delete=models.CASCADE)
def __str__(self):
return self.title
They are serialized:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('title', 'author', 'genre')
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = ('name', 'information')
and ViewSets are created for each:
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
class GenreViewSet(viewsets.ModelViewSet):
queryset = Genre.objects.all()
serializer_class = GenreSerializer
What I'd like to do is:
Sending a POST request to books/ endpoint. Sent data has to contain existing genre ID, it won't be saved to the database otherwise (it's done by default already).
Receiving information from the Genre model as a response.
Let me give a short example:
I'm sending this JSON:
{
"title": "Hercules Poirot",
"author": "Agatha Christie",
"genre": 1
}
Instead of repeated request from above I receive something like this:
{ "genre": "crime story" }
How to do this?
What you can do is add a custom create method within your BookViewSet to override the return statement.
You can take exemple on the default create method which is implemented within CreateModelMixin (https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py#L12)
You could tell how the nested field will work. Like this:
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = ('name', 'information')
class BookSerializer(serializers.ModelSerializer):
genre = GenreSerializer(read_only=True)
class Meta:
model = Book
fields = ('title', 'author', 'genre')
I use django rest in my project and until now for list of objects I used ListSerializer, when I needed to have min length and max length of list I googled and reached to ListField.
Before that my code worked fined without any error and misbehavior. Now I use ListField for my list field serializer, But I didn't get when to use ListSerializer? Can someone explain the difference between ListSerializer and FieldSerializer?
My sample code with ListSerializer:
tags = serializers.ListSerializer(child=serializers.CharField(allow_blank=False), required=False)
My sample code with ListField:
open_hour = serializers.ListField(child=serializers.DictField(), max_length=7, min_length=7)
Disclaimer: This answer is not complete
Can someone explain the difference between ListSerializer and
FieldSerializer?
I assume the question is difference between serializers.ListSerializer and serializers.ListField
Suppose we have two models as
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
def __str__(self):
return f'{self.name} : {self.artist}'
and serializer as
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
class MusicianSerializer(serializers.ModelSerializer):
AlbumSerializer(many=True, source='album_set')
class Meta:
fields = '__all__'
model = Musician
ListSerializer
As stated in official DRF doc
When a serializer is instantiated and many=True is passed, a
ListSerializer instance will be created. The serializer class then
becomes a child of the parent ListSerializer
For example, we could re-write the MusicianSerializer with ListSerializer as
class MusicianSerializer(serializers.ModelSerializer):
albums = serializers.ListSerializer(child=AlbumSerializer(), source='album_set')
class Meta:
fields = '__all__'
model = Musician
it would produce the results same as before. But, if we are trying to use ListField instead of ListSerializer It will raise an error
'RelatedManager' object is not iterable
When I checked the source code, I found that both ListSerializer and ListField are inherited from the same class (parent and grand parent are same)
I ran into this same problem and I believe I found a solution!
The trick is you need to create a new Serializer that inherits the ListSerializer class and override the to_representation() method to output your desired format.
If you look at the DRF source code for ListSerializer you can see the default to_representation() method looks like the following...
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, models.Manager) else data
return [
self.child.to_representation(item) for item in iterable
]
Example
models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
def __str__(self):
return f'{self.name} : {self.artist}'
serializers
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
class AlbumKeyValueSerializer(serializers.ListSerializer):
def to_representation(self, data):
reaction_count_set = {}
for item in data.all():
reaction_count_set[item.name] = item.artist
return reaction_count_set
class MusicianSerializer(serializers.ModelSerializer):
AlbumKeyValueSerializer(child=AlbumSerializer(), source='album_set')
class Meta:
fields = '__all__'
model = Musician
Given the model and serializer classes below, when I retrieve Track details, it'll only show the Track title but not the related Artist.
How would I also show the Artist name when retrieving Track details?
models.py
class Artist (models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Track (models.Model):
artist = models.ForeignKey(Artist, blank=True, null=True, on_delete=models.SET_NULL, verbose_name="Artist")
title = models.CharField(max_length=100, verbose_name="Title")
def __str__(self):
return self.title
serializers.py
class ArtistSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
class Meta:
model = Artist
fields = ('id', 'name')
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = '__all__'
I think you need custom field, try this serializer:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('title', 'artist','artist_name')
artist_name = serializers.SerializerMethodField('get_artists_name')
def get_artists_name(self, obj):
return obj.artist.name
It produce something like this.
[
{
"title": "Don't let me down",
"artist": 2,
"artist_name": "The Beatles"
},
{
"title": "Summertime",
"artist": 1,
"artist_name": "Ella Fitzgerald"
}
]
Try this serializer,
class ArtistSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = '__all__' # or array of fieldnames like ['name_1', 'name_2']
class TrackSerializer(serializers.ModelSerializer):
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('title', 'artist')
Inorder to retrieve Artist details, which is a ForeignKey model, you need to use a nested serializer in django-rest-framework.
By using the TrackSerializer with a nested ArtistSerializer, the retrieved data would look something like this,
{
"title": "Some_Title",
"artist": {
"id": 2, #or id of the artist.
"name": "Artist_name"
}
}
As you can see in the official django rest framework documentations
You should define a serializer field for nested items
First create your Artist (nested item) serializer
class ArtistSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
class Meta:
model = Artist
fields = ('id', 'name')
Then you can use it on related model serializers
class TrackSerializer(serializers.ModelSerializer):
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('title', 'artist')
In the current version of DRF you can simply do this
class TrackSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
model = Track
fields = '__all__'
StringRelatedField may be used to represent the target of the relationship using its __str__ method.
REF