diff --git a/main/migrations/0024_likepost.py b/main/migrations/0024_likepost.py new file mode 100644 index 0000000..09f8940 --- /dev/null +++ b/main/migrations/0024_likepost.py @@ -0,0 +1,27 @@ +# Generated by Django 3.1.2 on 2024-08-22 14:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('main', '0023_auto_20240818_1418'), + ] + + operations = [ + migrations.CreateModel( + name='LikePost', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.profile')), + ], + options={ + 'unique_together': {('user', 'content_type', 'object_id')}, + }, + ), + ] diff --git a/main/models.py b/main/models.py index 27aa66f..ccfab7f 100644 --- a/main/models.py +++ b/main/models.py @@ -3,6 +3,8 @@ from django.db import models from django.db.models.deletion import CASCADE from django.db.models import UniqueConstraint +from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes.fields import GenericForeignKey import boto3 import environ @@ -243,4 +245,19 @@ def get_attachment_presigned_url(attachment, expires_in=expires_in): }, ExpiresIn=expires_in ) - return url \ No newline at end of file + return url + +class LikePost(models.Model): + """ + This class That a user like a Post (Could be Question/Answer) + """ + user = models.ForeignKey(Profile, on_delete=models.CASCADE) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey('content_type', 'object_id') + + class Meta: + unique_together = ('user', 'content_type', 'object_id') + + def __str__(self): + return f'{self.user.username} liked {self.content_object}: {self.content_type} {self.object_id}' \ No newline at end of file diff --git a/main/views_tanyateman.py b/main/views_tanyateman.py index 563667e..64403c6 100644 --- a/main/views_tanyateman.py +++ b/main/views_tanyateman.py @@ -11,7 +11,8 @@ from main.views_calculator import score_component from .serializers import AddQuestionSerializer, CalculatorSerializer, QuestionSerializer, ScoreComponentSerializer, TanyaTemanProfileSerializer, UserCumulativeGPASerializer, UserGPASerializer, CourseForSemesterSerializer, SemesterWithCourseSerializer, HideVerificationQuestionSerializer, AnswerQuestionSerializer, AnswerSerializer from .utils import get_recommended_score, get_score, response, response_paged, update_course_score, validate_body, check_notexist_and_create_user_cumulative_gpa, validate_body_minimum, add_semester_gpa, delete_semester_gpa, update_semester_gpa, update_cumulative_gpa, get_fasilkom_courses, add_course_to_semester, validate_params, delete_course_to_semester, get_paged_questions -from .models import Calculator, Course, Profile, Question, Answer +from .models import Calculator, Course, LikePost, Profile, Question, Answer +from django.contrib.contenttypes.models import ContentType from django.db.models import Q import boto3 import environ @@ -71,8 +72,41 @@ def tanya_teman(request): if not id.isnumeric(): return response(error="id should be a number", status=status.HTTP_400_BAD_REQUEST) return tanya_teman_with_id(request, id) + + if request.method == 'PUT': + is_like = request.query_params.get("is_like") + if is_like is None: + return response(error="The only allowed PUT request for /tanya-teman is to like/unlike a Question.", status=status.HTTP_400_BAD_REQUEST) + + id = request.query_params.get('id') + if id is None: + return response(error="id is required", status=status.HTTP_400_BAD_REQUEST) + if not id.isnumeric(): + return response(error="id should be a number", status=status.HTTP_400_BAD_REQUEST) + + question = Question.objects.filter(pk=id).first() + if question is None: + return response(error="No matching question", status=status.HTTP_404_NOT_FOUND) + + content_type = ContentType.objects.get_for_model(Question) + question_like = LikePost.objects.filter(content_type=content_type, object_id=id, user=user).first() + if question_like is None: + LikePost.objects.create( + user=user, + content_type=content_type, + object_id=id + ) + question.like_count += 1 + question.save() + else: + question_like.delete() + question.like_count -= 1 + question.save() + + return response(status=status.HTTP_200_OK) -@api_view(['GET', 'POST']) + +@api_view(['GET', 'POST', 'PUT']) def jawab_teman(request): user = Profile.objects.get(username=str(request.user)) @@ -125,6 +159,39 @@ def jawab_teman(request): Q(question=question), Q(verification_status=Answer.VerificationStatus.APPROVED) | Q(user=user)) return jawab_teman_paged(request, all_replies) + + if request.method == 'PUT': + is_like = request.query_params.get("is_like") + if is_like is None: + return response(error="The only allowed PUT request for /jawab-teman is to like/unlike an Answer.", status=status.HTTP_400_BAD_REQUEST) + + id = request.query_params.get('id') + if id is None: + return response(error="id is required", status=status.HTTP_400_BAD_REQUEST) + if not id.isnumeric(): + return response(error="id should be a number", status=status.HTTP_400_BAD_REQUEST) + + answer = Answer.objects.filter(pk=id).first() + if answer is None: + return response(error="No matching answer", status=status.HTTP_404_NOT_FOUND) + + content_type = ContentType.objects.get_for_model(Answer) + answer_like = LikePost.objects.filter(content_type=content_type, object_id=id, user=user).first() + if answer_like is None: + LikePost.objects.create( + user=user, + content_type=content_type, + object_id=id + ) + answer.like_count += 1 + answer.save() + else: + answer_like.delete() + answer.like_count -= 1 + answer.save() + + return response(status=status.HTTP_200_OK) + def tanya_teman_with_id(request, id): user = Profile.objects.get(username=str(request.user)) @@ -146,24 +213,39 @@ def tanya_teman_with_id(request, id): def tanya_teman_paged(request, questions, is_history): page = request.query_params.get('page') + user = Profile.objects.get(username=str(request.user)) if page is None: return response(error='page is required', status=status.HTTP_400_BAD_REQUEST) questions, total_page = get_paged_questions(questions, page) + + list_questions = None + if is_history: + list_questions = QuestionSerializer(questions, many=True).data + else: + list_questions = HideVerificationQuestionSerializer(questions, many=True).data + list_questions = add_is_liked(user, list_questions, is_question=True) + return response_paged(data={ - 'questions': QuestionSerializer(questions, many=True).data - if is_history else - HideVerificationQuestionSerializer(questions, many=True).data + 'questions': list_questions }, total_page=total_page) def jawab_teman_paged(request, answers): page = request.query_params.get('page') + user = Profile.objects.get(username=str(request.user)) + question = answers[0].question if len(answers) > 0 else None + like_count = question.like_count if question != None else 0 + reply_count = question.reply_count if question != None else 0 if page is None: return response(error='page is required', status=status.HTTP_400_BAD_REQUEST) answers, total_page = get_paged_questions(answers, page) + list_answers = AnswerSerializer(answers, many=True).data + list_answers = add_is_liked(user, list_answers, is_question=False) return response_paged(data={ - 'answers': AnswerSerializer(answers, many=True).data + 'answers': list_answers, + 'like_count': like_count, + 'reply_count': reply_count }, total_page=total_page) @@ -190,12 +272,12 @@ def filtered_question(request): questions = questions.filter(Q(question_text__icontains=keyword)) if is_paling_banyak_disukai: - return questions.order_by('like_count') + return questions.order_by('-like_count') if is_terverifikasi: - return questions.filter(verification_status=Question.VerificationStatus.APPROVED).order_by('created_at') + return questions.filter(verification_status=Question.VerificationStatus.APPROVED).order_by('-created_at') if is_menunggu_verifikasi: - return questions.filter(verification_status=Question.VerificationStatus.WAITING).order_by('created_at') - return questions.order_by('created_at') + return questions.filter(verification_status=Question.VerificationStatus.WAITING).order_by('-created_at') + return questions.order_by('-created_at') ''' upload_attachment returns ('error', response) if the attachment file is not valid @@ -232,4 +314,16 @@ def upload_attachment(attachment_file): s3.upload_fileobj(attachment_file, bucket_name, key) return "success", key except Exception as e: - return "error", response(error=str(e), status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return "error", response(error=str(e), status=status.HTTP_400_BAD_REQUEST) + +def add_is_liked(user, posts, is_question): + list_posts = [] + if is_question: + content_type = ContentType.objects.get_for_model(Question) + else: + content_type = ContentType.objects.get_for_model(Answer) + for post in posts: + post_like = LikePost.objects.filter(content_type=content_type, object_id=post['id'], user=user).first() + post['liked_by_user'] = 1 if post_like != None else 0 + list_posts.append(post) + return list_posts \ No newline at end of file