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