diff --git a/administration/migrations/0008_auto_20201009_1829.py b/administration/migrations/0008_auto_20201009_1829.py new file mode 100644 index 0000000000000000000000000000000000000000..0302d90c1ccf01e6a7a1b313ed4ce7ee95c79573 --- /dev/null +++ b/administration/migrations/0008_auto_20201009_1829.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2020-10-09 11:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('administration', '0007_auto_20200929_1218'), + ] + + operations = [ + migrations.AlterField( + model_name='verificationreport', + name='status', + field=models.CharField(choices=[('PENDING', 'Diproses'), ('APPROVE', 'Diterima'), ('DISAPPROVE', 'Ditolak'), ('REVISION', 'Perbaikan'), ('BLOCKED', 'Diblokir')], default='PENDING', max_length=30), + ), + ] diff --git a/administration/templates/administration/includes/sidebar.html b/administration/templates/administration/includes/sidebar.html index 6f639a5e1be5c11e6aa51de3c22edc30491d636f..d8829433f64e515a2c8417285ed53c8792fbfd93 100644 --- a/administration/templates/administration/includes/sidebar.html +++ b/administration/templates/administration/includes/sidebar.html @@ -21,6 +21,11 @@ <span>Statistik Materi</span></a> </li> + <li class="nav-item"> + <a class="nav-link" href="/administration/laporan-materi/"> + <span>Laporan Materi</span></a> + </li> + <!-- Divider --> <hr class="sidebar-divider my-0"> diff --git a/administration/templates/laporan_materi.html b/administration/templates/laporan_materi.html new file mode 100644 index 0000000000000000000000000000000000000000..5b92f17dce51124d0ff32ba034ac282131d2954e --- /dev/null +++ b/administration/templates/laporan_materi.html @@ -0,0 +1,114 @@ +{% extends 'administration/base_administrasi2.html' %} +{% load static %} + +{% block title %} +<title>Laporan Materi | Digipus</title> +{% endblock %} + +{% block content %} +<!-- Page Heading --> +<h1 class="h3 mb-2 text-gray-800">Laporan Materi</h1> +<p class="mb-4">Tekan blokir materi untuk memblokir materi.<br>Klik lihat laporan untuk melihat laporan dari pelapor.</ehp> + +<!-- DataTales Example --> +<div class="card shadow mb-4"> + <div class="card-header py-3"> + <div class="d-flex"> + <div class="mr-auto p-2"> + <h6 class="m-0 font-weight-bold text-primary">Materi yang Dilaporkan</h6> + </div> + </div> + </div> + <div class="card-body"> + <div class="table-responsive"> + <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> + <thead> + <tr> + <th scope="col">Judul</th> + <th scope="col">Jumlah Laporan</th> + <th scope="col">Pilihan</th> + </tr> + </thead> + <tfoot> + <tr> + <th scope="col">Judul</th> + <th scope="col">Jumlah Laporan</th> + <th scope="col">Pilihan</th> + </tr> + </tr> + </tfoot> + <tbody> + {% for materi in materi_dilaporkan %} + <tr> + <td>{{ materi.title }}</td> + <td>{{ materi.jumlah_laporan }}</td> + <td class="verif-buttons"> + <a href="{% url 'administration:laporan-materi-detail' materi.id %}" class="accept-button button-decoration">Lihat Laporan</a> + <button type="button" class="reject-button button-decoration" data-toggle="modal" data-target="#confirmModal{{ materi.id }}">Blokir</button> + <div class="modal fade" id="confirmModal{{ materi.id }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="exampleModalLabel">Konfirmasi Pemblokiran Materi</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <p>Anda akan memblokir materi + <br><br><span style="text-transform: lowercase; font-weight: 900;">{{ materi.title }}</span><br> + email kontributor: {{ materi.uploader.email }}</p> + </div> + <div class="modal-footer"> + <a href="{% url 'administration:blok-materi' materi.id %}" id="btn-hapus-{{ materi.id }}" type="button" class="btn btn-secondary">Blokir</a> + <button type="button" class="btn btn-danger" data-dismiss="modal">Batal</button> + </div> + </div> + </div> + </div> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> +</div> +<div class="card shadow mb-4"> + <div class="card-header py-3"> + <h6 class="m-0 font-weight-bold text-primary" id="titleTabelPending">Materi yang Diblokir</h6> + </div> + <div class="card-body"> + <div class="table-responsive"> + <table class="table table-bordered" id="dataTablePending" aria-describedby="titleTabelPending"> + {% if not materi_diblokir %} + Tidak ada materi yang diblokir + {% else %} + <thead> + <tr> + <th scope="col">Judul</th> + <th scope="col">Kontributor</th> + </tr> + </thead> + <tfoot> + <tr> + <th scope="col">Judul</th> + <th scope="col">Kontributor</th> + </tr> + </tfoot> + <tbody> + {% for materi in materi_diblokir %} + <tr> + <td>{{ materi.title }}</td> + <td><a href="{% url 'katalog-per-kontributor' materi.uploader.email %}"> + {{ materi.uploader.name }} + </a></td> + </tr> + {% endfor %} + </tbody> + {% endif %} + </table> + </div> + </div> +</div> +{% endblock %} \ No newline at end of file diff --git a/administration/templates/laporan_materi_detail.html b/administration/templates/laporan_materi_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..7507bfa6fc85f4d8c682bca6a2e564a30ac64197 --- /dev/null +++ b/administration/templates/laporan_materi_detail.html @@ -0,0 +1,55 @@ +{% extends 'administration/base_administrasi2.html' %} +{% load static %} + +{% block title %} +<title>Laporan Materi | Digipus</title> +{% endblock %} + +{% block content %} +<!-- Page Heading --> +<h1 class="h3 mb-2 text-gray-800">Laporan Materi</h1> +<p class="mb-4">Klik tolak untuk menolak laporan.</ehp> + +<!-- DataTales Example --> +<div class="card shadow mb-4"> + <div class="card-header py-3"> + <h6 class="m-0 font-weight-bold text-primary" id="titleTabelPending"> + <a href="{% url 'detail-materi' materi.id %}">{{ materi.title }}</a></h6> + </div> + <div class="card-body"> + <div class="table-responsive"> + <table class="table table-bordered" id="dataTablePending" aria-describedby="titleTabelPending"> + {% if not laporan_materi %} + Tidak ada laporan untuk materi {{ materi.title }} + {% else %} + <thead> + <tr> + <th scope="col">Pelapor</th> + <th scope="col">Alasan Melapor</th> + <th scope="col">Pilihan</th> + </tr> + </thead> + <tfoot> + <tr> + <th scope="col">Pelapor</th> + <th scope="col">Alasan Melaporr</th> + <th scope="col">Pilihan</th> + </tr> + </tfoot> + <tbody> + {% for laporan in laporan_materi %} + <tr> + <td><a href="{% url 'katalog-per-kontributor' laporan.user.email %}">{{ laporan.user.name }}</a></td> + <td>{{ laporan.laporan }}</td> + <td class="verif-buttons"> + <a href="{% url 'administration:tolak-laporan' laporan.id %}" class="reject-button button-decoration">Tolak</a> + </td> + </tr> + {% endfor %} + </tbody> + {% endif %} + </table> + </div> + </div> +</div> +{% endblock %} \ No newline at end of file diff --git a/administration/templates/registrasi_admin.html b/administration/templates/registrasi_admin.html index d4b1011cb70f5b9c1a8df0ebc4afdfb5fc857500..7d3a0151bb82231d953cbcbc96034ec44a3a8710 100644 --- a/administration/templates/registrasi_admin.html +++ b/administration/templates/registrasi_admin.html @@ -55,6 +55,11 @@ <span>Statistik Materi</span></a> </li> + <li class="nav-item"> + <a class="nav-link" href="/administration/laporan-materi/"> + <span>Laporan Materi</span></a> + </li> + <!-- Divider --> <hr class="sidebar-divider my-0"> diff --git a/administration/templates/setting_verifikasi.html b/administration/templates/setting_verifikasi.html index e36b1ce9153632ef229f100e58ea25b6cb0c6870..f21254ef032ac3fdae82641d1827a99e6c5403fd 100644 --- a/administration/templates/setting_verifikasi.html +++ b/administration/templates/setting_verifikasi.html @@ -64,6 +64,11 @@ <span>Statistik Materi</span></a> </li> + <li class="nav-item"> + <a class="nav-link" href="/administration/laporan-materi/"> + <span>Laporan Materi</span></a> + </li> + <!-- Divider --> <hr class="sidebar-divider my-0"> diff --git a/administration/templates/settings.html b/administration/templates/settings.html index 2df695227dacf6b33136ffbaec744bfc59058910..6381a4e088abdaa87f41c4d531d01183de455015 100644 --- a/administration/templates/settings.html +++ b/administration/templates/settings.html @@ -69,6 +69,11 @@ > </li> + <li class="nav-item"> + <a class="nav-link" href="/administration/laporan-materi/"> + <span>Laporan Materi</span></a> + </li> + <!-- Divider --> <hr class="sidebar-divider my-0" /> diff --git a/administration/tests.py b/administration/tests.py index fff6daef00396bad3176cbc8c260e3d7f6fd27b4..eedbf4c10ca54a788845f82f760fad8496430c14 100644 --- a/administration/tests.py +++ b/administration/tests.py @@ -6,7 +6,7 @@ from django.urls import resolve from administration import models, views from administration.utils import id_generator from administration.forms import EditAdminStatusForm -from app.models import Category, Materi +from app.models import Category, Materi, LaporanMateri from authentication.models import User from bs4 import BeautifulSoup @@ -1289,3 +1289,291 @@ class KelolaMateriViewTests(TestCase): response.context['materi_list'].filter(id=invalid_id_materi), "Materi_list including materi that should not be exists" ) + + +class LaporanMateriTest(TestCase): + def setUp(self): + self.client = Client() + self.admin_credential = { + "email": "admin@gov.id", + "password": id_generator() + } + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": id_generator() + } + self.admin = get_user_model().objects.create_user( + **self.admin_credential, name="Admin", is_admin=True) + self.contributor = get_user_model().objects.create_user( + **self.contributor_credential, name="Kontributor", is_contributor=True + ) + self.cover = SimpleUploadedFile("cover.jpg", b"Test file") + self.content = SimpleUploadedFile("content.txt", b"Test file") + self.materi = Materi.objects.create( + title="Ayat-ayat cinta", author="Axel", uploader=self.contributor, + publisher="X Prod", descriptions="Kisah kasih cinta", + status="APPROVE", cover=self.cover, content=self.content + ) + self.materi2 = Materi.objects.create( + title="Cinta ayat-ayat", author="Lexa", uploader=self.contributor, + publisher="Y Prod", descriptions="Cinta kasih kisah", + status="APPROVE", cover=self.cover, content=self.content + ) + self.laporan = LaporanMateri.objects.create( + laporan="materi ini kopas orang lain", user_id=self.contributor.id, + materi_id=self.materi.id + ) + self.laporan2 = LaporanMateri.objects.create( + laporan="materi ini kopas orang lain", user_id=self.contributor.id, + materi_id=self.materi2.id + ) + self.url = "/administration/laporan-materi/" + + def test_admin_can_access_laporanmateri_page(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_contributor_cant_access_laporanmateri_page(self): + self.client.login(**self.contributor_credential) + response = self.client.get(self.url) + self.assertEqual(response.status_code, 403) + self.client.logout() + + def test_anonymous_cant_access_laporanmateri_page(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 403) + + def test_reported_materi_exist(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.url) + bs = BeautifulSoup(response.rendered_content, features="html.parser") + el = str(bs.findAll("td")) + self.assertIn(self.materi.title, el) + self.assertIn(self.materi2.title, el) + self.client.logout() + + +class LaporanMateriDetailTest(TestCase): + def setUp(self): + self.client = Client() + self.admin_credential = { + "email": "admin@gov.id", + "password": id_generator() + } + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": id_generator() + } + self.admin = get_user_model().objects.create_user( + **self.admin_credential, name="Admin", is_admin=True) + self.contributor = get_user_model().objects.create_user( + **self.contributor_credential, name="Kontributor", is_contributor=True + ) + self.cover = SimpleUploadedFile("cover.jpg", b"Test file") + self.content = SimpleUploadedFile("content.txt", b"Test file") + self.materi = Materi.objects.create( + title="Ayat-ayat cinta", author="Axel", uploader=self.contributor, + publisher="X Prod", descriptions="Kisah kasih cinta", + status="APPROVE", cover=self.cover, content=self.content + ) + self.materi2 = Materi.objects.create( + title="Cinta ayat-ayat", author="Lexa", uploader=self.contributor, + publisher="Y Prod", descriptions="Cinta kasih kisah", + status="APPROVE", cover=self.cover, content=self.content + ) + self.laporan = LaporanMateri.objects.create( + laporan="materi ini kopas orang lain", user_id=self.contributor.id, + materi_id=self.materi.id + ) + self.laporan2 = LaporanMateri.objects.create( + laporan="materi ini kopas orang lain", user_id=self.contributor.id, + materi_id=self.materi.id + ) + self.url = "/administration/laporan-materi/" + + def test_admin_can_access_laporanmateridetail_page(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.url + str(self.laporan.materi_id) + '/') + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_contributor_cant_access_laporanmateridetail_page(self): + self.client.login(**self.contributor_credential) + response = self.client.get(self.url + str(self.laporan.materi_id) + '/') + self.assertEqual(response.status_code, 403) + self.client.logout() + + def test_anonymous_cant_access_laporanmateridetail_page(self): + response = self.client.get(self.url + str(self.laporan.materi_id) + '/') + self.assertEqual(response.status_code, 403) + + def test_cant_access_unknown_laporanmaterialdetail_page(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.url + '100/') + self.assertEqual(response.status_code, 404) + self.client.logout() + + def test_reported_report_exist(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.url + str(self.laporan.materi_id) + '/') + bs = BeautifulSoup(response.rendered_content, features="html.parser") + el = str(bs.findAll("td")) + self.assertIn(self.laporan.user.name, el) + self.assertIn(self.laporan.laporan, el) + self.assertIn(self.laporan2.user.name, el) + self.assertIn(self.laporan2.laporan, el) + self.client.logout() + + +class BlockMateriTest(TestCase): + def setUp(self): + self.client = Client() + self.admin_credential = { + "email": "admin@gov.id", + "password": id_generator() + } + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": id_generator() + } + self.admin = get_user_model().objects.create_user( + **self.admin_credential, name="Admin", is_admin=True + ) + self.contributor = get_user_model().objects.create_user( + **self.contributor_credential, name="Kontributor", is_contributor=True + ) + self.cover = SimpleUploadedFile("cover.jpg", b"Test file") + self.content = SimpleUploadedFile("content.txt", b"Test file") + self.materi = Materi.objects.create( + title="Ayat-ayat cinta", author="Axel", uploader=self.contributor, + publisher="X Prod", descriptions="Kisah kasih cinta", + status="APPROVE", cover=self.cover, content=self.content + ) + self.materi2 = Materi.objects.create( + title="Cinta ayat-ayat", author="Lexa", uploader=self.contributor, + publisher="Y Prod", descriptions="Cinta kasih kisah", + status="APPROVE", cover=self.cover, content=self.content + ) + self.materi3 = Materi.objects.create( + title="Ayat cinta ayat", author="ealx", uploader=self.contributor, + publisher="Z Prod", descriptions="Kisah cinta kasih", + status="PENDING", cover=self.cover, content=self.content + ) + self.laporan = LaporanMateri.objects.create( + laporan="materi ini kopas orang lain", user_id=self.contributor.id, + materi_id=self.materi.id + ) + self.url = "/administration/blok-materi/" + + def test_admin_can_block_materi(self): + self.client.login(**self.admin_credential) + self.assertEqual(self.materi.status, "APPROVE") + response = self.client.get(self.url + str(self.materi.id) +'/') + self.assertEqual(response.status_code, 302) + self.materi.refresh_from_db() + self.assertEqual(self.materi.status, "BLOCKED") + self.client.logout() + + def test_contributor_cant_block_materi(self): + self.client.login(**self.contributor_credential) + self.assertEqual(self.materi.status, "APPROVE") + response = self.client.get(self.url + str(self.materi.id) +'/') + self.assertEqual(response.status_code, 403) + self.materi.refresh_from_db() + self.assertEqual(self.materi.status, "APPROVE") + self.client.logout() + + def test_anonymous_cant_block_materi(self): + self.assertEqual(self.materi.status, "APPROVE") + response = self.client.get(self.url + str(self.materi.id) +'/') + self.assertEqual(response.status_code, 403) + self.materi.refresh_from_db() + self.assertEqual(self.materi.status, "APPROVE") + + def test_cant_block_unreported_materi(self): + self.client.login(**self.admin_credential) + self.assertEqual(self.materi2.status, "APPROVE") + response = self.client.get(self.url + str(self.materi2.id) +'/') + self.assertEqual(response.status_code, 302) + self.materi2.refresh_from_db() + self.assertEqual(self.materi2.status, "APPROVE") + self.client.logout() + + def test_cant_block_non_approve_status_materi(self): + self.client.login(**self.admin_credential) + self.assertEqual(self.materi3.status, "PENDING") + response = self.client.get(self.url + str(self.materi3.id) +'/') + self.assertEqual(response.status_code, 302) + self.materi3.refresh_from_db() + self.assertEqual(self.materi3.status, "PENDING") + self.client.logout() + + def test_cant_block_unknown_materi(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.url + '100/') + self.assertEqual(response.status_code, 404) + self.client.logout() + + +class RejectReportTest(TestCase): + def setUp(self): + self.client = Client() + self.admin_credential = { + "email": "admin@gov.id", + "password": id_generator() + } + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": id_generator() + } + self.admin = get_user_model().objects.create_user( + **self.admin_credential, name="Admin", is_admin=True + ) + self.contributor = get_user_model().objects.create_user( + **self.contributor_credential, name="Kontributor", is_contributor=True + ) + self.cover = SimpleUploadedFile("cover.jpg", b"Test file") + self.content = SimpleUploadedFile("content.txt", b"Test file") + self.materi = Materi.objects.create( + title="Ayat-ayat cinta", author="Axel", uploader=self.contributor, + publisher="X Prod", descriptions="Kisah kasih cinta", + status="APPROVE", cover=self.cover, content=self.content + ) + self.laporan = LaporanMateri.objects.create( + laporan="materi ini kopas orang lain", user_id=self.contributor.id, + materi_id=self.materi.id + ) + self.url = "/administration/tolak-laporan/" + + def test_admin_can_reject_report(self): + self.client.login(**self.admin_credential) + self.assertEqual(self.laporan.is_rejected, False) + response = self.client.get(self.url + str(self.laporan.id) +'/') + self.assertEqual(response.status_code, 302) + self.laporan.refresh_from_db() + self.assertEqual(self.laporan.is_rejected, True) + self.client.logout() + + def test_contributor_cant_reject_report(self): + self.client.login(**self.contributor_credential) + self.assertEqual(self.laporan.is_rejected, False) + response = self.client.get(self.url + str(self.laporan.id) +'/') + self.assertEqual(response.status_code, 403) + self.laporan.refresh_from_db() + self.assertEqual(self.laporan.is_rejected, False) + self.client.logout() + + def test_anonymous_cant_reject_report(self): + self.assertEqual(self.laporan.is_rejected, False) + response = self.client.get(self.url + str(self.laporan.id) +'/') + self.assertEqual(response.status_code, 403) + self.laporan.refresh_from_db() + self.assertEqual(self.laporan.is_rejected, False) + + def test_cant_reject_unknown_report(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.url + '100/') + self.assertEqual(response.status_code, 404) + self.client.logout() diff --git a/administration/urls.py b/administration/urls.py index b6970aa97471877910b11ac5ad026cb5d2b14f50..22ba7a66b15ddab39b4e480104eb97d9f11fe12d 100644 --- a/administration/urls.py +++ b/administration/urls.py @@ -9,7 +9,9 @@ from administration.views import VerificationView, DetailVerificationView, \ delete_verification, StatisticsView, \ StatisticApiView, EditCategoryView, \ EditAdminStatusView, delete_category, \ - generatedummy, KelolaMateriView + generatedummy, KelolaMateriView, \ + LaporanMateriView, LaporanMateriDetailView, \ + tolak_laporan, blok_materi app_name = "administration" @@ -36,5 +38,10 @@ urlpatterns = [ path("hapus-admin/<int:pk>/", delete_admin), path("hapus-kontributor/<int:pk>/", delete_contributor), path("kelola-materi/", KelolaMateriView.as_view()), + path("laporan-materi/", LaporanMateriView.as_view()), + path("laporan-materi/<int:pk>/", + LaporanMateriDetailView.as_view(), name="laporan-materi-detail"), + path("tolak-laporan/<int:pk>/", tolak_laporan, name="tolak-laporan"), + path("blok-materi/<int:pk>/", blok_materi, name="blok-materi"), path("generate-dummy", generatedummy), ] diff --git a/administration/views.py b/administration/views.py index ee97ade6000afb612d6318e07f5a4ec815b91169..864f01e4963587b36be31a21ab89cf0284ad9e97 100644 --- a/administration/views.py +++ b/administration/views.py @@ -3,6 +3,7 @@ from datetime import datetime, date from django.core.exceptions import PermissionDenied from django.contrib.auth.hashers import make_password +from django.db.models import Count from django.http import HttpResponseRedirect, JsonResponse from django.shortcuts import get_object_or_404, render from django.views.generic import TemplateView, View @@ -11,7 +12,7 @@ from django.utils import timezone, lorem_ipsum from dateutil.relativedelta import relativedelta from administration.models import VerificationReport, VerificationSetting, DeletionHistory from administration.forms import CategoryForm, VerificationSettingForm, RegistrasiAdminForm, PeriodForm, EditAdminStatusForm -from app.models import Category, Materi, ViewStatistics, DownloadStatistics, Comment, Like, getRandomColor +from app.models import Category, Materi, ViewStatistics, DownloadStatistics, Comment, Like, getRandomColor, LaporanMateri from app.views import permission_denied from authentication.models import User from datetime import datetime @@ -21,6 +22,7 @@ from administration.utils import generate_time_step from django.core import management ADMINISTRATION_MANAGEMENT = "/administration/kelola-admin/" +ADMINISTRATION_REPORT = "/administration/laporan-materi/" def get_start_end_date(period): if period == 'ALL_TIME': @@ -644,3 +646,58 @@ class KelolaMateriView(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) return self.render_to_response(context) + +class LaporanMateriView(TemplateView): + template_name = "laporan_materi.html" + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated or not request.user.is_admin: + raise PermissionDenied(request) + return super(LaporanMateriView, self).dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + context = super(LaporanMateriView, self).get_context_data(**kwargs) + context["laporan_materi"] = LaporanMateri.objects.filter(is_rejected=False) + context["materi_dilaporkan"] = Materi.objects \ + .filter(laporanmateri__id__in=context["laporan_materi"], status="APPROVE") \ + .annotate(jumlah_laporan=Count('laporanmateri__materi_id')) \ + .order_by('-jumlah_laporan') \ + .distinct() + context["materi_diblokir"] = Materi.objects.filter(status="BLOCKED") + return self.render_to_response(context=context) + +class LaporanMateriDetailView(TemplateView): + template_name = "laporan_materi_detail.html" + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated or not request.user.is_admin: + raise PermissionDenied(request) + return super(LaporanMateriDetailView, self).dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + context = super(LaporanMateriDetailView, self).get_context_data(**kwargs) + context["laporan_materi"] = LaporanMateri.objects.filter(materi_id=kwargs["pk"], is_rejected=False) + context["materi"] = get_object_or_404(Materi, id=kwargs["pk"]) + return self.render_to_response(context=context) + +def blok_materi(request, *args, **kwargs): + if not request.user.is_authenticated or not request.user.is_admin: + raise PermissionDenied(request) + + materi = get_object_or_404(Materi, pk=kwargs["pk"]) + if materi.status == "APPROVE" and LaporanMateri.objects.filter(is_rejected=False, materi_id=materi.id): + materi.status = "BLOCKED" + materi.save() + + return HttpResponseRedirect(ADMINISTRATION_REPORT) + +def tolak_laporan(request, *args, **kwargs): + if not request.user.is_authenticated or not request.user.is_admin: + raise PermissionDenied(request) + + laporan = get_object_or_404(LaporanMateri, pk=kwargs["pk"]) + laporan.is_rejected = True + laporan.save() + + return HttpResponseRedirect(ADMINISTRATION_REPORT + str(laporan.materi_id)) + diff --git a/app/migrations/0019_auto_20201009_1829.py b/app/migrations/0019_auto_20201009_1829.py new file mode 100644 index 0000000000000000000000000000000000000000..15bec11ed71a0b3062e68aeceda2e7434ec804e8 --- /dev/null +++ b/app/migrations/0019_auto_20201009_1829.py @@ -0,0 +1,34 @@ +# Generated by Django 3.1 on 2020-10-09 11:29 + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('app', '0018_merge_20201009_0700'), + ] + + operations = [ + migrations.AlterField( + model_name='materi', + name='status', + field=models.CharField(choices=[('PENDING', 'Diproses'), ('APPROVE', 'Diterima'), ('DISAPPROVE', 'Ditolak'), ('REVISION', 'Perbaikan'), ('BLOCKED', 'Diblokir')], default='PENDING', max_length=30), + ), + migrations.CreateModel( + name='LaporanMateri', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('laporan', models.TextField(default='', validators=[django.core.validators.MinValueValidator(30), django.core.validators.MaxValueValidator(120)])), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), + ('is_rejected', models.BooleanField(default=False)), + ('materi', models.ForeignKey(max_length=120, on_delete=django.db.models.deletion.CASCADE, to='app.materi')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/app/migrations/0019_materi__search_vector.py b/app/migrations/0019_materi__search_vector.py new file mode 100644 index 0000000000000000000000000000000000000000..bd148040bd07b733963274efadcb1ad9995396c7 --- /dev/null +++ b/app/migrations/0019_materi__search_vector.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1 on 2020-10-09 11:19 + +import django.contrib.postgres.search +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0018_merge_20201009_0700'), + ] + + operations = [ + migrations.AddField( + model_name='materi', + name='_search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + ] diff --git a/app/migrations/0020_merge_20201009_2039.py b/app/migrations/0020_merge_20201009_2039.py new file mode 100644 index 0000000000000000000000000000000000000000..80867b028075f48fb2cd1b5838561fe9b20aa4cc --- /dev/null +++ b/app/migrations/0020_merge_20201009_2039.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1 on 2020-10-09 13:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0019_materi__search_vector'), + ('app', '0019_auto_20201009_1829'), + ] + + operations = [ + ] diff --git a/app/models.py b/app/models.py index b0333c1da1f631395826008f2b1a75437535b41c..58c85f2772eb29725e6494091137d7c505aeaed8 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,6 @@ import random +from django.contrib.postgres import search from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models @@ -12,6 +13,7 @@ VERIFICATION_STATUS = [ ("APPROVE", "Diterima"), ("DISAPPROVE", "Ditolak"), ("REVISION", "Perbaikan"), + ("BLOCKED", "Diblokir"), ] @@ -33,35 +35,59 @@ class Category(models.Model): return self.name +class MateriManager(models.Manager): + def search(self, search_text): + search_vector = search.SearchVector("title", weight="A") + search_query = search.SearchQuery(search_text) + + search_rank = search.SearchRank(search_vector, search_query) + + search_result = ( + self.get_queryset().filter(_search_vector=search_query).annotate(rank=search_rank).order_by("-rank") + ) + + return search_result + + class Materi(models.Model): cover = models.ImageField() content = models.FileField() - title = models.CharField(max_length=50, default='Judul') - author = models.CharField(max_length=30, default='Penyusun') + title = models.CharField(max_length=50, default="Judul") + author = models.CharField(max_length=30, default="Penyusun") uploader = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) publisher = models.CharField(max_length=30, default="Penerbit") pages = models.IntegerField(default=0) descriptions = models.TextField(default="Deskripsi") - status = models.CharField( - max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0]) + status = models.CharField(max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0]) categories = models.ManyToManyField(Category) date_created = models.DateTimeField(default=timezone.now) date_modified = models.DateTimeField(auto_now=True) + _search_vector = search.SearchVectorField(null=True, editable=False) + + objects = MateriManager() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + + if "update_fields" not in kwargs or "_search_vector" not in kwargs["update_fields"]: + self._search_vector = search.SearchVector("title", weight="A") + self.save(update_fields=["_search_vector"]) + @property def is_published(self): published = False if self.verificationreport_set.exists(): - report = self.verificationreport_set.latest('timestamp') - published = True if report.status == 'Diterima' else False + report = self.verificationreport_set.latest("timestamp") + published = True if report.status == "Diterima" else False return published @property def published_date(self): published_date = None if self.verificationreport_set.exists(): - report = self.verificationreport_set.latest('timestamp') - if report.status == 'Diterima': + report = self.verificationreport_set.latest("timestamp") + if report.status == "Diterima": published_date = report.timestamp return published_date @@ -82,8 +108,7 @@ class Comment(models.Model): profile = models.CharField(max_length=100, default=getRandomColor) comment = models.CharField(max_length=240, default="comments") materi = models.ForeignKey(Materi, models.SET_NULL, null=True) - user = models.ForeignKey( - User, on_delete=models.SET_NULL, blank=True, null=True) + user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True) timestamp = models.DateTimeField(default=timezone.now) def __str__(self): @@ -95,22 +120,20 @@ class Like(models.Model): timestamp = models.DateTimeField(default=timezone.now) session_id = models.CharField(max_length=32, blank=False) + class ReqMaterial(models.Model): title = models.CharField(max_length=100) timestamp = models.DateTimeField(default=timezone.now) class ViewStatistics(models.Model): - materi = models.ForeignKey( - Materi, models.SET_NULL, null=True, related_name="baca") + materi = models.ForeignKey(Materi, models.SET_NULL, null=True, related_name="baca") timestamp = models.DateTimeField(default=timezone.now) 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") + 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) @@ -158,4 +181,12 @@ class RatingContributor(models.Model): if 1 <= self.score <= 5: super().save(force_insert, force_update, using, update_fields) else: - raise ValidationError("Rating score must be integer between 1-5") \ No newline at end of file + raise ValidationError("Rating score must be integer between 1-5") + + +class LaporanMateri(models.Model): + materi = models.ForeignKey(Materi, on_delete=models.CASCADE, max_length=120) + user = models.ForeignKey(User, on_delete=models.CASCADE) + laporan = models.TextField(validators=[MinValueValidator(30), MaxValueValidator(120)], default="") + timestamp = models.DateTimeField(default=timezone.now) + is_rejected = models.BooleanField(default=False) diff --git a/app/tests.py b/app/tests.py index 69dc1d1ccca8fb6288215680807d03fa5d5c617f..f32aaf82aa3344e5128895b4cd64657f0eca4774 100644 --- a/app/tests.py +++ b/app/tests.py @@ -33,7 +33,7 @@ from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri, SuksesLoginKontributorView, SuntingProfilView, ProfilAdminView, PostsView, SuntingProfilAdminView, RevisiMateriView, ReqMateriView, KatalogPerKontributorView, - UploadMateriView, UploadMateriExcelView) + UploadMateriView, UploadMateriExcelView, MateriFavorite) from app.forms import SuntingProfilForm from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata @@ -2163,3 +2163,41 @@ class MateriModelTest(TestCase): Like.objects.create(timestamp=datetime.now(), materi=self.materi, session_id="dummysessionid2") self.assertEqual(2, self.materi.like_count) + +class MateriFavoriteTest(TestCase): + + def _request_as_user(self): + self.client.login(**self.user_credentials) + return self.client.get(self.url) + + def test_url_resolves_to_favorite_view(self): + found = resolve(self.url) + self.assertEqual(found.func.__name__, MateriFavorite.as_view().__name__) + + def test_returns_200_on_authenticated_access(self): + response = self._request_as_user() + self.assertEqual(response.status_code, 200) + + def test_returns_403_on_unauthenticated_access(self): + response = self.client.get(self.url) + self.assertRaises(PermissionDenied) + self.assertEqual(response.status_code, 403) + + html = response.content.decode("utf-8") + self.assertIn(ERROR_403_MESSAGE, html) + + def test_returns_correct_template(self): + response = self._request_as_user() + self.assertTemplateUsed(response, "user_favorite_materi.html") + + # def test_success_returns_correct_comment_post_groupings_by_context(self): + # post_comment_group_dict = self.data + + # response = self._request_as_user() + + # response_user = response.context_data["user"] + # self.assertEqual(response_user, self.user) + + # response_data = response.context_data["likes"] + # actual_data = post_comment_group_dict + # self.assertDictEqual(response_data, actual_data) diff --git a/app/views.py b/app/views.py index 94353f25fa8f3d8365220d22aaf2134d0c30b00f..1fbf229df0ba125f88fbffab32f4b8e1205057b2 100644 --- a/app/views.py +++ b/app/views.py @@ -6,34 +6,41 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied, ValidationError -from django.core.paginator import Paginator from django.db.models import Q, Count -from django.http import (Http404, HttpResponse, HttpResponseRedirect, - JsonResponse) +from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse from django.urls import reverse from django.shortcuts import get_object_or_404, redirect from django.template import loader -from django.urls import reverse from django.views import defaults from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.views.generic import TemplateView from administration.models import VerificationReport from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm -from app.models import Category, Comment, Materi, Like, ViewStatistics, DownloadStatistics, ReqMaterial, Rating, \ - RatingContributor +from app.models import ( + Category, + Comment, + Materi, + Like, + ViewStatistics, + DownloadStatistics, + ReqMaterial, + Rating, + RatingContributor, +) from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata from authentication.models import User import django import pandas as pd from io import BytesIO -from django.contrib import messages from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive from pydrive.auth import AuthenticationRejected -def permission_denied(request, exception, template_name = 'error_403.html'): + +def permission_denied(request, exception, template_name="error_403.html"): return defaults.permission_denied(request, exception, template_name) + class DaftarKatalog(TemplateView): paginate_by = 2 template_name = "app/katalog_materi.html" @@ -48,16 +55,19 @@ class DaftarKatalog(TemplateView): lstMateri = Materi.objects.filter(status="APPROVE").order_by("date_modified") url = "" - getSearch = request.GET.get('search') + getSearch = request.GET.get("search") if getSearch: url = url + "&search={0}".format(getSearch) - lstMateri = lstMateri.filter( - Q(title__icontains=getSearch) | - Q(author__icontains=getSearch) | - Q(uploader__name__icontains=getSearch) | - Q(descriptions__icontains=getSearch) | - Q(publisher__icontains=getSearch) - ).distinct() + lstMateri = ( + lstMateri.search(getSearch) + .filter( + Q(author__icontains=getSearch) + | Q(uploader__name__icontains=getSearch) + | Q(descriptions__icontains=getSearch) + | Q(publisher__icontains=getSearch) + ) + .distinct() + ) getKategori = request.GET.get("kategori") if getKategori: @@ -68,31 +78,32 @@ class DaftarKatalog(TemplateView): getSort = request.GET.get("sort") if getSort: url = url + "&sort={0}".format(getSort) - if(getSort == "judul"): - lstMateri = lstMateri.order_by('title') - elif(getSort == "penulis"): - lstMateri = lstMateri.order_by('author') - elif(getSort == "pengunggah"): - lstMateri = lstMateri.order_by('uploader') - elif(getSort == "terbaru"): - lstMateri = lstMateri.order_by('-date_created') - elif(getSort == "terlama"): - lstMateri = lstMateri.order_by('date_created') - elif(getSort == "terpopuler"): - lstMateri = lstMateri.annotate(count=Count('like__id')).order_by('-count') + if getSort == "judul": + lstMateri = lstMateri.order_by("title") + elif getSort == "penulis": + lstMateri = lstMateri.order_by("author") + elif getSort == "pengunggah": + lstMateri = lstMateri.order_by("uploader") + elif getSort == "terbaru": + lstMateri = lstMateri.order_by("-date_created") + elif getSort == "terlama": + lstMateri = lstMateri.order_by("date_created") + elif getSort == "terpopuler": + lstMateri = lstMateri.annotate(count=Count("like__id")).order_by("-count") context["materi_list"] = lstMateri paginator = Paginator(context["materi_list"], 15) - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) context["materi_list"] = page_obj context["url"] = url return self.render_to_response(context=context) + class KatalogPerKontributorView(TemplateView): template_name = "app/katalog_kontri.html" - + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) contributor = get_object_or_404(User, email=kwargs["email"]) @@ -101,22 +112,25 @@ class KatalogPerKontributorView(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) - - materi_list = Materi.objects.filter(status="APPROVE", uploader=context["contributor"]).order_by("date_modified") - + + materi_list = Materi.objects.filter(status="APPROVE", uploader=context["contributor"]).order_by( + "date_modified" + ) + paginator = Paginator(materi_list, 15) - page_number = request.GET.get('page') + page_number = request.GET.get("page") materi_list_by_page = paginator.get_page(page_number) context["materi_list"] = materi_list_by_page contributor = get_object_or_404(User, email=kwargs["email"]) - context["form_rating"] = RatingContributorForm(initial={'user': contributor}) + context["form_rating"] = RatingContributorForm(initial={"user": contributor}) return self.render_to_response(context=context) def post(self, request, *args, **kwargs): data = RatingContributorForm(request.POST) if data.is_valid(): data.save() - return redirect('katalog-per-kontributor', email=kwargs['email']) + return redirect("katalog-per-kontributor", email=kwargs["email"]) + class DetailMateri(TemplateView): template_name = "app/detail_materi.html" @@ -129,29 +143,27 @@ class DetailMateri(TemplateView): context["session_id"] = self.request.session.session_key context["materi_data"] = materi context["report"] = VerificationReport.objects.filter(materi=materi) - context["has_liked"] = Like.objects.filter( - materi=materi, session_id=self.request.session.session_key).exists() - publishedDate = '' - if(materi.published_date == None): - publishedDate = 'n.d' - else : - publishedDate = materi.published_date.strftime('%Y-%m-%d %H:%M') - citationAPA = materi.author+' . (' + publishedDate +') . ' + materi.title +' . '+materi.publisher + context["has_liked"] = Like.objects.filter(materi=materi, session_id=self.request.session.session_key).exists() + publishedDate = "" + if materi.published_date == None: + publishedDate = "n.d" + else: + publishedDate = materi.published_date.strftime("%Y-%m-%d %H:%M") + citationAPA = materi.author + " . (" + publishedDate + ") . " + materi.title + " . " + materi.publisher context["citationAPA"] = citationAPA context["citationIEEE"] = get_citation_ieee(self.request, materi) - context['materi_rating_score'] = 0 + context["materi_rating_score"] = 0 if self.request.user.is_authenticated: materi_rating = Rating.objects.filter(materi=materi, user=self.request.user).first() if materi_rating is not None: - context['materi_rating_score'] = materi_rating.score + context["materi_rating_score"] = materi_rating.score return context def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) - query_set_for_comment = Comment.objects.filter( - materi=context["materi_data"]) + query_set_for_comment = Comment.objects.filter(materi=context["materi_data"]) context["comment_data"] = query_set_for_comment return self.render_to_response(context=context) @@ -171,31 +183,29 @@ class DetailMateri(TemplateView): context = self.get_context_data(*args, **kwargs) context["error_message"] = "Anda belum menuliskan komentar" context["materi_data"] = get_object_or_404(Materi, pk=kwargs["pk"]) - query_set_for_comment = Comment.objects.filter( - materi=context["materi_data"]) + query_set_for_comment = Comment.objects.filter(materi=context["materi_data"]) context["comment_data"] = query_set_for_comment return self.render_to_response(context=context) materi = get_object_or_404(Materi, pk=kwargs["pk"]) user_obj = request.user if request.user.is_authenticated else None - comment = Comment.objects.create(comment=commentText, - username=self.get_user_name(request), materi=materi, user=user_obj) + comment = Comment.objects.create( + comment=commentText, username=self.get_user_name(request), materi=materi, user=user_obj + ) comment.save() return HttpResponseRedirect(request.path) def toggle_like(request): - if request.method == 'POST': - materi_id = request.POST.get('materi_id', None) - session_id = request.POST.get('session_id', None) + if request.method == "POST": + materi_id = request.POST.get("materi_id", None) + session_id = request.POST.get("session_id", None) if materi_id is None or session_id is None: return JsonResponse({"success": False, "msg": "Missing parameter"}) materi = get_object_or_404(Materi, pk=materi_id) - has_liked = Like.objects.filter( - materi=materi, session_id=session_id).exists() + has_liked = Like.objects.filter(materi=materi, session_id=session_id).exists() if has_liked: - like = get_object_or_404( - Like, materi=materi, session_id=session_id) + like = get_object_or_404(Like, materi=materi, session_id=session_id) like.delete() return JsonResponse({"success": True, "liked": True}) else: @@ -207,47 +217,61 @@ def toggle_like(request): def delete_comment(request, pk_materi, pk_comment): comment = get_object_or_404(Comment, pk=pk_comment) - url = '/materi/' + str(pk_materi) + "/" + url = "/materi/" + str(pk_materi) + "/" comment.delete() return HttpResponseRedirect(url) + def get_citation_ieee(request, materi): current_date = datetime.datetime.now() current_day = str(current_date.day) current_month = current_date.strftime("%b") current_year = str(current_date.year) published_date = "" - if(materi.published_date == None): + if materi.published_date == None: published_date = "n.d" - else : - published_date = materi.published_date.strftime('%Y') - + else: + published_date = materi.published_date.strftime("%Y") + author_list = materi.author.split(",") author_list_abbrv = "" for author_name in author_list: author_name_split = author_name.split(" ") author_name_abbrv = "" for j, name in enumerate(author_name_split): - if j < (len(author_name_split)-1): + if j < (len(author_name_split) - 1): abbrv_name = name[0].upper() author_name_abbrv = author_name_abbrv + abbrv_name + ". " else: author_name_abbrv = author_name_abbrv + name author_list_abbrv = author_list_abbrv + author_name_abbrv + ", " - citation_result = author_list_abbrv + \ - materi.title + ". " + \ - materi.publisher + ", " + published_date + ". " + \ - "Accessed on: " + current_month + ". " + current_day + ", " + current_year + \ - ". [Online]. " + \ - "Available: " + request.build_absolute_uri() + citation_result = ( + author_list_abbrv + + materi.title + + ". " + + materi.publisher + + ", " + + published_date + + ". " + + "Accessed on: " + + current_month + + ". " + + current_day + + ", " + + current_year + + ". [Online]. " + + "Available: " + + request.build_absolute_uri() + ) return citation_result + def add_rating_materi(request): - if request.method == 'POST' and request.user.is_authenticated: + if request.method == "POST" and request.user.is_authenticated: - materi_id = request.POST.get('materi_id', None) - rating_score = request.POST.get('rating_score', None) + materi_id = request.POST.get("materi_id", None) + rating_score = request.POST.get("rating_score", None) if materi_id is None or rating_score is None: return JsonResponse({"success": False, "msg": "Missing param"}, status=422) @@ -273,8 +297,9 @@ def add_rating_materi(request): return JsonResponse({"success": False, "msg": "Rating already exist"}, status=409) Rating(materi=materi, user=request.user, score=rating_score).save() - return JsonResponse({"success": True, "msg": "Rating successfully created", "rating_score": rating_score}, - status=201) + return JsonResponse( + {"success": True, "msg": "Rating successfully created", "rating_score": rating_score}, status=201 + ) return JsonResponse({"success": False, "msg": "Forbidden"}, status=403) @@ -287,15 +312,14 @@ def download_materi(request, pk): mimetype = mimetypes.guess_type(file_path) with open(file_path, "rb") as fh: response = HttpResponse(fh.read(), content_type=mimetype[0]) - response["Content-Disposition"] = "attachment; filename=" + \ - os.path.basename(file_path) + response["Content-Disposition"] = "attachment; filename=" + os.path.basename(file_path) 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) + 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: @@ -313,8 +337,7 @@ def view_materi(request, pk): try: with open(file_path, "rb") as fh: response = HttpResponse(fh.read(), content_type=mimetype[0]) - response["Content-Disposition"] = "inline; filename=" + \ - os.path.basename(file_path) + response["Content-Disposition"] = "inline; filename=" + os.path.basename(file_path) ViewStatistics(materi=materi).save() return response except Exception as e: @@ -338,18 +361,17 @@ class UploadMateriView(TemplateView): if form.is_valid(): materi = form.save(commit=False) materi.uploader = request.user - konten = form.cleaned_data['content'] + konten = form.cleaned_data["content"] try: self.validate_file_extension(konten) except ValidationError: messages.error(request, "Materi gagal diunggah, format file tidak sesuai") return HttpResponseRedirect("/unggah/") materi.save() - kateg = form.cleaned_data['categories'] + kateg = form.cleaned_data["categories"] for i in kateg: materi.categories.add(i) - messages.success( - request, "Materi berhasil diunggah, periksa riwayat unggah anda") + messages.success(request, "Materi berhasil diunggah, periksa riwayat unggah anda") return HttpResponseRedirect("/unggah/") else: context = self.get_context_data(**kwargs) @@ -360,16 +382,16 @@ class UploadMateriView(TemplateView): def get(self, request, *args, **kwargs): if request.user.is_authenticated == False or not request.user.is_contributor: raise PermissionDenied(request) - + context = self.get_context_data(**kwargs) context["form"] = UploadMateriForm return self.render_to_response(context) def validate_file_extension(self, value): ext = os.path.splitext(value.name)[1] # [0] returns path+filename - valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.png', '.xlsx', '.xls', '.mp4', '.mp3'] + valid_extensions = [".pdf", ".doc", ".docx", ".jpg", ".png", ".xlsx", ".xls", ".mp4", ".mp3"] if not ext.lower() in valid_extensions: - raise ValidationError('Unsupported file extension.') + raise ValidationError("Unsupported file extension.") class UploadMateriHTML(TemplateView): @@ -393,23 +415,19 @@ class UploadMateriExcelView(TemplateView): def get(self, request, *args, **kwargs): - if 'template' in self.request.GET: + if "template" in self.request.GET: - data_frame = pd.DataFrame({ - 'Title': [], - 'Author': [], - 'Publisher': [], - 'Categories': [], - 'Description': [], - }) + data_frame = pd.DataFrame( + {"Title": [], "Author": [], "Publisher": [], "Categories": [], "Description": [],} + ) with BytesIO() as b: - writer = pd.ExcelWriter(b, engine='xlsxwriter') #pylint: disable=abstract-class-instantiated + writer = pd.ExcelWriter(b, engine="xlsxwriter") # pylint: disable=abstract-class-instantiated data_frame.to_excel(writer, index=0) writer.save() response = HttpResponse( - b.getvalue(), - content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + b.getvalue(), content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ) response["Content-Disposition"] = "attachment; filename=template.xlsx" @@ -419,66 +437,65 @@ class UploadMateriExcelView(TemplateView): context = self.get_context_data(**kwargs) return self.render_to_response(context) - def post(self, request, *args, **kwargs): - excel_file = request.FILES['excel'] - excel = pd.read_excel(excel_file) + excel_file = request.FILES["excel"] + excel = pd.read_excel(excel_file) - row,lines = excel.shape + row, lines = excel.shape categories = Category.objects.all() - + field_length = { - 'title' : 50, - 'author' : 30, - 'publisher' : 30, + "title": 50, + "author": 30, + "publisher": 30, } message = None # First pass, validate input for i in range(row): - + # Validate Categories - for c in excel['Categories'][i].split(","): + for c in excel["Categories"][i].split(","): sel_cat = categories.filter(name=c) if sel_cat.count() == 0: message = f"Kategori %s tidak ditemukan" % c break - if len(excel['Title'][i]) > field_length['title']: - message = f"Title maksimal %d karakter" % field_length['title'] + if len(excel["Title"][i]) > field_length["title"]: + message = f"Title maksimal %d karakter" % field_length["title"] - if len(excel['Author'][i]) > field_length['author']: - message = f"Author maksimal %d karakter" % field_length['author'] + if len(excel["Author"][i]) > field_length["author"]: + message = f"Author maksimal %d karakter" % field_length["author"] - if len(excel['Publisher'][i]) > field_length['publisher']: - message = f"Publisher maksimal %d karakter" % field_length['publisher'] + if len(excel["Publisher"][i]) > field_length["publisher"]: + message = f"Publisher maksimal %d karakter" % field_length["publisher"] if message != None: break - + if message != None: messages.error(request, message) - return HttpResponseRedirect('/unggah_excel/') + return HttpResponseRedirect("/unggah_excel/") # Second pass, save data with django.db.transaction.atomic(): for i in range(row): materi = Materi( - title=excel['Title'][i], - author=excel['Author'][i], - publisher=excel['Publisher'][i], - descriptions=excel['Description'][i], - uploader=request.user - ) + title=excel["Title"][i], + author=excel["Author"][i], + publisher=excel["Publisher"][i], + descriptions=excel["Description"][i], + uploader=request.user, + ) materi.save() - - for c in excel['Categories'][i].split(","): + + for c in excel["Categories"][i].split(","): materi.categories.add(categories.get(name=c)) - messages.success(request, 'Materi berhasil diunggah') + messages.success(request, "Materi berhasil diunggah") - return HttpResponseRedirect('/unggah_excel/') + return HttpResponseRedirect("/unggah_excel/") class DashboardKontributorView(TemplateView): @@ -490,8 +507,7 @@ class DashboardKontributorView(TemplateView): return super(DashboardKontributorView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - context = super(DashboardKontributorView, - self).get_context_data(**kwargs) + context = super(DashboardKontributorView, self).get_context_data(**kwargs) return context def get(self, request, *args, **kwargs): @@ -567,24 +583,22 @@ class SuntingProfilView(TemplateView): current_user = self.request.user - form = SuntingProfilForm( - request.POST, request.FILES, instance=current_user) + form = SuntingProfilForm(request.POST, request.FILES, instance=current_user) if form.is_valid(): current_user.default_profile_picture = True # Removing exifdata from profile picture on upload if request.FILES: - f_name = request.FILES['profile_picture'].name + f_name = request.FILES["profile_picture"].name f_name = get_random_filename(f_name) f_path = settings.MEDIA_ROOT + "/" + f_name - request.FILES['profile_picture'].name = f_name + request.FILES["profile_picture"].name = f_name - form = SuntingProfilForm( - request.POST, request.FILES, instance=current_user) + form = SuntingProfilForm(request.POST, request.FILES, instance=current_user) form.save() remove_image_exifdata(f_path) - else: + else: form.save() return HttpResponseRedirect("/profil/") else: @@ -602,8 +616,7 @@ class SuntingProfilAdminView(TemplateView): return super(SuntingProfilAdminView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - context = super(SuntingProfilAdminView, - self).get_context_data(**kwargs) + context = super(SuntingProfilAdminView, self).get_context_data(**kwargs) return context def get(self, request, *args, **kwargs): @@ -621,24 +634,22 @@ class SuntingProfilAdminView(TemplateView): current_user = self.request.user - form = SuntingProfilForm( - request.POST, request.FILES, instance=current_user) + form = SuntingProfilForm(request.POST, request.FILES, instance=current_user) if form.is_valid(): current_user.default_profile_picture = True # Removing exifdata from profile picture on upload if request.FILES: - f_name = request.FILES['profile_picture'].name + f_name = request.FILES["profile_picture"].name f_name = get_random_filename(f_name) f_path = settings.MEDIA_ROOT + "/" + f_name - request.FILES['profile_picture'].name = f_name + request.FILES["profile_picture"].name = f_name - form = SuntingProfilForm( - request.POST, request.FILES, instance=current_user) + form = SuntingProfilForm(request.POST, request.FILES, instance=current_user) form.save() remove_image_exifdata(f_path) - else: + else: form.save() return HttpResponseRedirect("/profil-admin/") else: @@ -646,6 +657,7 @@ class SuntingProfilAdminView(TemplateView): context["form"] = form return self.render_to_response(context) + class ReqMateriView(TemplateView): template_name = "req_materi.html" @@ -655,8 +667,7 @@ class ReqMateriView(TemplateView): return super(ReqMateriView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - context = super(ReqMateriView, - self).get_context_data(**kwargs) + context = super(ReqMateriView, self).get_context_data(**kwargs) return context def get(self, request, *args, **kwargs): @@ -664,7 +675,7 @@ class ReqMateriView(TemplateView): return self.render_to_response(context) def post(self, request, *args, **kwargs): - title = request.POST.get('title', None) + title = request.POST.get("title", None) if title is None: return JsonResponse({"success": False, "msg": "Missing parameter"}) ReqMaterial(title=title).save() @@ -680,8 +691,7 @@ class SuksesLoginKontributorView(TemplateView): return super(SuksesLoginKontributorView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - context = super(SuksesLoginKontributorView, - self).get_context_data(**kwargs) + context = super(SuksesLoginKontributorView, self).get_context_data(**kwargs) return context def get(self, request, *args, **kwargs): @@ -728,11 +738,9 @@ class PostsView(TemplateView): 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 } + posts_data = {post.id: {"data": post, "comments": []} for post in posts} - comments = Comment.objects \ - .filter(materi__id__in=posts_data.keys()) \ - .order_by("-timestamp") + 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) @@ -762,19 +770,18 @@ class RevisiMateriView(TemplateView): context = self.get_context_data(**kwargs) return self.render_to_response(context) - def post(self,request, *args, **kwargs): + def post(self, request, *args, **kwargs): if request.user.is_authenticated == False: raise PermissionDenied(request) current_materi = get_object_or_404(Materi, pk=kwargs["pk"]) - form = UploadMateriForm( - request.POST, request.FILES, instance=current_materi) + form = UploadMateriForm(request.POST, request.FILES, instance=current_materi) if form.is_valid(): materi = form.save(commit=False) materi.uploader = request.user materi.status = "REVISION" materi.save() - kateg = form.cleaned_data['categories'] + kateg = form.cleaned_data["categories"] for i in kateg: materi.categories.add(i) materi.save() @@ -784,6 +791,7 @@ class RevisiMateriView(TemplateView): context["form_revisi"] = form return self.render_to_response(context) + def pages(request): context = {} # All resource paths end in .html. @@ -798,31 +806,31 @@ 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) + 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') + 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') + 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 - context["user_name"] = 'Guest' + context["user_name"] = "Guest" return self.render_to_response(context) + def upload_to_gdrive(file_path, title): gauth = GoogleAuth() gauth.LocalWebserverAuth() @@ -830,10 +838,11 @@ def upload_to_gdrive(file_path, title): drive = GoogleDrive(gauth) file1 = drive.CreateFile() file1.SetContentFile(file_path) - file1['title'] = title - print('title: %s, mimeType: %s' % (file1['title'], file1['mimeType'])) + file1["title"] = title + print("title: %s, mimeType: %s" % (file1["title"], file1["mimeType"])) file1.Upload() + def save_to_gdrive(request, pk): materi = get_object_or_404(Materi, pk=pk) path = materi.content.path @@ -872,4 +881,4 @@ class MateriFavorite(TemplateView): context["user"] = user context["likes"] = likes_data - return self.render_to_response(context=context) \ No newline at end of file + return self.render_to_response(context=context) diff --git a/pyproject.toml b/pyproject.toml index a70904f11f778a303dac2180fec540896169b88b..05df741509f11bbfe88ab75b26b7a6e836637ddf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,5 +12,6 @@ exclude = ''' | buck-out | build | dist + | migrations )/ ''' \ No newline at end of file