From 2ed45c29bf9655d17f7d6770090c0907a38d3594 Mon Sep 17 00:00:00 2001 From: MucahitErdoganUnlu Date: Sun, 17 Nov 2024 23:12:29 +0300 Subject: [PATCH] feat(be): connect upvote, downvote, bookmark to forum question fields. update tests accordingly --- ...r_forumdownvote_forum_question_and_more.py | 29 +++++++++++ backend/core/models.py | 6 +-- backend/core/serializers/serializers.py | 32 +++++++++--- backend/core/tests/test_forum_bookmark.py | 10 +++- .../core/tests/test_forum_upvote_downvote.py | 33 ++++++++++--- backend/core/tests/test_take_quiz.py | 49 ++++++++++++++++++- 6 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 backend/core/migrations/0006_alter_forumdownvote_forum_question_and_more.py diff --git a/backend/core/migrations/0006_alter_forumdownvote_forum_question_and_more.py b/backend/core/migrations/0006_alter_forumdownvote_forum_question_and_more.py new file mode 100644 index 00000000..68cbde4e --- /dev/null +++ b/backend/core/migrations/0006_alter_forumdownvote_forum_question_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.1.2 on 2024-11-17 20:02 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_rename_date_forumanswer_created_at'), + ] + + operations = [ + migrations.AlterField( + model_name='forumdownvote', + name='forum_question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='downvotes', to='core.forumquestion'), + ), + migrations.AlterField( + model_name='forumupvote', + name='forum_question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='upvotes', to='core.forumquestion'), + ), + migrations.AlterField( + model_name='takequiz', + name='quiz', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='takes', to='core.quiz'), + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index 82b577b3..1ca85e6b 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -69,7 +69,7 @@ def __str__(self): return self.choice_text class TakeQuiz(models.Model): - quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE) + quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='takes') user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) date = models.DateTimeField(auto_now_add=True) @@ -122,7 +122,7 @@ def __str__(self): class ForumUpvote(models.Model): user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) - forum_question = models.ForeignKey(ForumQuestion, on_delete=models.CASCADE) + forum_question = models.ForeignKey(ForumQuestion, on_delete=models.CASCADE, related_name='upvotes') created_at = models.DateTimeField(auto_now_add=True) class Meta: @@ -140,7 +140,7 @@ def __str__(self): class ForumDownvote(models.Model): user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) - forum_question = models.ForeignKey(ForumQuestion, on_delete=models.CASCADE) + forum_question = models.ForeignKey(ForumQuestion, on_delete=models.CASCADE, related_name='downvotes') created_at = models.DateTimeField(auto_now_add=True) class Meta: diff --git a/backend/core/serializers/serializers.py b/backend/core/serializers/serializers.py index f015f096..47567875 100644 --- a/backend/core/serializers/serializers.py +++ b/backend/core/serializers/serializers.py @@ -3,7 +3,9 @@ from rest_framework import serializers from ..models import (CustomUser, ForumQuestion, Quiz, QuizQuestion, QuizQuestionChoice, RateQuiz, - Tag, ForumBookmark, ForumAnswer) + Tag, ForumBookmark, ForumAnswer, ForumUpvote, ForumDownvote, TakeQuiz) +from .forum_vote_serializer import ForumUpvoteSerializer, ForumDownvoteSerializer +from .take_quiz_serializer import TakeQuizSerializer User = get_user_model() queryset = User.objects.all() @@ -90,16 +92,22 @@ def get_is_bookmarked(self, obj): return ForumBookmark.objects.filter(user=user, forum_question=obj).exists() def get_is_upvoted(self, obj): - return Faker().boolean() + user = self.context['request'].user + if not user.is_authenticated: + return False + return ForumUpvote.objects.filter(user=user, forum_question=obj).exists() def get_upvotes_count(self, obj): - return Faker().random_int(min=0, max=100) + return obj.upvotes.count() def get_is_downvoted(self, obj): - return Faker().boolean() + user = self.context['request'].user + if not user.is_authenticated: + return False + return ForumDownvote.objects.filter(user=user, forum_question=obj).exists() def get_downvotes_count(self, obj): - return Faker().random_int(min=0, max=100) + return obj.downvotes.count() def create(self, validated_data): # Extract tags from validated_data @@ -171,9 +179,8 @@ class QuizSerializer(serializers.ModelSerializer): author = UserInfoSerializer(read_only=True) # Assuming UserInfoSerializer is defined tags = TagSerializer(many=True) # Assuming TagSerializer is defined rating = serializers.SerializerMethodField() - - num_taken = serializers.IntegerField(default=0, read_only=True) - is_taken = serializers.BooleanField(default=False, read_only=True) + is_taken = serializers.SerializerMethodField() + num_taken = serializers.SerializerMethodField() class Meta: @@ -184,6 +191,15 @@ class Meta: ) read_only_fields = ("difficulty", 'created_at', 'num_taken', 'is_taken', 'rating', "author") + def get_is_taken(self, obj): + user = self.context['request'].user + if not user.is_authenticated: + return False + return TakeQuiz.objects.filter(quiz=obj, user=user).exists() + + def get_num_taken(self, obj): + return obj.takes.count() + def get_rating(self, obj): from django.db.models import Avg, Count diff --git a/backend/core/tests/test_forum_bookmark.py b/backend/core/tests/test_forum_bookmark.py index 982a1991..197a2659 100644 --- a/backend/core/tests/test_forum_bookmark.py +++ b/backend/core/tests/test_forum_bookmark.py @@ -43,7 +43,8 @@ def test_create_forum_bookmark(self): """Test creating a forum bookmark""" # Delete the existing bookmark to test creation self.forum_bookmark.delete() - + response = self.client.get(reverse('forum-question-detail', args=[self.forum_question.id]), format='json') + self.assertFalse(response.data['is_bookmarked']) # Send POST request to create a new bookmark response = self.client.post(reverse('forumbookmark-list'), self.data, format='json') @@ -51,14 +52,19 @@ def test_create_forum_bookmark(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertTrue(ForumBookmark.objects.filter(user=self.user, forum_question=self.forum_question).exists()) + response = self.client.get(reverse('forum-question-detail', args=[self.forum_question.id]), format='json') + self.assertTrue(response.data['is_bookmarked']) + + def test_delete_forum_bookmark(self): """Test deleting a forum bookmark""" # Send DELETE request to remove the bookmark + self.assertTrue(self.client.get(reverse('forum-question-detail', args=[self.forum_question.id]), format='json').data['is_bookmarked']) response = self.client.delete(reverse('forumbookmark-detail', args=[self.forum_bookmark.id])) - # Assertions self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(ForumBookmark.objects.filter(id=self.forum_bookmark.id).exists()) + self.assertFalse(self.client.get(reverse('forum-question-detail', args=[self.forum_question.id]), format='json').data['is_bookmarked']) def test_cannot_bookmark_same_forum_question_twice(self): """Test that the same forum question cannot be bookmarked twice""" diff --git a/backend/core/tests/test_forum_upvote_downvote.py b/backend/core/tests/test_forum_upvote_downvote.py index c1e5578c..6ba1573f 100644 --- a/backend/core/tests/test_forum_upvote_downvote.py +++ b/backend/core/tests/test_forum_upvote_downvote.py @@ -26,31 +26,46 @@ def setUp(self): self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {str(refresh.access_token)}') # Create a ForumQuestion - self.forum_question = ForumQuestion.objects.create( - title="Test Forum Question", - question="This is a test question for votes.", - author=self.user - ) - + # self.forum_question = ForumQuestion.objects.create( + # title="Test Forum Question", + # question="This is a test question for votes.", + # author=self.user + # ) + self.forum_question_response = self.client.post(reverse('forum-question-list'), { + "title": "Test Forum Question", + "question": "This is a test question for votes.", + "tags": [ + {"name": "Django", "linked_data_id": "123", "description": "A web framework."}, + {"name": "DRF", "linked_data_id": "456", "description": "Django Rest Framework."} + ] + }, format='json').data + + + self.forum_question = ForumQuestion.objects.get(title='Test Forum Question') # Vote data self.data = {"forum_question": self.forum_question.id} def test_create_forum_upvote(self): + question_response = self.client.get(reverse("forum-question-detail", args=[self.forum_question.id])) + upvotes_count = question_response.data['upvotes_count'] """Test creating a forum vote""" # Send POST request to create a new vote response = self.client.post(reverse('forum-upvote-list'), self.data, format='json') - # Assertions self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertTrue(ForumUpvote.objects.filter(user=self.user, forum_question=self.forum_question).exists()) self.assertIn("id", response.data) self.assertIn("user", response.data) self.assertIn("forum_question", response.data) + response = self.client.get(reverse("forum-question-detail", args=[self.forum_question.id])) + self.assertEqual(upvotes_count + 1, response.data['upvotes_count']) def test_delete_forum_upvote(self): """Test deleting a forum vote""" # Create a vote to delete self.client.post(reverse('forum-upvote-list'), self.data, format='json') + response = self.client.get(reverse("forum-question-detail", args=[self.forum_question.id])) + upvotes_count = response.data['upvotes_count'] forum_vote = ForumUpvote.objects.get(user=self.user, forum_question=self.forum_question) # Send DELETE request to remove the vote @@ -60,6 +75,10 @@ def test_delete_forum_upvote(self): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(ForumUpvote.objects.filter(id=forum_vote.id).exists()) + response = self.client.get(reverse("forum-question-detail", args=[self.forum_question.id])) + self.assertEqual(response.data['upvotes_count'], upvotes_count - 1) + + def test_cannot_upvote_same_forum_question_twice(self): """Test that the same forum question cannot be voted twice""" # Create the first vote diff --git a/backend/core/tests/test_take_quiz.py b/backend/core/tests/test_take_quiz.py index d01d6984..670572ed 100644 --- a/backend/core/tests/test_take_quiz.py +++ b/backend/core/tests/test_take_quiz.py @@ -23,6 +23,11 @@ def test_take_quiz(self): } ] } + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 0) + response = self.client.post(reverse('take-quiz-list'), data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data["user"], self.user.id) @@ -31,8 +36,16 @@ def test_take_quiz(self): self.assertIn("date", response.data) self.assertEqual(response.data["answers"][0]["question"], data["answers"][0]["question"]) self.assertEqual(response.data["answers"][0]["answer"], data["answers"][0]["answer"]) + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertTrue(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 1) def test_take_quiz_already_taken(self): + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 0) data = { "quiz": self.quiz.id, @@ -44,9 +57,18 @@ def test_take_quiz_already_taken(self): ] } self.client.post(reverse('take-quiz-list'), data, format='json') + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertTrue(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 1) + response = self.client.post(reverse('take-quiz-list'), data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data[0], "You have already taken this quiz.") + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertTrue(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 1) def test_take_quiz_invalid_answer(self): @@ -101,6 +123,9 @@ def test_take_quiz_invalid_answer(self): str(response.data[0]), "['The answer must belong to the same question.']" ) + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(response.data["is_taken"]) def test_take_quiz_invalid_question(self): @@ -155,8 +180,19 @@ def test_take_quiz_invalid_question(self): str(response.data[0]), "['The question must belong to the same quiz.']" ) + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 0) + def test_take_quiz_delete(self): + + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 0) + data = { "quiz": self.quiz.id, "answers": [ @@ -167,9 +203,20 @@ def test_take_quiz_delete(self): ] } response = self.client.post(reverse('take-quiz-list'), data, format='json') + response_id = response.data['id'] self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.delete(reverse('take-quiz-detail', kwargs={'pk': response.data['id']})) + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertTrue(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 1) + + response = self.client.delete(reverse('take-quiz-detail', kwargs={'pk': response_id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + response = self.client.get(reverse('quiz-detail', kwargs={'pk': self.quiz.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(response.data["is_taken"]) + self.assertEqual(response.data["num_taken"], 0) + def test_take_quiz_patch(self): data = {