Use A Serializer Class Inside Other One In Django - python

I want to have a serializers that use two model at once (If it possible)
models.py
class Club(models.Model):
id = models.AutoField(primary_key=True)
clubname = models.CharField(max_length=50, blank=True, null=True)
location = models.CharField(max_length=50, blank=True, null=True)
scores = models.IntegerField(blank=True, null=True)
serializers.py
class ShowAllClubSerializer(serializers.ModelSerializer):
class Meta:
model = Club
fields
class ShowClubPictures(serializers.ModelSerializer):
class Meta:
model = Clubpictures
fields = ['picture']
views.py
#api_view(["GET", ])
#permission_classes((IsAuthenticated, ))
def show_all_clubs_view(request):
if request.method == "GET":
clubs = Club.objects.all()
if clubs:
for club in clubs:
pictures = Clubpictures.objects.filter(clubid=club.id)
serializer1 = ShowAllClubSerializer(club)
serializer2 = ShowClubPictures(pictures[0])
return Response(serializer1.data, status=status.HTTP_200_OK)
# return Response(serializer2.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
Now I Have These In serializers1 and serializers2 Separately:
{
"clubname": "Club Name",
"location": "Somewhere",
"scores": 5,
}
{
"picture": "/media/images/Screenshot.png"
}
How can I take something like this in result:
{
"clubname": "Club Name",
"location": "Somewhere",
"scores": 5,
"picture": "/media/images/Screenshot.png"
}

You can use nested serializers to achieve that.
Your ClubSerializer would look something like this:
class ClubSerializer(serializers.ModelSerializer):
pictures = ClubPictureSerializer(many=True)
class Meta:
model = Club
fields = ('clubname', 'location', 'scores', 'pictures')
class ClubPictures(serializers.ModelSerializer):
class Meta:
model = Clubpictures
fields = ['picture']
assuming you have a ForeignKey from the ClubPicture to your Club with a related_name of pictures.
Also, on your view, you don't need to loop through Club.objects.all() and serialize each object individually - ModelSerializer/Serializer accept a many=True parameters that handles multiple objects already.

Related

Django RestFramework nested serializer field many=false

how can I create a nested serializer field without using (many=True)?
The following code works fine:
from music.models import Track, Album
from rest_framework import serializers
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
This json works fine:
{
"album_name": "Black Album",
"artist": "Metallica",
"tracks": [
{
"order": 1,
"title": "Enter Sandman",
"duration": 245
},
{
"order": 2,
"title": "Sad but True",
"duration": 264
},
{
"order": 3,
"title": "The Unforgiven",
"duration": 159
}
]
}
but I need to get this json working, one object, without the square brackets []:
{
"album_name": "Black Album",
"artist": "Metallica",
"tracks":
{
"order": 1,
"title": "Enter Sandman",
"duration": 245
}
}
I've tried to remove the (many=True) but I receive either the following error:
create() argument after ** must be a mapping, not str
models:
from django.db import models
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()
class Meta:
unique_together = ['album', 'order']
ordering = ['order']
def __str__(self):
return '%d: %s' % (self.order, self.title)
views.py
from rest_framework import viewsets
from music.serializers import AlbumSerializer
from music.models import Album
class STMusic(viewsets.ModelViewSet):
serializer_class = AlbumSerializer
queryset = Album.objects.all()
How to fix it?
def create(self, validated_data):
track_data = validated_data.pop('tracks')
album = Album.objects.create(**validated_data)
Track.objects.create(album=album, **track_data)
return album
Ok I found the solution based on the comments from tsantor on this other post: Django Rest Framework: AttributeError when Serializer many=False, but not when many=True
It seems if you are using a ForeignKey relationship on your model you need to add (many=True) to your serializer as DRF creates a list based on the OneToMany relationship. If you need to POST only one object, you need to use a OneToOne relationship in your model (which makes sense) so that DRF expects only one object and not a list.
So the working code is:
models.py
from django.db import models
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.OneToOneField(Album, related_name='track', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ['album', 'order']
ordering = ['order']
def __str__(self):
return '%d: %s' % (self.order, self.title)
serializer.py
class AlbumSerializer(serializers.ModelSerializer):
track = TrackSerializer()
class Meta:
model = Album
fields = ['album_name', 'artist', 'track']
def create(self, validated_data):
track_data = validated_data.pop('tracks')
album = Album.objects.create(**validated_data)
Track.objects.create(album=album, **track_data)
return album

Django foreign key rest framework

Is there any way to display the name of the user in the "likedBy" section of the view, instead of the user id? Using django rest framework
From view I get , ignore comments:
[
{
"id": 3,
"title": "ddsa",
"content": "dsadsa",
"created": "2021-02-10T08:07:42.758400Z",
"updated": "2021-02-10T08:07:42.758400Z",
"author": 1,
"category": [
{
"pk": 1,
"name": "Life"
}
],
"likedBy": [
1
],
"comments": [
{
"id": 2,
"content": "ghfa",
"created": "2021-02-10T08:08:02.407950Z",
"post": 3,
"author": 1,
"likedBy": [
1
]
}
]
}
]
Views.py:
class PostViewSet(FlexFieldsMixin, generics.ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permit_list_expands = ['category', 'comments']
Models.py
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
Serializers.py:
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
}
How serialize ManyToMany field to display text values
Given that you are not serializing a relation, but rather an attribute of your model which is related to your user, I believe you have to use a serializer.SerializerMethodField(). This allows you to do the following:
class PostSerializer(FlexFieldsModelSerializer):
liked_by = serializers.SerializerMethodField(method_name="get_user_likes")
class Meta:
model = Post
fields = (
"title",
"content",
"category",
"author",
"created",
"update",
"liked_by"
)
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
}
#classmethod
def get_user_likes(obj):
# do whatever you like here, but I tend to call a
# method on my model to keep my serializer file
# nice and tidy
# you'll need to define a UserSerializer
return UserSerializer(obj.get_user_likes(), many=True, read_only=True)
And in your Post model:
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
def get_user_likes(self):
return self.likedBy.all()
You can of course define the full method in your serializer, but as I said I like to keep the serializer as clean as possible and put all methods associated with models in my models.py.
#classmethod
def get_user_likes(obj):
# you'll need to define a UserSerializer
return UserSerializer(obj.likedBy.all(), many=True, read_only=True)
So you can set
likedBy = serializers.ReadOnlyField(source='get_likedBy')
in your PostSerializer class
and define function in your Post model class like below:
#property
def get_likedBy(self):
liked_by = []
for user in self.users_liked_post.all():
liked_by.append(user.name)
return liked_by
just use correct related_name instead of users_liked_post
If you add likedBy to your expandable fields, and then add ?expand=likedBy to your url, it should give you all the information that you outline in the UserSerializer, or write a new serializer named LikedBySerializer. Also as a general rule, try not to use '__all__' it's a good way to leak data. Happy coding!
class LikedBySerializer(FlexFieldsModelSerializer):
class Meta:
model = User
fields = '__all__'
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
'likedBy': ('blogApi.LikedBySerializer', {'many': True}),
}

Django REST RetrieveAPIView combining querysets

I'm trying to build a queryset which combines two query results, namely from Category and Course. Every Course has a Category foreign key. Is there a way to add the respective Courses to each Category?
Example:
{
"id": 61,
"name": "fgfdf",
"courses":
{
"id": 1,
"category": 61,
"title": "mytitle"
"active": true
},
{
...
}
}
Url
path('dict/<pk>/', DictView.as_view(), name='detail')
Models
class Category(models.Model):
name = models.CharField(max_length=255, blank=False, null=False)
class Course(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=255, blank=False, null=False)
active = models.BooleanField(default=True)
View
This is what I imagined but it's obviously incorrect, I've done some research but I couldn't find what I needed.
class DictView(RetrieveAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
def get_queryset(self):
queryset = Category.objects.all()
courses = list(Course.objects.filter(category=pk))
queryset['courses'] = courses;
return queryset
One way is defining serializers like this:
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = "__all__"
class CategorySerializer(serializers.ModelSerializer):
courses = CourseSerializer(source='course_set', many=True)
class Meta:
model = Category
fields = "__all__"
Then, you don't need to override get_queryset anymore.
If you wish to apply filters for courses, say you only want active courses, you can do the following:
class CategorySerializer(serializers.ModelSerializer):
courses = serializers.SerializerMethodField()
def get_courses(self, obj):
active_courses = obj.course_set.filter(active=True)
return CourseSerializer(active_courset, many=True).data
class Meta:
model = Category
fields = "__all__"

Serialize ManyToManyFields with a Through Model in Django REST Framework

I have this M2M relation with through model as
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Please note that, I have extra fields date_joined and invite_reason in the through model.
Now, I want to serialize the Group queryset using DRF and thus I choose the below serializer setup.
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = "__all__"
class GroupSerializer(serializers.ModelSerializer):
members = PersonSerializer(read_only=True, many=True)
class Meta:
model = Group
fields = "__all__"
and it is returning the following response,
[
{
"id": 1,
"members": [
{
"id": 1,
"name": "Jerin"
}
],
"name": "Developer"
},
{
"id": 2,
"members": [
{
"id": 1,
"name": "Jerin"
}
],
"name": "Team Lead"
}
]
Here, the members field returning the Person information, which is perfect.
But,
How can I add the date_joined and invite_reason field/info into the members field of the JSON response?
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = "__all__"
def serialize_membership(self, person_instance):
# simple method to serialize the through model fields
membership_instance = person_instance \
.membership_set \
.filter(group=self.context["group_instance"]) \
.first()
if membership_instance:
return MembershipSerializer(membership_instance).data
return {}
def to_representation(self, instance):
rep = super().to_representation(instance)
return {**rep, **self.serialize_membership(instance)}
class MembershipSerializer(serializers.ModelSerializer): # create new serializer to serialize the through model fields
class Meta:
model = Membership
fields = ("date_joined", "invite_reason")
class GroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField() # use `SerializerMethodField`, can be used to pass context data
def get_members(self, group):
return PersonSerializer(
group.members.all(),
many=True,
context={"group_instance": group} # should pass this `group` instance as context variable for filtering
).data
class Meta:
model = Group
fields = "__all__"
Notes:
Override the to_representation(...) method of PersonSerializer to inject extra data into the members field of the JSON
We need person instance/pk and group instance/pk to identify the Membership instance to be serialized. For that, we have used the serializer context to pass essential data

How to create object within nested serializer on API create call

I have an API with nested serializers where I overwrote the create method. My nested serializer has Foreign Keys to another model. Now I want to create objects of this other model in the same API call. This is where I am stuck.
My data looks like so:
[
{
"project": "project1",
"name": "Graph1",
"description": "testdescription",
"nodes": [
{
"id": 16,
"name": "target1",
"graph": 49
},
{
"id": 15,
"name": "Node1",
"graph": 49
}
],
"edges": [
{
"id": 14,
"name": "Edge1",
"graph": 49,
"source": 15,
"target": 16
}
]
}
]
The fields source and target are Foreign Keys to the model Node.
Now, I can send this data without a problem when the fields source and target are already existent in the database.
But what I want is, that I send the data and I create a new Node object (source) and a new Node object (target) in the same call.
So far I overwrote the create method to enable nested serialization like so:
class GraphSerializer(serializers.ModelSerializer):
nodes = NodeSerializer(many=True)
edges = EdgeSerializer(many=True)
class Meta:
model = Graph
fields = ('project',
'name',
'description',
'nodes',
'edges',
)
def create(self, validated_data):
nodes_data = validated_data.pop('nodes')
edges_data = validated_data.pop('edges')
graph = Graph.objects.create(**validated_data)
for node_data in nodes_data:
Node.objects.create(graph=graph,**node_data)
for edge_data in edges_data:
Edge.objects.create(graph=graph, **edge_data)
return graph
that works, but like I said I need to create the node objects within the edges with the same call. Is there any way to do this? I can't find any sources online on how to do this.
Any help is very very much appreciated! Thanks so much!
My models
class Graph(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
name = models.CharField(max_length=120, blank=True)
description = models.CharField(max_length=400, blank=True)
def __str__(self):
return self.name
#property
def nodes(self):
return self.node_set.all()
#property
def edges(self):
return self.edge_set.all()
class Node(models.Model):
name = models.CharField(max_length=120, blank=True)
graph = models.ForeignKey(Graph, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Edge(models.Model):
name = models.CharField(max_length=120, blank=True)
graph = models.ForeignKey(Graph, on_delete=models.CASCADE)
source = models.ForeignKey(Node, on_delete=models.CASCADE, related_name='source_set')
target = models.ForeignKey(Node, on_delete=models.CASCADE, related_name='target_set')
def __str__(self):
return self.name
You can achieve the custom functionality by WritableNestedSerializers. By default nested serializers are used for read-only but in order to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be saved. So yes it can be done.
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
Another way is to use drf-writable-nested package. Here you create the parent serializers and refer them in the serializer where the relation is required. Only the difference is Update/Create methods are defined in the package which you'll have to write by yourself in above method.
For example, for the following model structure:
Models.py
from django.db import models
class Site(models.Model):
url = models.CharField(max_length=100)
class User(models.Model):
username = models.CharField(max_length=100)
class AccessKey(models.Model):
key = models.CharField(max_length=100)
class Profile(models.Model):
sites = models.ManyToManyField(Site)
user = models.OneToOneField(User)
access_key = models.ForeignKey(AccessKey, null=True)
class Avatar(models.Model):
image = models.CharField(max_length=100)
profile = models.ForeignKey(Profile, related_name='avatars')
serializers.py
from rest_framework import serializers
from drf_writable_nested import WritableNestedModelSerializer
class AvatarSerializer(serializers.ModelSerializer):
image = serializers.CharField()
class Meta:
model = Avatar
fields = ('pk', 'image',)
class SiteSerializer(serializers.ModelSerializer):
url = serializers.CharField()
class Meta:
model = Site
fields = ('pk', 'url',)
class AccessKeySerializer(serializers.ModelSerializer):
class Meta:
model = AccessKey
fields = ('pk', 'key',)
class ProfileSerializer(WritableNestedModelSerializer):
# Direct ManyToMany relation
sites = SiteSerializer(many=True)
# Reverse FK relation
avatars = AvatarSerializer(many=True)
# Direct FK relation
access_key = AccessKeySerializer(allow_null=True)
class Meta:
model = Profile
fields = ('pk', 'sites', 'avatars', 'access_key',)
class UserSerializer(WritableNestedModelSerializer):
# Reverse OneToOne relation
profile = ProfileSerializer()
class Meta:
model = User
fields = ('pk', 'profile', 'username',)

Categories