Fakultas Ilmu Komputer UI

Commit 21095a1c authored by Jonathan Christopher Jakub's avatar Jonathan Christopher Jakub
Browse files

Merge branch '1706040151-75' into 'master'

[#75] Group comments per post on User Comments page

Closes #75

See merge request !6
parents b2e24caa f96ddea6
Pipeline #57344 passed with stages
in 8 minutes and 33 seconds
.posts-vertically-centered {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 1rem;
}
.posts-space-between-container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
#posts-profile-picture {
border-radius: 50%;
}
#posts-user-profile {
display: flex;
flex-direction: row;
}
#posts-user-name {
display: flex;
justify-content: center;
align-items: center;
margin-left: 1rem;
font-size: 1.5rem;
}
#posts-img {
width: 100px;
height: auto;
height: inherit !important;
margin-right: 1rem;
}
#posts-info {
display: flex;
flex-direction: column;
justify-content: center;
}
#posts-comment-info {
display: flex;
flex-direction: row;
margin: 0.5rem 6rem 0.5rem 6rem;
padding: 0.5rem;
}
......@@ -29,8 +29,8 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'comments' user.id %}">
<span>Komentar</span></a>
<a class="nav-link" href="{% url 'posts' %}">
<span>Materi Diunggah</span></a>
</li>
</ul>
\ No newline at end of file
</ul>
{% extends 'app/base_dashboard.html' %}
{% load static %}
{% block title %}
<title>Materi Diunggah | Digipus</title>
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="{% static 'app/css/user_uploaded_posts.css' %}"/>
{% endblock %}
{% block content %}
<div class="posts-space-between-container bg-white rounded shadow bd-highlight" style="padding: 1rem 1.5rem; margin-bottom: 1.5rem;">
<div id="posts-user-profile">
<img id="posts-profile-picture" src="{{ user.profile_picture.url }}" alt="profile-picture" width="100px" height="100px"/>
<div id="posts-user-name">
Materi oleh <strong>&nbsp{{ user.name }}</strong>
</div>
</div>
<div class="posts-vertically-centered">
<span>{{ posts|length }}</span>
<span>Materi</span>
</div>
</div>
<div style="padding: 1rem 0" id="posts">
{% if posts %}
{% for _, post in posts.items %}
<div id="post-{{ post.data.id }}">
<div class="posts-space-between-container bg-white rounded shadow" style="margin: 0.5rem 2rem; padding: 1rem;">
<div id="posts-user-profile">
{% if post.data.cover %}
<img id="posts-img" src="{{ post.data.cover.url }}" alt="profile-picture"/>
{% else %}
</div style="background-color: grey; width: 100px; height: 100px;">
{% endif %}
<div id="posts-info">
<span><a class="ml-auto p-1 link" style="text-align: left; font-size: 2rem;" href="{% url 'detail-materi' post.data.id %}">
{{ post.data.title }}
</a></span>
<span style="font-size: 0.75rem; padding-left: 0.3rem;">{{ post.data.date_created }}</span>
</div>
</div>
<div class="posts-vertically-centered">
<span>{{ post.comments|length }}</span>
<span>Komentar</span>
</div>
</div>
{% for comment in post.comments %}
<div id="post-{{ post.data.id }}-comment-{{ comment.id }}">
<div id="posts-comment-info" class="bg-white rounded shadow" >
<img id="posts-profile-picture" src="{{ comment.user.profile_picture.url }}" alt="profile-picture" width="40px" height="40px" style="margin: 18px"/>
<div style="display: flex; align-items: center;">
<div style="display: flex; flex-direction: column;">
<span style="font-size: 0.9rem;"><strong>{{ comment.user.name }}</strong> - {{ comment.timestamp }}</span>
{{ comment.comment }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
{% else %}
<div class="text-center h5">
Anda belum memiliki materi yang telah diunggah / disetujui
</div>
{% endif %}
</div>
{% endblock %}
......@@ -2,7 +2,7 @@ import json
from io import StringIO
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.management import call_command
from django.test import Client, RequestFactory, TestCase
......@@ -17,7 +17,7 @@ from .models import Category, Comment, Materi, Like, Rating
from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri,
ProfilKontributorView, SuksesLoginAdminView,
SuksesLoginKontributorView, SuntingProfilView,
ProfilAdminView, CommentsView, SuntingProfilAdminView, RevisiMateriView)
ProfilAdminView, PostsView, SuntingProfilAdminView, RevisiMateriView)
from app.forms import SuntingProfilForm
......@@ -160,69 +160,108 @@ class DetailMateriTest(TestCase):
self.assertEqual(Comment.objects.all().filter(
comment="This is new comment by Anonymous").count(), 0)
class ViewCommentsTest(TestCase):
def setUp(self):
self.client = Client()
self.contributor_credential = {
"email": "kontributor@gov.id",
"password": "passwordtest"
}
self.matt_damon_credential = {
"email": "mattdamon@gov.id",
"password": "passwordtest"
class PostsViewTest(TestCase):
@classmethod
def generate_posts_data(cls, user):
POST_COUNT = 3
COMMENTS_COUNT_PER_POST = [1, 0, 3]
assert POST_COUNT == len(COMMENTS_COUNT_PER_POST)
sample_file = SimpleUploadedFile("Test.jpg", b"Test file")
sample_category = Category.objects.create(name="Test Category")
post_comment_group_dict = {}
for _ in range(POST_COUNT):
post = Materi.objects.create(
uploader=user,
cover=sample_file,
content=sample_file,
)
post.categories.add(sample_category)
post_comment_group_dict[post.id] = {
"data": post,
"comments": [],
}
for i, post_id in enumerate(post_comment_group_dict):
post = post_comment_group_dict[post_id]["data"]
for _ in range(COMMENTS_COUNT_PER_POST[i]):
comment = Comment.objects.create(materi=post)
post_comment_group_dict[post_id]["comments"].append(comment)
# order by latest (-timestamp)
post_comment_group_dict[post_id]["comments"].reverse()
return post_comment_group_dict
@classmethod
def setUpTestData(cls):
cls.url = '/posts/'
cls.user_credentials = {
"email": "user@email.com",
"password": "justpass"
}
self.contributor = get_user_model().objects.create_user(
**self.contributor_credential, name="Kontributor", is_contributor=True)
self.mattDamon = get_user_model().objects.create_user(
**self.matt_damon_credential, name="Matt Damon", is_contributor=True)
self.cover = SimpleUploadedFile(
"Cherprang_Areekul40_nJM9dGt.jpg", b"Test file")
self.content = SimpleUploadedFile("Bahan_PA_RKK.pdf", b"Test file")
Materi(title="Materi 1", author="Agas", uploader=self.contributor,
publisher="Kelas SC", descriptions="Deskripsi Materi 1",
status="APPROVE", cover=self.cover, content=self.content).save()
Materi(title="Materi 2", author="Matt", uploader=self.mattDamon,
publisher="Kelas SC", descriptions="Deskripsi Materi 2",
status="APPROVE", cover=self.cover, content=self.content).save()
self.materi1 = Materi.objects.first()
self.materi2 = Materi.objects.get(uploader=self.mattDamon)
self.commentByKontributor = Comment.objects.create(username='saul', comment="this is contributor comment", materi=self.materi1, user=self.contributor)
self.commentByMatt = Comment.objects.create(username='saul', comment="this is Matt Damon", materi=self.materi2, user=self.mattDamon)
self.url = '/kontributor/' + str(self.contributor.id) + '/comments/'
cls.user = User.objects.create_user(**cls.user_credentials, is_contributor=True)
cls.data = cls.generate_posts_data(cls.user)
def test_comments_url_exist(self):
self.client.login(**self.contributor_credential)
response = self.client.get(self.url)
def _request_as_user(self):
self.client.login(**self.user_credentials)
return self.client.get(self.url)
def test_url_resolves_to_posts_view(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, PostsView.as_view().__name__)
def test_returns_200_on_authenticated_access(self):
response = self._request_as_user()
self.assertEqual(response.status_code, 200)
self.assertNotEqual(response.status_code, 404)
def test_comments_using_comments_template(self):
self.client.login(**self.contributor_credential)
def test_returns_403_on_unauthenticated_access(self):
response = self.client.get(self.url)
self.assertTemplateUsed(response, 'comments.html')
self.assertRaises(PermissionDenied)
self.assertEqual(response.status_code, 403)
def test_comments_using_comments_func(self):
self.client.login(**self.contributor_credential)
found = resolve(self.url)
self.assertEqual(found.func.__name__, CommentsView.as_view().__name__)
def test_returns_correct_template(self):
response = self._request_as_user()
self.assertTemplateUsed(response, "user_uploaded_posts.html")
def test_comments_page_render_comments(self):
self.client.login(**self.contributor_credential)
response = self.client.get(self.url)
self.assertContains(response, "this is contributor comment")
self.assertNotContains(response, 'bukan comment')
def test_success_returns_correct_comment_post_groupings_by_context(self):
post_comment_group_dict = self.data
def test_comments_page_only_render_specific_user_comments(self):
self.client.login(**self.contributor_credential)
response = self.client.get(self.url)
self.assertContains(response, "this is contributor comment")
self.assertNotContains(response, "this is Matt Damon")
response = self._request_as_user()
response_user = response.context_data["user"]
self.assertEqual(response_user, self.user)
response_data = response.context_data["posts"]
actual_data = post_comment_group_dict
self.assertDictEqual(response_data, actual_data)
def test_html_contains_grouped_posts_and_comments(self):
response = self._request_as_user()
posts = list(self.data.keys())
comments = {
i: [comment.id for comment in self.data[post_id]["comments"]]
for i, post_id in enumerate(posts)
}
self.assertRegex(
str(response.content),
rf'.*(<div id="post-{posts[2]}">)' + \
rf'.*(<div id="post-{posts[2]}-comment-{comments[2][0]}">)' + \
rf'.*(<div id="post-{posts[2]}-comment-{comments[2][1]}">)' + \
rf'.*(<div id="post-{posts[2]}-comment-{comments[2][2]}">)' + \
rf'.*(<div id="post-{posts[1]}">)' + \
rf'.*(<div id="post-{posts[0]}">)' + \
rf'.*(<div id="post-{posts[0]}-comment-{comments[0][0]}">)'
)
def test_comments_page_only_for_specific_contributor(self):
self.client.login(**self.matt_damon_credential)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 403)
self.assertNotEqual(response.status_code, 200)
class TemplateLoaderTest(TestCase):
def test_template_loader_url_exist(self):
......
......@@ -4,7 +4,7 @@ from app import views
from app.views import (DashboardKontributorView, ProfilKontributorView,
SuksesLoginAdminView, SuksesLoginKontributorView,
SuntingProfilView, UploadMateriHTML, UploadMateriView,
ProfilAdminView, CommentsView, SuntingProfilAdminView, ReqMateriView)
ProfilAdminView, PostsView, SuntingProfilAdminView, ReqMateriView)
urlpatterns = [
path("", views.DaftarKatalog.as_view(), name="daftar_katalog"),
......@@ -24,7 +24,7 @@ urlpatterns = [
path("sukses-admin/", SuksesLoginAdminView.as_view(), name="sukses-admin"),
re_path(r"^.*\.html", views.pages, name="pages"),
path("profil-admin/", ProfilAdminView.as_view(), name="profil-admin"),
path('kontributor/<int:pk_user>/comments/', CommentsView.as_view(), name='comments'),
path("posts/", PostsView.as_view(), name='posts'),
path("sunting-admin/", SuntingProfilAdminView.as_view(), name="sunting-admin"),
path("req-materi/", ReqMateriView.as_view(), name="req-materi"),
path("post-req-materi/", views.post_req_materi, name="post-req-materi"),
......
......@@ -486,33 +486,35 @@ class SuksesLoginAdminView(TemplateView):
return self.render_to_response(context)
class CommentsView(TemplateView):
template_name = "comments.html"
class PostsView(TemplateView):
template_name = "user_uploaded_posts.html"
def dispatch(self, request, *args, **kwargs):
if not request.user.pk == kwargs["pk_user"]:
if not request.user.is_authenticated:
raise PermissionDenied(request)
return super(CommentsView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(CommentsView, self).get_context_data(**kwargs)
user = get_object_or_404(User, pk=kwargs["pk_user"])
users_materi = Materi.objects.filter(uploader=user)
numb_of_comments = 0
qset_comments = Comment.objects.none()
for materi in users_materi:
materi_comments = Comment.objects.filter(materi=materi).count()
numb_of_comments += materi_comments
qset_comments |= Comment.objects.filter(materi=materi)
context['comments'] = qset_comments.order_by('-timestamp')
context["numb_of_comments"] = numb_of_comments
context["numb_of_materi"] = users_materi.count()
return context
return super(PostsView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
user = self.request.user
posts = Materi.objects.filter(uploader=user).order_by("-date_created")
posts_data = { post.id: { "data": post, "comments": [] } for post in posts }
comments = Comment.objects \
.filter(materi__id__in=posts_data.keys()) \
.order_by("-timestamp")
for comment in comments:
posts_data[comment.materi.id]["comments"].append(comment)
context["user"] = user
context["posts"] = posts_data
return self.render_to_response(context=context)
class RevisiMateriView(TemplateView):
template_name = "revisi.html"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment