From db2ed274e10a2e09df2b8cf23be376b828956116 Mon Sep 17 00:00:00 2001 From: Bunga Amalia Date: Sat, 10 Oct 2020 08:25:53 +0700 Subject: [PATCH 1/2] [RED] Test like and dislike on material's comments --- app/tests.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/app/tests.py b/app/tests.py index e9bbc71..0fd5c7a 100644 --- a/app/tests.py +++ b/app/tests.py @@ -26,9 +26,11 @@ from digipus.settings import TIME_ZONE from .models import ( Category, Comment, + DislikeComment, DownloadStatistics, Materi, Like, + LikeComment, Rating, ReqMaterial, RatingContributor, @@ -322,6 +324,96 @@ class DetailMateriTest(TestCase): response = self.client.get(url) self.assertContains(response, "Anonymous") + def test_comment_disliked_by_anonymous(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get(comment="This is new comment by Anonymous").id + response = self.client.get(url_materi) + session_id = response.context["session_id"] + + payload = {"comment": comment, "session_id": session_id} + ajax_response = self.client.post("/comment/dislike/", payload) + num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment).count() + self.assertEqual(num_of_comment_dislikes, 0) + + def test_comment_liked_by_anonymous(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get(comment="This is new comment by Anonymous").id + response = self.client.get(url_materi) + session_id = response.context["session_id"] + + payload = {"comment": comment, "session_id": session_id} + ajax_response = self.client.post("/comment/like/", payload) + num_of_comment_likes = LikeComment.objects.filter(comment=comment).count() + self.assertEqual(num_of_comment_likes, 0) + + def test_comment_undisliked_by_anonymous(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get(comment="This is new comment by Anonymous") + response = self.client.get(url_materi) + session_id = response.context["session_id"] + + payload = {"comment": comment, "session_id": session_id} + ajax_response = self.client.post("/comment/dislike/", payload) + + ajax_response = self.client.post("/comment/dislike/", payload) + num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment, session_id=session_id).count() + self.assertEqual(num_of_comment_dislikes, 0) + + def test_comment_unliked_by_anonymous(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get(comment="This is new comment by Anonymous") + response = self.client.get(url_materi) + session_id = response.context["session_id"] + + payload = {"comment": comment, "session_id": session_id} + ajax_response = self.client.post("/comment/like/", payload) + + ajax_response = self.client.post("/comment/like/", payload) + num_of_comment_likes = LikeComment.objects.filter(comment=comment, session_id=session_id).count() + self.assertEqual(num_of_comment_likes, 0) + + def test_comment_new_does_not_have_dislike(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + response = self.client.get(url_materi) + session_id = response.context["session_id"] + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + + comment = Comment.objects.get(comment="This is new comment by Anonymous") + comment_dislike_counter = DislikeComment.objects.filter(comment=comment, session_id=session_id).count() + self.assertEqual(comment_dislike_counter, 0) + + def test_comment_new_does_not_have_like(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + response = self.client.get(url_materi) + session_id = response.context["session_id"] + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + + comment = Comment.objects.get(comment="This is new comment by Anonymous") + comment_like_counter = LikeComment.objects.filter(comment=comment, session_id=session_id).count() + self.assertEqual(comment_like_counter, 0) + def test_detail_materi_contains_form_comment(self): response = self.client.get(self.url) self.assertContains(response, "Beri komentar...") -- GitLab From d5312c54f20ebf07cdcc62eac916b1cfba3af9a8 Mon Sep 17 00:00:00 2001 From: Bunga Amalia Date: Sat, 10 Oct 2020 08:32:33 +0700 Subject: [PATCH 2/2] [GREEN] Implement like and dislike on material's comments --- .../0021_dislikecomment_likecomment.py | 33 ++++++ app/models.py | 22 ++++ app/templates/app/detail_materi.html | 100 +++++++++++++++++- app/urls.py | 2 + app/views.py | 44 ++++++++ 5 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 app/migrations/0021_dislikecomment_likecomment.py diff --git a/app/migrations/0021_dislikecomment_likecomment.py b/app/migrations/0021_dislikecomment_likecomment.py new file mode 100644 index 0000000..1ce9111 --- /dev/null +++ b/app/migrations/0021_dislikecomment_likecomment.py @@ -0,0 +1,33 @@ +# Generated by Django 3.1 on 2020-10-09 16:19 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0020_merge_20201009_2039'), + ] + + operations = [ + migrations.CreateModel( + name='LikeComment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), + ('session_id', models.CharField(max_length=32)), + ('comment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.comment')), + ], + ), + migrations.CreateModel( + name='DislikeComment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), + ('session_id', models.CharField(max_length=32)), + ('comment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.comment')), + ], + ), + ] diff --git a/app/models.py b/app/models.py index aa54208..7edbd13 100644 --- a/app/models.py +++ b/app/models.py @@ -108,6 +108,28 @@ class Comment(models.Model): def __str__(self): return self.username + @property + def like_count(self): + count = LikeComment.objects.filter(comment=self).count() + return count + + @property + def dislike_count(self): + count = DislikeComment.objects.filter(comment=self).count() + return count + + +class LikeComment(models.Model): + comment = models.ForeignKey(Comment, models.SET_NULL, null=True) + timestamp = models.DateTimeField(default=timezone.now) + session_id = models.CharField(max_length=32, blank=False) + + +class DislikeComment(models.Model): + comment = models.ForeignKey(Comment, models.SET_NULL, null=True) + timestamp = models.DateTimeField(default=timezone.now) + session_id = models.CharField(max_length=32, blank=False) + class Like(models.Model): materi = models.ForeignKey(Materi, models.SET_NULL, null=True) diff --git a/app/templates/app/detail_materi.html b/app/templates/app/detail_materi.html index e15f5f9..c68a885 100644 --- a/app/templates/app/detail_materi.html +++ b/app/templates/app/detail_materi.html @@ -284,11 +284,35 @@ {% else %} {% endif %} -

{{comment.user.name}}

-

-

- {{ comment.timestamp|naturaltime }} -

+
+

{{comment.user.name}}

+

+

+ {{ comment.timestamp|naturaltime }} +

+
+ +
+
{% if user.is_admin %} @@ -422,6 +446,46 @@ }); }); + function postLikeComment(comment_id) { + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + $.ajax({ + type: 'POST', + url: "{% url 'comment-like-toggle' %}", + data: { + 'comment_id': comment_id, + 'session_id': "{{ session_id }}" + }, + success: likeComment, + dataType: 'html' + }); + } + + function postDislikeComment(comment_id) { + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + $.ajax({ + type: 'POST', + url: "{% url 'comment-dislike-toggle' %}", + data: { + 'comment_id': comment_id, + 'session_id': "{{ session_id }}" + }, + success: dislikeComment, + dataType: 'html' + }); + } + function postAddRating(rating_score) { $.ajaxSetup({ beforeSend: function (xhr, settings) { @@ -459,6 +523,32 @@ document.getElementById("thumb").firstChild.data = " Disukai" } } + + function likeComment(data, jqXHR) { + var data = $.parseJSON(data) + likeIcon = $('#thumb-like-comment-icon-' + data['comment_id']) + likeValue = $('#like-comment-' + data['comment_id']) + if (data['liked']) { + likeIcon.removeClass("fas fa-thumbs-up").addClass('far fa-thumbs-up') + likeValue.text(parseInt(likeValue.text())-1) + } else { + likeIcon.removeClass("far fa-thumbs-up").addClass('fas fa-thumbs-up') + likeValue.text(parseInt(likeValue.text())+1) + } + } + + function dislikeComment(data, jqXHR) { + var data = $.parseJSON(data) + dislikeIcon = $('#thumb-dislike-comment-icon-' + data['comment_id']) + dislikeValue = $('#dislike-comment-' + data['comment_id']) + if (data['disliked']) { + dislikeIcon.removeClass("fas fa-thumbs-down").addClass('far fa-thumbs-down') + dislikeValue.text(parseInt(dislikeValue.text())-1) + } else { + dislikeIcon.removeClass("far fa-thumbs-down").addClass('fas fa-thumbs-down') + dislikeValue.text(parseInt(dislikeValue.text())+1) + } + } function getCitation(text){ var $temp = $(""); diff --git a/app/urls.py b/app/urls.py index 7ccc70c..7034bd1 100644 --- a/app/urls.py +++ b/app/urls.py @@ -13,6 +13,8 @@ urlpatterns = [ path("materi/like/", views.toggle_like, name="PostLikeToggle"), path("delete//", views.delete_comment, name="delete-comment"), + path("comment/like/", views.toggle_like_comment, name="comment-like-toggle"), + path("comment/dislike/", views.toggle_dislike_comment, name="comment-dislike-toggle"), path("materi//unduh", views.download_materi, name="download-materi"), path("materi//view", views.view_materi, name="view-materi"), path("dashboard/", DashboardKontributorView.as_view(), name="dashboard"), diff --git a/app/views.py b/app/views.py index 4f582ea..1400b61 100644 --- a/app/views.py +++ b/app/views.py @@ -19,8 +19,10 @@ from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm from app.models import ( Category, Comment, + DislikeComment, Materi, Like, + LikeComment, ViewStatistics, DownloadStatistics, ReqMaterial, @@ -164,7 +166,14 @@ class DetailMateri(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) query_set_for_comment = Comment.objects.filter(materi=context["materi_data"]) + has_liked = {} + has_disliked = {} + for comment in query_set_for_comment: + has_liked[comment.id] = LikeComment.objects.filter(comment=comment, session_id=self.request.session.session_key).exists() + has_disliked[comment.id] = DislikeComment.objects.filter(comment=comment, session_id=self.request.session.session_key).exists() context["comment_data"] = query_set_for_comment + context["has_liked"] = has_liked + context["has_disliked"] = has_disliked return self.render_to_response(context=context) def get_user_name(self, request): @@ -221,6 +230,41 @@ def delete_comment(request, pk_materi, pk_comment): comment.delete() return HttpResponseRedirect(url) +def toggle_like_comment(request): + if request.method == "POST": + comment_id = request.POST.get("comment_id", None) + session_id = request.POST.get("session_id", None) + if comment_id is None or session_id is None: + return JsonResponse({"success": False, "msg": "Missing parameter", "comment_id": comment_id}) + comment = get_object_or_404(Comment, pk=comment_id) + has_liked = LikeComment.objects.filter(comment=comment, session_id=session_id).exists() + if has_liked: + like = get_object_or_404(LikeComment, comment=comment, session_id=session_id) + like.delete() + return JsonResponse({"success": True, "liked": True, "comment_id": comment_id}) + else: + LikeComment(comment=comment, session_id=session_id).save() + return JsonResponse({"success": True, "liked": False, "comment_id": comment_id}) + else: + return JsonResponse({"success": False, "msg": "Unsuported method", "comment_id": comment_id}) + +def toggle_dislike_comment(request): + if request.method == "POST": + comment_id = request.POST.get("comment_id", None) + session_id = request.POST.get("session_id", None) + if comment_id is None or session_id is None: + return JsonResponse({"success": False, "msg": "Missing parameter", "comment_id": comment_id}) + comment = get_object_or_404(Comment, pk=comment_id) + has_disliked = DislikeComment.objects.filter(comment=comment, session_id=session_id).exists() + if has_disliked: + dislike = get_object_or_404(DislikeComment, comment=comment, session_id=session_id) + dislike.delete() + return JsonResponse({"success": True, "disliked": True, "comment_id": comment_id}) + else: + DislikeComment(comment=comment, session_id=session_id).save() + return JsonResponse({"success": True, "disliked": False, "comment_id": comment_id}) + else: + return JsonResponse({"success": False, "msg": "Unsuported method", "comment_id": comment_id}) def get_citation_ieee(request, materi): current_date = datetime.datetime.now() -- GitLab