Django REST RetrieveAPIView combining querysets - python

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__"

Related

How to create serializer with nested data DRF?

I have seen many tutorials about nested serializer, but unfortunately I can`t solve this task. Please, give me some tips.
I need to create this JSON
{
"external_id": "11",
"details": [
{
"amount": 7,
"price": "12.00",
"product": {
"name": "Car"
}
}
]
}
My models consist the next relative:
from django.db import models
class Order(models.Model):
NEW = 'new'
ACCEPTED = 'accepted'
FAILED = 'failed'
order_status = [
(NEW, 'new'),
(ACCEPTED, 'accepted'),
(FAILED, 'failed'),
]
status = models.CharField(max_length=12, choices=order_status, default='new', blank=False)
created_at = models.DateTimeField(auto_now_add=True)
external_id = models.CharField(max_length=128)
def __str__(self):
return f'Order № {self.external_id}'
class Product(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return self.name
class OrderDetail(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE,
related_name='details',
null=True, blank=True)
amount = models.IntegerField(null=True, blank=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE,
related_name='product',
null=True)
price = models.DecimalField(decimal_places=2, max_digits=6, null=True, blank=True)
def __str__(self):
return f'Detail for {self.order}, detail for product {self.product}'
My view
class ProductViewSet(ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
class OrderViewSet(ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
pagination_class = ContentRangeHeaderPagination
class OrderDetailViewSet(ModelViewSet):
queryset = OrderDetail.objects.all()
serializer_class = OrderDetailSerializer
My serializer
class OrderDetailSerializer(serializers.ModelSerializer):
class Meta:
model = OrderDetail
fields = ['id', 'amount', 'price']
depth = 1
class ProductSerializer(serializers.ModelSerializer):
product = OrderDetailSerializer(many=True)
class Meta:
model = Product
fields = ['id', 'name', 'product']
class OrderSerializer(serializers.ModelSerializer):
details = OrderDetailSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'status', 'created_at', 'external_id', 'details']
depth = 2
def create(self, validated_data): # works for first nesting
print(validated_data)
details_data = validated_data.pop('details')
request = Order.objects.create(**validated_data)
for detail_data in details_data: #
products_data = detail_data.pop('product')
request_detail = OrderDetail.objects.create(order=request, **detail_data)
for product_data in products_data:
Product.objects.create(product=request_detail, **product_data)
return request
I have errors when I try to send POST request. => KeyError 'products'
I wanted to get product fields using a loop. But I can't get this field, because I didn't identified it.
My question is: how to receive this field in OrderSerializer.
Thanks for your answers.

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

Use A Serializer Class Inside Other One In Django

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.

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

Getting all of foreign key's fields

I have 3 models. One model is the User model, another is a Post model, and the other is a notifications model. I want to use Django's REST framework to show all of the notifications for the signed in user, plus each user that interacted with the signed in user.
class Notification(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='actors')
actor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='users')
post = models.ForeignKey(Post, on_delete=models.CASCADE, blank=True, null=True)
type = models.ForeignKey(NotifType, on_delete=models.CASCADE)
is_read = models.BooleanField()
created_at = models.DateTimeField(auto_now_add=True)
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=200)
share_count = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
url = models.CharField(max_length=150)
serializers.py
class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
views.py
class GetNotifications(ListAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
user = get_object_or_404(User, id=self.request.user.id)
return User.objects.select_related().filter(users__user=user).order_by("-users__created_at")
What I have currently returns all users who have interacted with the signed in user. However, it doesn't return the details about the notification. I want all details about each notification, including the details about the post. So the output should be something like this:
{
"post": {
"id": 1,
"status": "Hello!",
.
.
.
},
"user": {
"username": "Ben",
"avatar": "example.png"
.
.
},
is_read: 1,
created_at: 2017-07-21,
type: 2
}
change your view as:
class GetNotifications(ListAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
## user = get_object_or_404(User, id=self.request.user.id)
## i believe the above line is redundant, you can use self.request.user to access the loged-in user
return Notification.objects.select_related().filter(user=self.request.user).order_by("created_at")
then change your notification serializer as:
class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
exclude = ('user',)
depth = 1
or you can define new serializer for actor and post and use it like this:
class NotificationSerializer(serializers.ModelSerializer):
actor = ActorSerializer()
post = PostSerializer()
class Meta:
model = Notification
exclude = ('user',)
You define the serializer for user and posts. Your serializers.py should be
class NotificationSerializer(serializers.ModelSerializer):
post = PostSerializer()
user = UserSerializer()
class Meta:
model = Notification
fields = '__all__'
try this
class NotificationSerial(serializers.ModelSerializer):
class Meta:
model = Notification
fields = '__all__'
class PostSerial(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class NotificationSerializer(serializers.ModelSerializer):
post = PostSerial(many=True, source='post_set')
user = NotificationSerial(many=True, source='actors')
class Meta:
model = User
fields = '__all__'

Categories