From c1b97dfd4cfdf3f3b97ebfe7e2cb81dafd610d13 Mon Sep 17 00:00:00 2001 From: Wenxiu Liao Date: Sat, 19 Nov 2022 17:45:23 -0500 Subject: [PATCH] implement comment feature for activities --- project/activities/migrations/0002_comment.py | 40 ++++ .../activities/migrations/max_migration.txt | 2 +- project/activities/models.py | 12 ++ .../templates/activities/comments_detail.html | 25 +++ project/activities/tests.py | 196 ++++++++++++++++++ project/activities/urls.py | 12 ++ project/activities/views.py | 62 +++++- .../templates/circles/circle_activity.html | 19 ++ 8 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 project/activities/migrations/0002_comment.py create mode 100644 project/activities/templates/activities/comments_detail.html diff --git a/project/activities/migrations/0002_comment.py b/project/activities/migrations/0002_comment.py new file mode 100644 index 0000000..686f173 --- /dev/null +++ b/project/activities/migrations/0002_comment.py @@ -0,0 +1,40 @@ +# Generated by Django 4.1.3 on 2022-11-19 22:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("activities", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Comment", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("user_id", models.BigIntegerField()), + ("text", models.CharField(max_length=250)), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ( + "activity", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="comment", + to="activities.activity", + ), + ), + ], + ), + ] diff --git a/project/activities/migrations/max_migration.txt b/project/activities/migrations/max_migration.txt index cbab66d..e7426ee 100644 --- a/project/activities/migrations/max_migration.txt +++ b/project/activities/migrations/max_migration.txt @@ -1 +1 @@ -0001_initial +0002_comment diff --git a/project/activities/models.py b/project/activities/models.py index 63905fb..7276e06 100644 --- a/project/activities/models.py +++ b/project/activities/models.py @@ -89,3 +89,15 @@ def remaining_eligible_companions(self): remaining_eligible_companions = companions.difference(current_participants) return remaining_eligible_companions + + +class Comment(models.Model): + user_id = models.BigIntegerField() + text = models.CharField(max_length=250) + timestamp = models.DateTimeField(auto_now_add=True) + activity = models.ForeignKey( + to=Activity, + related_name="comment", + on_delete=models.CASCADE, + null=True, + ) diff --git a/project/activities/templates/activities/comments_detail.html b/project/activities/templates/activities/comments_detail.html new file mode 100644 index 0000000..e6cdaef --- /dev/null +++ b/project/activities/templates/activities/comments_detail.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} + +{% load crispy_forms_tags %} +{% load i18n %} + + +{% block content %} +

{{ activity_name }}

+

{{activity_date }}

+
+ + {% for comment in activity_comments_list %} +
+
+
+

{{ comment.text }}

+

{{ comment.name }}

+

{{ comment.timestamp }}

+
+
+
+ {% endfor %} + +
+{% endblock content %} \ No newline at end of file diff --git a/project/activities/tests.py b/project/activities/tests.py index 8e7d056..0263186 100644 --- a/project/activities/tests.py +++ b/project/activities/tests.py @@ -501,3 +501,199 @@ def test_remaining_eligible_companions(self): # Non-companion should not be in eligible companions list assert self.user_three not in remaining_eligible_companions + + + + +class ActivityAddCommentViewTest(TestCase): + def setUp(self): + self.companion_one = User.objects.create_user("test_one@user.com", "test12345") + self.companion_two = User.objects.create_user("test_two@user.com", "test12345") + + self.circle = Circle.objects.create(name="Test circle") + + Companion.objects.create( + circle=self.circle, + user=self.companion_one, + ) + Companion.objects.create( + circle=self.circle, + user=self.companion_two, + ) + self.user_without_circle = User.objects.create_user( + "test_three@user.com", + "test12345", + ) + + self.circle_with_companion = Circle.objects.create(name="Companion circle") + self.companionship_through = Companion.objects.create( + circle=self.circle_with_companion, + user=self.companion_one, + is_organizer=True, + ) + self.activity_for_circle_with_companion = Activity.objects.create( + activity_type=Activity.ActivityTypeChoices.APPOINTMENT, + activity_date="2022-10-23", + circle=self.circle_with_companion, + ) + + def test_anonymous_add_comment(self): + """Anonymous user should not be authorized to add comment""" + response = self.client.post( + reverse("activity-add-comment", + kwargs={ + "activity_id": self.activity_for_circle_with_companion.id, + }), + { + "activity_id": self.activity_for_circle_with_companion.id, + }, + ) + + self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN) + + def test_authenticated_non_companion_add_comment(self): + """Authenticated user who is not companion should not be authorized to add comment""" + self.client.force_login(self.user_without_circle) + + response = self.client.post( + reverse("activity-add-comment", + kwargs={ + "activity_id": self.activity_for_circle_with_companion.id, + }), + { + "activity_id": self.activity_for_circle_with_companion.id, + "user_comment": "HELLO", + "user_id": self.companion_one.id, + }, + ) + + self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN) + + + + def test_authenticated_organizer_add_comment(self): + """Organizer of activity should be able to add comemnt""" + self.client.force_login(self.companion_one) + + response = self.client.post( + reverse( + "activity-add-comment", + kwargs={ + "activity_id": self.activity_for_circle_with_companion.id, + }, + ), + { + "activity_id": self.activity_for_circle_with_companion.id, + "user_comment": "HELLO", + "user_id": self.companion_one.id, + }, + ) + + self.assertEqual(response.status_code, HTTPStatus.FOUND) + + def test_participant_add_comment(self): + """Participant of activity should be able to add comemnt""" + self.client.force_login(self.companion_one) + self.client.post( + reverse( + "activity-add-participant", + kwargs={ + "activity_id": self.activity_for_circle_with_companion.id, + }, + ), + { + "user_id": self.companion_two.id, + }, + ) + + response = self.client.post( + reverse( + "activity-add-comment", + kwargs={ + "activity_id": self.activity_for_circle_with_companion.id, + }, + ), + { + "activity_id": self.activity_for_circle_with_companion.id, + "user_comment": "HELLO", + "user_id": self.companion_two.id, + }, + ) + + self.assertEqual(response.status_code, HTTPStatus.FOUND) + + + + + +class ActivityCommentViewTest(TestCase): + def setUp(self): + self.companion_one = User.objects.create_user("test_one@user.com", "test12345") + self.companion_two = User.objects.create_user("test_two@user.com", "test12345") + + self.circle = Circle.objects.create(name="Test circle") + + Companion.objects.create( + circle=self.circle, + user=self.companion_one, + is_organizer=True, + ) + Companion.objects.create( + circle=self.circle, + user=self.companion_two, + ) + + self.activity_for_circle_with_companion = Activity.objects.create( + activity_type=Activity.ActivityTypeChoices.APPOINTMENT, + activity_date="2022-11-23", + circle=self.circle, + ) + + + + def test_anonymous_access(self): + """Anonymous user should not be authorized to view the comment page""" + response = self.client.get( + reverse("activity-view-comments", + kwargs={ + "activity_id": self.activity_for_circle_with_companion.id, + }), + { + "activity_id": self.activity_for_circle_with_companion.id, + }, + ) + + self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN) + + def test_authenticated_access(self): + """Authorized user should be authorized to view the comment page""" + self.client.force_login(self.companion_one) + comment= "wow, this activity looks cool!" + + response = self.client.post( + reverse( + "activity-add-comment", + kwargs={ + "activity_id": self.activity_for_circle_with_companion.id, + }, + ), + { + "activity_id": self.activity_for_circle_with_companion.id, + "user_comment": comment, + "user_id": self.companion_one.id, + }, + ) + response = self.client.get( + reverse( + "activity-view-comments", + kwargs={ + "activity_id": self.activity_for_circle_with_companion.id, + }, + ), + { + "activity_id": self.activity_for_circle_with_companion.id, + }, + ) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertContains(response, self.companion_two.display_name) + self.assertContains(response, comment) diff --git a/project/activities/urls.py b/project/activities/urls.py index 6e63589..1208ab9 100644 --- a/project/activities/urls.py +++ b/project/activities/urls.py @@ -7,6 +7,8 @@ ActivityRemoveParticipantView, ActivitySetDoneView, ActivityUpdateView, + ActivityAddCommentView, + ActivityViewCommentView, ) urlpatterns = [ @@ -40,4 +42,14 @@ ActivitySetDoneView.as_view(), name="activity-set-done", ), + path( + "update//add_comment", + ActivityAddCommentView.as_view(), + name="activity-add-comment", + ), + path( + "/comments", + ActivityViewCommentView.as_view(), + name="activity-view-comments", + ), ] diff --git a/project/activities/views.py b/project/activities/views.py index 7e07386..bc345a9 100644 --- a/project/activities/views.py +++ b/project/activities/views.py @@ -1,12 +1,12 @@ from circles.models import Circle from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.http import HttpResponseRedirect -from django.shortcuts import redirect +from django.shortcuts import redirect, render from django.urls import reverse from django.views.generic import View from .forms import ActivityModelForm -from .models import Activity +from .models import Activity, Comment, User class ActivityCreateView(UserPassesTestMixin, LoginRequiredMixin, View): @@ -221,3 +221,61 @@ def get(self, request, activity_id, *args, **kwargs): kwargs={"pk": self.activity.circle.id}, ) ) + + +class ActivityAddCommentView(UserPassesTestMixin, LoginRequiredMixin, View): + raise_exception = True + + def test_func(self, *args, **kwargs): + """Only activity participants or circle's care organizers can comment activity""" + self.activity = Activity.objects.get(id=self.kwargs["activity_id"]) + + user_is_participant = self.request.user in self.activity.participants.all() + user_is_organizer = self.request.user in self.activity.circle.organizers + + user_can_update_activity = user_is_participant or user_is_organizer + + return user_can_update_activity + + def post(self, request, activity_id, *args, **kwargs): + """Adds user comments to the comment database.""" + user_id = request.POST["user_id"] + text = request.POST["user_comment"] + + activity = Activity.objects.get(id = activity_id) + new_comment = Comment(user_id=user_id, text=text, activity=activity) + new_comment.save() + return redirect( + reverse( + "circle-detail", + kwargs={"pk": activity.circle.id}, + ) + ) + + +class ActivityViewCommentView(UserPassesTestMixin, LoginRequiredMixin, View): + raise_exception = True + + def test_func(self, *args, **kwargs): + """Allow everyone to view comments for activities""" + + return True + + def get(self, request, activity_id, *args, **kwargs): + """Fetch all comments for activity with activity_id""" + activity = Activity.objects.get(id=activity_id) + activity_comments_list = Comment.objects.filter(activity = activity) + newList = [ + { + "name": str(User.objects.get(id = t.user_id).display_name), + "text" : str(t.text), + "timestamp" : str(t.timestamp).split(".")[0] + } for t in activity_comments_list + ] + context = { + "activity_name": str(activity), + "activity_date": activity.activity_date, + "activity_comments_list": newList, + } + template_name="activities/comments_detail.html" + return render(request, template_name, context) \ No newline at end of file diff --git a/project/circles/templates/circles/circle_activity.html b/project/circles/templates/circles/circle_activity.html index abd6813..66dddee 100644 --- a/project/circles/templates/circles/circle_activity.html +++ b/project/circles/templates/circles/circle_activity.html @@ -95,6 +95,25 @@ {% else %} {% translate "No participants" %} {% endif %} + +
+ + {% csrf_token %} + + +
+
+ +
+ {% csrf_token %} + +
+