Skip to content

Commit 023275b

Browse files
committed
implement comment feature for activities
1 parent 7590418 commit 023275b

File tree

8 files changed

+365
-3
lines changed

8 files changed

+365
-3
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Generated by Django 4.1.3 on 2022-11-19 22:37
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("activities", "0001_initial"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="Comment",
16+
fields=[
17+
(
18+
"id",
19+
models.BigAutoField(
20+
auto_created=True,
21+
primary_key=True,
22+
serialize=False,
23+
verbose_name="ID",
24+
),
25+
),
26+
("user_id", models.BigIntegerField()),
27+
("text", models.CharField(max_length=250)),
28+
("timestamp", models.DateTimeField(auto_now_add=True)),
29+
(
30+
"activity",
31+
models.ForeignKey(
32+
null=True,
33+
on_delete=django.db.models.deletion.CASCADE,
34+
related_name="comment",
35+
to="activities.activity",
36+
),
37+
),
38+
],
39+
),
40+
]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0001_initial
1+
0002_comment

project/activities/models.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,15 @@ def remaining_eligible_companions(self):
8989
remaining_eligible_companions = companions.difference(current_participants)
9090

9191
return remaining_eligible_companions
92+
93+
94+
class Comment(models.Model):
95+
user_id = models.BigIntegerField()
96+
text = models.CharField(max_length=250)
97+
timestamp = models.DateTimeField(auto_now_add=True)
98+
activity = models.ForeignKey(
99+
to=Activity,
100+
related_name="comment",
101+
on_delete=models.CASCADE,
102+
null=True,
103+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{% extends 'base.html' %}
2+
3+
{% load crispy_forms_tags %}
4+
{% load i18n %}
5+
6+
7+
{% block content %}
8+
<h1>{{ activity_name }}</h1>
9+
<h1>{{activity_date }}</h1>
10+
<div class="row row-cols-lg-5 row-cols-md-3 row-cols-2 g-4">
11+
12+
{% for comment in activity_comments_list %}
13+
<div class="col">
14+
<div class="card h-100">
15+
<div class="card-body">
16+
<h2> {{ comment.text }} </h2>
17+
<h6> {{ comment.name }} </h6>
18+
<h6> {{ comment.timestamp }} </h6>
19+
</div>
20+
</div>
21+
</div>
22+
{% endfor %}
23+
24+
</div>
25+
{% endblock content %}

project/activities/tests.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,3 +501,199 @@ def test_remaining_eligible_companions(self):
501501

502502
# Non-companion should not be in eligible companions list
503503
assert self.user_three not in remaining_eligible_companions
504+
505+
506+
507+
508+
class ActivityAddCommentViewTest(TestCase):
509+
def setUp(self):
510+
self.companion_one = User.objects.create_user("[email protected]", "test12345")
511+
self.companion_two = User.objects.create_user("[email protected]", "test12345")
512+
513+
self.circle = Circle.objects.create(name="Test circle")
514+
515+
Companion.objects.create(
516+
circle=self.circle,
517+
user=self.companion_one,
518+
)
519+
Companion.objects.create(
520+
circle=self.circle,
521+
user=self.companion_two,
522+
)
523+
self.user_without_circle = User.objects.create_user(
524+
525+
"test12345",
526+
)
527+
528+
self.circle_with_companion = Circle.objects.create(name="Companion circle")
529+
self.companionship_through = Companion.objects.create(
530+
circle=self.circle_with_companion,
531+
user=self.companion_one,
532+
is_organizer=True,
533+
)
534+
self.activity_for_circle_with_companion = Activity.objects.create(
535+
activity_type=Activity.ActivityTypeChoices.APPOINTMENT,
536+
activity_date="2022-10-23",
537+
circle=self.circle_with_companion,
538+
)
539+
540+
def test_anonymous_add_comment(self):
541+
"""Anonymous user should not be authorized to add comment"""
542+
response = self.client.post(
543+
reverse("activity-add-comment",
544+
kwargs={
545+
"activity_id": self.activity_for_circle_with_companion.id,
546+
}),
547+
{
548+
"activity_id": self.activity_for_circle_with_companion.id,
549+
},
550+
)
551+
552+
self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN)
553+
554+
def test_authenticated_non_companion_add_comment(self):
555+
"""Authenticated user who is not companion should not be authorized to add comment"""
556+
self.client.force_login(self.user_without_circle)
557+
558+
response = self.client.post(
559+
reverse("activity-add-comment",
560+
kwargs={
561+
"activity_id": self.activity_for_circle_with_companion.id,
562+
}),
563+
{
564+
"activity_id": self.activity_for_circle_with_companion.id,
565+
"user_comment": "HELLO",
566+
"user_id": self.companion_one.id,
567+
},
568+
)
569+
570+
self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN)
571+
572+
573+
574+
def test_authenticated_organizer_add_comment(self):
575+
"""Organizer of activity should be able to add comemnt"""
576+
self.client.force_login(self.companion_one)
577+
578+
response = self.client.post(
579+
reverse(
580+
"activity-add-comment",
581+
kwargs={
582+
"activity_id": self.activity_for_circle_with_companion.id,
583+
},
584+
),
585+
{
586+
"activity_id": self.activity_for_circle_with_companion.id,
587+
"user_comment": "HELLO",
588+
"user_id": self.companion_one.id,
589+
},
590+
)
591+
592+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
593+
594+
def test_participant_add_comment(self):
595+
"""Participant of activity should be able to add comemnt"""
596+
self.client.force_login(self.companion_one)
597+
self.client.post(
598+
reverse(
599+
"activity-add-participant",
600+
kwargs={
601+
"activity_id": self.activity_for_circle_with_companion.id,
602+
},
603+
),
604+
{
605+
"user_id": self.companion_two.id,
606+
},
607+
)
608+
609+
response = self.client.post(
610+
reverse(
611+
"activity-add-comment",
612+
kwargs={
613+
"activity_id": self.activity_for_circle_with_companion.id,
614+
},
615+
),
616+
{
617+
"activity_id": self.activity_for_circle_with_companion.id,
618+
"user_comment": "HELLO",
619+
"user_id": self.companion_two.id,
620+
},
621+
)
622+
623+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
624+
625+
626+
627+
628+
629+
class ActivityCommentViewTest(TestCase):
630+
def setUp(self):
631+
self.companion_one = User.objects.create_user("[email protected]", "test12345")
632+
self.companion_two = User.objects.create_user("[email protected]", "test12345")
633+
634+
self.circle = Circle.objects.create(name="Test circle")
635+
636+
Companion.objects.create(
637+
circle=self.circle,
638+
user=self.companion_one,
639+
is_organizer=True,
640+
)
641+
Companion.objects.create(
642+
circle=self.circle,
643+
user=self.companion_two,
644+
)
645+
646+
self.activity_for_circle_with_companion = Activity.objects.create(
647+
activity_type=Activity.ActivityTypeChoices.APPOINTMENT,
648+
activity_date="2022-11-23",
649+
circle=self.circle,
650+
)
651+
652+
653+
654+
def test_anonymous_access(self):
655+
"""Anonymous user should not be authorized to view the comment page"""
656+
response = self.client.get(
657+
reverse("activity-view-comments",
658+
kwargs={
659+
"activity_id": self.activity_for_circle_with_companion.id,
660+
}),
661+
{
662+
"activity_id": self.activity_for_circle_with_companion.id,
663+
},
664+
)
665+
666+
self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN)
667+
668+
def test_authenticated_access(self):
669+
"""Authorized user should be authorized to view the comment page"""
670+
self.client.force_login(self.companion_one)
671+
comment= "wow, this activity looks cool!"
672+
673+
response = self.client.post(
674+
reverse(
675+
"activity-add-comment",
676+
kwargs={
677+
"activity_id": self.activity_for_circle_with_companion.id,
678+
},
679+
),
680+
{
681+
"activity_id": self.activity_for_circle_with_companion.id,
682+
"user_comment": comment,
683+
"user_id": self.companion_one.id,
684+
},
685+
)
686+
response = self.client.get(
687+
reverse(
688+
"activity-view-comments",
689+
kwargs={
690+
"activity_id": self.activity_for_circle_with_companion.id,
691+
},
692+
),
693+
{
694+
"activity_id": self.activity_for_circle_with_companion.id,
695+
},
696+
)
697+
self.assertEqual(response.status_code, HTTPStatus.OK)
698+
self.assertContains(response, self.companion_two.display_name)
699+
self.assertContains(response, comment)

project/activities/urls.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
ActivityRemoveParticipantView,
88
ActivitySetDoneView,
99
ActivityUpdateView,
10+
ActivityAddCommentView,
11+
ActivityViewCommentView,
1012
)
1113

1214
urlpatterns = [
@@ -40,4 +42,14 @@
4042
ActivitySetDoneView.as_view(),
4143
name="activity-set-done",
4244
),
45+
path(
46+
"update/<slug:activity_id>/add_comment",
47+
ActivityAddCommentView.as_view(),
48+
name="activity-add-comment",
49+
),
50+
path(
51+
"<slug:activity_id>/comments",
52+
ActivityViewCommentView.as_view(),
53+
name="activity-view-comments",
54+
),
4355
]

project/activities/views.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from circles.models import Circle
22
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
33
from django.http import HttpResponseRedirect
4-
from django.shortcuts import redirect
4+
from django.shortcuts import redirect, render
55
from django.urls import reverse
66
from django.views.generic import View
77

88
from .forms import ActivityModelForm
9-
from .models import Activity
9+
from .models import Activity, Comment, User
1010

1111

1212
class ActivityCreateView(UserPassesTestMixin, LoginRequiredMixin, View):
@@ -221,3 +221,61 @@ def get(self, request, activity_id, *args, **kwargs):
221221
kwargs={"pk": self.activity.circle.id},
222222
)
223223
)
224+
225+
226+
class ActivityAddCommentView(UserPassesTestMixin, LoginRequiredMixin, View):
227+
raise_exception = True
228+
229+
def test_func(self, *args, **kwargs):
230+
"""Only activity participants or circle's care organizers can comment activity"""
231+
self.activity = Activity.objects.get(id=self.kwargs["activity_id"])
232+
233+
user_is_participant = self.request.user in self.activity.participants.all()
234+
user_is_organizer = self.request.user in self.activity.circle.organizers
235+
236+
user_can_update_activity = user_is_participant or user_is_organizer
237+
238+
return user_can_update_activity
239+
240+
def post(self, request, activity_id, *args, **kwargs):
241+
"""Adds user comments to the comment database."""
242+
user_id = request.POST["user_id"]
243+
text = request.POST["user_comment"]
244+
245+
activity = Activity.objects.get(id = activity_id)
246+
new_comment = Comment(user_id=user_id, text=text, activity=activity)
247+
new_comment.save()
248+
return redirect(
249+
reverse(
250+
"circle-detail",
251+
kwargs={"pk": activity.circle.id},
252+
)
253+
)
254+
255+
256+
class ActivityViewCommentView(UserPassesTestMixin, LoginRequiredMixin, View):
257+
raise_exception = True
258+
259+
def test_func(self, *args, **kwargs):
260+
"""Allow everyone to view comments for activities"""
261+
262+
return True
263+
264+
def get(self, request, activity_id, *args, **kwargs):
265+
"""Fetch all comments for activity with activity_id"""
266+
activity = Activity.objects.get(id=activity_id)
267+
activity_comments_list = Comment.objects.filter(activity = activity)
268+
newList = [
269+
{
270+
"name": str(User.objects.get(id = t.user_id).display_name),
271+
"text" : str(t.text),
272+
"timestamp" : str(t.timestamp).split(".")[0]
273+
} for t in activity_comments_list
274+
]
275+
context = {
276+
"activity_name": str(activity),
277+
"activity_date": activity.activity_date,
278+
"activity_comments_list": newList,
279+
}
280+
template_name="activities/comments_detail.html"
281+
return render(request, template_name, context)

0 commit comments

Comments
 (0)