Fakultas Ilmu Komputer UI

Commit cc57ceb5 authored by Arief Pratama's avatar Arief Pratama
Browse files

Merge branch 'master' of...

Merge branch 'master' of https://gitlab.cs.ui.ac.id/pmpl/class-project/marjinal-digipus into 2006560831-5-v2
parents 211402dd c632c270
Pipeline #59994 passed with stages
in 18 minutes
import datetime
import os
import random
from functools import cmp_to_key
from django.contrib import messages
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ValidationError
from django.db.models import Count, Q
from django.db.models import Case, When, Count, Q
from django.shortcuts import get_object_or_404
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
......@@ -67,6 +68,8 @@ class DafterKatalogService:
@staticmethod
def apply_options(lst_materi, request, url):
should_random = bool(request.GET.get("random"))
should_get_recommendation = bool(request.GET.get("recommendation"))
get_search = request.GET.get("search")
if get_search:
lst_materi, url = DafterKatalogService.search_materi(get_search, lst_materi, url)
......@@ -76,10 +79,24 @@ class DafterKatalogService:
get_sort = request.GET.get("sort")
if get_sort:
lst_materi, url = DafterKatalogService.apply_sort(get_sort, lst_materi, url)
if should_random:
if should_get_recommendation:
lst_materi = DafterKatalogService.get_recommendation(lst_materi)
elif should_random:
lst_materi = DafterKatalogService.apply_random(lst_materi)
return lst_materi, url
@staticmethod
def get_recommendation(lst_materi):
sorted_list = sorted(
lst_materi,
key=cmp_to_key(lambda a, b: 1 if (a.like_count, a.date_modified) < (b.like_count, b.date_modified) else -1),
)
ids = [materi.id for materi in sorted_list]
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(ids)])
return Materi.objects.filter(id__in=ids).order_by(preserved)
@staticmethod
def apply_random(lst_materi):
return random.sample(list(lst_materi), len(lst_materi))
......
......@@ -150,4 +150,57 @@ body{
color: #ffffff;
background-color: #615CFD;
}
\ No newline at end of file
#container-materi-ordering {
display: flex;
flex-flow: row wrap;
flex-basis: auto;
}
#container-materi-ordering > * {
margin: 0 8px ;
}
#container-materi-ordering > *:first-child {
margin-left: 0px;
}
#container-materi-ordering > *:last-child {
margin-right: 0px;
}
.btn-materi-ordering {
border-radius: 2px;
font-size: 14px;
padding: 8px 16px;
height: 38px;
border-radius: 2px;
color: #ffffff;
background-color: #615CFD;
border: none;
}
.btn-materi-ordering:hover {
color: #615CFD;
background-color: #ffffff;
}
@media only screen and (max-width: 768px) {
#container-materi-ordering {
flex-direction: column;
}
#container-materi-ordering > * {
margin-left: 0 !important;
margin-right: 0 !important;
margin: 8px 0;
}
#container-materi-ordering > *:first-child {
margin-top: 0px;
}
#container-materi-ordering > *:last-child {
margin-bottom: 0px;
}
}
\ No newline at end of file
......@@ -76,10 +76,13 @@
</div>
</div>
</header>
<a href="/download-history/" class="btn-history">Riwayat Unduh</a><br><br>
<a href="?random=1" class="btn-history">Acak Materi</a><br><br>
<a href="/download-history/" class="btn-history">Riwayat Unduh</a><br><br>
<div id="container-materi-ordering">
<a href="?random=1" class="btn-materi-ordering">Acak Materi</a><br><br>
<a href="?recommendation=1" class="btn-materi-ordering">Rekomendasi</a><br><br>
</div>
<div class="container">
<div class="row content">
......
......@@ -3178,3 +3178,102 @@ class LandingPageNavbarTest(TestCase):
self.assertContains(response, '<a class="nav-link" href="/login">Login</a>')
self.assertContains(response, '<a class="nav-link" href="/login_admin">Login Admin</a>')
self.assertNotContains(response, '<a class="nav-link" href="/logout">LogoutX</a>')
class MateriRecommendationTest(TestCase):
def setUp(self):
self.contributor = User.objects.create(
email="kontributor@gov.id", password="passwordtest", name="kontributor", is_contributor=True
)
self.cover = SimpleUploadedFile("ExampleCover221.jpg", b"Test file")
self.content = SimpleUploadedFile("ExampleFile221.pdf", b"Test file")
def test_able_to_get_recommendation_by_like_counts(self):
materi1 = Materi.objects.create(
title="Materi 1",
author="Nandhika",
uploader=self.contributor,
publisher="Publisher",
descriptions="Deskripsi Materi 1",
status="APPROVE",
cover=self.cover,
content=self.content,
date_modified=datetime.now(),
date_created=datetime.now(),
)
Like.objects.create(materi=materi1)
Like.objects.create(materi=materi1)
materi2 = Materi.objects.create(
title="Materi 2",
author="Prayoga",
uploader=self.contributor,
publisher="Publisher",
descriptions="Deskripsi Materi 2",
status="APPROVE",
cover=self.cover,
content=self.content,
date_modified=datetime.now(),
date_created=datetime.now(),
)
Like.objects.create(materi=materi2)
Like.objects.create(materi=materi2)
Like.objects.create(materi=materi2)
materi3 = Materi.objects.create(
title="Materi 3",
author="Nandhika Prayoga",
uploader=self.contributor,
publisher="Publisher",
descriptions="Deskripsi Materi 3",
status="APPROVE",
cover=self.cover,
content=self.content,
date_modified=datetime.now(),
date_created=datetime.now(),
)
response = Client().get("/?recommendation=1")
list = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", response.content.decode())]
self.assertEqual(list, [2, 1, 3])
def test_set_date_as_tiebreak_if_like_counts_is_same(self):
materi2 = Materi.objects.create(
title="Materi 2",
author="Prayoga",
uploader=self.contributor,
publisher="Publisher",
descriptions="Deskripsi Materi 2",
status="APPROVE",
cover=self.cover,
content=self.content,
date_modified=datetime.now(),
date_created=datetime.now(),
)
Like.objects.create(materi=materi2)
Like.objects.create(materi=materi2)
materi1 = Materi.objects.create(
title="Materi 1",
author="Nandhika",
uploader=self.contributor,
publisher="Publisher",
descriptions="Deskripsi Materi 1",
status="APPROVE",
cover=self.cover,
content=self.content,
date_modified=datetime.now(),
date_created=datetime.now(),
)
Like.objects.create(materi=materi1)
Like.objects.create(materi=materi1)
response = Client().get("/?recommendation=1")
list = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", response.content.decode())]
self.assertEqual(list, [1, 2])
......@@ -17,7 +17,7 @@ class DiscussionForm(ModelForm):
fields = ['title', 'description']
widgets = {
'title': TextInput(attrs={'class': 'form-control'}),
'description': Textarea(attrs={'class': 'form-control'}),
'description': Textarea(attrs={'class': 'form-control', 'id': 'summernote'}),
}
materi = MateriChoiceField(queryset=Materi.objects.filter(status="APPROVE"), to_field_name='pk', required=False)
......@@ -28,7 +28,7 @@ class DiscussionCommentForm(ModelForm):
model = DiscussionComment
fields = ['description']
widgets = {
'description': Textarea(attrs={'class': 'form-control', 'rows': 3})
'description': Textarea(attrs={'class': 'form-control', 'id': 'summernote'})
}
materi = MateriChoiceField(queryset=Materi.objects.filter(status="APPROVE"), to_field_name='pk', required=False)
......@@ -5,6 +5,8 @@
<link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'forum/forum_discussion_create.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'forum/bootstrap-select.min.css' %}"/>
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="container">
......@@ -23,4 +25,13 @@
{% endblock content %}
{% block extra_scripts %}
<script src="{% static 'forum/js/bootstrap-select.min.js' %}"></script>
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
<script>
$(document).ready(function () {
$('#summernote').summernote({
tabsize: 10,
minHeight: 250
});
});
</script>
{% endblock extra_scripts %}
\ No newline at end of file
......@@ -8,8 +8,19 @@
<div class="container">
{% if object.user == request.user %}
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete
"{% if object.title %}{{ object.title }}{% else %}{{ object.description }}{% endif %}"?</p>
<p>Are you sure you want to delete</p>
{% if object.title %}
<p>{{ object.title }}?</p>
{% else %}
<div>
<div class="card">
<div class="card-body">
{{ object.description|safe }}
</div>
</div>
?
</div>
{% endif %}
<button type="submit" class="btn btn-danger">Submit</button>
</form>
{% else %}
......
......@@ -5,6 +5,8 @@
<link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'forum/forum_discussion_detail.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'forum/bootstrap-select.min.css' %}"/>
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="container">
......@@ -39,22 +41,22 @@
</div>
</div>
{% endfor %}
</div>
<p>{{ object.description }}</p>
{% endfor %}
</div>
<div>{{ object.description|safe }}</div>
</div>
<div class="col-md-2">
<div class="card-body">
<p>{{ object.user.name }}</p>
<p>{{ object.updated_at }}</p>
{% if object.user.id == request.user.id %}
<a href="{% url 'forum_discussion_delete' object.id %}"
class="btn btn-outline-danger"> Delete</a>
{% endif %}
</div>
</div>
<div class="col-md-2">
<div class="card-body">
<p>{{ object.user.name }}</p>
<p>{{ object.updated_at }}</p>
{% if object.user.id == request.user.id %}
<a href="{% url 'forum_discussion_delete' object.id %}"
class="btn btn-outline-danger"> Delete</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
......@@ -84,7 +86,7 @@
</div>
{% endfor %}
</div>
<p>{{ comment.description }}</p>
<div>{{ comment.description|safe }}</div>
</div>
</div>
<div class="col-md-2">
......@@ -94,11 +96,11 @@
{% if comment.user.id == request.user.id %}
<a href="{% url 'forum_discussion_comment_delete' object.id comment.id %}"
class="btn btn-outline-danger"> Delete</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
......@@ -128,28 +130,22 @@
<p class="card-text">You need to log in first before commenting on this page!</p>
<a href="{% url 'login' %}?next={{ request.path }}" class="btn btn-primary">Login</a>
{% endif %}
</div>
</div>
</div>
</div>
<div class="pagination">
<span class="before">
{% if page_obj.has_previous %}
<a href="?page=1">first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
</span>
<span class="current">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.</span>
<span class="after">
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last</a>
{% endif %}
</span>
</div>
</div>
</div>
{% include 'forum/forum_pagination.html' %}
</div>
{% endblock content %}
{% block extra_scripts %}
<script src="{% static 'forum/js/bootstrap-select.min.js' %}"></script>
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
<script>
$(document).ready(function () {
$('#summernote').summernote({
tabsize: 10,
minHeight: 200
});
});
</script>
{% endblock extra_scripts %}
\ No newline at end of file
......@@ -31,21 +31,6 @@
{% empty %}
<h4 id="no-discussion">There is no discussion at the moment</h4>
{% endfor %}
<div class="pagination">
<span class="before">
{% if page_obj.has_previous %}
<a href="?page=1">first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
</span>
<span class="current">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.</span>
<span class="after">
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last</a>
{% endif %}
</span>
</div>
{% include 'forum/forum_pagination.html' %}
</div>
{% endblock content %}
\ No newline at end of file
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" tabindex="-1">Previous</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="" tabindex="-1">Previous</a>
</li>
{% endif %}
{% for page in pages %}
{% if page == "..." %}
<li class="page-item disabled"><a class="page-link" href="">{{ page }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ page }}">{{ page }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" tabindex="-1">Next</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="" tabindex="-1">Next</a>
</li>
{% endif %}
</ul>
</nav>
\ No newline at end of file
......@@ -170,6 +170,10 @@ class DiscussionCommentModelTest(TestCase):
self.assertEqual(discussion_comment.materi.count(), 2)
def pagination_html(page):
return '<li class="page-item"><a class="page-link" href="?page={}">{}</a></li>'.format(page, page)
class ForumHomePageTest(TestCase):
def setUp(self):
self.USER_CONTRIBUTOR = User.objects.create_contributor(email=CONTRIBUTOR_EMAIL, password=CONTRIBUTOR_PASSWORD)
......@@ -208,15 +212,24 @@ class ForumHomePageTest(TestCase):
self.assertIn("User Test", request.content.decode())
def test_forum_home_pagination(self):
for i in range(11):
for i in range(51):
num = str(i)
Discussion.objects.create(title="Discussion " + num, description="Description " + num,
user=self.USER_CONTRIBUTOR)
request = self.client.get(FORUM_BASE_URL)
if Discussion.objects.all().count() <= 10:
self.assertIn("Page 1 of 1", request.content.decode())
else:
self.assertIn("Page 1 of 2", request.content.decode())
page_counter = Discussion.objects.all().count()
content = request.content.decode()
if page_counter == 5:
self.assertIn(pagination_html(1), content)
self.assertNotIn(pagination_html(2), content)
elif page_counter == 15:
self.assertIn(pagination_html(1), content)
self.assertIn(pagination_html(2), content)
elif page_counter == 51:
self.assertIn(pagination_html(1), content)
self.assertIn(pagination_html(2), content)
self.assertIn('<li class="page-item disabled"><a class="page-link" href="">...</a></li>', content)
self.assertIn(pagination_html(6), content)
class ForumCreateDiscussionPageTest(TestCase):
......@@ -259,6 +272,12 @@ class ForumCreateDiscussionPageTest(TestCase):
self.assertEqual(new_discussion.title, title)
self.assertEqual(new_discussion.materi.count(), 2)
def test_create_discussion_using_summernote(self):
self.client.login(email=CONTRIBUTOR_EMAIL, password=CONTRIBUTOR_PASSWORD)
request = self.client.get(URL_CREATE_DISCUSSION)
self.assertIn('id="summernote"', request.content.decode())
self.assertIn('$(\'#summernote\').summernote({', request.content.decode())
class ForumDiscussionDetailPageTest(TestCase):
def setUp(self):
......@@ -279,18 +298,21 @@ class ForumDiscussionDetailPageTest(TestCase):
def test_discussion_detail_page_pagination(self):
discussion_id = str(self.DISCUSSION.id)
data = []
for i in range(25):
for i in range(101):
data.append(DiscussionComment(description="Description " + str(i), user=self.USER_CONTRIBUTOR,
discussion=self.DISCUSSION))
DiscussionComment.objects.bulk_create(data)
self.assertEqual(DiscussionComment.objects.count(), 25)
self.assertEqual(DiscussionComment.objects.count(), 101)
request = self.client.get(URL_DISCUSSION_DETAIL + discussion_id)
self.assertIn('Page 1 of 2', request.content.decode())
content = request.content.decode()
self.assertIn(pagination_html(1), content)
self.assertIn(pagination_html(2), content)
self.assertIn('<li class="page-item disabled"><a class="page-link" href="">...</a></li>', content)
self.assertIn(pagination_html(6), content)
self.assertIn('Description 1', request.content.decode())
def test_discussion_detail_show_correct_title(self):
......@@ -470,6 +492,12 @@ class ForumDiscussionDetailPageTest(TestCase):
with self.assertRaises(ObjectDoesNotExist):
DiscussionComment.objects.get(id=comment_id)
def test_create_discussion_using_summernote(self):
self.client.login(email=CONTRIBUTOR_EMAIL, password=CONTRIBUTOR_PASSWORD)
request = self.client.get(URL_CREATE_DISCUSSION)
self.assertIn('id="summernote"', request.content.decode())
self.assertIn('$(\'#summernote\').summernote({', request.content.decode())
class DiscussionFormTest(TestCase):
def setUp(self):
......
......@@ -14,11 +14,42 @@ URL_FORUM_HOME_PAGE = '/forum'
URL_LOGIN_PAGE = '/login'
def pagination(current, last):
delta = 2
left = current - delta
right = current + delta + 1
pages = []
pages_with_dots = []
for i in range(last + 1):
if i == 0:
continue
if i == 1 or i == last or (left <= i < right):
pages.append(i)
temp = None
for page in pages:
if temp:
if page - temp == 2:
pages_with_dots.append(temp + 1)
elif page - temp != 1:
pages_with_dots.append("...")
pages_with_dots.append(page)
temp = page
return pages_with_dots
class ForumHomePage(ListView):
paginate_by = 10
queryset = Discussion.objects.order_by('-updated_at')
template_name = 'forum/forum_home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
page_context = context.get('page_obj')
context['pages'] = pagination(page_context.number, page_context.paginator.num_pages)
return context
class ForumCreateDiscussion(LoginRequiredMixin, CreateView):
form_class = DiscussionForm
......@@ -57,6 +88,10 @@ class ForumDiscussionDetail(FormMixin, DetailView, MultipleObjectMixin):
def get_context_data(self, **kwargs):
comments = DiscussionComment.objects.filter(discussion=self.get_object()).order_by('updated_at')
context = super(ForumDiscussionDetail, self).get_context_data(object_list=comments, **kwargs)
page_context = context.get('page_obj')
context['pages'] = pagination(page_context.number, page_context.paginator.num_pages)
return context