Fakultas Ilmu Komputer UI

Commit afb13d6e authored by SAMUEL TUPA FEBRIAN's avatar SAMUEL TUPA FEBRIAN
Browse files

[#77] Profile: User Download History

parent f7557f93
# Generated by Django 3.0.4 on 2020-09-30 04:50
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('app', '0014_rating'),
]
operations = [
migrations.AddField(
model_name='downloadstatistics',
name='downloader',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='riwayat_unduh', to=settings.AUTH_USER_MODEL),
),
]
# Generated by Django 3.1 on 2020-10-09 00:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('app', '0017_auto_20201005_2145'),
('app', '0015_downloadstatistics_downloader'),
]
operations = [
]
......@@ -98,6 +98,8 @@ class ViewStatistics(models.Model):
class DownloadStatistics(models.Model):
materi = models.ForeignKey(
Materi, models.SET_NULL, null=True, related_name="unduh")
downloader = models.ForeignKey(
User, models.SET_NULL, blank=True, null=True, related_name="riwayat_unduh")
timestamp = models.DateTimeField(default=timezone.now)
......
......@@ -62,6 +62,21 @@ body{
background-color: #615CFD;
}
.btn-history {
width: auto;
padding: 8px 8px;
border-radius: 2px;
background-color: #ffffff;
color: #615CFD;
border: none;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
.btn-history:hover{
color: #ffffff;
background-color: #615CFD;
}
.content {
display: flex;
margin: 5px;
......
......@@ -49,7 +49,6 @@
<body style="background-color: #f8f8f8;">
<!-- Page Content -->
<div class="container">
<header class="jumbotron my-4">
<div class="container">
<div class="row header">
......@@ -61,15 +60,17 @@
<input type="text" name='search' class="form-control" placeholder="Tulis di sini"
value='{{request.GET.search}}'>
</div>
<button type="submit" class="btn btn-cari">Cari</button>
<button type="submit" class="btn btn-cari">Cari</button>
</form>
<p class="pageTitle">Tidak menemukan materi yang kamu cari ? ajukan permintaan materi kami <a
href="/req-materi">disini</a></p>
</div>
</div>
</div>
</div>
</header>
<a href="/download-history/" class="btn-history">Riwayat Unduh</a><br><br>
<div class="container">
<div class="row content">
<div class="col-3 sidebar">
......@@ -154,7 +155,6 @@
<div class="center">
<div class="pagination">
<span class="step-links">
<span class="current">
Page {{ materi_list.number }} of {{ materi_list.paginator.num_pages }}
</span>
......
{% extends 'base.html' %}
{% load static %}
{% block title %}
<title>Riwayat Unduh | Digipus</title>
{% endblock %}
{% block header %}
<link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" />
<link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
<link href="{% static 'css/sb-admin-2.min.css' %}" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/button.css' %}">
<link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet">
{% endblock %}
{% block content %}
<br><br>
<h1 id="riwayat_unduh" class="h3 mb-2 text-gray-800">Riwayat Unduh | {{ user_name }}</h1>
{% if riwayat_list %}
<p class="mb-4">Tekan tombol detail untuk informasi lebih lanjut tentang materi</p>
<div class="card shadow mb-4">
<div class="card-body">
<div class="table-responsive">
<table aria-describedby="riwayat_unduh" class="table table-bordered" id="dataTable">
<thead>
<tr>
<th id="judul_materi">Judul Materi</th>
<th id="author">Pembuat Materi</th>
<th id="download_time">Waktu Download</th>
<th id="detail_buttons">Detail</th>
</tr>
</thead>
<tbody>
{% for riwayat in riwayat_list %}
<tr>
<td>{{riwayat.materi.title}}</td>
<td>{{riwayat.materi.author}}</td>
<td>{{riwayat.timestamp|date:"d F Y H:i:s"}}</td>
<td class="verif-buttons">
<span>
<a href="/materi/{{riwayat.materi.id}}/" class="accept-button button-decoration"
style="background-color:#4e73df">Detail</a>
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<p class="mb-4">Anda belum mengunduh materi. Silahkan unduh materi yang anda butuhkan</p>
{% endif %}
<span>
<a href="/" class="btn btn-primary main-content" style="background-color:#4e73df; right: 0px;">Back to Katalog</a>
</span>
<br>
{% endblock %}
\ No newline at end of file
import json, tempfile, os
import pandas as pd
from io import StringIO
from bs4 import BeautifulSoup
from datetime import datetime
from django.conf import settings
from django.contrib import messages as dj_messages
from django.contrib.auth import get_user_model
from django.core import serializers
from django.core.files import File
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, TestCase, TransactionTestCase
from django.urls import resolve
from django.contrib import messages as dj_messages
from django.db.utils import IntegrityError
from django.urls import resolve
from django.test import Client, RequestFactory, TestCase, TransactionTestCase
from pytz import timezone
from time import sleep
from administration.models import VerificationSetting, VerificationReport
from administration.utils import id_generator
from app.views import UploadMateriView, add_rating_materi
from app.views import UploadMateriHTML, add_rating_materi
from authentication.models import User
from .models import Category, Comment, Materi, Like, Rating, ReqMaterial, RatingContributor
from digipus.settings import TIME_ZONE
from .models import (Category, Comment, DownloadStatistics, Materi, Like,
Rating, ReqMaterial, RatingContributor, ViewStatistics)
from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri,
ProfilKontributorView, SuksesLoginAdminView,
SuksesLoginKontributorView, SuntingProfilView,
ProfilAdminView, PostsView, SuntingProfilAdminView,
RevisiMateriView, ReqMateriView, KatalogPerKontributorView,
UploadMateriExcelView)
UploadMateriView, UploadMateriExcelView)
from app.forms import SuntingProfilForm
from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata
import pandas as pd
class DaftarKatalogTest(TestCase):
def test_daftar_katalog_url_exist(self):
......@@ -1658,3 +1666,318 @@ class RatingContributorTest(TransactionTestCase):
self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count())
self.client.post(url, data={"user": self.contributor.id, "score": 0})
self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count())
class UserDownloadHistoryTest(TestCase):
def setUp(self):
self.user1_credential = {
"email": "anonim1@gov.id",
"password": id_generator()
}
self.user1_anonim = get_user_model().objects.create_user(
**self.user1_credential, name="Anonim1")
self.user2_credential = {
"email": "anonim2@gov.id",
"password": id_generator()
}
self.user2_anonim = get_user_model().objects.create_user(
**self.user2_credential, name="Anonim2")
self.contributor_credential = {
"email": "kontributor@gov.id",
"password": id_generator()
}
self.contributor = get_user_model().objects.create_user(
**self.contributor_credential, name="Kontributor", is_contributor=True
)
self.client = Client()
content = b"Test file"
self.cover = SimpleUploadedFile(
"cover.jpg",
content
)
self.content = SimpleUploadedFile(
"content.txt",
content
)
Materi(title="Materi 1", author="Agas", uploader=self.contributor,
publisher="Kelas SC", descriptions="Deskripsi Materi 1",
status="PENDING", cover=self.cover, content=self.content).save()
self.materi1 = Materi.objects.first()
self.download_url = f"/materi/{self.materi1.id}/unduh"
self.history_url = "/download-history/"
def test_multiple_insert_download_statistic_with_user(self):
DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save()
num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
self.assertEqual(num_of_downloads, 1)
DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save()
num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
self.assertEqual(num_of_downloads, 2)
def test_download_statistics_bound_to_specific_user(self):
DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save()
num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
self.assertEqual(num_of_downloads, 1)
DownloadStatistics(materi=self.materi1).save()
num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
self.assertEqual(num_of_downloads, 1)
DownloadStatistics(materi=self.materi1, downloader=self.user2_anonim).save()
user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
user2_num_of_downloads = self.user2_anonim.riwayat_unduh.all().count()
self.assertEqual(user1_num_of_downloads, 1)
self.assertEqual(user2_num_of_downloads, 1)
def test_registered_user_download(self):
# Login
self.client.login(**self.user1_credential)
self.client.get(self.download_url)
num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
self.assertEqual(num_of_downloads, 1)
# Logout
self.client.logout()
def test_unregistered_user_download(self):
self.client.get(self.download_url)
downloaded_materi = self.client.session['downloaded_materi']
num_of_downloads = DownloadStatistics.objects.filter(
pk__in=downloaded_materi).count()
self.assertEqual(num_of_downloads, 1)
def test_registered_user_multiple_download(self):
# Login
self.client.login(**self.user1_credential)
self.client.get(self.download_url)
num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
self.assertEqual(num_of_downloads, 1)
self.client.get(self.download_url)
num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
self.assertEqual(num_of_downloads, 2)
# Logout
self.client.logout()
def test_unregistered_user_multiple_download(self):
self.client.get(self.download_url)
downloaded_materi = self.client.session['downloaded_materi']
num_of_downloads = DownloadStatistics.objects.filter(
pk__in=downloaded_materi).count()
self.assertEqual(num_of_downloads, 1)
self.client.get(self.download_url)
downloaded_materi = self.client.session['downloaded_materi']
num_of_downloads = DownloadStatistics.objects.filter(
pk__in=downloaded_materi).count()
self.assertEqual(num_of_downloads, 2)
def test_registered_user_doesnt_use_session_when_download(self):
# Login
self.client.login(**self.user1_credential)
self.client.get(self.download_url)
self.assertFalse('downloaded_materi' in self.client.session)
# Logout
self.client.logout()
def test_download_history_bound_to_specific_user(self):
# Login Anonym 1
self.client.login(**self.user1_credential)
self.client.get(self.download_url)
num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
self.assertEqual(num_of_downloads, 1)
# Logout Anonym 1
self.client.logout()
# Unregistered User download
self.client.get(self.download_url)
user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
downloaded_materi = self.client.session['downloaded_materi']
guest_num_of_downloads = DownloadStatistics.objects.filter(
pk__in=downloaded_materi).count()
self.assertEqual(user1_num_of_downloads, 1)
self.assertEqual(guest_num_of_downloads, 1)
# Login Anonym 2
self.client.login(**self.user2_credential)
self.client.get(self.download_url)
user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
user2_num_of_downloads = self.user2_anonim.riwayat_unduh.all().count()
self.assertEqual(user1_num_of_downloads, 1)
self.assertEqual(guest_num_of_downloads, 1)
self.assertEqual(user2_num_of_downloads, 1)
# Logout Anonym 2
self.client.logout()
class DownloadHistoryViewTest(TestCase):
def setUp(self):
self.user_credential = {
"email": "anonim1@gov.id",
"password": id_generator()
}
self.user_anonim = get_user_model().objects.create_user(
**self.user_credential, name="Anonim")
self.contributor_credential = {
"email": "kontributor@gov.id",
"password": id_generator()
}
self.contributor = get_user_model().objects.create_user(
**self.contributor_credential, name="Kontributor", is_contributor=True
)
self.client = Client()
content1 = b"Test file"
content2 = b"File Test"
self.cover1 = SimpleUploadedFile("cover1.jpg",content1)
self.content1 = SimpleUploadedFile("content1.txt",content1)
self.cover2 = SimpleUploadedFile("cover2.jpg",content2)
self.content2 = SimpleUploadedFile("content2.txt",content2)
self.materi1 = Materi.objects.create(title="Materi 1", author="Agas", uploader=self.contributor,
publisher="Kelas SC", descriptions="Deskripsi Materi 1",
status="PENDING", cover=self.cover1, content=self.content1)
self.materi2 = Materi.objects.create(title="Materi 2", author="Danin", uploader=self.contributor,
publisher="Kelas DDP", descriptions="Deskripsi Materi 2",
status="PENDING", cover=self.cover2, content=self.content2)
self.download_url1 = f"/materi/{self.materi1.id}/unduh"
self.download_url2 = f"/materi/{self.materi2.id}/unduh"
self.history_url = "/download-history/"
# Login
self.client.login(**self.user_credential)
def tearDown(self):
# Logout
self.client.logout()
def test_allow_registered_user(self):
response = self.client.get(self.history_url)
self.assertEqual(response.status_code, 200)
def test_allow_unregistered_user(self):
# Forced Logout
self.client.logout()
response = self.client.get(self.history_url)
self.assertEqual(response.status_code, 200)
def test_download_history_using_correct_template(self):
response = self.client.get(self.history_url)
self.assertTemplateUsed(response, "download_history.html")
def test_download_history_has_user_name(self):
response = self.client.get(self.history_url)
resp_html = response.content.decode('utf8')
self.assertIn(self.user_anonim.name, resp_html)
def test_registered_user_download_history_correctly_displayed(self):
self.client.get(self.download_url1)
self.client.get(self.download_url2)
self.client.get(self.download_url1)
jkt_timezone = timezone(TIME_ZONE)
download_history = self.user_anonim.riwayat_unduh.all()
response = self.client.get(self.history_url)
resp_html = response.content.decode('utf8')
for riwayat in download_history:
downloaded_materi = riwayat.materi
self.assertIn(downloaded_materi.title, resp_html)
self.assertIn(downloaded_materi.author, resp_html)
jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone)
self.assertIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html)
def test_unregistered_user_download_history_correctly_displayed(self):
self.client.logout()
self.client.get(self.download_url1)
self.client.get(self.download_url2)
self.client.get(self.download_url1)
jkt_timezone = timezone(TIME_ZONE)
response = self.client.get(self.history_url)
resp_html = response.content.decode('utf8')
for riwayat_id in self.client.session['downloaded_materi']:
riwayat = DownloadStatistics.objects.get(pk=riwayat_id)
downloaded_materi = riwayat.materi
self.assertIn(downloaded_materi.title, resp_html)
self.assertIn(downloaded_materi.author, resp_html)
jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone)
self.assertIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html)
def test_download_history_not_display_if_user_changed(self):
self.client.get(self.download_url1)
self.client.get(self.download_url2)
self.client.get(self.download_url1)
self.client.logout()
jkt_timezone = timezone(TIME_ZONE)
download_history = self.user_anonim.riwayat_unduh.all()
response = self.client.get(self.history_url)
resp_html = response.content.decode('utf8')
for riwayat in download_history:
downloaded_materi = riwayat.materi
self.assertNotIn(downloaded_materi.title, resp_html)
self.assertNotIn(downloaded_materi.author, resp_html)
jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone)
self.assertNotIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html)
def test_unregistered_user_download_history_wont_be_saved_if_user_changes(self):
self.client.logout()
self.client.get(self.download_url1)
self.client.get(self.download_url2)
self.client.get(self.download_url1)
self.client.get(self.history_url)
self.client.login(**self.user_credential)
self.client.logout()
self.assertFalse('downloaded_materi' in self.client.session)
def test_download_history_sorted_by_download_time(self):
# download with 1 second interval to differ download time
self.client.get(self.download_url1)
sleep(1)
self.client.get(self.download_url2)
sleep(1)
self.client.get(self.download_url1)
sleep(1)
self.client.get(self.download_url2)
response = self.client.get(self.history_url)
resp_html = response.content.decode('utf8')
table_html = ("<table" + resp_html.split("<table")[1]).split("</table>")[0] + "</table>"
soup = BeautifulSoup(table_html, 'html.parser')
histories_html = soup.find('tbody').find_all('tr')
prev_timestamp = None
for riwayat_html in histories_html:
materi_data = riwayat_html.find_all("td")
date_format = "%d %B %Y %H:%M:%S"
materi_timestamp = datetime.strptime(materi_data[2].get_text(), date_format)
if prev_timestamp:
self.assertTrue(prev_timestamp > materi_timestamp)
prev_timestamp = materi_timestamp
def test_no_history_display_message(self):
no_history_msg = "Anda belum mengunduh materi. Silahkan unduh materi yang anda butuhkan"
response = self.client.get(self.history_url)
resp_html = response.content.decode('utf8')
self.assertIn(no_history_msg, resp_html)
\ No newline at end of file
......@@ -2,7 +2,7 @@ from django.urls import path, re_path
from app import views
from app.views import (DashboardKontributorView, ProfilKontributorView,
SuksesLoginAdminView, SuksesLoginKontributorView,
SuksesLoginAdminView, SuksesLoginKontributorView, DownloadHistoryView,
SuntingProfilView, UploadMateriHTML, UploadMateriView, UploadMateriExcelView,
ProfilAdminView, PostsView, SuntingProfilAdminView,
ReqMateriView, KatalogPerKontributorView)
......@@ -16,6 +16,7 @@ urlpatterns = [
path("materi/<int:pk>/unduh", views.download_materi, name="download-materi"),
path("materi/<int:pk>/view", views.view_materi, name="view-materi"),
path("dashboard/", DashboardKontributorView.as_view(), name="dashboard"),
path("download-history/", DownloadHistoryView.as_view(), name="download-history"),
path("revisi/materi/<int:pk>/", views.RevisiMateriView.as_view(), name="revisi"),
path("unggah/", UploadMateriView.as_view(), name="unggah"),
path("unggah_excel/", UploadMateriExcelView.as_view(), name="unggah_excel"),
......
......@@ -248,7 +248,14 @@ def download_materi(request, pk):
response = HttpResponse(fh.read(), content_type=mimetype[0])
response["Content-Disposition"] = "attachment; filename=" + \
os.path.basename(file_path)
DownloadStatistics(materi=materi).save()
if request.user.is_authenticated:
DownloadStatistics(materi=materi, downloader=request.user).save()
else:
downloaded_materi = DownloadStatistics.objects.create(materi=materi)
if 'downloaded_materi' not in request.session:
request.session['downloaded_materi'] = []
request.session['downloaded_materi'].append(downloaded_materi.pk)
request.session.modified = True
return response
except Exception as e:
raise Http404("File tidak dapat ditemukan.")
......@@ -750,3 +757,27 @@ def pages(request):
template = loader.get_template("error-404.html")
return HttpResponse(template.render(context, request))
class DownloadHistoryView(TemplateView):
template_name = "download_history.html"
def get_context_data(self, **kwargs):
context = super(DownloadHistoryView,
self).get_context_data(**kwargs)
return context
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
if request.user.is_authenticated:
current_user = self.request.user
riwayat_list = current_user.riwayat_unduh.all().order_by('-timestamp')
context["riwayat_list"] = riwayat_list
context["user_name"] = current_user.name
else:
has_downloaded_materi = 'downloaded_materi' in request.session
downloaded_materi = request.session['downloaded_materi'] if has_downloaded_materi else []
riwayat_list = DownloadStatistics.objects.filter(
pk__in=downloaded_materi).order_by('-timestamp')
context["riwayat_list"] = riwayat_list