How to count deep relationship fields with Django? - django-models

How could I count the relationship of the models below?
class Client(models.Model):
name = models.CharField(max_length=150)
class Pet(models.Model):
client = models.ForeignKey(Client, related_name='pet')
name = models.CharField(max_length=150)
class Photo(models.Model):
pet = models.ForeignKey(Pet, related_name='photo')
photo = models.ImageField(storage=DIR)
I would like to return the total pet and total photos taken of all pets dr to a single customer client, with the following answer:
[
{
"name": "john",
"totalPet": 3, // imagine 10 photos were taken of each pet
"totalPhoto": 30,
},
{
"name": "Mary",
"totalPet": 2, // imagine that 10 photos were taken of one and 5
// of another pet
"totalPhoto": 15,
},
]
My serializer is as follows:
class ClientSerializer(ModelSerializer):
totalPet = serializers.SerializerMethodField()
def get_totalPet(self, obj):
return obj.pet.count()
class Meta:
model = Client
fields = ('name', 'totalPet')
As can be observed, I can total the amount of pets for each customer, but I can't find a way to total the photos of all pets of a customer.
What would be the best way to do this photo count?

You can perform a filter on Photo model with your client.pet queryset, using the __in lookup.
At first, I would define a custom QuerySet for our Photo model:
from django.db import models
class PhotoQuerySet(models.QuerySet):
def for_client(self, client):
# Get all related pets to the current client
client_pets = client.pet.all()
# Filter Photos that are linked with these pets
return self.filter(pet__in=client_pets)
And then inside the model:
class Photo(models.Model):
...
objects = PhotoQuerySet.as_manager()
This will allow you to do the following: Photo.objects.for_client(client=...)
After that, you can use this method in the serializer:
class ClientSerializer(ModelSerializer):
total_pets = serializers.SerializerMethodField()
total_photos = serializers.SerializerMethodField()
def get_total_pets(self, obj):
return obj.pet.count()
def get_total_photos(self, obj):
# Calling our newly created method and aggregating count
return Photo.objects.for_client(client=obj).count()
class Meta:
model = Client
fields = ('name', 'total_pets', 'total_photos')

Related

How can I send the Name of the Item instead of PK in Django-Rest-Framework using POSTMAN?

So I am creating a E-Commerce API using the Django Rest Framework and I have been trying to send the name of the Item instead of the PK of the Item to create an order.
These are the models I am using:
class Product(models.Model):
product_tag = models.CharField(max_length=10)
name = models.CharField(max_length=100)
description = models.CharField(max_length=500, null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.IntegerField()
stock = models.IntegerField()
image = models.ImageField(default="default.png")
in_stock = models.BooleanField(default=True)
date_created = models.DateField(auto_now_add=True)
class Meta:
ordering = ["-date_created"]
def __str__(self):
return self.name
class PlacedOrder(models.Model):
ordered_by = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
phone = models.CharField(max_length=15)
address = models.CharField(max_length=100)
zipcode = models.CharField(max_length=100)
items = models.ManyToManyField(Product)
total_price = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
def __str__(self):
return f"{self.ordered_by}"
This is my serializer that I am working with to create an order:
class PlacedOrderSerializer(serializers.ModelSerializer):
ordered_by = serializers.ReadOnlyField(source="ordered_by.email")
class Meta:
model = PlacedOrder
fields = (
"id",
"created_at",
"ordered_by",
"first_name",
"last_name",
"phone",
"address",
"zipcode",
"items",
"total_price",
)
Here is the view I am using to create an order:
class CreateOrder(generics.ListCreateAPIView):
permission_classes = [IsAuthenticated]
queryset = PlacedOrder.objects.all()
serializer_class = PlacedOrderSerializer
def perform_create(self, serializer):
serializer.save(ordered_by=self.request.user)
This is my input in POSTMAN:
{
"first_name": "yes",
"last_name": "no",
"phone": "0100000000",
"address": "whatever address",
"zipcode": "254",
"items": [
1,
1,
2
],
"total_price": "69"
}
and this is the output:
{
"id": 13,
"created_at": "2022-10-26T20:56:08.789574Z",
"ordered_by": "bal#bal.com",
"first_name": "yes",
"last_name": "no",
"phone": "0100000000",
"address": "whatever address",
"zipcode": "254",
"items": [
1,
2
],
"total_price": 69
}
I basically want it to take the name and quantity of the items instead of the PK of the product in the input.
I have tried using RelatedField but that makes it so that "items" just goes null to the backend without taking any products and makes a blank order.
Try this approach:
class PlacedOrderSerializer(serializers.ModelSerializer):
# HERE
ordered_by = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model = PlacedOrder
fields = (
"id",
"created_at",
"ordered_by",
"first_name",
"last_name",
"phone",
"address",
"zipcode",
"items",
"total_price",
)
But make sure to add self.name to the models __str__.
You need to use a SlugRelatedField [DRF-doc] here, like:
class PlacedOrderSerializer(serializers.ModelSerializer):
ordered_by = serializers.ReadOnlyField(source="ordered_by.email")
items = serializers.SlugRelatedField(
many=True,
slug_field='name',
queryset=Product.objects
)
class Meta:
model = PlacedOrder
fields = (
# ...
)
This will thus make the serializer interpret the name fields, and query the Product objects with that name, and add these to the many-to-many relation.
Note that this will result in a query for each item, so if the list of items can be long, you might want to consider a different strategy, like serializing a list of product PKs, and then in the view construct a mapping name -> product, and use that to construct the many-to-many relation.
Note: It is normally better to use a slug field (a short identifier), than a name field for these kinds of relations. Slugs are meant to be database friendly (only contain ascii characters, no spaces, etc), and furthermore slugs normally do not change, whilst a name can. So if you later rename a Product, then the order will no longer point to the correct product.

Count the no of movies in django rest framework

I want to count the no of movies worked by the actors in api. i have tried this but not working
models.py
class Actor(models.Model):
actors = models.CharField(max_length=100)
class Genre(models.Model):
genre = models.CharField(max_length=100)
class Movie(models.Model):
name = models.CharField(max_length=100)
actors = models.ManyToManyField(Actor, related_name="actor_movies")
genre = models.ManyToManyField(Genre,related_name="genre_movies")
serializers.py
class ActorSerializer(serializers.HyperlinkedModelSerializer):
actor_movies = serializers.RelatedField(source='actor.movies', read_only = True)
class Meta:
model = Actor
fields = ['id','actors','actor_movies']
class GenreSerializer(serializers.HyperlinkedModelSerializer):
genre_movies = serializers.RelatedField(source='genre.names', read_only = True)
class Meta:
model = Genre
fields = ['id','genre','genre_movies']
class MovieSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Movie
fields = ['url','name','actors','genre']
im getting output like this
[
{
"id": 1,
"genre": "Ajith"
},
{
"id": 2,
"genre": "Vijay"
}
]
in actual output i need the total no of movies he worked also.
You can use a SerializerMethodField:
class ActorSerializer(serializers.HyperlinkedModelSerializer):
count_actor_movies= serializers.SerializerMethodField()
def get_count_actor_movies(self, instance):
return instance.actor_movies.count()
class Meta:
model = Actor
fields = ['id','actors','count_actor_movies']
For more details, the official docs are here.

how to get foreignkey fields in serializer

In serializer, class I am trying to get the category details such as "name" but the following code gives me the foreignkey id
models.py
class Category(MP_Node, Timestamps):
name = models.CharField(_('Name'), max_length=255, db_index=True)
class VideoCategory(Category):
image = models.ImageField(upload_to='video_categories', blank=True, null=True, max_length=255)
class VideoCategoryVideo(BaseModel, Timestamps, SoftDelete):
video = models.ForeignKey(Video, on_delete=models.CASCADE)
category = models.ForeignKey(VideoCategory, on_delete=models.CASCADE, null=True)
serializers.py
class VideoCategoryVideoSerializer(serializers.ModelSerializer):
class Meta:
model = VideoCategoryVideo
fields = ('category', )
class VideosDetailsListSerializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
class Meta:
model = Video
fields = ('id', 'create_date', 'category')
def get_category(self, data):
cate = VideoCategoryVideo.objects.filter(video=data.id)
category = VideoCategoryVideoSerializer(cate, many=True)
return category.data
result is:
"category": [
{
"category": 1
}]
but the expected result is
"category": [
{
"name": "cate_name"
}]
You can add a CharField to VideoCategoryVideoSerializer and specify the source of the value like this:
class VideoCategoryVideoSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='category.name', read_only=True)
class Meta:
model = VideoCategoryVideo
fields = ('category', 'name')
This will tell the serializer to get the value for name from the related category.

Filtering the fields listed by the CreateModelMixin based on ForeignKey in Django Rest Framework

Hello, I am starting to learn Django Rest Framework, i have come across a problem, that i couldn't solve, that is described below:
I have defined three models in models.py:
Endpoint Model
class Endpoint(models.Model):
name = models.CharField(max_length=128)
created_by = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
def __str__(self) -> str:
return self.name
MLAlgorithm model:
class MLAlgorithm(models.Model):
name = models.CharField(max_length=128)
created_by = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
parent_endpoint = models.ForeignKey('Endpoint', on_delete=models.CASCADE, related_name="mlalgorithms")
def __str__(self) -> str:
return self.name
ABTest model:
class ABTest(models.Model):
title = models.CharField(max_length=1000)
created_by = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
parent_endpoint = models.ForeignKey('Endpoint', on_delete=models.CASCADE, related_name="abtests")
parent_mlalgorithm_1 = models.ForeignKey('MLAlgorithm', on_delete=models.CASCADE, related_name="parent_mlalgorithm_1")
parent_mlalgorithm_2 = models.ForeignKey('MLAlgorithm', on_delete=models.CASCADE, related_name="parent_mlalgorithm_2")
def __str__(self) -> str:
return self.title
and defined their serializers:
Endpoint Serializer:
class EndpointSerializer(serializers.ModelSerializer):
class Meta:
model = Endpoint
read_only_fields = ("id", "name", "created_by", "created_at")
fields = read_only_fields
MLAlgorithm Serializer:
class MLAlgorithmSerializer(serializers.ModelSerializer):
class Meta:
model = MLAlgorithm
read_only_fields = (
"id", "name", "created_by", "created_at", "parent_endpoint")
fields = read_only_fields
ABTest Serializer:
class ABTestSerializer(serializers.ModelSerializer):
class Meta:
model = ABTest
read_only_fields = ("id", "created_at")
fields = (
"id", "title", "created_by", "created_at", "parent_endpoint", "parent_mlalgorithm_1", "parent_mlalgorithm_2")
Then i created a viewset for the ABTest:
class ABTestViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.RetrieveModelMixin):
serializer_class = ABTestSerializer
queryset = ABTest.objects.all()
My problem is in the ABTestView:
for example, i have created two endpoints, and each endpoint has two algorithms:
endpoint_1:
algorithm_1
algorithm_2
endpoint_2:
algorithm_3
algorithm_4
but when i select a specific endpoint in the view, i want only the specific algorithms to be shown not all the algorithms available in the database
enter image description here
i don't know how to force algorithm filtering in the view, should i filter the algorithms by their endpoints in the view or before serialization, unfortunately i am struggling to implement it.
Thank you in advance.

Removing Null nested fields in Django Serializer --- or how to iterate with Null?

This issue I am having is likely a React iteration problem but I think taking care of it at the root inside Django will be more efficient instead of on the front end.
Possible remedies:
ternary operation to conditionally display objects and parameters if not null
remove null fields from response to avoid needing to do ternary operation through django itself
Although, in the first place, I cannot seem to be able to iterate through my response.
I have a double nested serializer:
Models:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
class VideoProduct(models.Model):
...
class ImageProduct(models.Model):
...
class ProfileProduct(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
video_product = models.ForeignKey(VideoProduct, on_delete=models.CASCADE, blank=True)
image_product = models.ForeignKey(VideoProduct, on_delete=models.CASCADE, blank=True)
class Meta:
unique_together = ('profile', 'video_product')
unique_together = ('profile', 'image_product')
Views.py:
class VideoProductViewSet(viewsets.ModelViewSet):
queryset = VideoProduct.objects.all()
serializer_class = VideoProductSerializer
class ImageProductViewSet(viewsets.ModelViewSet):
queryset = ImageProduct.objects.all()
serializer_class = VideoProductSerializer
class ProfileProductsViewSet(viewsets.ModelViewSet):
queryset = ProfileProduct.objects.all()
serializer_class = ProfileProductsSerializer
class ProfileBySlug(generics.ListAPIView):
serializer_class = ProfileBySlugSerializer
def get_queryset(self):
slug = self.request.query_params.get('slug', None)
queryset = Profile.objects.filter(slug=slug)
if slug is not None:
queryset = queryset.filter(slug=slug)
return queryset
Serializers:
class VideoProductSerializer(serializers.ModelSerializer):
class Meta:
model = VideoProduct
fields = ['id', 'price']
class ImageProductSerializer(serializers.ModelSerializer):
class Meta:
model = ImageProduct
fields = ['id', 'price']
class ProfileProductsSerializer(serializers.ModelSerializer):
video_product = VideoProductSerializer(read_only=True)
image_product = ImageProductSerializer(read_only=True)
class Meta:
model = ProfileProduct
fields = ['id', 'video_product', 'image_product']
class ProfileBySlugSerializer(serializers.ModelSerializer):
profile_products_set = serializers.SerializerMethodField('get_profile_products')
def get_profile_products(self, profile):
qs = ProfileProduct.objects.filter(profile=profile)
serializer = ProfileProductsSerializer(instance=qs, many=True)
return serializer.data
class Meta:
model = Profile
fields = ['id', 'title', 'description', 'slug', 'image', 'status', 'profile_products_set']
The issue is that a ProfileProduct will only have one association to either a video_product or image_product but my responses are like this:
API Request:
axios.get(`http://127.0.0.1:8000/api/v1/profile/`, {
params: {
slug: this.state.slug,
}
})
.then(res => {
this.setState({ profile: res.data.results[0] });
this.setState({ products: res.data.results[0].profile_products_set });
....
Entire response:
[{…}]
0:
description: "Description"
id: 1
profile_products_set: Array(2)
0:
video_product: {id: 1, price: "0.00", status: true}
image_product: null
__proto__: Object
1: {image_product: {…}, video_product: null}
length: 3
__proto__: Array(0)
title: "Admin Title"
__proto__: Object
length: 1
__proto__: Array(0)
I set the state for profile_products_set with:this.setState({ products: res.data.results[0].profile_products_set })
Now when I go to iterate within React:
{ this.state.products.map(product =>
<div>
<li>{product.id}</li>
<li>{product.price}</li>
</div>
)}
It is blank with only bullet points.
As mentioned above, how can I one iterate over this and display it on the front end, and then if possible, remove the null fields from the response all together to avoid needing to do and ternary operations based on the product type
You can use to_representation to override the serialization. You can then remove the entries with None value.
class ProfileProductsSerializer(serializers.ModelSerializer):
video_product = VideoProductSerializer(read_only=True)
image_product = ImageProductSerializer(read_only=True)
class Meta:
model = ProfileProduct
fields = ['id', 'video_product', 'image_product']
def to_representation(self, instance):
data = super().to_representation()
return {k: v for k, v in data.items() if v is not None}

Resources