How to show a field seperately in drf - django

I have Reviews & Ratings serializer. I want to show the total count of reviews in response. The current implementation I am getting review count but it shows on all review response like below:
[
{
"review_count": 2,
"user": "don sebastian",
"rating": 3.9,
"review": "Rating for pendant 1 by Don",
"created_at": "2022-11-27",
"updated_at": "2022-11-27"
},
{
"review_count": 2,
"user": "Jackson Patrick Gomez",
"rating": 4.5,
"review": "cool review Pendant 1",
"created_at": "2022-11-27",
"updated_at": "2022-11-29"
}
]
What I want to get is like this review_count seperatley
[
"review_count": 2,
{
"user": "don sebastian",
"rating": 3.9,
"review": "Rating for pendant 1 by Don",
"created_at": "2022-11-27",
"updated_at": "2022-11-27"
},
{
"user": "Jackson Patrick Gomez",
"rating": 4.5,
"review": "cool review Pendant 1",
"created_at": "2022-11-27",
"updated_at": "2022-11-29"
}
]
#Serializer.py
class ReviewSerializer(ModelSerializer):
user = SerializerMethodField()
review_count = SerializerMethodField()
class Meta:
model = ReviewRatings
fields = ["review_count", "user", "rating", "review", "created_at", "updated_at"]
def get_user(self, obj):
return f"{obj.user.first_name} {obj.user.last_name}"
def get_review_count(self, obj):
#Views.py
class ShowReviews(APIView):
def get(self, request, *args, **kwargs):
product_slug = self.kwargs['product_slug']
rating = request.GET.get('rating')
reviews = ReviewRatings.objects.filter(product__slug=product_slug)
review_count = reviews.count()
if rating == 'lowest':
reviews = reviews.order_by('rating')
elif rating == 'highest':
reviews = reviews.order_by('-rating')
if not reviews:
return Response({"error": "No reviews for this product yet"}, status=404)
serializer = ReviewSerializer(reviews, many=True, context={"count":review_count})
return Response(serializer.data, status=status.HTTP_200_OK)
#Edited Views.py
class ShowReviews(APIView):
def get(self, request, *args, **kwargs):
product_slug = self.kwargs['product_slug']
rating = request.GET.get('rating')
reviews = ReviewRatings.objects.filter(product__slug=product_slug)
review_count = reviews.count()
if rating == 'lowest':
reviews = reviews.order_by('rating')
elif rating == 'highest':
reviews = reviews.order_by('-rating')
if not reviews:
return Response({"error": "No reviews for this product yet"}, status=404)
serializer = ReviewSerializer(reviews, many=True)
dict_copy = serializer.data.copy()
dict_copy[0]={"review_count": review_count}
return Response(dict_copy, status=status.HTTP_200_OK)

The easiest way I would assume is to edit your endpoint response directly.
Assuming you approached DRF as instructed; (this is an easy guideline for you to set up), you are going to remove review_count from your serializer and approach your get method like so:
def get(self, request, *args, **kwargs):
..........
serializer = ReviewSerializer(reviews, many=True)
dict_copy = serializer.data.copy()
dict_copy[0]=str(Review.objects.all().count()) # or wherever that count comes from
return Response(dict_copy, status=status.HTTP_200_OK)
This way you are making a copy of the serializer and then appending your value there. Since serializer.data returns an array with a dict inside in order to access that point then you will do dict_copy[0]. Otherwise if you want to have access to it as a key-value pair you can assign it as
dict_copy[0]={"review_count": str(Review.objects.all().count())}
and your result will be something like this:
[
{
"review_count": "4"
},
{
"user": "don sebastian",
"rating": 3.9,
"review": "Rating for pendant 1 by Don",
"created_at": "2022-11-27",
"updated_at": "2022-11-27"
},
Lastly you can try directly editing your response and adding there a key-pair value but I assume it will do you no good as this is outside of your serializer:
x = Review.objects.all().count()
return Response(( {"review_count":str(x)}, serializer.data), status=status.HTTP_200_OK)
[
{
"review_count": "4"
},
[
{
"user": "don sebastian",
"rating": 3.9,
"review": "Rating for pendant 1 by Don",
"created_at": "2022-11-27",
"updated_at": "2022-11-27"
},
....
]

Related

Group by in serializer in Django Rest Framework

In Django Rest Framework, I want to group my serializer data by date, How can I do that.
I have to group it by date on the basis of timestamp date.
my model: mongo model with some more fields also but only uses these two fields
class MyModel(Document):
#mongo model
candidate_id = StringField(required=True, db_field='c')
timestamp = DateTimeField(required=True, db_field='d')
...
... more fields
My Views: my views file
class MyView(generics.ListAPIView):
serializer_class = MySerializer
def get_queryset(self):
recruiter_id = self.request.GET.get("recruiter_id")
activity_list = MyModel.objects.filter(recruiter_id=str(recruiter_id)).order_by('- timestamp')
return activity_list
my serializer: using serializermethod field to get data from other DB by using candidate id from activity model
class MySerializer(serializers.Serializer):
candidate_id = serializers.CharField()
name = serializers.SerializerMethodField()
designation = serializers.SerializerMethodField()
age = serializers.SerializerMethodField()
timestamp = serializers.DateTimeField(format="%H:%M")
def get_name(self, obj):
return something
def get_designation(self, obj):
return something
def get_age(self, obj):
return something
I want to group my response by Date like :
[
{
"date": "2022-12-05",
"candidates":[
{
"candidate_id": 1,
"name": "One",
"designation": "Lead",
"age": 30,
"timestamp": "14:30"
}
]
},
{
"date": "2022-12-04",
"candidates":[
{
"candidate_id": 2,
"name": "Two",
"designation": "Lead",
"age": 30,
"timestamp": "14:30",
}
]
}
]
my actual response is like:
[
{
"candidate_id": 1,
"name": "One",
"designation": "Lead",
"age": 30,
"activity_timestamp": "13:12"
},
{
"candidate_id": 2,
"name": "Two",
"designation": "Lead",
"age": 30,
"activity_timestamp": "13:12"
}
]

When including resources DRF returns my soft deleted records

I have DRF API that supports including the books of an author in an authors get request.
Our API has a soft delete system where Book(2) marked as deleted.
But when I do the request below Book(2) is still included in the response.
I would like to have only Book(1) in the response of this request.
GET http://localhost/authors/2?include=books
API returns to me:
{
"data": {
"type": "Author",
"id": "2",
"attributes": {...},
"relationships": {
"books": {
"meta": {
"count": 2
},
"data": [
{
"type": "Book",
"id": "1"
},
{
"type": "Book",
"id": "2"
}
]
}
}
},
"included": [
{
"type": "Book",
"id": "1",
"attributes": {...}
},
{
"type": "Book",
"id": "2",
"attributes": {...}
}
]
}
I have a BaseModel that handles the soft deletion overriding the delete method:
class BaseModel(Model):
archive = BoolYesNoField(db_column="archive", default=False, null=False)
created = DateTimeField(db_column="created", auto_now_add=True, null=False)
updated = DateTimeField(db_column="updated", auto_now=True, null=False)
objects = BaseManager()
all_objects = BaseManager(alive_only=False)
def delete(self):
self.archive = True
self.save()
relate_obj_delete(self)
def hard_delete(self):
super(Model, self).delete()
def undelete(self):
self.archive = False
self.save()
Manager
class BaseManager(Manager):
def __init__(self, *args, **kwargs):
self.alive_only = kwargs.pop("alive_only", True)
super(BaseManager, self).__init__(*args, **kwargs)
def get_queryset(self):
if self.alive_only:
return BaseQuerySet(self.model).filter(archive=False)
return BaseQuerySet(self.model)
def hard_delete(self):
return self.get_queryset().hard_delete()
AuthorViewSet
class AuthorViewSet(BaseViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
filterset_class = AuthorFilterSet
Serializer
class AuthorSerializer(BaseSerializer):
books = ResourceRelatedField(many=True, read_only=True)
included_serializers = {
"books": BookSerializer,
}
class Meta(BaseSerializer.Meta):
model = Author
If someone could just point me out in the right direction I would be really helpful.
There is probably a function that I need to override somewhere but I can't seem to find it.
Your issue is in your serializer. By default Django will use Model._base_manager to fetch related objects and not your custom manager. You need to specify in your nested ResourceRelatedField field which manager you would like to use
class AuthorSerializer(BaseSerializer):
books = ResourceRelatedField(
queryset=Book.objects,
many=True,
read_only=True
)
...

Django REST serialize field group by date

Problem: Not able to group my JSON output by date
SOLUTION IN BOTTOM OF THIS POST
I am serializing a model and getting this output:
[
{
"date": "2020-11-24",
"name": "Chest",
"workout": {
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 80
}
},
{
"date": "2020-11-24",
"name": "Chest",
"workout": {
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 85
}
},
{
"date": "2020-11-24",
"name": "Chest",
"workout": {
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 90
}
},
I want to get it like the JSON below and group it by date.
[
{
"date": "2020-11-24",
"workout": {
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 80
},
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 85
},
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 90
},
}
]
I have one model:
class WorkoutLog(models.Model):
date = models.DateField()
name = models.CharField(max_length=50) #When save() name = Workout.name
exercise = models.ForeignKey('Exercise', related_name='log', on_delete=models.CASCADE)
repetitions = models.IntegerField()
weight = models.IntegerField()
Trying to serialize and group the JSON by date:
class WorkoutLogSerializer(serializers.ModelSerializer):
class Meta:
model = WorkoutLog
fields = ['date', 'workout']
workout = serializers.SerializerMethodField('get_workout')
def get_workout(self, obj):
return {
'name': obj.name,
'exercise': obj.exercise_id,
'repetitions': obj.repetitions,
'weight': obj.weight,
}
The code lets me custom the field layout, but not really grouping it by date. Do you have any suggestions on how to structure it?
Many thanks for all help!
in case its needed, here is my view.py
def workout_log(request):
if request.method == 'GET':
workout_log = WorkoutLog.objects.all()
serializer = WorkoutLogSerializer(workout_log, many=True)
return JsonResponse(serializer.data, safe=False)
SOLUTION BY Mahmoud Adel:
views.py
def workout_log(request):
if request.method == 'GET':
workout_log = WorkoutLog.objects.order_by('date').values('date').distinct()
serializer = WorkoutLogSerializer(workout_log, many=True)
return JsonResponse(serializer.data, safe=False)
serializers.py
class WorkoutFieldSerializer(serializers.Serializer):
name = serializers.CharField()
#exercise = serializers.IntegerField()
repetitions = serializers.IntegerField()
weight = serializers.IntegerField()
class WorkoutLogSerializer(serializers.ModelSerializer):
class Meta:
model = WorkoutLog
fields = ['date', 'workout']
workout = serializers.SerializerMethodField('get_workout')
def get_workout(self, obj):
workouts = WorkoutLog.objects.filter(date=obj['date'])
workout_serializer = WorkoutFieldSerializer(workouts, many=True)
return workout_serializer.data
You can do something like this
Let's start first with your view, I will tweak the queryset like that
def workout_log(request):
if request.method == 'GET':
workout_log = WorkoutLog.objects.order_by('date').distinct('date').only('date')
serializer = WorkoutLogSerializer(workout_log, many=True)
return JsonResponse(serializer.data, safe=False)
Then on your WorkoutLogSerializer
class WorkoutLogSerializer(serializers.ModelSerializer):
class Meta:
model = WorkoutLog
fields = ['date', 'workout']
workout = serializers.SerializerMethodField('get_workout')
def get_workout(self, obj):
workouts = WorkoutLog.objects.filter(date=obj.date)
workout_serializer = WorkoutSerializer(workouts, many=True)
return workout_serializer.data
And finally, create WorkoutSerializer
class WorkoutSerializer(serializers.Serializers):
name = serializers.CharField()
exercise = serializers.IntegerField()
repetitions = serializers.IntegerField()
weight = serializers.IntegerField()
The previous way will first be got to the DB to get the distinct dates then on WorkoutLogSerializer will use each date to select the corresponding objects that have it, then we serialize those objects.
We get the intended result by doing so, note that this will result in 2 DB hits, there may be another way that will do it in one DB hit, I will update my answer if I have figured it out
NOTE: I have written this to explain the flow and the logic I didn't run it, although it should work I may forget something that will show an error for you, feel free to try it.
UPDATE: check this answer comments if you are using SQLite.

django rest framework: include related model fields in same path

class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('id','product_id','sku', 'title','price','images')
class WishListSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = WishList
fields = ('wishlist_id','product',)
I have two serializers. Wishlist and Product. I want to list all wishlist products. It works fine now. But the product details is in "product" key element. Can I remove that product key, and show the product details along with the wishlist_id ?
Present result:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"wishlist_id":1,
"product": {
"id": 1460,
"product_id": "04396134-3c90-ea7b-24ba-1fb0db11dbe5",
"sku": "bb4sd817",
"title": "Trinity Belt",
}
},
{
"wishlist_id":2,
"product": {
"id": 596,
"product_id": "52588d22-a62c-779b-8044-0f8d9892e853",
"sku": "ml346",
"title": "Martina Liana",
}
}
]
}
Expected result:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"wishlist_id":1,
"id": 1460,
"product_id": "04396134-3c90-ea7b-24ba-1fb0db11dbe5",
"sku": "bb4sd817",
"title": "Trinity Belt",
},
{
"wishlist_id":2,
"id": 596,
"product_id": "52588d22-a62c-779b-8044-0f8d9892e853",
"sku": "ml346",
"title": "Martina Liana",
}
]
}
This is a very bad practice and you need a lot of effort to implement serialization and deserialization, especially in case of Post, Update etc.
I can think of 2 ways.
1) You could use in the WishListSerializer the missing fields as SerializerMethodField
example
product_id = serializers.SerializerMethodField()
def get_product_id(self, obj):
return obj.get_product().product_id
2)
class WishListSerializer(serializers.HyperlinkedModelSerializer):
product_id = serializers.CharField(source='product.product_id')
.......
class Meta:
model = WishList
fields = (product_id, ......)
You could "unnest" items in to_representation
docs on it https://www.django-rest-framework.org/api-guide/fields/#custom-fields
...
class WishListSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = WishList
fields = ('wishlist_id', 'product',)
# add
def to_representation(self, instance):
data = super().to_representation(instance)
flat_data = dict(wishlist_id=data['wishlist_id'], **data['product']) # construct (new dict) flat data
# or you could restructure same dict: data.update(data.pop('product'))
return flat_data

Django rest framework : Do not return array but list of objects with id as key

Django rest framework returns the following output at an API endpoint
[
{
"id": "QFELD_2.3.2.QF1",
"siteuxid": "VBKM02_Abschlusstest",
"section": 2,
"maxpoints": 4,
"intest": true,
"uxid": "KA0",
"points": 0,
"value": 0,
"rawinput": "",
"state": 3
},
{
"id": "QFELD_2.3.2.QF2",
"siteuxid": "VBKM02_Abschlusstest",
"section": 2,
"maxpoints": 4,
"intest": true,
"uxid": "KA1",
"points": 0,
"value": 0,
"rawinput": "",
"state": 3
},
...
Is it possible to return the data in an list object format like:
{
"QFELD_2.3.2.QF1" : {
"siteuxid": "VBKM02",
"section": 2,
"maxpoints": 4,
"intest": true,
"uxid": "KA0",
"points": 0,
"value": 0,
"rawinput": "",
"state": 3
},
"QFELD_2.3.2.QF2" : {
"siteuxid": "VBKM02",
"section": 2,
"maxpoints": 4,
"intest": true,
"uxid": "KA1",
"points": 0,
"value": 0,
"rawinput": "",
"state": 3
},
...
My Serializer is:
class ScoreSerializer(serializers.ModelSerializer):
id = serializers.CharField(required=False, allow_blank=True, max_length=100, source='q_id')
class Meta:
model = Score
fields = ('id', 'siteuxid', 'section', 'maxpoints', 'intest', 'uxid', 'points', 'value', 'rawinput', 'state')
And View is:
class ScoreViewSet(viewsets.ModelViewSet):
serializer_class = ScoreSerializer
The ListMixin code a is good place to get started. You should get the serializer's result and transform it as you like.
class ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
# Might need to change this not to transform all the data
data = {i['id']: i for i in serializer.data}
return self.get_paginated_response(data)
serializer = self.get_serializer(queryset, many=True)
data = {i['id']: i for i in serializer.data}
return Response(data)