Django REST framework JSON array GET - python

I am running a Django 2.0 and DRF (Django REST Framework) 3.8.0.
I want to be able to GET JSON in a specific format as seen in GOAL NESTED JSON ARRAY.
Right now, I am able to retrieve an JSON Array as shown in MY CURRENT JSON ARRAY. I have checked this question and it seems like we have the goal but i was unable to be successful.
I have my model, view and serializer below.
This is achieved by using this:
GET /studentlectures/1/get_studlect/ where 1 is {pk}
CURRENT JSON ARRAY:
[
{
"id": 1,
"lecture": 1,
"student": 1
},
{
"id": 19,
"lecture": 4,
"student": 1
}
]
GOAL NESTED JSON ARRAY
{
"id": 1,
"student_code": "60637-009",
"first_name": "Zoltan",
"last_name": "Drogo",
"lectures": [
{
"lecture_id": 1,
"subject_name": "English",
"teacher_id": 1,
"teacher_name": "Cirillo Kierans",
"room": "Room A",
"schedule": "08:00 AM - 10:00 AM"
},
{
"lecture_id": 2,
"subject_name": "Math",
"teacher_id": 3,
"teacher_name": "Johanna Probate",
"room": "Room C",
"schedule": "08:00 AM - 10:00 AM"
},
. . . . . .
}
MODEL:
class Studentlecture(models.Model):
student = models.ForeignKey(Student, default='')
lecture = models.ForeignKey(Lecture, default='')
studentlecture_name = models.CharField(max_length=20, default='ComputerScience Lectures')
def __str__(self):
return f'{self.studentlecture_name}'
VIEW:
class StudentlectureViewSet(ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
serializer_class = StudentlectureSerializer
queryset = Studentlecture.objects.all()
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
#/studentlectures/{pk}/get_studlect/ gives the lectures of student pk
#action(detail=True)
def get_studlect(self, request, *args, **kwargs):
student_lectures = Studentlecture.objects.all().filter(student_id=self.kwargs['pk'])
serializer = self.get_serializer(student_lectures, many=True)
return Response(serializer.data)
#/studentlectures/{pk}/get_lectstud/ gives the students of lecture pk
#action(detail=True)
def get_lectstud(self, request, *args, **kwargs):
lecture_students = Studentlecture.objects.all().filter(lecture_id=self.kwargs['pk'])
serializer = self.get_serializer(lecture_students, many=True)
print(serializer)
return Response(serializer.data)
SERIALIZER:
class StudentlectureSerializer(serializers.ModelSerializer):
class Meta:
model = Studentlecture
fields = ('url', 'id', 'lecture', 'student')

You can use RelatedField for that:
Serializer
from rest_framework.serializers import ModelSerializer, IntegerField
from lectures.fields import LectureRelatedField
class StudentLectureSerializer(ModelSerializer):
id = IntegerField(read_only=True)
lecture = LectureRelatedField(
queryset=Lecture.objects.all(), required=True
)
...
lectures.fields
from rest_framework.serializers import RelatedField
class LectureRelatedField(RelatedField):
def to_representation(self, obj):
data = {
'lecture_id': obj.lecture_id,
'subject_name': obj.subject_name,
'teacher_id': obj.teacher_id,
'teacher_name': obj.teacher_name,
'room': obj.room,
'schedule': obj.schedule
}
return data
def to_internal_value(self, pk):
return Lecture.objects.get(id=pk)
As a result you will get a json like this while making requests:
"lectures": [
{
"lecture_id": 1,
"subject_name": "English",
"teacher_id": 1,
"teacher_name": "Cirillo Kierans",
"room": "Room A",
"schedule": "08:00 AM - 10:00 AM"
},

Haven't been able to check back right away, but this what I have done:
#/studentlectures/{pk}/get_studlect/ gives the lectures of student pk
#action(detail=True)
def get_studlect(self, request, *args, **kwargs):
student_lectures = Studentlecture.objects.all().filter(student_id=self.kwargs['pk'])
serializer = self.get_serializer(student_lectures, many=True)
student = Student.objects.filter(id=self.kwargs['pk'])
lecture_nest = serializer.data
data = {
"id": student.values()[0]['id'],
"student_code": student.values()[0]['student_code'],
"first_name": student.values()[0]['first_name'],
"last_name": student.values()[0]['last_name'],
"lectures": lecture_nest
}
return Response(data)
I adapted Madi7's answer a bit and I know it looks really dirty but I am changing projects now.

Related

Django Rest Framework ordering by count of filtered pre-fetched related objects

I'm hoping someone might be able to help me. I am working with Django Rest Framework, and attempting to create an API that allows users to search for Providers that provide specific Procedures in particular Regions, and only return the relevant details.
Set up
I have these models (heavily simplified):
# models.py
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
class Region(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ["name"]
class Procedure(models.Model):
description = models.CharField(max_length=100)
class Meta:
ordering = ["description"]
class Provider(models.Model):
provider_name = models.CharField(max_length=200)
class Meta:
ordering = ["provider_name"]
class Provision(models.Model):
fk_procedure = models.ForeignKey(
Procedure,
related_name="providers",
on_delete=models.RESTRICT,
)
fk_provider = models.ForeignKey(
Provider,
related_name="services",
on_delete=models.CASCADE,
)
discount = models.FloatField(
validators=[MaxValueValidator(100), MinValueValidator(0)],
default=0,
)
class Meta:
ordering = ["-discount"]
unique_together = ["fk_procedure", "fk_provider"]
class ProvisionLinkRegion(models.Model):
fk_provision = models.ForeignKey(
Provision,
related_name="regions",
on_delete=models.CASCADE,
)
fk_region = models.ForeignKey(
Region,
related_name="services",
on_delete=models.RESTRICT,
)
location = models.BooleanField(default=False)
As you can see, there is a ManyToMany link between Provision and Region via ProvisionLinkRegion. I haven't defined this as a ManyToMany field though, as I need to store additional details (location) about the pairing.
I have defined the following serializers on these models:
# serializers.py
from rest_framework import serializers
from models import (
Provider,
Region,
Procedure,
ProvisionLinkRegion,
Provision,
)
class ProvisionLinkRegionSerializer(serializers.ModelSerializer):
class Meta:
model = ProvisionLinkRegion
fields = ["fk_region", "location"]
class ProvisionDetailsSerializer(serializers.ModelSerializer):
regions = ProvisionLinkRegionSerializer(many=True)
class Meta:
model = Provision
fields = ["fk_procedure", "discount", "mff_opt_out", "regions"]
class ProviderProvisionSerializer(serializers.ModelSerializer):
services = ProvisionDetailsSerializer(many=True)
number_services = serializers.IntegerField()
class Meta:
model = Provider
fields = [
"provider_name",
"services",
"number_services",
]
And have defined my API like this:
# api.py
from django.db.models import Prefetch, Count
from rest_framework import generics, pagination, permissions, status
from rest_framework.response import Response
from serializers import ProviderProvisionSerializer
from models import (
Provider,
ProvisionLinkRegion,
Provision,
)
class CustomPagination(pagination.PageNumberPagination):
page_size_query_param = "limit"
def get_paginated_response(self, data):
return Response(
{
"pagination": {
"previous": self.get_previous_link(),
"next": self.get_next_link(),
"count": self.page.paginator.count,
"current_page": self.page.number,
"total_pages": self.page.paginator.num_pages,
"items_on_page": len(data),
},
"results": data,
}
)
class ProvisionListAPI(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ProviderProvisionSerializer
pagination_class = CustomPagination
def get_queryset(self):
queryset = Provider.objects.distinct()
# Extract the query parameters
params = self.request.query_params
region_list = params["region"].split(",")
procedure_list = param["procedure"].split(",")
# Build up the prefetch Provision table filtering on the regions
# and services
services_prefetch_qs = (
Provision.objects.distinct()
.filter(regions__fk_region__in=region_list)
.filter(fk_procedure__in=procedure_list)
.prefetch_related(
Prefetch(
"regions",
queryset=ProvisionLinkRegion.objects.filter(
fk_region__in=region_list
),
)
)
)
# Apply the filters and prefetch required tables
queryset = queryset.filter(
services__regions__fk_region__in=region_list
).prefetch_related(
Prefetch("services", queryset=services_prefetch_qs),
)
# Add the ordering parameters
queryset = (
queryset.annotate(
number_services=Count("services", distinct=True) # FIXME
)
.filter(number_services__gt=0)
.order_by("-number_services")
)
return queryset.all()
def list(self, response):
# Check it has the right headers
params = self.request.query_params
if "procedure" not in params:
return Response(
{"detail": "procedure not provided as a query parameter"},
status.HTTP_400_BAD_REQUEST,
)
if "region" not in params:
return Response(
{"detail": "region not provided as a query parameter"},
status.HTTP_400_BAD_REQUEST,
)
# Paginate and filter queryset
queryset = self.get_queryset()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
My problem is, the number_services value is not correct, as it's not doing the count on the full filtered results. It's only doing it on the pre-filtered ones (although the region filter does work). I also don't want any providers to appear when they don't have any services (hence the .filter(number_services__gt=0)).
I think that it's related to not filtering on the main Provider queryset like I do with the region i.e. to include:
queryset = queryset.filter(services__fk_procedure__in=procedure_list)
But when I include this, it doesn't actually remove the services, but just the providers that don't provide ANY of those services, so the count is still off.
Example
If my data with no filtering or prefetching looks like this:
"results": [
{
"provider_name": "Provider 2.0",
"services": [
{
"fk_procedure": 3,
"discount": 0.05,
"regions": [
{
"fk_region": 1,
"location": true
},
{
"fk_region": 2,
"location": false
}
{
"fk_region": 3,
"location": true
}
]
},
{
"fk_procedure": 5,
"discount": 0.05,
"regions": [
{
"fk_region": 1,
"location": true
}
]
}
]
},
{
"provider_name": "Test Provider",
"services": [
{
"fk_procedure": 2,
"discount": 0.00,
"regions": [
{
"fk_region": 1,
"location": true
}
]
}
]
}
]
If I then run this on it:
GET /api/v1/provision?page=1&limit=10&region=1,3&services=3`
I want to show all the providers and the relevant details where they relate to either region 1 or 3, and procedure 3.
Actual Result
{
"pagination": {
"previous": null,
"next": null,
"count": 2,
"current_page": 1,
"total_pages": 1,
"items_on_page": 2
},
"results": [
{
"provider_name": "Provider 2.0",
"services": [
{
"fk_procedure": 3,
"discount": 0.05,
"regions": [
{
"fk_region": 1,
"location": true
},
{
"fk_region": 3,
"location": true
}
]
}
],
"number_services": 2
},
{
"provider_name": "Test Provider",
"services": [],
"number_services": 1
}
]
}
Desired Result
{
"pagination": {
"previous": null,
"next": null,
"count": 2,
"current_page": 1,
"total_pages": 1,
"items_on_page": 2
},
"results": [
{
"provider_name": "Provider 2.0",
"services": [
{
"fk_procedure": 3,
"discount": 0.05,
"regions": [
{
"fk_region": 1,
"location": true
},
{
"fk_region": 3,
"location": true
}
]
}
],
"number_services": 1,
}
]
}
Things I've tried
SerializerMethodField
I've been able to get number_services to work using a SerializerMethodField by including:
# serializers.py
class ProviderProvisionSerializer(serializers.ModelSerializer):
...
number_services = serializers.SerializerMethodField()
...
class Meta:
...
def get_number_services(self, obj):
return obj.services.count()
Unfortunately though, I can't use this for ordering, or filtering within the API, and I also can't use it with pagination either, so is pretty useless for what I need it for.
Subquery
In the get_queryset method for the API, I've also tried using what I currently have as a subquery, and using the main queryset where the ID is in the other one, but then I lose all the prefetch from the first subquery, and have regions and services that don't relate to my filter.
## TLDR
How do I filter a queryset in a get_queryset method for a ListAPIView on properties of the children of the main model, and be able to return a count of the remaining children after the filter has taken place?

How to get list of related objects in Django-rest-Framework

I am trying to implement Nested serializer for the first time in django-rest-framework
My requirement is i need the response data in below form :-
{
id: 0,
title: "Fresh",
data: [
[{
image: ".../assets/images/banana.jpeg",
name: "Fruits",
image_scaling: 2,
API: "http://database_api"
},
{
image: "../assets/images/banana.jpeg",
name: "Vegetables",
image_scaling: 2,
API: "http://database_api"
},
{
image: "../assets/images/banana.jpeg",
name: "Exotic Vegetables",
image_scaling: 2,
API: "http://database_api"
}
]
]
}
I am validating the token for the get api and listing the objects form DB.
My current implementation of views.py is :-
class home(APIView):
def get(self,request,format=None):
header_token = request.META.get('HTTP_AUTHORIZATION', None)
if header_token is not None:
token = header_token
access_token=token.split(' ')[1]
print(access_token)
if(validate_token(access_token) == None):
raise NotFound('user does exist')
queryset=Fresh.objects.all().select_related('data')
print(queryset)
serializer = FreshSerializer(queryset,many=True)
return Response(serializer.data)
Serializer.py is :-
class DataSerializer(serializers.ModelSerializer):
class Meta:
model=data
fields = ('name','image_scaling','image')
class FreshSerializer(serializers.ModelSerializer):
data=DataSerializer(read_only=True, many=False)
class Meta:
model = Fresh
fields = ('title','data')
models.py is :-
class data(models.Model):
name=models.CharField(max_length=9,null=True)
image_scaling=models.IntegerField(default=0,null=True)
image=models.URLField(max_length = 200)
def __str__(self):
return str(self.name)
class Fresh(models.Model):
title=models.CharField(max_length=9)
data=models.ForeignKey(data,on_delete=models.CASCADE,default=None, null=True)
def __str__(self):
return str(self.title)+" " +str(self.data)
My current output is :-
[
{
"title": "vegetable",
"data": {
"name": "vegetable",
"image_scaling": 2,
"image": "https......jpg"
}
},
{
"title": "exoticveg",
"data": {
"name": "exoticveg",
"image_scaling": 2,
"image": "https://.......jpg"
}
} ]
I was trying to do by listapiview and modelviewset, but i am not able to get the desired output.I have seen similar queries in stack-overflow, but no solution seems to work for me. I am not getting list of objects of foreign key , am i doing something wrong in my model or running wrong query? I am really stuck at it.
How shall i go about it.
Please help!!!

How do I deserialize list of multiple objects in django rest framework?

I am developing a rest API that gets requests composed of multiple objects and saves them to the database. Then, another array of objects is returned as the response. All objects are of only one model.
class Ratings(models.Model):
id = models.AutoField(primary_key = True)
userId = models.IntegerField()
movieId = models.IntegerField()
rating = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
timestamp = models.DateTimeField(auto_now = True)
class RatingsSerializer(serializers.ModelSerializer):
class Meta:
model = Ratings
fields = ('userId','movieId','rating')
class RecommendationGenerator(generics.ListCreateAPIView):
queryset = Ratings.objects.all()
serializer_class= RatingsSerializer
def post(self, request, format='json'):
serializer= RatingsSerializer(data = request.data, many = True)
if serializer.is_valid():
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
When I test it in Postman with the JSON:
[
{
"userId": 13,
"movieId": 1765,
"rating": 5
},
{
"userId": 13,
"movieId": 1733,
"rating": 3
},
{
"userId": 13,
"movieId": 1713,
"rating": 2
},
{
"userId": 13,
"movieId": 963,
"rating": 2
}
]
The result is [].
But for
{
"userId": 13,
"movieId": 1765,
"rating": 5
}
The result is
{
"userId": 13,
"movieId": 1765,
"rating": 5
}
How do I deserialize this data? What am I doing wrong here?
views should be like
class RecommendationGenerator(generics.ListCreateAPIView):
queryset = Ratings.objects.all()
serializer_class= RatingsSerializer
def post(self, request, format='json'):
serializer= RatingsSerializer(data = request.data, many = True)
if serializer.is_valid():
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
Try This
The code works fine. The problem was that the request sent was in the form of Text instead of 'application/json'. Thanks for the help anyways.

Django REST Framework updated nested object not in Model

I only want to update all order in my_stage by using PUT, something like:
"Payload":
{
"stages": [
{
"stage_id": 1,
"position": 2
},
{
"stage_id": 2,
"position": 3
},
{
"stage_id": 3,
"position": 4
}
]
}
and "Response":
{
"stages": [
{
"stage_id": 1,
"position": 2
},
{
"stage_id": 2,
"position": 3
},
{
"stage_id": 3,
"position": 4
}
]
}
But I don't have "stages" in my model so I cannot use ModelSerializer. But I have to implement create() first.
So what should I do to implement update? Thank you.
My stage model is:
class Stage(models.Model):
class Meta:
db_table = 'stage'
position = models.IntegerField()
Here are my serialzier.py
class StagePositionSerializer(ModelSerializer):
"""Serialize order"""
# company_id = IntegerField(read_only=True)
stage_id = IntegerField(source='id', read_only=True)
position = IntegerField(min_value=0)
class Meta:
model = Stage
fields = [
'stage_id',
'position'
]
class PositionSerializer(Serializer):
stages = StagePositionSerializer(many=True)
and my view.py
class StagePositionListView(APIView):
serializer_class = PositionSerializer
If you only want to have "stages:" prepended to your data you can create a custom endpoint in the viewset and specify the formatting you want returned.
I'm not sure why you have a model serializer as well as a non-model serializer. What does that buy you?
Here is some sample code which would add 'get_stages' to your api url.
views.py:
class StagePositionViewSet(viewsets.ModelViewSet):
queryset = Stage.objects.all()
serializer_class = StagePositionSerializer
#list_route(methods=['GET'])
def get_stages(self, request, **kwargs):
try:
stage_list = Stage.objects.all()
serializer = StagePositionSerializer(stage_list , many=True)
results = dict()
#this is where you add your prepended info
results['stages'] = serializer.data
return Response(results, status=status.HTTP_200_OK)
except Exception as e:
return Response(e, status=status.HTTP_400_BAD_REQUEST)
Then if you perform a GET at the URL whatever_your_url/is/get_stages you will get back the payload format you want.
You could easily take advantage of serializer(many=True), which will match a list of serializers. Your serializer would be:
class StagePositionSerializer(ModelSerializer):
class Meta:
model = Stage
fields = [
'id',
'position'
]
class PositionSerializer(Serializer):
stages = StagePositionSerializer(many=True)

django rest framework foreign key serializer issue

i am new to DJango Python webservices and i am facing small issue with the serializers.here is my code.
**views.py**
#csrf_exempt
#api_view(['GET'])
def sqlservice(request):
if request.method == 'GET':
posts = tbl_dcs.objects.all()
for each in posts:
manid = each.managerid_id
manname = Managerid.objects.get(id = manid)
nameser = managerSerializer(manname,many=False)
print nameser.data
serializer = PostSerializer(posts, many=True)
return Response({"resource":serializer.data})
**models.py**
from __future__ import unicode_literals
from django.db import models
class Managerid(models.Model):
managername = models.CharField(max_length=25)
def __unicode__(self):
return self.managername
class tbl_dcs(models.Model):
name = models.CharField(max_length=25)
location = models.CharField(max_length=50)
address = models.TextField()
managerid = models.ForeignKey(Managerid,related_name='items')
phonenumber = models.IntegerField()
def __unicode__(self):
return self.name
**serilizer.py**
from rest_framework import serializers
from mysqlservice.models import tbl_dcs
from mysqlservice.models import Managerid
class managerSerializer(serializers.ModelSerializer):
class Meta:
model = Managerid
fields = ('id', 'managername')
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = tbl_dcs
fields = ('id', 'name','location','address','managerid','phonenumber')
if i run the above code the result is...
{
"resource": [
{
"id": 1,
"name": "ramesh",
"location": "hyd",
"address": "kphb,hyd",
"managerid": 10,
"phonenumber": 345345
},
{
"id": 2,
"name": "kpti",
"location": "kphb",
"address": "kphb,6th phase,hyd",
"managerid": 10,
"phonenumber": 45456
}
]
}
my resulting json i am unable to get the managername.How to get the managername based on managerid from the different table?.Please correct me if i am wrong anywhere.
my final json should be like below...
{
"resource": [
{
"id": 1,
"name": "ramesh",
"location": "hyd",
"address": "kphb,hyd",
"managerid": 10,
"phonenumber": 345345,
"managername":"xxxxx"
},
{
"id": 2,
"name": "kpti",
"location": "kphb",
"address": "kphb,6th phase,hyd",
"managerid": 10,
"phonenumber": 45456,
"managername":"yyyyy"
}
]
}
use serializers.SerializerMethodField to get manager name like below..
class PostSerializer(serializers.ModelSerializer):
managername = serializers.SerializerMethodField('get_manager_name')
class Meta:
model = tbl_dcs
fields = ('id', 'name','location','address','managerid','phonenumber','managername')
def get_manager_name(self, obj):
return obj.managerid.managername

Categories

Resources