How to retrieve foreign key field in Django rest framework? - django

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

Related

I want to post a list of JSON values to a model's field in Django

I want to post a movie into the collection's movie field( list of movies).
I define the model as
class Movie(models.Model):
# collection = models.ForeignKey(Collection, on_delete = models.CASCADE) #, related_name='reviews'
title = models.CharField(max_length=200)
description = models.CharField(max_length=200)
genres = models.CharField(max_length=200)
uuid = models.CharField(max_length=200)
def __str__(self):
return self.title
class Collection(models.Model):
title = models.CharField(max_length=200)
uuid = models.CharField(max_length=200, primary_key = True)
description = models.CharField(max_length=200)
movie = models.ForeignKey(Movie, on_delete = models.CASCADE)
def __str__(self):
return self.title
this is how i am using the viewset
class CollectionViewSet(viewsets.ModelViewSet):
queryset = models.Collection.objects.all()
serializer_class = serializers.CollectionSerializer
but i am not able to enter values for the movie field
enter image description here
also my serializer
class CollectionSerializer(serializers.ModelSerializer):
class Meta:
model = Collection
fields = '__all__'
By default, DRF will represent the relationship with a PrimaryKeyRelatedField, thus expecting a movie ID.
To achieve what you want (create an instance of movie with a collection), you need to overwrite the foreign key field in your serializer with your own Movie serializer.
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class CollectionSerializer(serializers.ModelSerializer):
movie = MovieSerializer()
class Meta:
model = Collection
fields = '__all__'
def create(self, validated_data):
movie = validated_data.pop('movie')
movie = Movie .objects.create(**movie )
collection = Collection.objects.create(movie=movie, **validated_data)
return collection
You need to overwrite the create method so when creating a Collection, you also create a movie.
However, I am not sure the foreign key is set in the right model in your model. (a movie belongs to many collection but not the other way around?) If that's not what you want, just reverse the logic for the serializer.
Edit:
Sending the following should work fine:
{ "uuid": "1001",
"title": "Action",
"description": "Action Movies",
"movie": { "title": "The Burkittsville 7",
"description": "The story of Rustin Parr.",
"genres": "Horror",
"uuid": "5e904"
}
}
The only problem as I mentionned earlier is in your model you defined the foreign key field in collection. So it expects one single movie instance and not a list, thus I took off the brackets you put around movie. Maybe you should consider setting the foreign key in the Movie model, or use a Many to many relationship.
#models.py
class Movie(models.Model):
# collection = models.ForeignKey(Collection, on_delete = models.CASCADE) #, related_name='reviews'
title = models.CharField(max_length=200)
description = models.CharField(max_length=200)
genres = models.CharField(max_length=200)
uuid = models.CharField(max_length=200)
def __str__(self):
return self.title
class Collection(models.Model):
title = models.CharField(max_length=200)
uuid = models.CharField(max_length=200, primary_key = True)
description = models.CharField(max_length=200)
movie = models.ManyToManyField(Movie, on_delete = models.CASCADE)
def __str__(self):
return self.title
serializers.py:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class CollectionSerializer(serializers.ModelSerializer):
movie = MovieSerializer(read_only=True, many=True)
class Meta:
model = Collection
fields = '__all__'
hope this will give you better unserstand this will work for you

foreign key serialization drf

i created a model named 'Post'.
here is the code:
class Post(models.Model):
body = models.TextField(max_length=10000)
date = models.DateTimeField(default=datetime.now, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
ordering = ['-date']
i want to get all objects of Post model with users firstname and lastname.
in views.py:
#api_view(['GET'])
#permission_classes((IsAuthenticated,))
def allPost(request):
allpost = Post.objects.all()
serializer = PostSerializers(allpost, many=True)
return Response(serializer.data)
in serialisers.py:
class UserSerializers(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class PostSerializers(serializers.ModelSerializer):
user = serializers.RelatedField(many=True)
class Meta:
model = Post
fields = ('body','date','user')
You can make a serialzier with firstname and lastname:
class SimpleUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('first_name', 'last_name')
and then use that serializer as subserialiser:
class PostSerializer(serializers.ModelSerializer):
user = SimpleUserSerializer()
class Meta:
model = Post
fields = ('body','date','user')
This generates a JSON blob like:
{
"body": "Sample body text",
"date": "2020-12-11T12:34:56.789Z",
"user": {
"first_name": "MyFirst",
"last_name": "MyLast"
}
}
or you use make use of two CharFields:
class PostSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(source='user.first_name', read_only=True)
last_name = serializers.CharField(source='user.last_name', read_only=True)
class Meta:
model = Post
fields = ('body','date')
this generates as JSON blob:
{
"body": "Sample body text",
"date": "2020-12-11T12:34:56.789Z",
"first_name": "MyFirst",
"last_name": "MyLast"
}
Note: The name of a serializer class is normally singular, so PostSerializer instead of
PostSerializers.
There is no need to use related field in this case. many=True should be used if you're passing multiple objects (like you did with posts).
What you should do is user your UserSerializer in PostSerializer
class PostSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Post
fields = ('body','date','user')
Read docs for more info
https://www.django-rest-framework.org/api-guide/relations/#nested-relationships

Django REST Framework Serialize model with foreign key

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.

Return the other model's field as a response to POST request

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')

Include attributes from another referenced model in a filtered get request on django REST framework

My goal is to include the name attribute of the Brand model a Product references through models.ForeignKey, when I make a get request for products. Exactly what this piece of code returns in the python shell:
Product.objects.all().values('name', 'brand__name',)
returns this:
[
{'name': 'B-1', 'brand__name': 'B-brand'},
{'name': 'B-2', 'brand__name': 'B-brand'},
{'name': 'C-1', 'brand__name': 'C-brand'}
]
Im already using django-filters to filter my get requests.
Models:
class Brand(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=255)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, default=None)
def __str__(self):
return self.name
Serializers:
class BrandSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Brand
fields = ('id', 'url', 'name')
class ProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Product
fields = ('id', 'url', 'name', 'brand')
Filter:
class ProductFilter(filters.FilterSet):
name = filters.CharFilter(lookup_expr = 'icontains')
brand__name = filters.CharFilter(lookup_expr = 'icontains')
class Meta:
model = Product
fields = ('name' 'brand__name',)
View:
class ProductView(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filterset_class = ProductFilter
with brand__name in the filterSet, i am able to reference the name of the brand model a certain product is referencing and retrieve it. My goal is to also include that same name of the Brand along with the attributes of a product when I make the get request, which currently only yields the url/reference of the brand (along with all other atributes of Product).
If you want to return as a flat dictionary, you can do like this.
class ProductSerializer(serializers.HyperlinkedModelSerializer):
brand_name = serializer.CharField(source="brand__name")
class Meta:
model = Product
fields = ('id', 'url', 'sku', 'name', 'brand_name', 'price')
Ive solved my own issue, by defining brand as brandSerializer in ProductSerializer, i was able to return the whole brand object along with the product info, just like this:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
brand = BrandSerializer()
class Meta:
model = Product
fields = ('id', 'url', 'sku', 'name', 'brand', 'price')