In my model, I have a book and review class and I want to calculate the average rating. I use aggregate and Avg for that.
What I want is to display the list of books with their average rating.
models.py
class Book(models.Model):
author = models.ManyToManyField(Author, related_name='books')
title = models.CharField(max_length=200)
description = models.TextField(max_length=3000)
price = models.DecimalField(max_digits=6, decimal_places=2)
publisher = models.CharField(max_length=200)
language = models.CharField(max_length=200)
pages = models.PositiveSmallIntegerField()
isbn = models.CharField(max_length=13, validators=[validate_isbn(), MaxLengthValidator(13)])
cover_image = models.ImageField(upload_to='books/images')
publish = models.BooleanField(default=True)
#property
def average_rating(self):
avg_rating = Review.objects.filter(book_id=self.id).aggregate(Avg('rating'))
return avg_rating['rating__avg']
def __str__(self):
return self.title
class Review(models.Model):
RATING = [
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
]
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='reviews')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
description = models.TextField()
rating = models.PositiveSmallIntegerField(choices=RATING, default=5)
date_added = models.DateField(auto_now_add=True)
objects = ReviewManager()
def __str__(self):
return f"{self.user.username} {self.book.title}"
Now for each book, I have one extra query
How can I fix this issue?
please take a look at select_related and prefetch_related
Review.objects.filter(book_id=self.id).prefetch_related('author').select_related('author__book', 'author__user').aggregate(Avg('rating'))
You can use the values() to query. as mentioned in the docs https://docs.djangoproject.com/en/4.1/ref/models/querysets/#django.db.models.query.QuerySet.values
from django.db.models import Avg
qs = Book.objects.values("id").annotate(average_rating=Avg('reviews__rating'))
The result will be similar to
qs
<QuerySet [{'id': 1, 'average_rating': 5.0}, {'id': 2, 'average_rating': 2.0}]>
I used annotate in the ViewSet and my problem solved
views.py
class BookViewSet(ModelViewSet):
http_method_names = ["get", "post", "patch", "delete"]
queryset = Book.objects.prefetch_related('author').annotate(average_rating=Avg('reviews__rating'))
def get_serializer_class(self):
if self.action == 'list':
return BookSerializer
else:
return BookDetailSerializer
serializer.py
class BookSerializer(serializers.ModelSerializer):
average_rating = serializers.FloatField()
isbn = serializers.IntegerField()
class Meta:
model = Book
fields = [
"id",
"author",
"title",
"description",
"price",
"publisher",
"language",
"pages",
"isbn",
'publish',
"cover_image",
'average_rating',
]
Related
Please help me improve my code.. im newly placed and learning things
i want to display separately product field in my code just like category and sub-category in my code but i don't know how to. Also please point out if there anything that i can improve in my code
Here is my output:
[
{
"Category": {
"c_id": 1,
"parent_id": 15
},
"Sub_category": {
"sc_id": 1,
"is_active2": 1
},
"p_id": 2,
"group_id": "",
"product_name": "Fruit",
"is_prescription_needed": 1,
}]
Whereas my expected format is:
[
{
"Category": {
"c_id": 1,
"parent_id": 15
},
"Sub_category": {
"sc_id": 1,
"is_active2": 1
},
"Product": { # How do i separate this "Product" key and value
"p_id": 2,
"group_id": "",
"product_name": "Fruit",
"is_prescription_needed": 1
}]
here is my code:
Serialziers.py
class ProductCategorySerializer(serializers.ModelSerializer):
c_id = serializers.SerializerMethodField()
category_image = serializers.SerializerMethodField()
class Meta:
fields = ["c_id","parent_id","category","category_image","is_active"]
model = Category
def get_c_id(self,obj):
return obj.id
def get_category_image(self,obj):
return obj.image
class ProductSubCategorySerializer(serializers.ModelSerializer):
sc_id = serializers.SerializerMethodField()
is_active2 = serializers.SerializerMethodField()
class Meta:
fields = ["sc_id","sub_category","is_active2"]
model = ProductSubCategory
def get_sc_id(self,obj):
return obj.id
def get_is_active2(self,obj):
return obj.is_active
class ProductSerializer(serializers.ModelSerializer):
Category = ProductCategorySerializer(source='category', read_only=True)
Sub_category = ProductSubCategorySerializer(source='sub_category', read_only=True)
product_image = serializers.SerializerMethodField()
p_id = serializers.SerializerMethodField()
class Meta:
fields = ["Category","Sub_category", "p_id", "group_id", "product_name", "is_prescription_needed",
"product_description", "product_keypoint", "product_type", "product_image", "pack", "pack_unit", "hsn",
"company", "brand_id", "composition", "drug_details", "uses",
"side_effects", "cope_side_effects", "how_it_works", "safety_advice", "what_if_forget",
"medical_constraints", "created_at", "created_by", "modified_at", "modified_by"]
model = Product
def get_product_image(self, obj: Product):
image = obj.image
if len(image) == 0:
return ""
return image
def get_p_id(self,obj):
return obj.id
View.py
class categoryProduct(generics.GenericAPIView):
def post(self, request):
params = request.query_params
category = params.get("category")
sub_category = params.get("sub_category")
kwargs = {}
if category:
kwargs['category'] = category
else:
kwargs['category'] = 1
if sub_category:
kwargs['sub_category'] = sub_category
else:
kwargs['sub_category'] = 1
queryset = Product.objects.filter(**kwargs)
all_response = ProductSerializer(queryset, many=True)
#AFTER PAGINATION
return Response(all_response)
models.py
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, db_column='category')
sub_category = models.ForeignKey(ProductSubCategory, on_delete=models.CASCADE, db_column='sub_category')
group_id = models.CharField(max_length=200)
product_name = models.CharField(max_length=500)
is_prescription_needed = models.IntegerField()
...
class Meta:
managed = False
db_table = 'product'
class Category(models.Model):
category = models.CharField(max_length=50)
image = models.CharField(max_length=500)
store_image = models.CharField(max_length=1000)
is_active = models.IntegerField()
parent_id = models.IntegerField()
class Meta:
managed = False
db_table = 'category'
class ProductSubCategory(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, db_column="category")
sub_category = models.CharField(max_length=250)
is_active = models.IntegerField()
class Meta:
managed = False
db_table = 'sub_category'
You can make a separate field that is a a SerializerField of a serializer you create with the proper fields you want displayed under the Product key. This is how your other field, like Category get appropriately created. That is to say: remove the fields from the ProductSerializer into a new serializer, then add a Product SerializerField with the new serializer containing those keys.
One other way you can do this is to create a .to_representation method to change the output to precisely the format you want.
class ProductSerializer(serializers.ModelSerializer):
...
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['Product'] = {
"p_id": representation.pop('p_id')
"group_id": representation.pop('group_id'),
"product_name": representation.pop('product_name'),
"is_prescription_needed": representation.pop('is_prescription_needed')
}
return representation
am new to Django and currently trying the Foreign Key concept. I have three models as shown below.
class Basket(models.Model):
basket_name = models.CharField(max_length=5, unique=True)
def __str__(self):
return self.basket_name
class Product(models.Model):
Grams = 'GM'
Kilograms = 'KG'
WeightBased = 'WPG'
QuantityBased = 'CPG'
PRODUCT_UNIT_WT_CHOICES=[
(Grams, 'Grams'),
(Kilograms, 'Kilograms')
]
PRODUCT_TYPE_CHOICES =[
(WeightBased, 'Weight Based Product'),
(QuantityBased, 'Quantity Based Product')
]
product_name = models.CharField(max_length=30, unique=True)
product_description = models.TextField(max_length=300)
product_price = models.DecimalField(max_digits=5, decimal_places=2)
product_unit_weight = models.DecimalField(max_digits=5, decimal_places=2)
product_unit_weight_units = models.CharField(max_length=2, choices=PRODUCT_UNIT_WT_CHOICES, default=Grams)
product_type = models.CharField(max_length=3, choices=PRODUCT_TYPE_CHOICES, default=QuantityBased)
product_image = models.ImageField(upload_to=imageUploadPath, null=True, blank=True)
def __str__(self):
return self.product_name
class BasketProductMapping(models.Model):
basket_reference = models.ForeignKey(Basket, on_delete=models.CASCADE)
product_reference = models.ForeignKey(Product, on_delete=models.CASCADE)
mapped_basket_name = models.CharField(max_length=5,null=False, blank=False)
mapped_product_name = models.CharField(max_length=30, null=False, blank=False)
Here are my serializers:
class ProductSerializer(serializers.ModelSerializer):
product = serializers.CharField(read_only=True)
class Meta:
model = Product
fields = ['id', 'product_name', 'product_description', 'product_price', 'product_unit_weight',
'product_unit_weight_units', 'product_type', 'product_image']
class BasketSerializer(serializers.ModelSerializer):
basket = serializers.CharField(read_only=True)
class Meta:
model = Basket
fields = ('id', 'basket_name')
class BasketProductMappingSerializer(serializers.ModelSerializer):
basket_reference = serializers.CharField(source ='basket.basket_name', read_only=True)
product_reference = serializers.CharField(source='product.product_name', read_only=True)
class Meta:
model = BasketProductMapping
fields = ['id', 'basket_reference', 'product_reference', 'mapped_basket_name', 'mapped_product_name']
What I am trying to achieve is get a list of all the values in 'basket_name' and 'product_name' when I call the BasketProductMappingSerializer. But the output I am getting is this:
{
"status": 1,
"message": "Basket Product Mapping List",
"data": [
{
"id": 1,
"mapped_basket_name": "A1",
"mapped_product_name": "XYZ"
}
]
}
This is my views.py code:
class BasketProductViewSet(APIView):
def get(self, request):
if request.GET.get('id'):
print('Basket Product Mapping Details')
basketProductMappingData = BasketProductMapping.get(id = request.GET.get('id'))
serializer = BasketProductMappingSerializer(basketProductMappingData)
else:
basketProductMappingData = BasketProductMapping.objects.all().values('id', 'basket_reference__basket_name', 'product_reference__product_name', 'mapped_basket_name', 'mapped_product_name')
serializer = BasketProductMappingSerializer(basketProductMappingData, many=True)
response = {'status':1, 'message':"Basket Product Mapping List", 'data':serializer.data}
Where am I going wrong? Sorry if my question is very trivial. Thanks for your help in advance.
i see that there is an error in your views BasketProductMapping.get(id = request.GET.get('id')) should be BasketProductMapping.objects.get(id = request.GET.get('id'))
i don't know if it gonna work but can you try
basket_reference = serializers.ReadOnlyField(source ='basket.basket_name')
product_reference = serializers.ReadOnlyField(source='product.product_name')
instead of
basket_reference = serializers.CharField(source ='basket.basket_name', read_only=True)
product_reference = serializers.CharField(source='product.product_name', read_only=True)
serializers.py
class BuildPlanNewSerializer(serializers.ModelSerializer):
StatusName = serializers.CharField(source='BuildPlanStatusID.StatusName', read_only=True)
# build_plan_list_new = serializers.StringRelatedField(many=True, read_only=True)
total_build_plan_list = serializers.SerializerMethodField()
def get_total_build_plan_list(self, language):
return language.build_plan_list_new.count()
class Meta:
model = BuildPlanNew
fields = ('StatusName', 'total_build_plan_list')
class BuildPlanListNewSerializer(serializers.ModelSerializer):
sku = serializers.CharField(source='ProductID.sku', read_only=True)
class Meta:
model = BuildPlanListNew
fields = "__all__"
models.py
class BuildPlanNew(models.Model):
emp_id = models.ForeignKey(Employee, on_delete=models.CASCADE, null=True, blank=True)
StartDate = models.DateTimeField()
EndDate = models.DateTimeField()
BuildPlanStatusID = models.ForeignKey(GlobalStatus, on_delete=models.CASCADE)
class BuildPlanListNew(models.Model):
BuildPlanID = models.ForeignKey(BuildPlanNew, on_delete=models.CASCADE, null=True, blank=True, related_name="build_plan_list_new")
ProductID = models.ForeignKey(Product, on_delete=models.CASCADE)
TotalPlanQty = models.IntegerField()
TotalBuiltQty = models.IntegerField())
QtyPreset = models.IntegerField(default=None, max_length=256)
Objective = models.IntegerField(default=None, max_length=256)
QtyAssigned = models.IntegerField(default=None, max_length=256)
view.py
class BuildPlanNewView(viewsets.ModelViewSet):
queryset = BuildPlanNew.objects.all()
serializer_class = BuildPlanNewSerializer
class BuildPlanListNewView(viewsets.ModelViewSet):
queryset = BuildPlanListNew.objects.all()
serializer_class = BuildPlanListNewSerializer
Result i am getting:
[{
"StatusName": "1234",
"total_build_plan_list": 0
}]
Result i am expecting:
[{
"StatusName": "1234",
"total_build_plan_list": 0,
"QtyPreset_count":20,
"Objective_count":30
}]
Here i wants to fetch aggregated sum and average from foreign key table.
Need QtyPreset_count sum as QtyPreset_count
Need Objective_count sum as as Objective_count
I have shared my models views and serializers
Please have a look
just add fields to your serializer like you have done so far
from django.db.models import Sum, Avg
class BuildPlanNewSerializer(serializers.ModelSerializer):
StatusName = serializers.CharField(source='BuildPlanStatusID.StatusName', read_only=True)
# build_plan_list_new = serializers.StringRelatedField(many=True, read_only=True)
total_build_plan_list = serializers.SerializerMethodField()
QtyPreset_count = serializers.SerializerMethodField()
Objective_count = serializers.SerializerMethodField()
def get_total_build_plan_list(self, language):
return language.build_plan_list_new.count()
def get_QtyPreset_count(self, language):
return language.build_plan_list_new.aggregate(Sum('QtyPreset'))
# return language.build_plan_list_new.aggregate(Avg('QtyPreset')) if you need it's average
def get_Objective_count(self, language):
return language.build_plan_list_new.aggregate(Sum('Objective'))
class Meta:
model = BuildPlanNew
fields = ('StatusName', 'total_build_plan_list')
I've got a json in my view as below,
{
"name":"myname",
"age":30,
"day":20,
"month":"June",
"year":1988
}
How can I convert it to a nested JSON as below using Serializers?,
{
"name":"myname",
"age":30,
"DOB":{
"day":20,
"month":"June",
"year":1988
}
}
#No-One, Let suppose you have defined your models as follows.
http://www.django-rest-framework.org/api-guide/relations/
Use ForeignKey() for nested dictionary like {'day': 20, 'month': 'June', 'year': 1998}.
class Dob(models.Model):
day = models.IntegerField()
month = models.CharField(max_length=10)
year = models.IntegerField()
def __str__(self):
return str(self.day)
class User(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
age = models.IntegerField()
dob = models.ForeignKey(Dob, on_delete=models.CASCADE, null=False)
def __str__(self):
return self.name
Then I'll suggest you to define your serializers like this.
Please comment, if you've queries.
class DobSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Dob
fields = ('day', 'month', 'year')
class UserSerializer(serializers.HyperlinkedModelSerializer):
dob = DobSerializer(many=False, read_only=True);
class Meta:
model = User
fields = ('name', 'age', 'dob');
I got events that happen at locations:
class Event(models.Model):
title = models.CharField(max_length=200)
date_published = models.DateTimeField('published date',default=datetime.now, blank=True)
date_start = models.DateTimeField('start date')
date_end = models.DateTimeField('end date')
def __unicode__(self):
return self.title
description = models.TextField()
price = models.IntegerField(null=True, blank=True)
tags = TaggableManager()
location = models.ForeignKey(Location, blank=False)
class Location(models.Model):
location_title = models.CharField(max_length=200)
location_date_published = models.DateTimeField('published date',default=datetime.now, blank=True)
location_latitude = models.CharField(max_length=200)
location_longitude = models.CharField(max_length=200)
location_address = models.CharField(max_length=200)
location_city = models.CharField(max_length=200)
location_zipcode = models.CharField(max_length=200)
location_state = models.CharField(max_length=200)
location_country = models.CharField(max_length=200)
location_description = models.TextField()
def __unicode__(self):
return u'%s' % (self.location_title)
I can get the results of all via:
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
class Meta:
model = Event
depth = 2
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')
Which outputs:
{
"url": "http://localhost:8000/api/event/3/",
"id": 3,
"title": "Testing",
"date_start": "2013-03-10T20:19:00Z",
"date_end": "2013-03-10T20:19:00Z",
"description": "fgdgdfg",
"price": 10,
"location": {
"id": 2,
"location_title": "Mighty",
"location_date_published": "2013-03-10T20:16:00Z",
"location_latitude": "37.767475",
"location_longitude": "-122.406878",
"location_address": "119 Utah St, San Francisco, CA 94103, USA",
"location_city": "San Francisco",
"location_zipcode": "94103",
"location_state": "California",
"location_country": "United States",
"location_description": "Some place"
}
},
However, I don't want it to grab all fields, as I don't need all of them. How can I define what fields should be retrieved from my nested object? Thanks!
Serializers can be nested, so do something like this...
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = (...)
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
location = LocationSerializer()
class Meta:
model = Event
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')
I have been to this and did not get a perfect solution, But I did something you may check for it.
This method will not create nested serializers
**class LocationSerializer(serializers.ModelSerializer):**
class Meta:
model = Location
fields = (...) #does not matter
exclude = (...) #does not matter
class EventSerializer(serializers.ModelSerializer):**
loc_field_1 = serializers.CharField(required=False,*source='location.loc_field_1'*)
loc_field_2 = serializers.CharField(required=False,*source='location.loc_field_2'*)
***#ADD YOUR DESIRE FIELD YOU WANT TO ACCESS FROM OTHER SERIALIZERS***
class Meta:
model = Event
fields =('url','id','title','date_start','date_end','description', 'price', 'location')
I found this question when I was trying to figure out how to exclude certain fields from a serializer only when it was being nested. Looks like Tasawer Nawaz had that question as well. You can do that by overriding get_field_names. Here's an example based on Tom Christie's answer:
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = (...)
exclude_when_nested = {'location_title', 'location_date_published'} # not an official DRF meta attribute ...
def get_field_names(self, *args, **kwargs):
field_names = super(LinkUserSerializer, self).get_field_names(*args, **kwargs)
if self.parent:
field_names = [i for i in field_names if i not in self.Meta.exclude_when_nested]
return field_names
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
location = LocationSerializer()
class Meta:
model = Event
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')