How to do pagination for a serializer field? - django

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.

Related

POST id objects and retriving nested tables related to it

Can someone advise me on how to deal with how to retrieve data such as this example
JSON:
[
{
"deviceid": 4,
"devicename": "###",
"device_measurment": {
"deviceid": 4,
"measurement": "31.8",
}
},
]
How is it right now:
[
{
"deviceid": 4,
"devicename": "###",
},
{
"deviceid": 4,
"measurement": "31.8",
},
]
I understand the problem and why I am getting such a response, but I cannot handle this problem via the serializer. at the moment, I have the following
Views
from itertools import chain
class RetrieveSpecificDevicesView(APIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = SpecificDeviceSerializer
def post(self, request):
if self.request.method == 'POST':
serializer = SpecificDeviceSerializer(data=request.data)
if serializer.is_valid():
obj1= Devices.objects.using('###').filter(deviceid=serializer.data.get("deviceid"))
obj2= Measurements.objects.using('###').filter(deviceid=serializer.data.get("deviceid"))
dats= chain(obj1, obj2)
data = serializers.serialize('json', dats)
return HttpResponse(data, content_type="application/json")
return JsonResponse(serializer.erorr, status=status.HTTP_201_CREATED)
Serializer
class SpecificDeviceSerializer(serializers.Serializer):
deviceid = serializers.CharField(required=True)
class MeasurmentsSerializer(serializers.ModelSerializer):
class Meta:
model = Measurements
fields = ('measurementid','measurement')
Models
enter image description here
Update:
I am trying to implement via serializer but something is going wrong:
Note: By wrong I mean => *Got AttributeError when attempting to get a value for field devicesname on serializer SpecificDeviceSerializer*.
Views
class RetrieveSpecificDevicesView(APIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = SpecificcDeviceSerializer
def post(self, request):
if self.request.method == 'POST':
data = self.serializer_class.get_device(self, obj=request.data)
user = SpecificDeviceSerializer(data)
return HttpResponse(user)
Serializers
class SpecificDeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Devices
fields = ( 'deviceid', 'devicename')
class SpecificcDeviceSerializer(serializers.ModelSerializer):
def get_device(self, obj):
device = Devices.objects.using('###').filter(deviceid=obj.get("deviceid"))
return device
class Meta:
model = Devices
fields = ( 'deviceid', 'devicename')
Solution:
Serializer
class SpecificDeviceSerializer(serializers.ModelSerializer):
measurments = serializers.SerializerMethodField()
def get_measurments(self, obj):
device_measurements = obj.measurment_details.last()
serializer = MeasurmentsSerializer(device_measurements)
return serializer.data
class Meta:
model = Devices
fields = ('devicename','measurments')
Views
class RetrieveSpecificDevicesView(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated]
serializer_class = SpecificDeviceSerializer
def get_queryset(self):
return Devices.objects.using('###').filter(deviceid=self.request.data.get("deviceid"))
JSON:
[
{
"devicename": "RTMU 3068",
"measurments": {
"measurementid": 2980465,
"measurement": "25.6"
}
}
]
Documentation: Best for this case

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

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.

Django - needs to have a value for field "id" before this many-to-many relationship can be used

I have two serializers
class CheckItemSerializer(serializers.ModelSerializer):
class Meta:
model = CheckItem
fields = (
'item_name',
'amount',
)
class PaymentActionSerializer(serializers.ModelSerializer):
items = CheckItemSerializer(many=True, required=False)
class Meta:
model = PaymentAction
fields = [
'booking_number',
'date',
'guest_name',
'isRefund',
'lcode',
'payment_type',
'positions',
'total',
'items',
'id'
]
def create(self, validated_data):
action = PaymentAction.objects.create(**validated_data)
action.save()
if validated_data.get('items', None) is not None:
items = validated_data.pop('items')
if items is not None:
for item in items:
item_name = item['item_name']
amount = item['amount']
new_item = CheckItem.objects.create(
item_name=item_name,
amount=amount
)
new_item.save()
action.items.add(new_item)
action.save()
return action
and json
{"lcode": 123,
"total": 1,
"isRefund": false,
"booking_number": "333",
"guest_name": "me",
"positions": "1 night",
"date": "2019-07-22 00:00",
"payment_type": "nal",
"items": [
{
"item_name": "glazka",
"amount": "100"
},
{
"item_name": "glazka2",
"amount": "150"
}
]
}
and I get error
"<PaymentAction: PaymentAction object>" needs to have a value for field "id" before this many-to-many relationship can be used.
What am I doing wrong ?
You passed the items parameter to your PaymentAction object as well, but since at that point, your PaymentAction has not (yet) a primary key, it can not add these items to a many-to-many field.
You thus should pop that from the validated_data first:
def create(self, validated_data):
items = validated_data.pop('items', None)
action = PaymentAction.objects.create(**validated_data)
if items is not None:
items = [CheckItem.objects.create(**item) for item in items]
action.items.add(*items)
return action

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