DRF - Format Serializer's output from QuerySet - django

I am working with Django Rest Framework by firt time and now I'm trying to get an output like this:
{
"qty": 5,
"total": 20,
"items": [
{
"id": 1,
"name": "name_1"
},
{
"id": 2,
"name": "name_2"
}
]
}
from a Serializer. The result data in output above, came from a queryset. I'd like to work with the queryset inside the serializer class. I've not been able to get results as I want without makeing queries inside the serializer:
class ResSerializer(serializers.Serializer):
qty = serializers.SerializerMethodField()
items = serializers.SerializerMethodField()
total = serializers.SerializerMethodField()
def get_qty(self, obj):
try:
return Model.objects.filter(...)\
.aggregate(qty=Sum('job__long'))\
.get('qty')
except KeyError:
return 0
def get_items(self, obj):
print 'testing'
def get_total(self, obj):
return 0
class Meta:
fields = ('qty', 'items', 'total')
I'm calling Serializer like this:
queryset = Model.objects.filter(...)
serialized = ResSerializer(queryset, many=False, context={'current_user': request.user})
But this is not working as I want. Any sugestion? Thanks.
UPDATE
This is the model I query to:
class Intermediate(models.Model):
partner = models.ForeignKey('partner.Partner')
job = models.ForeignKey(Job)
joined_at = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
status = models.SmallIntegerField(default=STATUS_ACCEPTED)
reason_index = models.SmallIntegerField('Cancel reason', default=REASON_3)
start_time = models.TimeField(null=True)
end_time = models.TimeField(null=True)
start_date = models.DateField(null=True)
end_date = models.DateField(null=True)
And here's the view:
class ResView(CustomAPIView):
authentication_classes = (CustomTokenAuthentication, )
# permission_classes = (PartnerAuthenticatedOnly, ) # Uncomment this on server
def post(self, request, *args, **kwargs):
try:
queryset = JobPartner.objects.filter(...)
serialized = ResSerializer(queryset, many=False, context={'current_user': request.user})
response_success_object(self.response_dic, serialized.data)
return Response(self.response_dic)
except Exception, e:
print e

To get the items representation, you can use ItemsSerializer which will give the serialized data having id and name.
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel # specify your model
fields = ('id', 'name') # return these 2 fields in the representation
This serializer when dealing with multiple instances will return the serialized data in below fashion.
[
{
"id": 1,
"name": "name_1"
},
{
"id": 2,
"name": "name_2"
}
]
Now, the qty and total fields depends on the queryset and not a particular object of the queryset, it would be better if you compute them separately in your view. Then create a dictionary containing the fields items, qty and total and return it as the response.
class ResView(CustomAPIView):
authentication_classes = (CustomTokenAuthentication, )
# permission_classes = (PartnerAuthenticatedOnly, ) # Uncomment this on server
def post(self, request, *args, **kwargs):
try:
queryset = JobPartner.objects.filter(...)
qty = self.get_qty() # compute the value of qty
total = self.get_total() # compute the value of total
items_serializer = ItemsSerializer(queryset, many=True)
items = items_serializer.data # compute the value of items
return_dict = { # prepare response data
'qty' : qty,
'total': total,
'items': items
}
return Response(return_dict) # return the response
except Exception, e:
print e

Related

How to do pagination for a serializer field?

I have a task where I need to get stats and feedback by moderator ID. The 'stats' field is general, the 'feedback' field is a list of feedbacks. Can I make pagination for 'feedback' field? Of course I can make different endpoints for stats and feedback, but I'm not allowed to do this.
// GET /api/moderators/:id/feedback
{
"stats": [
{
"name": "123",
"value": -10
}
],
"feedback": [
{
"id": 1,
"createdBy": "FN LN",
"createdAt": "DT",
"comment": "",
"score": 5,
"categories": [
{
"name": "123",
"status": "POSITIVE/NEGETIVE/UNSET"
}
],
"webinarID": 123456
},
{
...
}
]
}
views.py
class TeacherFeedbackViewSet(ViewSet):
permission_classes = [IsAuthenticated, TeacherFeedbackPerm]
renderer_classes = [CamelCaseJSONRenderer]
#base_view
def list(self, request, pk):
moderator = get_object_or_404(Moderator, pk=pk)
serializer = ModeratorFeedback(moderator)
return Response(serializer.data)
serializers.py
class TeacherFeedbackSerializerDetail(ModelSerializer):
created_at = DateTimeField(source='datetime_filled')
created_by = SerializerMethodField(method_name='get_created_by')
categories = SerializerMethodField(method_name='get_categories')
webinar_id = IntegerField(source='webinar.id')
moderator_id = IntegerField(source='moderator.id')
class Meta:
model = TeacherFeedback
fields = ['id', 'created_by', 'created_at', 'categories', 'score', 'comment', 'moderator_id', 'webinar_id']
def get_categories(self, feedback: TeacherFeedback):
data = []
category_names = list(FeedbackCategory.objects.all().values_list('name', flat=True))
for category in feedback.category.all():
z = TeacherFeedbackCategory.objects.get(category=category, feedback=feedback)
data.append({"name": z.category.name, "status": z.status})
unset = list(map(lambda x: {"name": x, "status": "unset"},
list(set(category_names) - set([i["name"] for i in data]))))
return sorted(data + unset, key=lambda x: x["status"])
def get_created_by(self, feedback: TeacherFeedback):
return str(feedback.created_by.teacher)
class ModeratorFeedback(serializers.ModelSerializer):
stats = serializers.SerializerMethodField(method_name='get_stats_list')
feedback = TeacherFeedbackSerializerDetail(many=True, source='actual_feedbacks')
class Meta:
model = Moderator
fields = ['stats', 'feedback']
def get_stats_list(self, moderator: Moderator):
data = {}
for feedback in moderator.actual_feedbacks:
for category in feedback.category.all():
category_detail = TeacherFeedbackCategory.objects.get(feedback=feedback, category=category)
if category.name not in data:
data[category.name] = [category_detail.status]
else:
data[category.name].append(category_detail.status)
stats = []
for k, statuses in data.items():
weight = 100/len(statuses)
current_value = 0
for status in statuses:
if status == 'positive':
current_value += weight
else:
current_value -= weight
stats.append({"name": k, "value": float("{0:.2f}".format(current_value))})
return stats
In order to realize the pagination here, you need to make serializer for stats and feedback data respectively.
First you can define the ModeratorStats serializer.
class ModeratorStats(serializers.ModelSerializer):
stats = serializers.SerializerMethodField(method_name='get_stats_list')
class Meta:
model = Moderator
fields = ['stats']
def get_stats_list(self, moderator: Moderator):
...
And TeacherFeedbackSerializerDetail serializer is for the feedback.
Now in view,
from django.core.paginator import Paginator
from rest_framework.response import Response
class TeacherFeedbackViewSet(ViewSet):
...
#base_view
def list(self, request, pk):
moderator = get_object_or_404(Moderator, pk=pk)
# first get page and size param
page = int(request.GET.get('page', "1"))
size = int(request.GET.get('size', "10"))
# here I assumed the foreign key field name is `moderator`
query_set = TeacherFeedback.objects.filter(moderator__id = pk)
paginator = Paginator(query_set.order_by('id'), size)
feedbacks = paginator.page(page)
stats_data = ModeratorStats(moderator).data
feedbacks = TeacherFeedbackSerializerDetail(feedbacks, many=True).data
return Response({"stats": stats_data, "feedback": feedbacks})
And in frontend, you need to upload pagination params like ...?page=1&size=10.

Django NestedHyperlinkedModelSerializer not returning Foreign Key Field

Parent id or foreign key is not returning from child. I am trying drf-nested-routers example.
models.py
class Client(models.Model):
name = models.CharField(max_length=255)
class MailDrop(models.Model):
title = models.CharField(max_length=255)
client_id = models.ForeignKey(Client, on_delete=models.CASCADE)
serializers.py
class ClientSerializer(HyperlinkedModelSerializer):
class Meta:
model = Client
fields = ['id', 'name']
class MailDropSerializer(NestedHyperlinkedModelSerializer):
parent_lookup_kwargs = {
'client_pk': 'client_id',
}
class Meta:
model = MailDrop
fields = ['id', 'title']
views.py
class ClientViewSet(viewsets.ViewSet):
serializer_class = ClientSerializer
def list(self, request):
queryset = Client.objects.filter()
serializer = ClientSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Client.objects.filter()
client = get_object_or_404(queryset, pk=pk)
serializer = ClientSerializer(client)
return Response(serializer.data)
class MailDropViewSet(viewsets.ViewSet):
serializer_class = MailDropSerializer
def list(self, request, client_pk=None):
queryset = MailDrop.objects.filter(client_id=client_pk)
serializer = MailDropSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None, client_pk=None):
queryset = MailDrop.objects.filter(pk=pk, client_id=client_pk)
maildrop = get_object_or_404(queryset, pk=pk)
serializer = MailDropSerializer(maildrop)
return Response(serializer.data)
If I add fields = ['id', 'title', 'client_id'] in the MailDropSerializer, it throws the following error:
AssertionError: `NestedHyperlinkedRelatedField` requires the request in the serializer context. Add `con
text={'request': request}` when instantiating the serializer.
If I add the request in the serializer context, the output is as follows:
url: http://127.0.0.1:8000/clients/4/maildrops/
[
{
"id": 3,
"title": "Madison Mosley",
"client_id": "http://127.0.0.1:8000/clients/4/"
},
{
"id": 4,
"title": "Louis Chen",
"client_id": "http://127.0.0.1:8000/clients/4/"
}
]
I added the serializer context as follows:
def list(self, request, client_pk=None):
queryset = MailDrop.objects.filter(client_id=client_pk)
serializer_context = {
'request': request
}
serializer = MailDropSerializer(
queryset, many=True, context=serializer_context)
return Response(serializer.data)
I am trying to get something as follows:
[
{
"id": 3,
"title": "Madison Mosley",
"client_id": 4
},
{
"id": 4,
"title": "Louis Chen",
"client_id": 4
}
]
Just solved:
class MailDropSerializer(NestedHyperlinkedModelSerializer):
parent_lookup_kwargs = {
'client_pk': 'client_id',
}
client_id = IntegerField(source='client_id.id')
class Meta:
model = MailDrop
fields = ['id', 'title', 'client_id']

Upload Document in Django Rest Framework Dynamic Form

In my Djago-React Application I am creating a form which has dynamic fields as well as an option to upload the document
For that I am using multipart/form-data and its working fine when I am using it only to upload the document but as soon as I enter the data in dynamic fields the serializer stops handling the data.
Example data:
Form Data:
transaction_no: 2341
document: (binary)
items[0][product]: 5
items[0][quantity]: 1
items[0][fault]: Repairable
items[1][product]: 4
items[1][quantity]: 2
items[1][fault]: Return
allotment: 122
Response:
{"items":[{"product":["This field is required."]},{"product":["This field is required."]}]}
Serializer:
class DeliveredItemsSerializer(serializers.ModelSerializer):
class Meta:
model = DeliveredItems
fields = "__all__"
class DeliveredSerializer(serializers.ModelSerializer):
items = DeliveredItemsSerializer(many=True,required=False)
class Meta:
model = Delivered
fields = "__all__"
def create(self, validated_data):
items_objects = validated_data.pop('items', None)
prdcts = []
try:
for item in items_objects:
i = DeliveredItems.objects.create(**item)
prdcts.append(i)
instance = Delivered.objects.create(**validated_data)
print("prdcts", prdcts)
instance.items.set(prdcts)
return instance
except:
instance = Delivered.objects.create(**validated_data)
return instance
def update(self, instance, validated_data):
items_objects = validated_data.pop('items',None)
prdcts = []
try:
for item in items_objects:
print("item", item)
fk_instance, created = DeliveredItems.objects.update_or_create(pk=item.get('id'), defaults=item)
prdcts.append(fk_instance.pk)
instance.items.set(prdcts)
instance = super(DeliveredSerializer, self).update(instance, validated_data)
return instance
except:
instance = super(DeliveredSerializer, self).update(instance, validated_data)
return instance
Models:
class DeliveredItems(models.Model):
choices = (('Repairable', 'Repairable'),('Return', 'Return'), ('Damage', 'Damage'), ('Swap Return', 'Swap Return'))
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=0)
fault = models.CharField(max_length=100, default="Repairable", choices=choices)
class Delivered(models.Model):
allotment = models.ForeignKey(Allotment, on_delete=models.CASCADE)
delivered = models.BooleanField(default=False)
items = models.ManyToManyField(DeliveredItems)
document = models.FileField(upload_to='delivered/', null=True, blank=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
How can I handle the dynamic data that don't come in a list and comes as something like this items[0][product]: 5?
I need the data to be in this format:
{
"transaction_no": 2335,
"delivered": false,
"document": {
"uid": "rc-upload-1599739825759-7"
},
"items": [
{
"product": 4,
"quantity": "12",
"fault": "Repairable"
},
{
"product": 5,
"quantity": "1",
"fault": "Return"
}
],
"allotment": 116
}
please try this one if you want to handle form data in DRF
in view, please inherit viewsets.ModelViewSet
def dict_shallow_copy(d):
return dict(d.items())
def get_serializer(self, *args, **kwargs):
if "data" in kwargs:
data = kwargs.get("data", {})
if isinstance(data, dict):
data = dict_shallow_copy(data)
//get your data here, sameple like this
data['items'] = self.kwargs
data['transaction_no'] = self.kwargs
kwargs['data'] = data
else:
raise ValidationError("Invalid data type")
return super().get_serializer(*args, **kwargs)

Sorting the django objects based on SerializerMethodField

I am trying to order the user profiles based on the timestamp of the last message in between both the users.
I am using SerializerMethodField to get the timestamp of the last message.
is there any way I can sort the data?
class UserProfileSerializer(serializers.ModelSerializer):
lastmessage = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id','lastmessage']
def get_lastmessage(self,obj):
k = self.context.get('view').kwargs['sid']
data =( Message.objects.filter(receiver=obj.id,sender=k) | Message.objects.filter(sender=obj.id,receiver=k)).order_by('-timestamp').values('message','timestamp')
if len(data) == 0:
return ""
else:
data = data.first()
data["timestamp"] = str(data["timestamp"])
return str(data)
My view:
class UserChatViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserProfileSerializer
Now my views return:
[{
"id": 4,
"lastmessage": "{'message': 'random', 'timestamp': '2020-06-14 23:49:33.077749+00:00'}"
},
{
"id": 5,
"lastmessage": ""
},
{
"id": 6,
"lastmessage": "{'message': 'sample', 'timestamp': '2020-06-14 11:53:03.880833+00:00'}"
},
{
"id": 7,
"lastmessage": ""
}]
But I want it to sort based on the timestamp of last message
You can overwrite list in order to achieve this:
def list(self, request, *args, **kwargs):
response = super().list(request, args, kwargs)
# sort response.data['results']
return response
Also, lastmessage can be a dict instead of a str, so it's easier to work with.
The order of your response should be handled in the view .
from django.db.models import Subquery, OuterRef
lm = Message.objects.filter(sender=OuterRef("id"), receiver=self.kwargs['sid']).order_by('-timestamp')
data = User.objects.all().annotate(
lastmessage=Subquery(
lm.values('timestamp')[:1]
)
).order_by('-lastmessage__timestamp')

Adding Sum of Object Attributes to Django Rest Framework Response

I'm Trying to add the sum of multiple objects to a DRF response. For example, right now the response works with just listing the objects:
[
{
"id": "47d0deaa5c8c",
"amount": "25.00"
},
{
"id": "29787731",
"amount": "25.00"
}
]
But what I want is to be able to sum the amount attribute of those objects together and then include that in the response, so that it would look like this:
{
"sum":"50.00",
"objects":[
{
"id":"47d0deaa5c8c",
"amount":"25.00"
},
{
"id":"29787731",
"amount":"25.00"
}
]
}
Here's my current APIView:
class TransactionsList(GenericAPIView):
"""
Retrieve list of transactions
"""
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request):
"""List Transactions"""
transaction = Transaction.objects.all()
serializer = TransactionSerializer(transaction, many=True)
return Response(serializer.data)
And Serializer:
class TransactionSerializer(serializers.ModelSerializer):
class Meta:
model = Transaction
fields = ('id', 'amount')
How could I efficiently go about adding the sum field into the response?
change
return Response(serializer.data)
to
from django.db.models import Sum
all_sum = transaction.aggregate(Sum('amount'))['amount__sum']
return Response({'sum': all_sum if all_sum else 0 , 'objects': serializer.data})
Since the amount field seems string type. So, we've to convert to int before summing them.
class TransactionsList(GenericAPIView):
"""
Retrieve list of transactions
"""
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request):
"""List Transactions"""
transaction = Transaction.objects.all()
serializer = TransactionSerializer(transaction, many=True)
return_data = {"sum": str(sum([lambda items: int(items['amount'])])), "objects": serializer.data}
return Response(return_data)
If the amount field is int type, then you don't want to add do the type casting. If so, use this,
return_data = {"sum": sum([lambda items: items['amount']]), "objects": serializer.data}