How I can flatten many to many relationship using Django serializer - django

I am new to Django. While I am try to implement serializer for many to many relationship I got problem.I want to serialize a many to many field and need to create a dictionary based on the nested object.
models.py
class ClassRoom(models.Model):
name = models.CharField(max_length=10)
def __str__(self):
return self.name
class Teacher(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField(max_length=50)
class_room = models.ForeignKey(ClassRoom, on_delete=models.CASCADE)
teacher = models.ManyToManyField(Teacher)
def __str__(self):
return self.name
serializers.py
class ClassRoomSerializer(serializers.Serializer):
class Meta:
model = ClassRoom
fields = '__all__'
class TeacherSerializer(serializers.Serializer):
teacher_name = serializers.CharField(source="name", label="Teacher")
class Meta:
model = Teacher
fields = ['name']
class StudentSerializer(serializers.Serializer):
class_room = serializers.CharField(source="class_room.name")
teacher = TeacherSerializer(many=True)
student_name = serializers.CharField(source='name', label="Student")
class Meta:
model = Student
fields = ['student_name', 'teacher', 'class_room']
views.py
class HomeclassView(viewsets.ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
I got the response like this:
{
results: [
{
"class_room": "Class 1",
"teacher": [
{
"teacher_name": "Maria"
},
{
"teacher_name": "sara"
}
],
"student_name": "John"
}
]
}
But I am expecting the result in :
{
results: [
{
"class_room": "Class 1",
"teacher_name": "Maria",
"student_name": "John"
},
{
"class_room": "Class 1",
"teacher_name": "sara",
"student_name": "John"
},
]
}
Please help me to achieve this.
Thanks in advance

you should for on teachers in ModelViewSet:
class HomeclassView(viewsets.ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
flat_response = []
for teacher in response['teacher']:
res = response.copy()
del res['teacher']
res['teacher_name'] = teacher['teacher_name']
flat_response.append(res)
return Response(data=flat_response)

Related

Django rest API: How I can serialize data properly

I am new to django rest framework. I am getting the response data for not in the format I need. Any help will be appreciated. Below is my code.
model.py
class Price(models.Model):
ticker = models.CharField(max_length=15, null=False, blank=False)
price_date=models.DateField(null=False, blank=False)
daily_pe = models.FloatField(blank=True, null=True)
class Meta:
db_table= 'price'
unique_together = ('ticker', 'price_date')
def __str__(self):
return "%s %s %s " % (self.ticker, self.price_date, daily_pe)
serializers.py
class ChartSerilizer(serializers.ModelSerializer):
class Meta:
model = Price
fields = ['ticker', 'daily_pe']
view.py
class peChartViewSet(APIView):
def get(self, request):
queryset = Price.objects.all()
serializer = ChartSerilizer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
This is how am getting the response:
[
{
"ticker": "AAPL",
"daily_pe": 25.144920802584345
},
{
"ticker": "AAPL",
"daily_pe": 25.043920960028977
},
{
"ticker": "WMT",
"daily_pe": 24.930801136366966
},
{
"ticker": "WMT",
"daily_pe": 25.504480242081453
},
]
But I need the response like this :
[
{
"Ticker": "AAPL",
"daily_pe": [25.144920802584345, 25.043920960028977]
},
{
"Ticker": "WMT",
"daily_pe": [24.930801136366966,25.504480242081453]
}
]
you can do the thing you wanted by using ArrayAgg aggregation function
views.py
from django.contrib.postgres.aggregates import ArrayAgg
class peChartViewSet(APIView):
def get(self, request):
queryset = Price.objects.values('ticker').annotate(
daily_pe_list=ArrayAgg('daily_pe')
)
serializer = ChartSerilizer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
and also change daily_pe's type to ListField
serializers.py
class ChartSerilizer(serializers.ModelSerializer):
Ticker = serializers.CharField(source='ticker')
daily_pe = serializers.ListField(source='daily_pe_list')
class Meta:
model = Price
fields = ['Ticker', 'daily_pe']
based on your answer, you want result list in order of price_date, so for this purpose, you can add ordering argument to ArrayAgg like this:
queryset = Price.objects.values('ticker').annotate(
daily_pe_list=ArrayAgg('daily_pe', ordering='price_date')
)
* sorry I answered late, i wasn't around my pc.
There must me a better way, but I got desired result (almost).
Here is my updated view.py
class peChartViewSet(APIView):
def get(self, request):
tickers = ['AAPL', 'WMT']
data=[]
for tick in tickers:
print(tick)
# tick = request.query_params["ticker"]
queryset = Price.objects.all().filter(ticker=tick).order_by('price_date')
serializer = ChartSerilizer(queryset, many=True)
data.append({tick : serializer.data})
return Response(data)

Need to add one model fields in the another model serializers but throwing error while POST the request

models.py
class Product(models.Model):
product_id = models.AutoField(unique=True, primary_key=True)
product_name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "product_master"
def __str__(self):
return self.product_name
class Organisation(models.Model):
"""
Organisation model
"""
org_id = models.AutoField(unique=True, primary_key=True)
org_name = models.CharField(max_length=100)
org_code = models.CharField(max_length=20)
org_mail_id = models.EmailField(max_length=100)
org_phone_number = models.CharField(max_length=20)
org_address = models.JSONField(max_length=500, null=True)
product = models.ManyToManyField(Product, related_name='products')
org_logo = models.ImageField(upload_to='org_logo/')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "organisation_master"
def __str__(self):
return self.org_name
serializers.py
class Product_Serializers(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('product_id', 'product_name',)
class Organisation_Serializers(serializers.ModelSerializer):
product = Product_Serializers(many=True)
class Meta:
model = Organisation
fields = ('org_id', 'org_name', 'org_address', 'org_phone_number', 'org_mail_id','org_logo','org_code','product')
depth = 1
"
While i tried to do POST method for the organisation model I have tried giving the input for product as "product: 5" and "product: {"product_id": 5,"product_name": "time"} in the postman form data but it is showing as
{
"status": "error",
"code": 400,
"data": {
"product": [
"This field is required."
]
},
"message": "success"
}
Views.py
class Organisation_Viewset(DestroyWithPayloadMixin,viewsets.ModelViewSet):
renderer_classes = (CustomRenderer, ) #ModelViewSet Provides the list, create, retrieve, update, destroy actions.
queryset=models.Organisation.objects.all()
parser_classes = [MultiPartParser, FormParser]
serializer_class=serializers.Organisation_Serializers
def create(self, request, *args, **kwargs):
data = request.data
new_organisation= models.Organisation.objects.create(org_name=data["org_name"],org_code = ["org_code"], org_mail_id =data["org_mail_id"],org_phone_number= data["org_phone_number"], org_address=data["org_address"],org_logo = data["org_logo"])
new_organisation.save()
for product in data["product"]:
product_id = models.Product.objects.get(product_id=product["product_id"])
new_organisation.products.add(product_id)
serializer = serializers.Organisation_serializers(new_organisation)
return Response(serializer.data)
I need to post like this product: {"product_id": 5,"product_name": "time"}, what fields are available in the product model it should be posted on this product field.
Can you please suggest me a way as i tried many ways as per my knowledge but it dosen't worked.
you are using a tuple for fields, put a comma behind product in youre fields. If you use a list then dont use an end comma
fields = ('org_id', 'org_name', 'org_address', 'org_phone_number', 'org_mail_id','org_logo','org_code','product',)
depth = 1
Update your serializers to:
class Product_Serializers(serializers.Serializer):
product_id = serializers.IntegerField()
product_name = serializers.CharField(max_length=100)
class Organisation_Serializers(serializers.ModelSerializer):
product = Product_Serializers(many=True)
class Meta:
model = Organisation
fields = (
'org_id',
'org_name',
'org_address',
'org_phone_number',
'org_mail_id',
'org_logo',
'org_code',
'product'
)
depth = 1
Update your views as:
class Organisation_Viewset(ModelViewSet):
# ModelViewSet Provides the list, create, retrieve, update, destroy actions.
renderer_classes = (CustomRenderer,)
queryset = Organisation.objects.all()
parser_classes = [MultiPartParser, FormParser, JSONParser]
serializer_class = Organisation_Serializers
def create(self, request, *args, **kwargs):
serializer = Organisation_Serializers(data=request.data)
serializer.is_valid(raise_exception=True)
product_data = serializer.validated_data.pop('product')
does_not_exist = []
product_instances = []
for product in product_data:
try:
product_instance = Product.objects.get(
product_id=product['product_id'],
product_name=product['product_name']
)
product_instances.append(product_instance)
except Product.DoesNotExist:
does_not_exist.append(product)
if len(does_not_exist) > 0:
return Response({
'error': 'Product does not exist',
'does_not_exist': does_not_exist
}, status=400)
organization = Organisation.objects.create(**serializer.validated_data)
for product in product_instances:
organization.product.add(product)
organization.save()
return Response(Organisation_Serializers(organization).data, status=201)
Now we can send the list of product objects for the create API:
curl --location --request POST 'http://localhost:8000/api/organization/' \
--header 'Content-Type: application/json' \
--data-raw '{
"org_id": "12345",
"org_name": "test organization",
"org_address": "test",
"org_phone_number": "12345",
"org_mail_id": "test#te.st",
"org_code": "12345",
"product": [
{
"product_id": 1,
"product_name": "test p1"
},
{
"product_id": 2,
"product_name": "test p2"
}
]
}'

Django REST Multiple Nested Serializer Filter

Is there a way to filter multiple nested serializers?
I have a Student serializer that displays all of the students courses. The courses display all the homework and their scores. They way I want to design the backend is that every student will receive the same homework outline but only the scores change; so to reduce duplicate data I create the scores table that points to the homework and the student.
However, when I do a GET request it gets all of the students scores for that homework instance. Is there a way to filter it to only get a particular students score? I simplified my models for this example but the problem is essentially the same.
In my Response, you can see "score":1 and "score":2, these belong to two different students but I want to get the specific score that relates to the a particular Student.
Models
class Student(models.Model):
firstName = models.CharField(max_length=20)
age = models.IntegerField(default=18)
def __str__(self):
return self.firstName
class Course(models.Model):
courseName = models.CharField(max_length=20)
courseYear = models.IntegerField(default=2021)
student = models.ManyToManyField(Student, related_name='courses')
def __str__(self):
return self.courseName + " " + str(self.courseYear)
class Homework(models.Model):
hwName = models.CharField(max_length=20)
hwPossScore = models.IntegerField(default=100)
course = models.ForeignKey(
Course, related_name='homeworks', on_delete=models.CASCADE, null=True, blank=True)
students = models.ManyToManyField(Student)
def __str__(self):
return self.hwName
class Score(models.Model):
student = models.ForeignKey(
Student, related_name='studentScore', on_delete=models.CASCADE, null=True, blank=True)
homework = models.ForeignKey(
Homework, related_name='studentScore', on_delete=models.CASCADE, null=True, blank=True)
score = models.IntegerField(default=0)
def __str__(self):
return self.student.firstName + " " + str(self.score)
Serializers
class ScoreSerializer(serializers.ModelSerializer):
class Meta:
model = Score
fields = ['score', ]
class HomeworkSerializer(serializers.ModelSerializer):
studentScore = ScoreSerializer(many=True)
class Meta:
model = Homework
fields = ['hwName', 'hwPossScore', 'studentScore', ]
class CourseSerializer(serializers.ModelSerializer):
homeworks = HomeworkSerializer(many=True)
class Meta:
model = Course
fields = "__all__"
class StudentSerializer(serializers.ModelSerializer):
courses = CourseSerializer(many=True)
class Meta:
model = Student
fields = "__all__"
Views
class StudentView(APIView):
permission_classes = [permissions.IsAuthenticated, ]
def get_object(self, request):
try:
requestToken = request.META['HTTP_AUTHORIZATION'].split(' ')[1]
userObj = Token.objects.get(key=requestToken).user
studentObj = Student.objects.filter(user=userObj)[0]
# If requester token does not match user return permission denied
if(self.request.user != userObj):
raise PermissionDenied()
return studentObj
except Student.DoesNotExist:
raise Http404
def get(self, request, format=None):
# Create a field variable for all Board objects that count the number of topics for each board
student = self.get_object(request)
serializer = StudentSerializer(student)
return Response(serializer.data)
Response
{
"id": 1,
"courses": [
{
"id": 1,
"homeworks": [
{
"hwName": "HW1",
"hwPossScore": 100,
"studentScore": [
{
"score": 1
},
{
"score": 2
}
]
}
],
"courseName": "MATH 101",
"courseYear": 2021,
"student": [
1
]
}
],
"firstName": "Red",
"age": 18
}
I think you can use ScoreSerializer instead StudentSerializer. In ScoreSerializer, that class inheritance ModelSerializer you can use field = 'all', because in ScoreModel has student, score, and homeworks field.

Django in one serialize pull out child objects

This my models
class Dictionary(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
parentId = models.UUIDField(editable=True, null=True)
name = models.CharField(max_length=100)
date_create = models.DateTimeField(auto_now=True)
date_end = models.DateTimeField(auto_now=False, null=True)
class Teacher(models.Model):
name = models.CharField(max_length=100)
message = models.CharField(max_length=300)
status = models.OneToOneField(Dictionary, on_delete=models.CASCADE)
this is my urls
from django.urls import path
from . import views
urlpatterns = [
path('get', views.GetViewSet.as_view({'get': 'list'})),
]
This is ViewSet
class GetViewSet(viewsets.ModelViewSet):
MyApiObj = null
#property
def api_object(self):
return namedtuple("ApiObject", self.request.data.keys())(*self.request.data.values())
def get_serializer_class(self):
GeneralSerializer.Meta.model = apps.get_model(app_label=self.MyApiObj.app, model_name=self.MyApiObj.object)
return GeneralSerializer
def post(self, request):
self.MyApiObj = self.api_object
return self.select_api()
def select_api(self):
queryset = QueryHelper.select(self.MyApiObj)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Serializer
class GeneralSerializer(serializers.ModelSerializer):
class Meta:
model = None
fields = '__all__'
My post parameters to django
{
"app":"leads",
"object":"Teacher",
"settings":{
},
"data":{
}
}
answer:
[
{
"id": 1,
"name": "John",
"message": "Hi everyone",
"status": "e3b86ed4-8794-413b-994c-b1ec0a43eebe"
}
]
Problem is Dictionary(status) model give me id(uuid) but i need whole object without creating new serializer for Dictionary. i do univeral serializer for all models in my app
Try this:
class DictionarySerializer(serializers.ModelSerializer):
class Meta:
model = Dictionary
fields = '__all__'
class GeneralSerializer(serializers.ModelSerializer):
status = DictionarySerializer(required=True)
class Meta:
model = None
fields = '__all__'
But it is not good for me because 1) Without other serializer 2) I need Universal serializer for all models and with child model in all models of my project. Help me please)
I need something like this
[
{
"id": 1,
"status": {
"id": "e3b86ed4-8794-413b-994c-b1ec0a43eebe",
"parentId": "dc6cf7da-b82c-11e9-a2a3-2a2ae2dbcce4",
"name": "Spravochnik1",
"date_create": "2019-08-06T09:30:49.355439Z",
"date_end": "2019-08-06T09:29:57Z"
},
"name": "John",
"message": "Hi everyone"
}
]
for nested serialization check full ref here
and for your case add depth = 1
class GeneralSerializer(serializers.ModelSerializer):
status = DictionarySerializer(required=True)
class Meta:
model = None
fields = '__all__'
depth = 1

Django Rest Framework: writable nested serializer

I need to manage a JSON like this:
{
"name": "drink_type",
"description": "A type of drink",
"values": [
{
"value": "Coca-Cola",
"synonyms": [
"coca cola",
"coke"
]
},
{
"value": "Beer",
"synonyms": [
"beer"
]
}
]
}
models.py:
class Entity(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
class Value(models.Model):
entity = models.ForeignKey(Entity, related_name='values', on_delete=models.CASCADE)
value = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
class Synonymous(models.Model):
value = models.ForeignKey(Value, related_name='synonyms', on_delete=models.CASCADE)
synonymous = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
serializers.py:
class ValueSerializer(serializers.ModelSerializer):
synonyms = serializers.SlugRelatedField(
many=True,
slug_field='synonymous',
queryset=Synonymous.objects.all()
)
class Meta:
model = Value
fields = ('value', 'synonyms')
def create(self, validated_data):
synonyms_data = validated_data.pop('synonyms')
value = Value.objects.create(**validated_data)
for synonyomous_data in synonyms_data:
Synonymous.objects.create(value=value, **synonyomous_data)
return value
class EntitySerializer(serializers.ModelSerializer):
values = ValueSerializer(many=True)
class Meta:
model = Entity
fields = ('id', 'name', 'description', 'values')
def create(self, validated_data):
values_data = validated_data.pop('values')
entity = Entity.objects.create(**validated_data)
for value_data in values_data:
# How can I call the create method of values?
pass
return entity
views.py
#api_view(['GET', 'POST'])
def entity_list(request, format=None):
"""
List all entities, or create a new entity.
"""
if request.method == 'GET':
entities = Entity.objects.all()
serializer = EntitySerializer(entities, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = EntitySerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The serializers works fine when the GET view is called, but when I try to create a new Entity using the POST view, I'm not able to call the create method of ValueSerializer Class and the data created is like this:
{
"name": "drink_type",
"description": "A type of drink",
"values": []
}
Someone can help me?
Thank you!
class SynonymousSerializer(serializers.ModelSerializer):
class Meta:
model = Synonymous
fields = '__all__'
class ValueSerializer(serializers.ModelSerializer):
entity = serializers.PrimaryKeyRelatedField(queryset = Entity.objects.all())
synonyms = ValueSerializer(many=True)
class Meta:
model = Value
fields = ('id', 'synonyms')
class EntitySerializer(serializers.ModelSerializer):
values = ValueSerializer(many=True)
class Meta:
model = Entity
fields = ('id','values')
q = Entity.objects.all()
serializer = EntitySerializer(q)
print serializer.data