Django Reverse Foreign key with nested serializer gives multiple results - django

How to generate left outer join query using reverse foreign key relation and map it to the nested serializer? I want to filter result on multiple foreign keys.
models.py
class Question(models.Model):
question_name = models.CharField(max_length=1024)
class Paper(models.Model):
paper_name = models.CharField(max_length=128)
class Answer(models.Model):
score = models.IntegerField()
question_id = models.ForeignKey(Question, related_name='answer_questions')
paper_id = models.ForeignKey(Paper, related_name='answer_papers')
class Meta:
unique_together = ("question_id", "paper_id")
serializers.py
class PaperSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'paper_name')
model = Paper
class AnswerSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'score', 'question_id', 'paper_id')
model = Answer
class QuestionSerializer(serializers.ModelSerializer):
answer_questions = AnswerSerializer(many=True, allow_null=True)
class Meta:
fields = ('id', 'question_name', 'answer_questions')
model = Question
views.py
class QuestionList(generics.ListAPIView):
def get_queryset(self):
paper_id = self.kwargs['pk']
queryset = Question.objects.filter(answer_questions__paper_id=paper_id, answer_questions__question_id=F('id'))
return queryset
serializer_class = QuestionSerializer
My url is
localhost/papers/1/questions/
Expected output:
(1) List of all questions with answer object for corresponding question_id and paper_id only (single answer object) embedded in it.
(2) List should include all questions irrespective of they are answered or not (in case of question not answered, question with empty answer object should be returned) i.e. left outer join
[
{
"id": 1,
"question_id": 1,
"answer_questions": [
{
"id": 24,
"score": 5,
"question_id": 1,
"paper_id": 1
},
{
"id": 27,
"score": 8,
"question_id": 1,
"paper_id": 2
},
{
"id": 28,
"score": 6,
"question_id": 1,
"paper_id": 3
}
]
}
]
Current Output:
(1) I am getting multiple answer objects for particular question_id including all paper_ids. i.e. For question_id = 1 and paper_id = 2, My output shows questions with answer objects for question_id = 1 not filtering on paper_id.
(2) Only answered questions (as query is inner join query)
[
{
"id": 1,
"question_id": 1,
"answer_questions": [
{
"id": 24,
"score": 5,
"question_id": 1,
"paper_id": 1
}
]
}
]
If it's not a good way to implement, kindly suggest better way to optimize it.

I think you'll get desired output if you slightly change the get_quesyset() method.
class QuestionList(generics.ListAPIView):
def get_queryset(self):
return Question.objects.all()
serializer_class = QuestionSerializer
When you access the QuestionList list api you will get the output as follows
[
{
"id": 4,
"question_name": "qus-1",
"answer_questions": [
{
"id": 5,
"score": 50,
"question_id": 4,
"paper_id": 4
},
{
"id": 6,
"score": 10,
"question_id": 4,
"paper_id": 5
}
]
},
{
"id": 5,
"question_name": "qus-2",
"answer_questions": []
},
{
"id": 6,
"question_name": "que-3",
"answer_questions": [
{
"id": 7,
"score": 342,
"question_id": 6,
"paper_id": 4
}
]
}
]
Update-1
Change your serializer as below
class QuestionSerializer(serializers.ModelSerializer):
answer_questions = serializers.SerializerMethodField()
def get_answer_questions(self, question):
paper_id = self.context['view'].kwargs.get('paper_id')
return AnswerSerializer(question.answer_questions.filter(paper_id=paper_id), many=True).data
class Meta:
fields = ('id', 'question_name', 'answer_questions')
model = Question

Related

Create and update 3 tables OneToMany relationship with nested serializers in Django Rest Framework

I have 3 tables in my database : Quiz - question - answer, with this models
class Quiz(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
class Question(models.Model):
id = models.AutoField(primary_key=True)
question = models.CharField(max_length=255)
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name="questions")
class Answer(models.Model):
id = models.AutoField(primary_key=True)
answer = models.CharField(max_length=255)
is_correct = models.BooleanField()
question = models.ForeignKey(
Question, on_delete=models.CASCADE, related_name="answers"
)
i need to override create and update method for create or put/patch a quiz with questions and answers in one time with the default route.
i try différent things to do this.
My create methode is is operational
thise is the serializers of quizz questio nand answers :
class AnswerSerializer(ModelSerializer):
class Meta:
model = Answer
fields = (
"id",
"answer",
"is_correct",
)
class QuestionSerializer(ModelSerializer):
answers = AnswerSerializer(many=True, required=True)
class Meta:
model = Question
fields = ("id", "question", "answers")
class QuizSerializer(ModelSerializer):
questions = QuestionSerializer(many=True, required=True)
class Meta:
model = Quiz
fields = ("id", "name", "questions")
# create function for create a quiz with questions and answers
def create(self, validated_data: Dict[str, Any]) -> Quiz:
questions = validated_data.pop("questions")
quiz = Quiz.objects.create(**validated_data)
for question in questions:
answers = question.pop("answers")
question = Question.objects.create(
quiz=quiz, question=question.get("question")
)
for answer in answers:
Answer.objects.create(
question=question,
answer=answer.get("answer"),
is_correct=answer.get("is_correct"),
)
return quiz
my views :
class AnswerViewSet(ModelViewSet):
serializer_class = AnswerSerializer
def get_queryset(self) -> Answer:
return Answer.objects.all()
class QuestionViewSet(ModelViewSet):
serializer_class = QuestionSerializer
def get_queryset(self) -> Question:
return Question.objects.all()
class QuizViewSet(ModelViewSet):
serializer_class = QuizSerializer
def get_queryset(self) -> Quiz:
return Quiz.objects.all()
for the update this my last code with it i can update quiz table but not questions and answers
def update(self, instance, validated_data):
questions_data = validated_data.pop('questions')
for question_data in questions_data:
try:
question = instance.questions.get(id=question_data.get('id'))
answers_data = question_data.pop('answers')
question_serializer = QuestionSerializer(question,data=question_data)
question_serializer.is_valid(raise_exception=True)
question_serializer.save()
for answer_data in answers_data:
try:
answer = question.answers.get(id=answer_data.get('id'))
answer_serializer = AnswerSerializer(answer,data=answer_data)
answer_serializer.is_valid(raise_exception=True)
answer_serializer.save()
except Answer.DoesNotExist:
continue
except Question.DoesNotExist:
continue
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
this is the json for update :
{
"id": 6,
"name": "Quiz photoshop intermediaire",
"questions": [
{
"id": 1,
"question": "comment est composé un fichier image photoshop",
"quiz": 6,
"answers": [
{
"id": 1,
"answer": "de pizza 4 chaussures",
"is_correct": false,
"question": 1
},
{
"id": 2,
"answer": "d'un système de poulies",
"is_correct": false,
"question": 1
},
{
"id": 3,
"answer": "de calques",
"is_correct": true,
"question": 1
}
]
},
{
"id": 2,
"question": "quelles sont les modes de couleurs indesign",
"quiz": 6,
"answers": [
{
"id": 4,
"answer": "c'est un tquiz photoshop boulet !",
"is_correct": true,
"question": 2
},
{
"id": 5,
"answer": "CMJN",
"is_correct": true,
"question": 2
},
{
"id": 6,
"answer": "nono le petit robot",
"is_correct": false,
"question": 2
},
{
"id": 7,
"answer": "bombe de peinture",
"is_correct": false,
"question": 2
}
]
}
]
}
thanks for help :)

Django: Creating endpoint for fetching subcategories within a category

I am new to Django and wanted to create an endpoint that allows me to push all the existing subcategories within a category. I have seen methods where one can fetch specific data points within a given category but I am confused about how to create such custom endpoints.
Here's my Serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'categoryType')
class SubCategorySerializer(serializers.ModelSerializer):
categoryTitle = serializers.CharField(source="categoryType.categoryType", read_only=True)
class Meta:
model = SubCategory
fields = ('id', 'categoryTitle', 'subcategoryType')
#fields = '__all__'
Here's my model:
class Category(models.Model):
categoryType = models.CharField(max_length=100,blank = False, unique = True, null = False)
def __str__(self):
return self.categoryType
class SubCategory(models.Model):
subcategoryType = models.CharField(max_length=100)
categoryType = models.ForeignKey(Category,on_delete=models.CASCADE, null = True, related_name='category_type')
def __str__(self):
return f'{self.categoryType} :: {self.subcategoryType}'
When I hit subcategory endpoint, I get the data in following format:
[
{
"id": 2,
"categoryTitle": "Electronics",
"subcategoryType": "Mobile"
},
{
"id": 3,
"categoryTitle": "Electronics",
"subcategoryType": "Tablet"
},
{
"id": 4,
"categoryTitle": "Electronics",
"subcategoryType": "HardDisk"
},
{
"id": 5,
"categoryTitle": "Electronics",
"subcategoryType": "Laptop"
},
{
"id": 6,
"categoryTitle": "Sports",
"subcategoryType": "Tennis"
},
{
"id": 7,
"categoryTitle": "Sports",
"subcategoryType": "Cricket"
},
{
"id": 8,
"categoryTitle": "Sports",
"subcategoryType": "Football"
},
{
"id": 9,
"categoryTitle": "Sports",
"subcategoryType": "BasketBall"
}
]
but what I am interested in is getting the data of all the subcategories within a category passing category id in the endpoint:
something like this:
GET http://localhost:8000/categories/<categoryId> HTTP/1.1
which should return the data in the following way: (Since id 1 belongs to electronics, it should return all the subcategories that fall under electronics category)
[
{
"id": 2,
"subcategoryType": "Mobile"
},
{
"id": 3,
"subcategoryType": "Tablet"
},
{
"id": 4,
"subcategoryType": "HardDisk"
}
]
You need to add a nested serializer as below:
class CategorySerializer(serializers.ModelSerializer):
subCategories = SubCategorySerializer(source='category_type', many=True)
class Meta:
model = Category
fields = ('id', 'categoryType', 'subCategories')
Please note, you need to mention the related name in source, and have to add many=True, as one category can have many subcategories.
Use subcategory_set field.
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'categoryType', 'subcategory_set')
This will give you one more field in the output, that is subcategory_set. This will be a list, containing all the subcategory ID.
If you want complete data, use depth option
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'categoryType', 'subcategory_set')
depth = 1
Reference : https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization

Django Count with filter

iam trying to count with filter and it doesn't show any error but there is no count showing
I have tried all what I know and search every where didn't find any solution :
class UsersAnswersSerializer(serializers.ModelSerializer):
Answers = serializers.SerializerMethodField('get_answers')
def get_answers(self, obj):
queryset = AnswersModel.objects.filter(Question=obj.Answer.Question.id)\
.annotate(answersCount=Count('UsersAnswer', distinct=True))
serializer = Answersserializer(instance=queryset, many=True)
return serializer.data
class Meta:
model = UsersAnswerModel
fields = ['Answer', 'APNSDevice' ,'RegistrationID','Answers']
and this is my models :
class AnswersModel(models.Model):
Question = models.ForeignKey(QuestionsModel, related_name='QuestionAnswer', on_delete=models.CASCADE)
Answer = models.CharField(max_length=200)
class UsersAnswerModel(models.Model):
Answer = models.ForeignKey(AnswersModel, related_name='UsersAnswer', on_delete=models.CASCADE)
RegistrationID = models.CharField(max_length=200)
APNSDevice = models.CharField(max_length=200,default='no name')
class QuestionsModel(models.Model):
created = models.DateTimeField(auto_now_add=True)
Question = models.CharField(max_length=200)
what is get is
{
"Answer": 12,
"APNSDevice": "byname",
"RegistrationID": "asdasdasdasdasdasdasdasdsa",
"Answers": [
{
"id": 10,
"Question": 4,
"Answer": "Answer 1",
"UsersAnswer": [
"gfgdgdfgdf",
"c748dfd8aa7dd73a6c1ef17676aa7667161ff7e0f8e2ef21ef17e964a26150e4"
]
},
{
"id": 11,
"Question": 4,
"Answer": "Answer 2",
"UsersAnswer": [
"sfgdfgdf",
"c748dfd8aa7dd73a6c1ef17676aa7667161ff7e0f8e2ef21ef17e964a26150e4",
"c748dfd8aa7dd73a6c1ef17676aa7667161ff7e0f8e2ef21ef17e964a26150e4",
"c748dfd8aa7dd73a6c1ef17676aa7667161ff7e0f8e2ef21ef17e964a26150e4"
]
}
I need to make "UsersAnswer" show a count of its array
I think what you want to do is aggregate and count.
Replace annotate with aggregate

Reformat Django REST Framework Serializer to get Output

I am trying to get data in a particular format but i'm not able to get the desired output.
My Model:
class Category(models.Model):
name = models.CharField(max_length=40)
class Expense(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category_name")
description = models.CharField(max_length=200)
total_amount = models.IntegerField()
class Expense_Details(models.Model):
expense = models.ForeignKey(Expense, on_delete=models.CASCADE, related_name="payment")
user = models.IntegerField()
amount = models.FloatField()
type = models.CharField(max_length=100) ---->type is owe or lend
When I request /api/expenses/:
Expected Output
{
“total_expenses”: 10,
“Expenses”:
[{
“id”: 1,
“category”: 1,
“created_by”: 1, ------> user logged id
“description”: “lunch”,
“total_amount”: “105”,
“owe”: [{
“user_id”: 1,
“amount”: 10
},
{
“user_id”: 2,
“amount”: 95
}],
“lend”: [{
“user_id”: 3,
“amount”: 10
},
{
“user_id”: 4,
“amount”: 95
}],
}, ...
]
}
My output:
{
"results": [
{
"id": 1,
"category": 1,
"description": "lunch at the burj al arab",
"total_amount": 105,
"payment": [
{
"user": 1,
"amount": -10
},
{
"user": 2,
"amount": -95
},
{
"user": 3,
"amount": 10
},
{
"user": 4,
"amount": 95
}
]
}
]
}
My Serializer:
class ExpenseDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = Expense_Details
fields = ['user', 'amount']
class ExpenseSerializer(serializers.ModelSerializer):
payment = serializers.SerializerMethodField()
def get_payment(self, obj):
return ExpenseDetailsSerializer(obj.payment.all(), many=True).data
class Meta:
model = Expense
fields = ['id', 'category', 'description', 'total_amount', 'payment',]
What Query should I use to get Output in the above format? How will my serializer look like? How can I separate own and lend? Also I have stored own and lend with + and - sign to differentiate between them.
Use a ListField for the same.
Documentation: https://www.django-rest-framework.org/api-guide/fields/#listfield
Also refer How to serialize an 'object list' in Django REST Framework
Here you can try something like:
class ExpenseSerializer(serializers.Serializer):
payment = serializers.ListField(child=ExpenseDetailsSerializer())
def get_payment(self, obj):
return ExpenseDetailsSerializer(obj.payment.all(), many=True).data
class Meta:
model = Expense
fields = ['id', 'category', 'description', 'total_amount', 'payment',]

MethodField containing Serialized Object does not return proper Object's MethodField results

I have two serializers like this.
class CourseSerializer(serializers.ModelSerializer):
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(required=True, max_length=30, allow_blank=False)
chapters = serializers.SerializerMethodField()
def get_chapters(self, obj):
queryset = Chapter.objects.all().filter(course=obj.pk)
serializer_class = ChapterSerializer(queryset, many=True)
return serializer_class.data
class ChapterSerializer(serializers.ModelSerializer):
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(required=True, max_length=30, allow_blank=False)
course = serializers.StringRelatedField()
cards_count = serializers.SerializerMethodField()
cards_learned_count = serializers.SerializerMethodField()
cards_still_to_go = serializers.SerializerMethodField()
cards_not_learned = serializers.SerializerMethodField()
def get_cards_learned_count(self, obj):
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
queryset = Card.objects.all().filter(chapter=obj.pk)
card_count = 0
for q in queryset:
card_detail = UserCardDetail.objects.all().filter(card=q, user=user, learned=True)
card_detail.count()
card_count += card_detail.count()
return card_count
Now when I get the course serializer instead of getting the proper values in the chapter field, I get zeroes, but when I call the chapter serializer I get the right values.
I noticed that the MethodField works with obj - however shouldn't that be passed already?
Is there something extra I have to pass to the chapters field?
Edit: Output and Expected Output
OUTPUT
{
"name": "Example 1",
"pk": 32,
"locked": false,
"chapters": [
{
"pk": 15,
"name": "Transformatori",
"course": "Tesit",
"cards_count": 5,
"cards_learned_count": 0,
"cards_still_to_go": 0,
"cards_not_learned": 0,
"favorite_cards": 0
},
],
},
expected output
{
"name": "Example 1",
"pk": 32,
"locked": false,
"chapters": [
{
"pk": 15,
"name": "Transformatori",
"course": "Tesit",
"cards_count": 5,
"cards_learned_count": 3,
"cards_still_to_go": 0,
"cards_not_learned": 2,
"favorite_cards": 1
},
],
},