Sorting the django objects based on SerializerMethodField - django

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

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.

Return list of values of one column grouped by another column in Django viewset

Let's say I have a model:
class Entry(models.Model):
content = models.CharField(max_length=140)
email = models.EmailField()
upload_date = models.DateTimeField(default=timezone.now)
class Meta:
db_table = 'entries'
What I want to achieve is the viewset to return list of content and upload_date values per single email (since many entries can have the same email value), like that:
[
{
"email": "address1#test.test",
"entries": [
{
"upload_date": "2020-09-03",
"content": "test content 1"
},
{
"upload_date": "2020-09-02",
"content": "test content 2"
},
...
]
},
{
"email": "address2#test.test",
"entries": [
{
"upload_date": "2020-09-03",
"content": "test content 11"
},
{
"upload_date": "2020-09-02",
"content": "test content 12"
},
...
]
},
...
]
I tried messing around with .values() and .annotate(), but with no luck.
Quick answer with groupby itertools function :
def entry_converter(entry):
return {'upload_date': entry.upload_date, 'content': entry.content}
def grouping_key(entry):
return entry.email
from itertools import groupby
entries = Entry.objects.order_by('email')
grouped_entries = [dict(email=key, entries=list(map(entry_converter, group))) for key, group in groupby(entries, key=grouping_key)]
Update 1
Integration with DRF ViewSet
def entry_converter(entry):
return {'upload_date': entry.id, 'content': entry.id}
def grouping_key(entry):
return entry.email
def group_entries(entries):
return [
dict(email=key, entries=list(map(entry_converter, group))) for key, group in groupby(entries, key=grouping_key)
]
class EntrySerializer(ModelSerializer):
class Meta:
model = Entry
fields = '__all__'
class EntryViewSet(ModelViewSet):
queryset = Entry.objects.all()
serializer_class = EntrySerializer
def list(self, request, *args, **kwargs):
queryset = self.get_queryset().order_by('email')
data = group_entries(queryset)
page = self.paginate_queryset(data)
if page is not None:
return self.get_paginated_response(page)
return Response(data)
Keep in mind that I haven't used .filter_queryset() method of ModelViewSet as it may modify the queryset by adding wrong ordering or wrong filtering. Because groupby function needs sorted data as an input. Also, I have not overridden ModelSerializer class for generalizing .list() method of ModelViewSet as it may create extra complexity.

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}

How to add extra attributes to queryset in django rest api using serialiser and listViewAPI?

How do I add additional fields like count, status_code to the queryset in the django rest api?
class SongList(generics.ListAPIView):
"""
API endpoint that allows users to be viewed or edited===1111.
"""
serializer_class = SongSerialier
def get_queryset(self):
queryset = Song.objects.all()
id = self.request.query_params.get('id', None)
test=QuerySet()
return queryset
Above code returns only the content in the SongSerialier in the form of list, for example
[
{
"song_title": "song1",
"file_type": "mp3",
"album": 1,
"test": "1",
"my_field": false
},
{
"song_title": "song1",
"file_type": "mp3",
"album": 2,
"test": "1",
"my_field": false
}
]
I want my output like below format
{
status_code:200,
count:2,
total_count:4,
data:[
{
"song_title": "song1",
"file_type": "mp3",
"album": 1,
"test": "1",
"my_field": false
},
{
"song_title": "song1",
"file_type": "mp3",
"album": 2,
"test": "1",
"my_field": false
}
]
}
GOT THE SOLUTION
*You need to override the list method in the ListAPIView *
class SongList(generics.ListAPIView):
i=0
"""
API endpoint that allows users to be viewed or edited===1111.
"""
serializer_class = SongSerialier
def list(self, request, *args, **kwargs):
response = super(SongList, self).list(request, args, kwargs)
# Add data to response.data Example for your object:
print("Data ::: ",type(response),response,response.data)#,dict(response.data))
print ("datat type: ",type(response.data))
d1 = collections.OrderedDict()
d1['a'] = 'A'
d1['b'] = 'B'
d1['c'] = 'C'
d1['d'] = 'D'
d1['e'] = 'E'
response.data=d1
self.no+=1
print ("no:2 ",self.no)
# response.data["a_status_code"]=200
# response.data["a_status_text"] = "Success"
#response['10_mi_count'] = 10
# a=response.render().content
# print ("a: ",a)
return response
def get_queryset(self):
self.no=1
print ("no:1 ",self.no)
queryset = Song.objects.all().annotate(count_test=Count('id'))
id = self.request.query_params.get('id', None)
test=QuerySet()
#print( "=========111" ,type(queryset),self.serializer_class.data)
# print ("j1: ",j)
# j+=1
return queryset
This will give the result as
Check link
following is my serializer code
class SongSerializer(serializers.ModelSerializer):
test=serializers.SerializerMethodField()
my_field = serializers.SerializerMethodField('is_named_bar')
def is_named_bar(self, foo):
return foo.id == "bar"
def get_test(self, obj):
print ("OBJ, ",obj,type(obj),obj.id)
return "1"
class Meta:
model=Song
fields=('song_title','file_type','album','test','my_field')

DRF - Format Serializer's output from QuerySet

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