diff --git a/administration/templates/edit_kontributor.html b/administration/templates/edit_kontributor.html deleted file mode 100644 index 47e16ef164f42dc12bc70358741ae0ca3a566c9d..0000000000000000000000000000000000000000 --- a/administration/templates/edit_kontributor.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends 'administration/base_administrasi.html' %} -{% load static %} - -{% block content %} -<div class="card shadow mb-4"> - <div class="card-header py-3"> - <h6 class="m-0 font-weight-bold text-primary"> - Edit {{ page_title }} - </h6> - </div> - <div class="card-body"> - <form method="POST"> - {% csrf_token %} - {{ item.name }} - <div class="status d-flex align-items-baseline"> - {{ form.is_active }} <br> Active - </div> - <div class=""> - <button class="btn-sm btn-primary rounded p-12" type="submit"> - <i class="far fa-save" aria-hidden="true"></i> - Simpan - </button> - </div> - </form> - </div> -</div> -{% endblock %} \ No newline at end of file diff --git a/administration/templates/kelola_admin.html b/administration/templates/kelola_admin.html index dd020c9771ca6b73161182317499809216bf6e16..320b37e2db12b7e9067ee3c81de0430cdff2e6c9 100644 --- a/administration/templates/kelola_admin.html +++ b/administration/templates/kelola_admin.html @@ -1,5 +1,4 @@ {% extends 'administration/base_administrasi2.html' %} -{% load static %} {% block title %} <title>Kelola Admin | Digipus</title> diff --git a/administration/templates/registrasi_admin.html b/administration/templates/registrasi_admin.html index 659c58865f762f7ffe3ca580fa47189fc9da113c..3a14f4caafaeb9bc7d7fde1fff17de5a17728972 100644 --- a/administration/templates/registrasi_admin.html +++ b/administration/templates/registrasi_admin.html @@ -1,290 +1,89 @@ -{% load static %} - -<!DOCTYPE html> -<html lang="en"> - -<head> - - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <meta name="description" content=""> - <meta name="author" content=""> - - <title>Dasbor - Kelola Admin</title> - - <!-- Custom fonts for this template --> - <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet"> - - <!-- Custom styles for this template --> - <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> - <link href="{% static 'css/sb-admin-2.min.css' %}" rel="stylesheet"> - <link rel="stylesheet" href="{% static 'css/button.css' %}"> - - <!-- Custom styles for this page --> - <link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet"> - -</head> - -<body id="page-top" style="font-family: 'Poppins', sans-serif;"> - - <!-- Page Wrapper --> - <div id="wrapper"> - - <!-- Sidebar --> - <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar"> - - <!-- Sidebar - Brand --> - <a class="sidebar-brand d-flex align-items-center justify-content-center" href="{% url 'daftar_katalog' %}"> - <div class="sidebar-brand-icon rotate-n-15"> +{% extends 'administration/base_administrasi2.html' %} + +{% block title %} +<title>Dasbor - Kelola Admin</title> +{% endblock %} + +{% block content %} + <h1 class="h3 mb-2 text-gray-800">Registrasi Admin</h1> + <div class="form-margin"></div> + <div class="container admin-page"> + <div class="col-20"> + <form id="add_form" method="POST" novalidate enctype="multipart/form-data"> + {% csrf_token %} + <div class="col-md-6 admin-page"> + <div class="fieldWrapper"> + {{ form.name.errors }} + <label for="{{ form.subject.id_for_label }}">Nama:</label> + {{ form.name }} + </div> </div> - <div class="sidebar-brand-text mx-3">Digipus</div> - </a> - - <!-- Divider --> - <hr class="sidebar-divider my-0"> - - <!-- Nav Item - Dashboard --> - <li class="nav-item"> - <a class="nav-link" href="/administration/"> - <span>Verifikasi Materi</span></a> - </li> - - <li class="nav-item"> - <a class="nav-link" href="#"> - <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"> - - <li class="nav-item"> - <a class="nav-link" href="/administration/setting/verification/"> - <span>Pengaturan Verifikasi</span></a> - </li> - - <li class="nav-item"> - <a class="nav-link" href="/administration/setting/category/"> - <span>Pengaturan Kategori</span></a> - </li> - - <!-- Divider --> - <hr class="sidebar-divider my-0"> - - <li class="nav-item"> - <a class="nav-link" href="/administration/kelola-kontributor/"> - <span>Kelola Kontributor</span></a> - </li> - - <li class="nav-item"> - <a class="nav-link" href="/administration/kelola-admin/"> - <span>Kelola Admin</span></a> - </li> - - <!-- Divider --> - <hr class="sidebar-divider my-0"> - - </ul> - <!-- End of Sidebar --> - - <!-- Content Wrapper --> - <div id="content-wrapper" class="d-flex flex-column"> - - <!-- Main Content --> - <div id="content"> - - <!-- Topbar --> - <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow"> - - <!-- Sidebar Toggle (Topbar) --> - <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> - <em class="fa fa-bars"></em> - </button> - - <div class="sidebar-brand-text mx-3">Diskominfo Kota Depok</div> - - <!-- Topbar Navbar --> - <ul class="navbar-nav ml-auto"> - - <li class="nav-item"> - <a class="nav-link" href="/dashboard/"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">Administrasi</span> - </a> - </li> - - <li class="nav-item"> - <a class="nav-link" href="/profil/"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">Profil</span> - </a> - </li> - - <li class="nav-item"> - <a class="nav-link" href="/logout/"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">Logout</span> - </a> - </li> - - <div class="topbar-divider d-none d-sm-block"></div> - - <!-- Nav Item - User Information --> - <li class="nav-item dropdown no-arrow"> - <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown" - aria-haspopup="true" aria-expanded="false"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ user.name }}</span> - {% if not user.default_profile_picture %} - <img class="img-profile rounded-circle" src="https://i.ibb.co/9wgPzyZ/default-image.png" alt="User profile picture"> - {% else %} - <img class="img-profile rounded-circle" src="{{ user.profile_picture.url }}" alt="User profile picture"> - {% endif %} - </a> - </li> - - </ul> - - </nav> - <!-- End of Topbar --> - - <!-- Begin Page Content --> - <div class="container-fluid"> - - <!-- Page Heading --> - <h1 class="h3 mb-2 text-gray-800">Registrasi Admin</h1> - <div class="form-margin"></div> - <div class="container admin-page"> - <div class="col-20"> - <form id="add_form" method="POST" novalidate enctype="multipart/form-data"> - {% csrf_token %} - <div class="col-md-6 admin-page"> - <div class="fieldWrapper"> - {{ form.name.errors }} - <label for="{{ form.subject.id_for_label }}">Nama:</label> - {{ form.name }} - </div> - </div> - <div class="col-md-6 admin-page"> - <div class="fieldWrapper"> - {{ form.instansi.errors }} - <label for="{{ form.subject.id_for_label }}">Instansi:</label> - {{ form.instansi }} - </div> - </div> - <div class="col-md-6 admin-page"> - <div class="fieldWrapper"> - {{ form.nik.errors }} - <label for="{{ form.subject.id_for_label }}">NIK:</label> - {{ form.nik }} - </div> - </div> - <div class="col-md-6 admin-page"> - <div class="fieldWrapper"> - {{ form.alamat.errors }} - <label for="{{ form.subject.id_for_label }}">Alamat:</label> - {{ form.alamat }} - </div> - </div> - <div class="col-md-6 admin-page"> - <div class="fieldWrapper"> - {{ form.email.errors }} - <label for="{{ form.subject.id_for_label }}">Email:</label> - {{ form.email }} - </div> - </div> - <div class="col-md-6 admin-page"> - <div class="fieldWrapper"> - {{ form.nomor_telpon.errors }} - <label for="{{ form.subject.id_for_label }}">Nomor Telepon:</label> - {{ form.nomor_telpon }} - </div> - </div> - <div class="col-md-6 admin-page"> - <div class="fieldWrapper"> - {{ form.password.errors }} - <label for="{{ form.subject.id_for_label }}">Kata Sandi:</label> - {{ form.password }} - </div> - </div> - <div class="col-md-6 admin-page"> - <div class="fieldWrapper"> - {{ form.password2.errors }} - <label for="{{ form.subject.id_for_label }}">Ulang Kata Sandi:</label> - {{ form.password2 }} - </div> - </div> - - <div class="form-margin"></div> - <div class="col-md-6 admin-page"> - <div class="d-flex flex-row"> - <div class="p-2"> - <button type="submit" class="btn btn-success btn-edit" style="background-color: #615CFD; border-color: #615CFD;">Simpan</button> - </div> - <div class="p-2"> - <a href="/administrasi/kelola-admin" class="btn btn-secondary">Kembali Ke Kelola Admin</a> - </div> - </div> - <div class="fieldWrapper"> - - </div> - </div> - </form> - </div> - </div> + <div class="col-md-6 admin-page"> + <div class="fieldWrapper"> + {{ form.instansi.errors }} + <label for="{{ form.subject.id_for_label }}">Instansi:</label> + {{ form.instansi }} + </div> + </div> + <div class="col-md-6 admin-page"> + <div class="fieldWrapper"> + {{ form.nik.errors }} + <label for="{{ form.subject.id_for_label }}">NIK:</label> + {{ form.nik }} + </div> + </div> + <div class="col-md-6 admin-page"> + <div class="fieldWrapper"> + {{ form.alamat.errors }} + <label for="{{ form.subject.id_for_label }}">Alamat:</label> + {{ form.alamat }} + </div> + </div> + <div class="col-md-6 admin-page"> + <div class="fieldWrapper"> + {{ form.email.errors }} + <label for="{{ form.subject.id_for_label }}">Email:</label> + {{ form.email }} + </div> + </div> + <div class="col-md-6 admin-page"> + <div class="fieldWrapper"> + {{ form.nomor_telpon.errors }} + <label for="{{ form.subject.id_for_label }}">Nomor Telepon:</label> + {{ form.nomor_telpon }} + </div> + </div> + <div class="col-md-6 admin-page"> + <div class="fieldWrapper"> + {{ form.password.errors }} + <label for="{{ form.subject.id_for_label }}">Kata Sandi:</label> + {{ form.password }} + </div> + </div> + <div class="col-md-6 admin-page"> + <div class="fieldWrapper"> + {{ form.password2.errors }} + <label for="{{ form.subject.id_for_label }}">Ulang Kata Sandi:</label> + {{ form.password2 }} + </div> </div> - <!-- /.container-fluid --> - </div> - <!-- End of Main Content --> + <div class="form-margin"></div> + <div class="col-md-6 admin-page"> + <div class="d-flex flex-row"> + <div class="p-2"> + <button type="submit" class="btn btn-success btn-edit" + style="background-color: #615CFD; border-color: #615CFD;">Simpan</button> + </div> + <div class="p-2"> + <a href="/administration/kelola-admin" class="btn btn-secondary">Kembali Ke Kelola Admin</a> + </div> + </div> + <div class="fieldWrapper"> - <!-- Footer --> - <footer class="sticky-footer bg-white"> - <div class="container my-auto"> - <div class="copyright text-center my-auto"> - <span>Copyright © Diskominfo Kota Depok 2020</span> </div> </div> - </footer> - <!-- End of Footer --> - + </form> </div> - <!-- End of Content Wrapper --> - </div> - <!-- End of Page Wrapper --> - - <!-- Scroll to Top Button--> - <a class="scroll-to-top rounded" href="#page-top"> - <em class="fas fa-angle-up"></em> - </a> - - <!-- Bootstrap core JavaScript--> - <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" - integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" - crossorigin="anonymous"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" - integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" - crossorigin="anonymous"></script> - <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" - integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" - crossorigin="anonymous"></script> - - <!-- Core plugin JavaScript--> - <script src="https://code.jquery.com/jquery-3.5.0.min.js" - integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script> - - <!-- Custom scripts for all pages--> - <script src="{% static 'js/sb-admin-2.min.js' %}"></script> - - <!-- Page level plugins --> - <script src="{% static 'vendor/datatables/jquery.dataTables.min.js' %}"></script> - <script src="{% static 'vendor/datatables/dataTables.bootstrap4.min.js' %}"></script> - - <!-- Page level custom scripts --> - <script src="{% static 'js/demo/datatables-demo.js' %}"></script> - -</body> - -</html> \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/administration/views.py b/administration/views.py index 43a42b877c2b712001de03d85c77c362a8fab0b7..52cb59f2a4a0e4656b0d3ad0113a5533270e3a20 100644 --- a/administration/views.py +++ b/administration/views.py @@ -9,9 +9,10 @@ from django.utils import timezone from administration.models import VerificationReport, VerificationSetting, DeletionHistory from administration.forms import CategoryForm, VerificationSettingForm, RegistrasiAdminForm, PeriodForm, EditAdminStatusForm, EditKontributorStatusForm from administration.services import StatisticService, DetailVerificationService, LaporanMateriService -from app.models import Category, Materi, ViewStatistics, DownloadStatistics, Comment, Like, LaporanMateri +from app.models import Category, Materi, ViewStatistics, DownloadStatistics, Comment, Like, LaporanMateri, AdminNotification from authentication.models import User from datetime import datetime +from app.models import NotifikasiKontributor from administration.utils import generate_time_step @@ -82,11 +83,17 @@ class DetailVerificationView(TemplateView): verif_report = VerificationReport( report=report, materi=materi, user=self.request.user, status=materi.get_status_display()) verif_report.save() + + new_kontributor_notif = NotifikasiKontributor(materi=materi, user=materi.uploader, feedback=materi.get_status_display()) + new_kontributor_notif.save() + return HttpResponseRedirect("/administration/") def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) + if(request.user.is_admin): + AdminNotification.objects.filter(materi=context["materi_data"]).delete() return self.render_to_response(context=context) @@ -421,7 +428,7 @@ class EditAdminStatusView(TemplateView): class EditKontributorStatusView(TemplateView): - template_name = "edit_kontributor.html" + template_name = "edit_admin.html" def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not request.user.is_admin: @@ -583,6 +590,8 @@ def blok_materi(request, *args, **kwargs): if materi.status == "APPROVE" and LaporanMateri.objects.filter(is_rejected=False, materi_id=materi.id): materi.status = "BLOCKED" materi.save() + new_kontributor_notif = NotifikasiKontributor(materi=materi, user=materi.uploader, feedback=materi.get_status_display()) + new_kontributor_notif.save() return HttpResponseRedirect(ADMINISTRATION_REPORT) diff --git a/app/admin.py b/app/admin.py index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..333a094883a54391f866114ba0b3d146ae5d181b 100644 --- a/app/admin.py +++ b/app/admin.py @@ -1,3 +1,5 @@ from django.contrib import admin +from .models import AdminNotification # Register your models here. +admin.site.register(AdminNotification) \ No newline at end of file diff --git a/app/forms.py b/app/forms.py index 266720e86f912083e2c1335923114cc5c21c4bd3..7f22c130a7bd62a0d90ceb8e4fc88037f0f33eeb 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,18 +1,22 @@ from django import forms -from app.models import Materi, Category, RatingContributor +from app.models import Materi, Category, RatingContributor, GuestBook from authentication.models import User import datetime + def year_choices(): - return[(r,r) for r in range(2000, datetime.date.today().year+1)] + return[(r, r) for r in range(2000, datetime.date.today().year+1)] + class UploadMateriForm(forms.ModelForm): - - categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all(),widget=forms.CheckboxSelectMultiple(attrs={'style' : 'column-count:2'}),required=True) - release_year = forms.TypedChoiceField(coerce=int, choices=year_choices, initial=datetime.date.today().year) - yt_video_id = forms.CharField(label="Youtube Video Id", \ - help_text="This is not required.<br>\ + + categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all( + ), widget=forms.CheckboxSelectMultiple(attrs={'style': 'column-count:2'}), required=True) + release_year = forms.TypedChoiceField( + coerce=int, choices=year_choices, initial=datetime.date.today().year) + yt_video_id = forms.CharField(label="Youtube Video Id", + help_text="This is not required.<br>\ Please insert only Youtube link videos! Take a note for the video id!<br>\ Example : https://www.youtube.com/watch?v=DkJ-50GLi2I <br> has video id DkJ-50GLi2I", required=False) @@ -29,14 +33,13 @@ class UploadMateriForm(forms.ModelForm): field.widget.attrs["class"] = "form-control" field.widget.attrs["placeholder"] = field.initial field.initial = "" - self.fields['categories'].widget.attrs.update({'class' : "native"}) - + self.fields['categories'].widget.attrs.update({'class': "native"}) class SuntingProfilForm(forms.ModelForm): class Meta: model = User - fields = ["email","name","instansi", "nik", "alamat", "nomor_telpon", + fields = ["email", "name", "instansi", "nik", "alamat", "nomor_telpon", "profile_picture", "linkedin", "facebook", "twitter", "instagram", "biography", "is_subscribing_to_material_comments"] @@ -50,7 +53,6 @@ class SuntingProfilForm(forms.ModelForm): if any(self.errors): key = list(self.errors)[0] self.fields[key].widget.attrs["autofocus"] = "" - self.fields["email"].widget.attrs["readonly"] = True @@ -61,14 +63,43 @@ class RatingContributorForm(forms.ModelForm): fields = ['score', 'user', 'contributor'] SCORE_CHOICE = ( ('', 'Select score'), - ('1', '1'), # First one is the value of select option and second is the displayed value in option + # First one is the value of select option and second is the displayed value in option + ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ) widgets = { - 'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control', 'id':'form-rating'},), + 'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control', 'id': 'form-rating'},), 'user': forms.HiddenInput(), 'contributor': forms.HiddenInput() } + + +class GuestBookForm(forms.models.ModelForm): + class Meta: + model = GuestBook + fields = ['name', 'job', 'gender'] + gender_choices = ( + ('Male', 'Male'), + ('Female', 'Female') + ) + widgets = { + 'name': forms.fields.TextInput(attrs={ + 'placeholder': 'Input your name', + 'class': 'form-control input-lg' + }), + 'job': forms.fields.TextInput(attrs={ + 'placeholder': 'Input your job', + 'class': 'form-control input-lg' + }), + 'gender': forms.fields.Select(choices=gender_choices, attrs={ + 'class': 'form-control input-lg' + }) + } + error_messages = { + 'name': {'required': 'Name is required'}, + 'job': {'required': 'Job is required'}, + 'gender': {'required': 'Gender is required'} + } diff --git a/app/migrations/0028_adminnotification.py b/app/migrations/0028_adminnotification.py new file mode 100644 index 0000000000000000000000000000000000000000..c50ac34f7e7c30fe27215374bd50cbc26aa45b04 --- /dev/null +++ b/app/migrations/0028_adminnotification.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1 on 2020-10-31 14:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0027_readlater'), + ] + + operations = [ + migrations.CreateModel( + name='AdminNotification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('materi', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.materi')), + ], + ), + ] diff --git a/app/migrations/0028_notifikasikontributor.py b/app/migrations/0028_notifikasikontributor.py new file mode 100644 index 0000000000000000000000000000000000000000..8b198645300bae7ee7dcf9dd16dc7119de709310 --- /dev/null +++ b/app/migrations/0028_notifikasikontributor.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1 on 2020-10-31 16:29 + +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', '0027_readlater'), + ] + + operations = [ + migrations.CreateModel( + name='NotifikasiKontributor', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('feedback', models.CharField(default='', max_length=20)), + ('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/0029_guestbook.py b/app/migrations/0029_guestbook.py new file mode 100644 index 0000000000000000000000000000000000000000..57fc440f8a0ba208b54902b0259221c854158c99 --- /dev/null +++ b/app/migrations/0029_guestbook.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1 on 2020-10-31 18:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0028_adminnotification'), + ] + + operations = [ + migrations.CreateModel( + name='GuestBook', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField(default='')), + ('job', models.TextField(default='')), + ('gender', models.CharField(default='Male', max_length=6)), + ], + ), + ] diff --git a/app/migrations/0029_merge_20201101_0217.py b/app/migrations/0029_merge_20201101_0217.py new file mode 100644 index 0000000000000000000000000000000000000000..69826d1afcfbd93ecf7643f8109e292879d785b4 --- /dev/null +++ b/app/migrations/0029_merge_20201101_0217.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1 on 2020-10-31 19:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0028_notifikasikontributor'), + ('app', '0028_adminnotification'), + ] + + operations = [ + ] diff --git a/app/migrations/0030_merge_20201101_0621.py b/app/migrations/0030_merge_20201101_0621.py new file mode 100644 index 0000000000000000000000000000000000000000..e4e725f5d7b212a3eec1de8bb51575762eb79218 --- /dev/null +++ b/app/migrations/0030_merge_20201101_0621.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1 on 2020-10-31 23:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0029_guestbook'), + ('app', '0028_notifikasikontributor'), + ] + + operations = [ + ] diff --git a/app/migrations/0031_merge_20201101_1729.py b/app/migrations/0031_merge_20201101_1729.py new file mode 100644 index 0000000000000000000000000000000000000000..e284b5ff2882614bf7c9f7fae48e11fcf2d18c4c --- /dev/null +++ b/app/migrations/0031_merge_20201101_1729.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1 on 2020-11-01 10:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0030_merge_20201101_0621'), + ('app', '0029_merge_20201101_0217'), + ] + + operations = [ + ] diff --git a/app/models.py b/app/models.py index fb932f7546c54b0f22af0e02c9e1212f123b51f6..3386646a5856d39ed5eb4252cdb892f5c8057144 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,6 @@ import random import datetime +import math from django.contrib.postgres import search from django.core.exceptions import ValidationError @@ -25,9 +26,11 @@ def get_random_color(): color = "%06x" % random.randint(0, 0xFFFFFF) return color + def current_year(): return datetime.date.today().year + class Category(models.Model): name = models.CharField(max_length=20) description = models.TextField(blank=False, default="") @@ -47,35 +50,47 @@ class MateriManager(models.Manager): if self.alive_only: return SoftDeletionQuerySet(self.model).filter(deleted_at=None) return SoftDeletionQuerySet(self.model) - + def search(self, search_text): - search_vector = search.SearchVector("title", weight="A") + search_vector = None + for field, weight in Materi.SEARCH_INDEX: + if search_vector is None: + search_vector = search.SearchVector(field, weight=weight) + else: + search_vector += search.SearchVector(field, weight=weight) + 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") + self.get_queryset() + .filter(_search_vector=search_query) + .annotate(rank=search_rank) + .order_by("-rank") ) return search_result + class SoftDeletionQuerySet(models.query.QuerySet): def delete(self): return super(SoftDeletionQuerySet, self).update(deleted_at=timezone.now()) + class SoftDeleteModel(models.Model): deleted_at = models.DateTimeField(blank=True, null=True) all_objects = MateriManager(alive_only=False) - + class Meta: abstract = True - + def soft_delete(self): self.deleted_at = timezone.now() self.save() + class Materi(SoftDeleteModel): cover = models.ImageField() content = models.FileField() @@ -85,8 +100,9 @@ class Materi(SoftDeleteModel): publisher = models.CharField(max_length=30, default="Penerbit") release_year = models.IntegerField(default=current_year) pages = models.IntegerField(default=0) - descriptions = models.TextField(default="Deskripsi") - status = models.CharField(max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0]) + descriptions = models.TextField(default="Deskripsi") + 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) @@ -94,13 +110,35 @@ class Materi(SoftDeleteModel): _search_vector = search.SearchVectorField(null=True, editable=False) + SEARCH_INDEX = ( + ("title", "A"), + ("author", "A"), + ("publisher", "C"), + ("descriptions", "C"), + ("uploader", "D"), + ) + objects = MateriManager() def save(self, *args, **kwargs): super().save(*args, **kwargs) + search_index = {field: weight for ( + field, weight) in Materi.SEARCH_INDEX} + if "update_fields" in kwargs: + is_search_index_updated = bool( + set(search_index.keys()) & set(kwargs["update_fields"]) + ) + + if ("update_fields" not in kwargs) or (is_search_index_updated): + self._search_vector = None + for field, weight in search_index.items(): + if self._search_vector is None: + self._search_vector = search.SearchVector( + field, weight=weight) + else: + self._search_vector += search.SearchVector( + field, weight=weight) - 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 @@ -124,7 +162,7 @@ class Materi(SoftDeleteModel): def like_count(self): count = Like.objects.filter(materi=self).count() return count - + @property def comment_count(self): count = Comment.objects.filter(materi=self).count() @@ -135,6 +173,25 @@ class Materi(SoftDeleteModel): count = Review.objects.filter(materi=self).count() return count + @staticmethod + def earliest_materi_timestamp(): + return Materi.objects.earliest('date_created').date_created.timestamp() + + @property + def seconds_since_earliest_materi(self): + return self.date_created.timestamp() - Materi.earliest_materi_timestamp() + + @property + def view_count(self): + count = ViewStatistics.objects.filter(materi=self).count() + return count + + @property + def hot_score(self): + view_score = math.log(max(self.view_count, 1), 10) + time_score = self.seconds_since_earliest_materi / 604800 # 1 week + return round(view_score + time_score, 7) + @property def is_like(self): like = False @@ -148,7 +205,8 @@ class Comment(models.Model): profile = models.CharField(max_length=100, default=get_random_color) 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): @@ -158,18 +216,20 @@ class Comment(models.Model): def like_count(self): count = LikeComment.objects.filter(comment=self).count() return count - + @property def dislike_count(self): count = DislikeComment.objects.filter(comment=self).count() return count + class Review(models.Model): username = models.CharField(max_length=100) profile = models.CharField(max_length=100, default=get_random_color) review = models.TextField(default="review") 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): @@ -198,6 +258,7 @@ class ReqMaterial(models.Model): title = models.CharField(max_length=100) timestamp = models.DateTimeField(default=timezone.now) + class SubmitVisitor(models.Model): user_id = models.CharField(max_length=50) email = models.CharField(max_length=50) @@ -206,13 +267,16 @@ class SubmitVisitor(models.Model): 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) @@ -253,31 +317,57 @@ class Rating(models.Model): class RatingContributor(models.Model): timestamp = models.DateTimeField(auto_now=True) - score = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)]) - contributor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='contributor') - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user') + score = models.PositiveIntegerField( + validators=[MinValueValidator(1), MaxValueValidator(5)]) + contributor = models.ForeignKey( + User, on_delete=models.CASCADE, related_name='contributor') + user = models.ForeignKey( + User, on_delete=models.CASCADE, related_name='user') def save(self, force_insert=False, force_update=False, using=None, update_fields=None): 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") - + class Meta: unique_together = ["contributor", "user"] class LaporanMateri(models.Model): - materi = models.ForeignKey(Materi, on_delete=models.CASCADE, max_length=120) + 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="") + laporan = models.TextField( + validators=[MinValueValidator(30), MaxValueValidator(120)], default="") timestamp = models.DateTimeField(default=timezone.now) is_rejected = models.BooleanField(default=False) + +class GuestBook(models.Model): + name = models.TextField(default='') + job = models.TextField(default='') + gender = models.CharField(max_length=6, default="Male") + + class ReadLater(models.Model): materi = models.ForeignKey(Materi, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) timestamp = models.DateTimeField(default=timezone.now) class Meta: - unique_together = ["materi", "user"] \ No newline at end of file + unique_together = ["materi", "user"] + + +class NotifikasiKontributor(models.Model): + materi = models.ForeignKey( + Materi, on_delete=models.CASCADE, max_length=120) + user = models.ForeignKey(User, on_delete=models.CASCADE) + feedback = models.CharField(max_length=20, default="") + + def __str__(self): + return "Status: {}".format(self.feedback) + + +class AdminNotification(models.Model): + materi = models.ForeignKey(Materi, on_delete=models.CASCADE) diff --git a/app/services.py b/app/services.py index 95cbb4a64bbb1d5920de6760245bff4d984bbfc9..57ffa31f66c27b3c71f9026a636b471406c62995 100644 --- a/app/services.py +++ b/app/services.py @@ -19,7 +19,7 @@ from pydrive.drive import GoogleDrive from app.forms import SuntingProfilForm from app.models import (Category, Comment, DislikeComment, DownloadStatistics, Like, LikeComment, Materi, Rating, ReadLater, - ViewStatistics) + ViewStatistics, AdminNotification) from app.utils.fileManagementUtil import (get_random_filename, remove_image_exifdata) @@ -29,16 +29,7 @@ class DafterKatalogService: @staticmethod def search_materi(get_search, lst_materi, url): url = url + "&search={0}".format(get_search) - lst_materi = ( - lst_materi.search(get_search) - .filter( - Q(author__icontains=get_search) - | Q(uploader__name__icontains=get_search) - | Q(descriptions__icontains=get_search) - | Q(publisher__icontains=get_search) - ) - .distinct() - ) + lst_materi = lst_materi.search(get_search) return lst_materi, url @staticmethod @@ -63,6 +54,9 @@ class DafterKatalogService: lst_materi = lst_materi.order_by('date_created') elif (get_sort == "terpopuler"): lst_materi = lst_materi.annotate(count=Count('like__id')).order_by('-count') + elif (get_sort == "terhangat"): + lst_materi = sorted(lst_materi, + key=lambda t: (t.hot_score, t.date_created), reverse=True) elif (get_sort == "jumlah_unduh"): lst_materi = lst_materi.annotate(count=Count('unduh__id')).order_by('-count') elif (get_sort == "jumlah_tampilan"): @@ -359,6 +353,7 @@ class UploadMateriService: @staticmethod def upload_materi(form, materi): materi.save() + AdminNotification.objects.create(materi=materi) kateg = form.cleaned_data["categories"] for i in kateg: materi.categories.add(i) @@ -407,6 +402,7 @@ class UploadMateriService: os.path.join(settings.MEDIA_ROOT,default_cover)) materi.save() + AdminNotification.objects.create(materi=materi) for c in excel["Categories"][i].split(","): materi.categories.add(categories.get(name=c)) @@ -432,6 +428,7 @@ class RevisiMateriService: materi.uploader = request.user materi.status = "REVISION" materi.save() + AdminNotification.objects.create(materi=materi) kateg = form.cleaned_data["categories"] for i in kateg: materi.categories.add(i) diff --git a/app/static/app/css/guest_book.css b/app/static/app/css/guest_book.css new file mode 100644 index 0000000000000000000000000000000000000000..681d4f1d307a052b538c5d576c5650f5362f0c09 --- /dev/null +++ b/app/static/app/css/guest_book.css @@ -0,0 +1,63 @@ + +.form-style-6{ + font: 95% Arial, Helvetica, sans-serif; + max-width: 500px; + margin: 25px auto; + padding: 16px; + background: #F7F7F7; + align-content: center; + justify-content: center; +} +.form-style-6 h1{ + background: #43D1AF; + padding: 20px 0; + font-size: 140%; + font-weight: 300; + text-align: center; + color: #fff; + margin: -16px -16px 16px -16px; +} +.form-style-6 input[type="text"], +.form-style-6 select +{ + -webkit-transition: all 0.30s ease-in-out; + -moz-transition: all 0.30s ease-in-out; + -ms-transition: all 0.30s ease-in-out; + -o-transition: all 0.30s ease-in-out; + outline: none; + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + width: 100%; + background: #fff; + margin-bottom: 4%; + border: 1px solid #ccc; + color: #555; + font: 95% Arial, Helvetica, sans-serif; +} +.form-style-6 input[type="text"]:focus, +.form-style-6 textarea:focus, +.form-style-6 select:focus +{ + box-shadow: 0 0 5px #43D1AF; + padding: 3%; + border: 1px solid #43D1AF; +} + +.form-style-6 button[type="submit"]{ + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + width: 100%; + padding: 3%; + background: #1da1f2; + border-bottom: 2px solid #1da1f2; + border-top-style: none; + border-right-style: none; + border-left-style: none; + color: #fff; +} +.form-style-6 input[type="submit"]:hover, +.form-style-6 input[type="button"]:hover{ + background: #2EBC99; +} \ No newline at end of file diff --git a/app/templates/app/base_admin.html b/app/templates/app/base_admin.html deleted file mode 100644 index ff080f96ee2d739e2d7837189a7f9df131341030..0000000000000000000000000000000000000000 --- a/app/templates/app/base_admin.html +++ /dev/null @@ -1,165 +0,0 @@ -{% load static %} - -<!DOCTYPE html> -<html lang="en"> - -<head> - <title>Digipus</title> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <meta name="description" content=""> - <meta name="author" content=""> - - {% block title %}{% endblock %} - - <!-- Custom fonts for this template --> - <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet"> - - <!-- Custom styles for this template --> - <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> - <link href="{% static 'css/sb-admin-2.min.css' %}" rel="stylesheet"> - <link rel="stylesheet" href="{% static 'css/button.css' %}"> - - <!-- Custom styles for this page --> - <link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet"> - -</head> - -<body id="page-top" style="font-family: 'Poppins', sans-serif;"> - - <!-- Page Wrapper --> - <div id="wrapper"> - - <!-- Sidebar --> - <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar"> - - <!-- Sidebar - Brand --> - <a class="sidebar-brand d-flex align-items-center justify-content-center" href="{% url 'daftar_katalog' %}"> - <div class="sidebar-brand-icon rotate-n-15"> - </div> - <div class="sidebar-brand-text mx-3">Digipus</div> - </a> - - <!-- Divider --> - <hr class="sidebar-divider my-0"> - - <!-- Nav Item - Dashboard --> - <li class="nav-item"> - <a class="nav-link" href="/profil/"> - <span>Halaman Profil</span></a> - </li> - - <li class="nav-item"> - <a class="nav-link" href="/sunting/"> - <span>Sunting Profil</span></a> - </li> - - </ul> - <!-- End of Sidebar --> - - <!-- Content Wrapper --> - <div id="content-wrapper" class="d-flex flex-column"> - - <!-- Main Content --> - <div id="content"> - - <!-- Topbar --> - <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow"> - <!-- Sidebar Toggle (Topbar) --> - <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> - <em class="fa fa-bars"></em> - </button> - <div class="sidebar-brand-text mx-3">Diskominfo Kota Depok</div> - <!-- Topbar Navbar --> - <ul class="navbar-nav ml-auto"> - <li class="nav-item"> - <a class="nav-link" href="/administration/"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">Administrasi</span> - </a> - </li> - <li class="nav-item"> - <a class="nav-link" href="/profil/"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">Profil</span> - </a> - </li> - <li class="nav-item"> - <a class="nav-link" href="/logout/"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">Logout</span> - </a> - </li> - <div class="topbar-divider d-none d-sm-block"></div> - <!-- Nav Item - User Information --> - <li class="nav-item dropdown no-arrow"> - <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown" - aria-haspopup="true" aria-expanded="false"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ user.name }}</span> - {% if not user.default_profile_picture %} - <img class="img-profile rounded-circle" src="https://i.ibb.co/9wgPzyZ/default-image.png" alt="User profile picture"> - {% else %} - <img class="img-profile rounded-circle" src="{{ user.profile_picture.url }}" alt="User profile picture"> - {% endif %} - </a> - </li> - </ul> - </nav> - <!-- End of Topbar --> - - <!-- Begin Page Content --> - <div class="container-fluid"> - {% block content %}{% endblock %} - </div> - <!-- /.container-fluid --> - - </div> - <!-- End of Main Content --> - - <!-- Footer --> - <footer class="sticky-footer bg-white"> - <div class="container my-auto"> - <div class="copyright text-center my-auto"> - <span>Copyright © Diskominfo Kota Depok 2020</span> - </div> - </div> - </footer> - <!-- End of Footer --> - - </div> - <!-- End of Content Wrapper --> - - </div> - <!-- End of Page Wrapper --> - - <!-- Scroll to Top Button--> - <a class="scroll-to-top rounded" href="#page-top"> - <em class="fas fa-angle-up"></em> - </a> - - <!-- Bootstrap core JavaScript--> - <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" - integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" - crossorigin="anonymous"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" - integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" - crossorigin="anonymous"></script> - <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" - integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" - crossorigin="anonymous"></script> - - <!-- Core plugin JavaScript--> - <script src="https://code.jquery.com/jquery-3.5.0.min.js" - integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script> - - <!-- Custom scripts for all pages--> - <script src="{% static 'js/sb-admin-2.min.js' %}"></script> - - <!-- Page level plugins --> - <script src="{% static 'vendor/datatables/jquery.dataTables.min.js' %}"></script> - <script src="{% static 'vendor/datatables/dataTables.bootstrap4.min.js' %}"></script> - - <!-- Page level custom scripts --> - <script src="{% static 'js/demo/datatables-demo.js' %}"></script> - -</body> - -</html> \ No newline at end of file diff --git a/app/templates/app/base_dashboard.html b/app/templates/app/base_dashboard.html index c74bd589eb745d75b89cb9f1560428d76b24e4fb..d7303650586965b4cbee33dabf2ca56c4b249556 100644 --- a/app/templates/app/base_dashboard.html +++ b/app/templates/app/base_dashboard.html @@ -23,6 +23,9 @@ <!-- Custom styles for this page --> <link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet"> + + <!-- Bootstrap CSS --> + {% block stylesheets %}{% endblock %} {% block scripts %}{% endblock %} </head> @@ -69,6 +72,10 @@ <em class="fas fa-angle-up"></em> </a> + <!-- Bootstrap scripts --> + <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> </body> </html> \ No newline at end of file diff --git a/app/templates/app/base_profile.html b/app/templates/app/base_profile.html index 081dc2ef7b2c7c987d31d20c31301d3e81849d8b..f9975a5b732b99d096c2de0c44e19a9914156c02 100644 --- a/app/templates/app/base_profile.html +++ b/app/templates/app/base_profile.html @@ -71,6 +71,10 @@ <em class="fas fa-angle-up"></em> </a> + <!-- Bootstrap scripts --> + <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> </body> </html> \ No newline at end of file diff --git a/app/templates/app/detail_materi.html b/app/templates/app/detail_materi.html index c489843d40314a994c7a4e39dba192316ed30e46..e4a6562bfc0e9e5fb3220b783e0110e0a44e55cc 100644 --- a/app/templates/app/detail_materi.html +++ b/app/templates/app/detail_materi.html @@ -284,6 +284,11 @@ div.review { <em class="align-self-center far fa-square" id="readLaterText"></em> Baca Nanti </button> {% endif %} + <button class="btn btn-book shadow-sm p-2 mr-2 bg-white rounded align-self-center" + type="button" aria-haspopup="true" aria-expanded="false" data-toggle="modal" + data-target="#reportModal"> + <em></em>Laporkan + </button> </div> </div> </div> @@ -430,6 +435,12 @@ div.review { {{ review.timestamp|naturaltime }} </p> </div> + {% if user.is_admin %} + <a class="ml-auto p-1 bd-highlight close" + href="{% url 'delete-review' materi_data.id review.id %}"> + <span aria-hidden="true">×</span> + </a> + {% endif %} </div> <div class = 'review'> <p class="paragraph">{{review.review}}</p> @@ -469,6 +480,51 @@ div.review { </div> </div> </div> + +<div class="modal fade" id="reportModal" tabindex="-1" role="dialog" aria-labelledby="reportModal" + aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="reportModal"> + {% if user.is_authenticated %} + Laporkan Materi + {% else %} + Belum Login + {% endif %} + </h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <form method="POST"> + <div class="modal-body"> + {% if user.is_authenticated %} + <p>Judul materi: <b>{{ materi_data.title }}</b><br></p> + {% csrf_token %} + <div class="form-group"> + <textarea placeholder="Alasan laporan..." class="form-control" id="exampleFormControlTextarea1" + rows="3" name="report" required></textarea> + </div> + {% else %} + Login untuk melaporkan materi + {% endif %} + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button> + {% if user.is_authenticated %} + <button type="submit" class="btn btn-primary"> + Laporkan Materi + {% else %} + <button type="button" class="btn btn-primary" onclick="window.location.href = '/login';"> + Login + {% endif %} + </button> + </div> + </form> + </div> + </div> +</div> {% endblock content %} {% block extra_scripts %} <script src="https://kit.fontawesome.com/bc2cedd6b2.js" crossorigin="anonymous"></script> diff --git a/app/templates/app/includes/navbar_katalog_materi.html b/app/templates/app/includes/navbar_katalog_materi.html index e96adc0a452cb125e17693498ff2cbe15609194c..75a4d36f5146998166720904d306a664a4e76f4a 100644 --- a/app/templates/app/includes/navbar_katalog_materi.html +++ b/app/templates/app/includes/navbar_katalog_materi.html @@ -1,15 +1,12 @@ - <nav class="navbar navbar-expand-lg navbar-light static-top shadow katalog-navbar"> <!-- Sidebar Toggle (Topbar) --> - <div class="sidebar-brand-text navbar-brand">Digipus</div> + <nav class="navbar navbar-expand-lg navbar-light sticky-top shadow katalog-navbar"> <!-- Sidebar Toggle (Topbar) --> + <a class="sidebar-brand-text navbar-brand" href="/">Digipus</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> - + <div class="collapse navbar-collapse" id="navbarContent"> <ul class="navbar-nav ml-auto"> - <li class="nav-item active"> - <a class="nav-link" href="/">Home<span class="sr-only">(current)</span></a> - </li> <li class="nav-item"> <a class="nav-link" href="/forum">Forum</a> </li> @@ -19,18 +16,29 @@ <li class="nav-item"> <a class="nav-link" href="/most-contributor">Author</a> </li> + <li class="user-guide"> + <a class="nav-link" href="/userguide/">User Guide</a> + </li> {% if not request.user.is_authenticated %} <li class="nav-item"> - <a class="nav-link" href="/registrasi/umum">Registrasi Umum</a> - </li> - <li class="nav-item"> - <a class="nav-link" href="/registrasi">Registrasi Kontributor</a> - </li> - <li class="nav-item"> - <a class="nav-link" href="/login">Login</a> + <div class="dropdown"> + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Registrasi</a> + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> + <a class="dropdown-item" href="/registrasi/umum">Umum</a> + <a class="dropdown-item" href="/registrasi">Kontributor</a> + </div> + </div> </li> <li class="nav-item"> - <a class="nav-link" href="/login_admin">Login Admin</a> + <div class="dropdown"> + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Login</a> + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> + <a class="dropdown-item" href="/login">Kontributor</a> + <a class="dropdown-item" href="/login_admin">Admin</a> + </div> + </div> </li> {% else %} {% if request.user.is_contributor %} diff --git a/app/templates/app/includes/navigation.html b/app/templates/app/includes/navigation.html index 956d34c1041f387dcea48b5b4febb479efe13637..d83b7829d02e5e243ca0730e6b6d9ff4d548af41 100644 --- a/app/templates/app/includes/navigation.html +++ b/app/templates/app/includes/navigation.html @@ -11,6 +11,28 @@ <ul class="navbar-nav ml-auto"> {% if request.user.is_contributor %} + <li class="nav-item"> + <div class="dropdown"> + {% if kontributor_notif %} + <a id="notifBtn" class="nav-link dropdown-toggle" data-toggle="dropdown" href="#"role="button" aria-haspopup="true" aria-expanded="false" style="color: #4e73df;"> + Notifikasi</a> + <div class="dropdown-menu" aria-labelledby="notifBtn"> + {% for notif in kontributor_notif %} + <div class="dropdown-divider"></div> + <a class="dropdown-item" style="white-space: break-spaces;" href="{% url 'dashboard' %}">Materi "{{notif.materi.title}}" <br>{{ notif }}</a> + <div class="dropdown-divider"></div> + {% endfor %} + {% else %} + <a id="notifBtn" class="nav-link dropdown-toggle" data-toggle="dropdown" href="#"role="button" aria-haspopup="true" aria-expanded="false"> + Notifikasi</a> + <div class="dropdown-menu" aria-labelledby="notifBtn"> + <a class="dropdown-item disabled" href="#" style="white-space: break-spaces;">Anda tidak memiliki notifikasi baru</a> + {% endif %} + </div> + </a> + </div> + </li> + <li class="nav-item"> <a class="nav-link" href="/dashboard/"> <span class="mr-2 d-none d-lg-inline text-gray-600 small">Dasbor</span> diff --git a/app/templates/app/katalog_materi.html b/app/templates/app/katalog_materi.html index 62736dfe989318cc88f0b122fb4ca01e737cc96a..703c9fe62fad1b77b69b87378af9e3f28be08eb5 100644 --- a/app/templates/app/katalog_materi.html +++ b/app/templates/app/katalog_materi.html @@ -66,6 +66,7 @@ </div> <button type="submit" class="btn btn-cari">Cari</button> </form> + <p class="pageTitle">Tidak tahu cara menggunakan Digipus? Silahkan kunjungi <a href="/userguide/"> user guide ini</a></p> <p class="pageTitle">Tidak menemukan materi yang kamu cari ? ajukan permintaan materi kami <a href="/req-materi">disini</a></p> <p class="pageTitle">Ingin diskusi lebih mendalam? Silahkan kunjungi <a @@ -98,16 +99,20 @@ </h2> </div> - <div id="collapseOne" class="collapse" aria-labelledby="headingOne" + <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample"> <div class="card-body"> - <ul> - {% for itemKategori in kategori_list %} - <li> - <a href="?kategori={{itemKategori.pk}}">{{itemKategori.name}}</a> - </li> - {% endfor %} - </ul> + {% if kategori_list.count != 0 %} + <ul> + {% for itemKategori in kategori_list %} + <li> + <a href="?kategori={{itemKategori.pk}}">{{itemKategori.name}}</a> + </li> + {% endfor %} + </ul> + {% else %} + <p class="text-center">Belum ada kategori</p> + {% endif %} </div> </div> </div> @@ -134,6 +139,9 @@ <li> <a href="?sort=terpopuler">terpopuler</a> </li> + <li> + <a href="?sort=terhangat">terhangat</a> + </li> <li> <a href="?sort=judul">judul</a> </li> diff --git a/app/templates/guest_book.html b/app/templates/guest_book.html new file mode 100644 index 0000000000000000000000000000000000000000..b2d32fce5267d65a487a12d6b0bc9612cee1a30c --- /dev/null +++ b/app/templates/guest_book.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} +{% load static %} +{% block title %}Digipus Home{% endblock %} +{% block header %} + +<!DOCTYPE html> +<html lang="en"> + +<head> + <title>Digipus Home</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, inital-scale=1"> + + <link rel="stylesheet" type="text/css" href="{% static 'app/css/guest_book.css' %}"> + {% endblock header %} + +</head> + +<!-- Page Content --> +{% block content %} +<div class="form-group form-style-6" id="myForm"> + <form method="POST"> + <h3 style="text-align: center;">Buku Tamu Non Anggota</h3> + {% csrf_token %} + {{form}} + <button type="submit" class="btn btn-success">Submit</button> + </form> +</div> +{% endblock %} + +</html> \ No newline at end of file diff --git a/app/templates/sukses_admin.html b/app/templates/sukses_admin.html index 40692fdb1abd598551b99f2a644c22352e9f63d1..b40fbd810a520ab9df96a05bbbb203c02a66419d 100644 --- a/app/templates/sukses_admin.html +++ b/app/templates/sukses_admin.html @@ -47,6 +47,23 @@ <span> <a class="btn btn-primary main-content" href="/administration/">Halaman Administrasi</a> </span> + <div class="profile-margin"></div> + {% if notifications%} + <h3>Materi Baru</h3> + {% endif %} + <table class="table main-content"> + <tbody> + {% for i in notifications %} + <tr> + <td> + <a href="/administration/detail-verif/{{i.materi.id}}"> + Materi {{i.materi.title}} butuh verifikasi + </a> + </td> + </tr> + {% endfor %} + </tbody> + </table> </div> </div> </div> diff --git a/app/tests.py b/app/tests.py index f428616f4b27e3277b84c471f51889e73dbddd05..bfcfcafa318de14089d70f7ac65a9c2a3586b3d9 100644 --- a/app/tests.py +++ b/app/tests.py @@ -1,3 +1,13 @@ +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.chrome.options import Options +from selenium.common.exceptions import NoSuchElementException +from selenium import webdriver +from django.test import LiveServerTestCase +import requests +from statistics import mean +import mock +import pandas as pd import base64 import datetime as dt import json @@ -6,8 +16,10 @@ import random import re import tempfile import time +import itertools +from django.test import override_settings from datetime import datetime -from io import StringIO +from io import StringIO, BytesIO from time import sleep import mock @@ -23,16 +35,17 @@ from django.contrib import messages as dj_messages from django.contrib.auth import get_user_model from django.core import mail, serializers from django.core.exceptions import PermissionDenied, ValidationError +from django.core.files.uploadedfile import SimpleUploadedFile, InMemoryUploadedFile from django.core.files import File -from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command from django.db.utils import IntegrityError +from pytz import timezone, UTC from django.test import (Client, RequestFactory, TestCase, TransactionTestCase, override_settings) from django.urls import resolve, reverse from django.utils import timezone -from app.forms import SuntingProfilForm, year_choices +from app.forms import SuntingProfilForm, year_choices, GuestBookForm from app.utils.fileManagementUtil import (get_random_filename, remove_image_exifdata) from app.utils.PasswordValidator import PasswordPolicyValidator @@ -40,27 +53,18 @@ from app.views import UploadMateriHTML, add_rating_materi from .models import (Category, Comment, DislikeComment, DownloadStatistics, Like, LikeComment, Materi, Rating, RatingContributor, - ReadLater, ReqMaterial, Review, ViewStatistics) + ReadLater, ReqMaterial, Review, ViewStatistics, GuestBook, AdminNotification, NotifikasiKontributor) from .services import DetailMateriService from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, PostsView, ProfilView, ReqMateriView, RevisiMateriView, SuksesLoginAdminView, + LaporanMateri, SuksesLoginKontributorView, SuntingProfilView, UploadMateriExcelView, UploadMateriView, password_success) ERROR_403_MESSAGE = "Kamu harus login untuk mengakses halaman ini" -from statistics import mean - -import requests -from django.test import LiveServerTestCase -from selenium import webdriver -from selenium.common.exceptions import NoSuchElementException -from selenium.webdriver.chrome.options import Options -from selenium.webdriver.common.keys import Keys -from webdriver_manager.chrome import ChromeDriverManager - class DaftarKatalogTest(TestCase): def test_daftar_katalog_url_exist(self): @@ -86,66 +90,20 @@ class DaftarKatalogTest(TestCase): resp = Materi.objects.get(id=materi.id) self.assertEqual(resp, materi) - def test_materi_model_generate_search_vector_after_save(self): - Materi(title="Eating book").save() - - search_vector_new_materi = list(Materi.objects.values_list("_search_vector", flat=True)) - expected_search_vector = ["'book':2A 'eat':1A"] - - self.assertSequenceEqual(search_vector_new_materi, expected_search_vector) - - def test_search_text_on_empty_database(self): - search_query = "test" - - search_result = list(Materi.objects.search(search_query)) - expected_search_result = [] - - self.assertSequenceEqual(search_result, expected_search_result) - - def test_search_text_on_unmatched_data(self): - Materi(title="test satu sekarang").save() - Materi(title="test dua nanti").save() - - search_query = "besok" - - search_result = list(Materi.objects.search(search_query)) - expected_search_result = [] - - self.assertSequenceEqual(search_result, expected_search_result) - - def test_search_text_return_list_matched_by_rank(self): - materi_2 = Materi(title="ini lumayan cocok lumayan cocok") - materi_2.save() - - materi_1 = Materi(title="ini sangat cocok sangat cocok sangat cocok") - materi_1.save() - - materi_4 = Materi(title="ini tidak") - materi_4.save() - - materi_3 = Materi(title="ini sedikit cocok") - materi_3.save() - - search_query = "ini cocok" - - search_result = list(Materi.objects.search(search_query)) - expected_search_result = [materi_1, materi_2, materi_3] - - self.assertSequenceEqual(search_result, expected_search_result) class DaftarKatalogSortingByJumlahUnduhTest(TestCase): def setUp(self): self.client = Client() self.contributor_credential = { - "email": "kontributor@gov.id", - "password": "passwordtest" - } + "email": "kontributor@gov.id", + "password": "passwordtest" + } self.contributor_credential_2 = { - "email": "kontributor2@gov.id", - "password": "passwordtest" - } + "email": "kontributor2@gov.id", + "password": "passwordtest" + } self.contributor = get_user_model().objects.create_user( **self.contributor_credential, name="Kontributor 1", is_contributor=True) @@ -173,6 +131,7 @@ class DaftarKatalogSortingByJumlahUnduhTest(TestCase): response = self.client.get("/?sort=jumlah_unduh") self.assertRegex(str(response.content), rf'.*Materi 2.*Materi 1.*') + class DaftarKatalogSortingByJumlahTampilanTest(TestCase): def setUp(self): self.contributor_credential = { @@ -207,14 +166,171 @@ class DaftarKatalogSortingByJumlahTampilanTest(TestCase): response = self.client.get("/?sort=jumlah_tampilan") self.assertRegex(str(response.content), rf'.*Materi 2.*Materi 1.*') + +class DaftarKatalogSortingByTerhangatTest(TestCase): + @classmethod + def generate_view_materi(cls, materi, view_count): + for _ in itertools.repeat(None, view_count): + ViewStatistics.objects.create(materi=materi) + + def get_displayed_materi_in_number(self): + response = self.client.get("/?sort=terhangat") + lst = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", + response.content.decode())] + return lst + + def setUp(self): + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": id_generator() + } + self.contributor = get_user_model().objects.create_user( + name="Kontributor", + is_contributor=True, + **self.contributor_credential, + ) + self.client = Client() + content = b"Test file" + self.cover = SimpleUploadedFile( + "cover.jpg", + content + ) + self.content = SimpleUploadedFile( + "content.txt", + content + ) + + self.materi_data = { + "author": "Reyhan", + "uploader": self.contributor, + "publisher": "Publisher", + "descriptions": "Deskripsi Materi", + "status": "APPROVE", + "cover": self.cover, + "content": self.content, + } + + def test_1_week_difference_give_1_hot_score_difference(self): + materi1 = Materi.objects.create( + title='Materi 1', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi2 = Materi.objects.create( + title='Materi 2', + date_created=datetime(2020, 10, 8, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi3 = Materi.objects.create( + title='Materi 3', + date_created=datetime(2020, 10, 15, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + self.generate_view_materi(materi1, 1) + self.generate_view_materi(materi2, 1) + self.generate_view_materi(materi3, 1) + + self.assertAlmostEqual(materi3.hot_score - materi2.hot_score, 1) + self.assertAlmostEqual(materi2.hot_score - materi1.hot_score, 1) + + def test_10_exponential_view_count_difference_give_1_hot_score_difference(self): + materi1 = Materi.objects.create( + title='Materi 1', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi2 = Materi.objects.create( + title='Materi 2', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi3 = Materi.objects.create( + title='Materi 3', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + self.generate_view_materi(materi1, 1) + self.generate_view_materi(materi2, 10) + self.generate_view_materi(materi3, 100) + + self.assertAlmostEqual(materi3.hot_score - materi2.hot_score, 1) + self.assertAlmostEqual(materi2.hot_score - materi1.hot_score, 1) + + def test_0_and_1_views_has_the_same_hot_score(self): + materi1 = Materi.objects.create( + title='Materi 1', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi2 = Materi.objects.create( + title='Materi 2', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + self.generate_view_materi(materi1, 0) + self.generate_view_materi(materi2, 1) + + self.assertAlmostEqual(materi1.hot_score, materi2.hot_score) + + def test_page_has_option_sort_by_hottest(self): + response = self.client.get("/") + self.assertIn("terhangat", response.content.decode()) + + def test_page_display_sort_by_hottest(self): + materi1 = Materi.objects.create( + title='Materi 1', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi2 = Materi.objects.create( + title='Materi 2', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi3 = Materi.objects.create( + title='Materi 3', + date_created=datetime(2020, 10, 8, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi4 = Materi.objects.create( + title='Materi 4', + date_created=datetime(2020, 10, 9, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + self.generate_view_materi(materi1, 11) + self.generate_view_materi(materi2, 10) + self.generate_view_materi(materi3, 1) + self.generate_view_materi(materi4, 1) + + lst = self.get_displayed_materi_in_number() + self.assertEqual(lst, [4, 1, 3, 2]) + + def test_prefer_newest_materi_if_hot_score_is_same(self): + materi1 = Materi.objects.create( + title='Materi 1', + date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + materi2 = Materi.objects.create( + title='Materi 2', + date_created=datetime(2020, 10, 8, 7, 0, 0, tzinfo=UTC), + **self.materi_data + ) + self.generate_view_materi(materi1, 10) + self.generate_view_materi(materi2, 1) + + lst = self.get_displayed_materi_in_number() + self.assertEqual(lst, [2, 1]) + + class DaftarKatalogSortingByJumlahKomentarTest(TestCase): def setUp(self): self.client = Client() self.contributor_credential = { - "email": "kontributor@gov.id", - "password": "passwordtest" - } + "email": "kontributor@gov.id", + "password": "passwordtest" + } self.contributor_credential_2 = { "email": "kontributor2@gov.id", @@ -242,25 +358,27 @@ class DaftarKatalogSortingByJumlahKomentarTest(TestCase): url = f"/materi/{self.last_uploaded_material.id}/" self.client.login(**self.contributor_credential_2) - self.client.post(url, {"comment": "This is new comment by Kontributor 2"}) + self.client.post( + url, {"comment": "This is new comment by Kontributor 2"}) def test_sorting_by_jumlah_komentar(self): response = self.client.get("/?sort=jumlah_komentar") self.assertRegex(str(response.content), rf'.*Materi 2.*Materi 1.*') + class DaftarKatalogPerKontributorTest(TestCase): def setUp(self): self.client = Client() self.contributor_credential = { - "email": "kontributor@gov.id", - "password": "passwordtest" - } + "email": "kontributor@gov.id", + "password": "passwordtest" + } self.contributor_credential_2 = { - "email": "kontributor2@gov.id", - "password": "passwordtest" - } + "email": "kontributor2@gov.id", + "password": "passwordtest" + } self.contributor = get_user_model().objects.create_user( **self.contributor_credential, name="Kontributor 1", is_contributor=True) @@ -293,7 +411,8 @@ class DaftarKatalogPerKontributorTest(TestCase): def test_katalog_per_kontributor_using_katalog_per_kontributor_func(self): found = resolve(self.url) - self.assertEqual(found.func.__name__, KatalogPerKontributorView.as_view().__name__) + self.assertEqual(found.func.__name__, + KatalogPerKontributorView.as_view().__name__) # def test_katalog_per_kontributor_show_daftar_materi_kontributor(self): # response = self.client.get(self.url) @@ -312,16 +431,17 @@ class DetailMateriTest(TestCase): def _get_materi_info_html(self, info_name, info_value): info_html = '<div class="info" id="1"><dl class="col col-4">' info_html += f'<dt class="info-name">{info_name}</dt>' + '</dl><dd>' - info_html += f'<p class="info-content">{info_value}</p>' + '</dd></div>' + info_html += f'<p class="info-content">{info_value}</p>' + \ + '</dd></div>' return info_html def check_materi_info_in_html(self, info_name, info_value, html_content): expected_content = self._get_materi_info_html(info_name, info_value) - self.assertIn(expected_content, re.sub(">\s*<","><", html_content)) + self.assertIn(expected_content, re.sub(">\s*<", "><", html_content)) def check_materi_info_not_in_html(self, info_name, info_value, html_content): expected_content = self._get_materi_info_html(info_name, info_value) - self.assertNotIn(expected_content, re.sub(">\s*<","><", html_content)) + self.assertNotIn(expected_content, re.sub(">\s*<", "><", html_content)) def setUp(self): self.client = Client() @@ -348,14 +468,14 @@ class DetailMateriTest(TestCase): "ExampleCover921.jpg", b"Test file") self.content = SimpleUploadedFile("ExampleFile921.pdf", b"Test file") - self.materi1 = Materi.objects.create(title="Materi 1", author="Agas", - uploader=self.contributor, publisher="Kelas SC", - descriptions="Deskripsi Materi 1", status="APPROVE", - cover=self.cover, content=self.content) - self.materi2 = Materi.objects.create(title="Materi 2", author="Agad", - uploader=self.contributor, publisher="Kelas SM", - descriptions="Deskripsi Materi 2", status="APPROVE", - cover=self.cover, content=self.content) + self.materi1 = Materi.objects.create(title="Materi 1", author="Agas", + uploader=self.contributor, publisher="Kelas SC", + descriptions="Deskripsi Materi 1", status="APPROVE", + cover=self.cover, content=self.content) + self.materi2 = Materi.objects.create(title="Materi 2", author="Agad", + uploader=self.contributor, publisher="Kelas SM", + descriptions="Deskripsi Materi 2", status="APPROVE", + cover=self.cover, content=self.content) self.url = "/materi/" + str(self.materi1.id) + "/" self.download_url1 = self.url + "unduh" self.url2 = "/materi/" + str(self.materi2.id) + "/" @@ -365,14 +485,15 @@ class DetailMateriTest(TestCase): self.lcount_info_name = "Jumlah Like" self.materi_with_published_date = Materi.objects.create(title="Materi 1", author="Agas", uploader=self.contributor, - publisher="Kelas SC", descriptions="Deskripsi Materi 1", - status="APPROVE", cover=self.cover, content=self.content, - date_modified=timezone.now(), date_created=timezone.now()) - self.materi_with_published_date_url = "/materi/" + str(self.materi_with_published_date.id) + "/" - VerificationReport.objects.create(report='{"feedback": "Something", "kriteria": [{"title": "Kriteria 1", "status": true},' + \ - ' {"title": "Kriteria 2", "status": true}, {"title": "Kriteria 3", "status": true}]}', \ - timestamp="2020-10-09 06:21:33", status="Diterima", materi= self.materi_with_published_date, \ - user=self.materi_with_published_date.uploader) + publisher="Kelas SC", descriptions="Deskripsi Materi 1", + status="APPROVE", cover=self.cover, content=self.content, + date_modified=timezone.now(), date_created=timezone.now()) + self.materi_with_published_date_url = "/materi/" + \ + str(self.materi_with_published_date.id) + "/" + VerificationReport.objects.create(report='{"feedback": "Something", "kriteria": [{"title": "Kriteria 1", "status": true},' + + ' {"title": "Kriteria 2", "status": true}, {"title": "Kriteria 3", "status": true}]}', + timestamp="2020-10-09 06:21:33", status="Diterima", materi=self.materi_with_published_date, + user=self.materi_with_published_date.uploader) def test_detail_materi_url_exist(self): response = Client().get(self.url) @@ -413,6 +534,14 @@ class DetailMateriTest(TestCase): self.assertIn("Anda belum menuliskan komentar", response.context["error_message"]) + def test_post_blank_report(self): + url = self.url + self.client.login(**self.anonymous_credential) + response = self.client.post(url, {"report": ""}) + self.assertIn("error_message", response.context) + self.assertIn("Anda belum menuliskan komentar", + response.context["error_message"]) + def test_comment_rendered_to_template(self): url = self.url self.client.login(**self.contributor_credential) @@ -420,6 +549,13 @@ class DetailMateriTest(TestCase): response = Client().get(url) self.assertContains(response, "This is my new comment") + def test_report_rendered_to_template(self): + url = self.url + self.client.login(**self.contributor_credential) + self.client.post(url, {"report": "This is my new comment"}) + response = Client().get(url) + self.assertEqual(response.status_code, 200) + def test_comment_by_admin(self): url = self.url self.client.login(**self.admin_credential) @@ -448,14 +584,17 @@ class DetailMateriTest(TestCase): self.client.get("/logout/") self.client.login(**self.anonymous_credential) - self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) - comment = Comment.objects.get(comment="This is new comment by Anonymous").id + self.client.post( + url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get( + comment="This is new comment by Anonymous").id response = self.client.get(url_materi) session_id = response.context["session_id"] payload = {"comment": comment, "session_id": session_id} self.client.post("/comment/dislike/", payload) - num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment).count() + num_of_comment_dislikes = DislikeComment.objects.filter( + comment=comment).count() self.assertEqual(num_of_comment_dislikes, 0) def test_comment_liked_by_anonymous(self): @@ -463,14 +602,17 @@ class DetailMateriTest(TestCase): self.client.get("/logout/") self.client.login(**self.anonymous_credential) - self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) - comment = Comment.objects.get(comment="This is new comment by Anonymous").id + self.client.post( + url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get( + comment="This is new comment by Anonymous").id response = self.client.get(url_materi) session_id = response.context["session_id"] payload = {"comment": comment, "session_id": session_id} self.client.post("/comment/like/", payload) - num_of_comment_likes = LikeComment.objects.filter(comment=comment).count() + num_of_comment_likes = LikeComment.objects.filter( + comment=comment).count() self.assertEqual(num_of_comment_likes, 0) def test_comment_undisliked_by_anonymous(self): @@ -478,16 +620,19 @@ class DetailMateriTest(TestCase): self.client.get("/logout/") self.client.login(**self.anonymous_credential) - self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) - comment = Comment.objects.get(comment="This is new comment by Anonymous") + self.client.post( + url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get( + comment="This is new comment by Anonymous") response = self.client.get(url_materi) session_id = response.context["session_id"] - + payload = {"comment": comment, "session_id": session_id} self.client.post("/comment/dislike/", payload) - + self.client.post("/comment/dislike/", payload) - num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment, session_id=session_id).count() + num_of_comment_dislikes = DislikeComment.objects.filter( + comment=comment, session_id=session_id).count() self.assertEqual(num_of_comment_dislikes, 0) def test_comment_unliked_by_anonymous(self): @@ -495,16 +640,19 @@ class DetailMateriTest(TestCase): self.client.get("/logout/") self.client.login(**self.anonymous_credential) - self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) - comment = Comment.objects.get(comment="This is new comment by Anonymous") + self.client.post( + url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get( + comment="This is new comment by Anonymous") response = self.client.get(url_materi) session_id = response.context["session_id"] - + payload = {"comment": comment, "session_id": session_id} self.client.post("/comment/like/", payload) - + self.client.post("/comment/like/", payload) - num_of_comment_likes = LikeComment.objects.filter(comment=comment, session_id=session_id).count() + num_of_comment_likes = LikeComment.objects.filter( + comment=comment, session_id=session_id).count() self.assertEqual(num_of_comment_likes, 0) def test_comment_new_does_not_have_dislike(self): @@ -514,10 +662,13 @@ class DetailMateriTest(TestCase): response = self.client.get(url_materi) session_id = response.context["session_id"] - self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + self.client.post( + url_materi, {"comment": "This is new comment by Anonymous"}) - comment = Comment.objects.get(comment="This is new comment by Anonymous") - comment_dislike_counter = DislikeComment.objects.filter(comment=comment, session_id=session_id).count() + comment = Comment.objects.get( + comment="This is new comment by Anonymous") + comment_dislike_counter = DislikeComment.objects.filter( + comment=comment, session_id=session_id).count() self.assertEqual(comment_dislike_counter, 0) def test_comment_new_does_not_have_like(self): @@ -527,10 +678,13 @@ class DetailMateriTest(TestCase): response = self.client.get(url_materi) session_id = response.context["session_id"] - self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + self.client.post( + url_materi, {"comment": "This is new comment by Anonymous"}) - comment = Comment.objects.get(comment="This is new comment by Anonymous") - comment_like_counter = LikeComment.objects.filter(comment=comment, session_id=session_id).count() + comment = Comment.objects.get( + comment="This is new comment by Anonymous") + comment_like_counter = LikeComment.objects.filter( + comment=comment, session_id=session_id).count() self.assertEqual(comment_like_counter, 0) def test_comment_sends_email_to_contributor_that_subscribes(self): @@ -542,12 +696,13 @@ class DetailMateriTest(TestCase): is_subscribing_to_material_comments=True ) - material = Materi.objects.create(title="Materi-subscribed", author="Tester", - uploader=contributor_subscribed, publisher="Kelas PMPL", - descriptions="Deskripsi Materi subscribed", status="APPROVE", - cover=self.cover, content=self.content) + material = Materi.objects.create(title="Materi-subscribed", author="Tester", + uploader=contributor_subscribed, publisher="Kelas PMPL", + descriptions="Deskripsi Materi subscribed", status="APPROVE", + cover=self.cover, content=self.content) url = "/materi/" + str(material.id) + "/" - self.client.login(**self.contributor_credential) # comment with other user + # comment with other user + self.client.login(**self.contributor_credential) prev_outbox_count = len(mail.outbox) @@ -569,12 +724,13 @@ class DetailMateriTest(TestCase): is_subscribing_to_material_comments=False ) - material = Materi.objects.create(title="Materi-not-subscribed", author="Tester", - uploader=contributor_not_subscribed, publisher="Kelas PMPL", - descriptions="Deskripsi Materi non-subscribed", status="APPROVE", - cover=self.cover, content=self.content) + material = Materi.objects.create(title="Materi-not-subscribed", author="Tester", + uploader=contributor_not_subscribed, publisher="Kelas PMPL", + descriptions="Deskripsi Materi non-subscribed", status="APPROVE", + cover=self.cover, content=self.content) url = "/materi/" + str(material.id) + "/" - self.client.login(**self.contributor_credential) # comment with other user + # comment with other user + self.client.login(**self.contributor_credential) prev_outbox_count = len(mail.outbox) @@ -599,12 +755,13 @@ class DetailMateriTest(TestCase): is_subscribing_to_material_comments=True ) - material = Materi.objects.create(title="Materi-subscribed", author="Tester", - uploader=contributor_subscribed, publisher="Kelas PMPL", - descriptions="Deskripsi Materi subscribed", status="APPROVE", - cover=self.cover, content=self.content) + material = Materi.objects.create(title="Materi-subscribed", author="Tester", + uploader=contributor_subscribed, publisher="Kelas PMPL", + descriptions="Deskripsi Materi subscribed", status="APPROVE", + cover=self.cover, content=self.content) url = "/materi/" + str(material.id) + "/" - self.client.login(**contributor_subscribed_credentials) # comment with the same user + # comment with the same user + self.client.login(**contributor_subscribed_credentials) prev_outbox_count = len(mail.outbox) @@ -639,7 +796,8 @@ class DetailMateriTest(TestCase): def test_detail_materi_not_contains_form_comment(self): response = self.client.get(self.url) self.assertNotContains(response, "Beri komentar...") - self.assertContains(response, "Login terlebih dahulu untuk berkomentar") + self.assertContains( + response, "Login terlebih dahulu untuk berkomentar") def create_and_delete_comment(self, is_admin=False, is_contributor=False): url = self.url @@ -658,7 +816,8 @@ class DetailMateriTest(TestCase): def test_delete_comments_by_admin(self): self.create_and_delete_comment(is_admin=True) - count = Comment.objects.all().filter(comment="This is new comment by Anonymous").count() + count = Comment.objects.all().filter( + comment="This is new comment by Anonymous").count() self.assertEqual(count, 0) def test_delete_comments_by_contributor(self): @@ -667,7 +826,8 @@ class DetailMateriTest(TestCase): self.assertRaises(PermissionDenied) self.assertEqual(response.status_code, 403) - count = Comment.objects.all().filter(comment="This is new comment by Anonymous").count() + count = Comment.objects.all().filter( + comment="This is new comment by Anonymous").count() self.assertEqual(count, 1) def test_delete_comments_by_anonymous(self): @@ -676,10 +836,10 @@ class DetailMateriTest(TestCase): self.assertRaises(PermissionDenied) self.assertEqual(response.status_code, 403) - count = Comment.objects.all().filter(comment="This is new comment by Anonymous").count() + count = Comment.objects.all().filter( + comment="This is new comment by Anonymous").count() self.assertEqual(count, 1) - def test_review_models_can_create_new_object(self): test = Review.objects.create( username="saul", profile="121212", review="232323") @@ -726,6 +886,44 @@ class DetailMateriTest(TestCase): self.client.post(url, {"review": "This is new review by Anonymous"}) response = self.client.get(url) self.assertContains(response, "Anonymous") + + def create_and_delete_review(self, is_admin=False, is_contributor=False): + url = self.url + self.client.login(**self.admin_credential) + self.client.post(url, {"review": "A review by Anonymous"}) + delete_url = "/review/delete/" + str(self.materi1.id) + "/" + str( + Review.objects.get(review="A review by Anonymous").id) + if is_admin: + self.client.login(**self.admin_credential) + if is_contributor: + self.client.login(**self.contributor_credential) + if not is_admin and not is_contributor: + self.client.login(**self.anonymous_credential) + response = self.client.get(delete_url) + return response + + def test_delete_review_by_admin(self): + self.create_and_delete_review(is_admin=True) + count = Review.objects.all().filter(review="A review by Anonymous").count() + self.assertEqual(count, 0) + + def test_delete_review_by_contributor(self): + response = self.create_and_delete_review(is_contributor=True) + + self.assertRaises(PermissionDenied) + self.assertEqual(response.status_code, 403) + + count = Review.objects.all().filter(review="A review by Anonymous").count() + self.assertEqual(count, 1) + + def test_delete_review_by_regular_user(self): + response = self.create_and_delete_review() + + self.assertRaises(PermissionDenied) + self.assertEqual(response.status_code, 403) + + count = Review.objects.all().filter(review="A review by Anonymous").count() + self.assertEqual(count, 1) def test_detail_materi_contains_review_count(self): url = self.url @@ -735,7 +933,7 @@ class DetailMateriTest(TestCase): self.assertContains(response, "Review (0)") self.client.post( - url, {"review": review }) + url, {"review": review}) self.client.post( url, {"review": review}) response = self.client.get(url) @@ -749,7 +947,8 @@ class DetailMateriTest(TestCase): def test_detail_materi_not_contains_form_review(self): response = self.client.get(self.url) self.assertNotContains(response, "Beri Review") - self.assertContains(response, "Login terlebih dahulu untuk Membuat review") + self.assertContains( + response, "Login terlebih dahulu untuk Membuat review") def test_tombol_citasiAPA(self): response = self.client.get(self.url) @@ -757,25 +956,27 @@ class DetailMateriTest(TestCase): def test_hasil_citasi_APA_materi_has_no_published_date(self): response = self.client.get(self.url) - expected = self.materi1.author + " . (n.d) . " + self.materi1.title + " . " + self.materi1.publisher + expected = self.materi1.author + \ + " . (n.d) . " + self.materi1.title + " . " + self.materi1.publisher self.assertIn(expected, response.context["citationAPA"]) self.assertIn(expected, response.context["citationAPA"]) def test_hasil_citasi_APA_materi_has_published_date(self): response = self.client.get(self.materi_with_published_date_url) - published_date = self.materi_with_published_date.published_date.strftime("%Y-%m-%d %H:%M") - expected = ( - self.materi_with_published_date.author - + " . (" - + published_date - + ") . " - + self.materi_with_published_date.title - + " . " - + self.materi_with_published_date.publisher - ) + published_date = self.materi_with_published_date.published_date.strftime( + "%Y-%m-%d %H:%M") + expected = ( + self.materi_with_published_date.author + + " . (" + + published_date + + ") . " + + self.materi_with_published_date.title + + " . " + + self.materi_with_published_date.publisher + ) self.assertIn(expected, response.context["citationAPA"]) - + def test_citation_IEEE_button(self): response = self.client.get(self.url) self.assertContains(response, "Citate IEEE") @@ -787,19 +988,19 @@ class DetailMateriTest(TestCase): current_month = current_date.strftime("%b") current_year = str(current_date.year) - expected = ( - "Agas, " - + "Materi 1. " - + "Kelas SC, n.d. " - + "Accessed on: " - + current_month - + ". " - + current_day - + ", " - + current_year - + ". [Online]. " - + "Available: http://testserver" - + self.url + expected = ( + "Agas, " + + "Materi 1. " + + "Kelas SC, n.d. " + + "Accessed on: " + + current_month + + ". " + + current_day + + ", " + + current_year + + ". [Online]. " + + "Available: http://testserver" + + self.url ) self.assertIn(expected, response.context["citationIEEE"]) @@ -809,7 +1010,8 @@ class DetailMateriTest(TestCase): current_day = str(current_date.day) current_month = current_date.strftime("%b") current_year = str(current_date.year) - published_date = self.materi_with_published_date.published_date.strftime('%Y') + published_date = self.materi_with_published_date.published_date.strftime( + '%Y') expected = "Agas, " + \ "Materi 1. " + \ @@ -879,10 +1081,11 @@ class DetailMateriTest(TestCase): context2 = {} DetailMateriService.init_materi_download_count(context1, self.materi1) DetailMateriService.init_materi_download_count(context2, self.materi2) - self.assertNotEqual(context1['materi_download_count'], context2['materi_download_count']) + self.assertNotEqual( + context1['materi_download_count'], context2['materi_download_count']) self.assertEqual(context1['materi_download_count'], 3) self.assertEqual(context2['materi_download_count'], 2) - + def test_download_count_displayed_on_template_when_no_download(self): response = self.client.get(self.url) html = response.content.decode("utf-8") @@ -923,16 +1126,20 @@ class DetailMateriTest(TestCase): dcount_materi1 = self.materi1.unduh.all().count() dcount_materi2 = self.materi2.unduh.all().count() - self.check_materi_info_in_html(self.dcount_info_name, dcount_materi1, html) - self.check_materi_info_not_in_html(self.dcount_info_name, dcount_materi2, html) - self.check_materi_info_in_html(self.dcount_info_name, dcount_materi2, html2) - self.check_materi_info_not_in_html(self.dcount_info_name, dcount_materi1, html2) - + self.check_materi_info_in_html( + self.dcount_info_name, dcount_materi1, html) + self.check_materi_info_not_in_html( + self.dcount_info_name, dcount_materi2, html) + self.check_materi_info_in_html( + self.dcount_info_name, dcount_materi2, html2) + self.check_materi_info_not_in_html( + self.dcount_info_name, dcount_materi1, html2) + def test_like_count_displayed_on_template_when_no_like(self): response = self.client.get(self.url) html = response.content.decode("utf-8") self.check_materi_info_in_html(self.lcount_info_name, 0, html) - + def test_like_count_displayed_on_template_when_single_like(self): payload = { 'materi_id': self.materi1.id, @@ -942,8 +1149,8 @@ class DetailMateriTest(TestCase): response = self.client.get(self.url) html = response.content.decode("utf-8") - self.check_materi_info_in_html(self.lcount_info_name, 1, html) - + self.check_materi_info_in_html(self.lcount_info_name, 1, html) + def test_like_count_displayed_on_template_when_multiple_like(self): payload1 = { 'materi_id': self.materi1.id, @@ -955,17 +1162,17 @@ class DetailMateriTest(TestCase): } self.client.post(self.like_url, payload1) self.client.post(self.like_url, payload2) - + response = self.client.get(self.url) html = response.content.decode("utf-8") self.check_materi_info_in_html(self.lcount_info_name, 2, html) - + def test_like_count_displayed_on_template_when_unlike(self): payload = { 'materi_id': self.materi1.id, 'session_id': "dummysession" } - + # Like materi once self.client.post(self.like_url, payload) response = self.client.get(self.url) @@ -978,6 +1185,7 @@ class DetailMateriTest(TestCase): html = response.content.decode("utf-8") self.check_materi_info_in_html(self.lcount_info_name, 0, html) + class PostsViewTest(TestCase): @classmethod @@ -1005,14 +1213,14 @@ class PostsViewTest(TestCase): "comments": [], } - for j in range (LIKES_COUNT_PER_POST[i]): + for j in range(LIKES_COUNT_PER_POST[i]): Like.objects.create( - timestamp=timezone.now(), - materi=post, + timestamp=timezone.now(), + materi=post, session_id=("dummysession-" + str(i) + '-' + str(j)) ) time.sleep(0.1) - + for i, post_id in enumerate(post_comment_group_dict): post = post_comment_group_dict[post_id]["data"] @@ -1032,7 +1240,8 @@ class PostsViewTest(TestCase): "email": "user@email.com", "password": "justpass" } - cls.user = User.objects.create_user(**cls.user_credentials, is_contributor=True) + cls.user = User.objects.create_user( + **cls.user_credentials, is_contributor=True) cls.data = cls.generate_posts_data(cls.user) def _request_as_user(self): @@ -1082,12 +1291,12 @@ class PostsViewTest(TestCase): self.assertRegex( str(response.content), - rf'.*(<div id="post-{posts[2]}">)' + \ - rf'.*(<div id="post-{posts[2]}-comment-{comments[2][0]}">)' + \ - rf'.*(<div id="post-{posts[2]}-comment-{comments[2][1]}">)' + \ - rf'.*(<div id="post-{posts[2]}-comment-{comments[2][2]}">)' + \ - rf'.*(<div id="post-{posts[1]}">)' + \ - rf'.*(<div id="post-{posts[0]}">)' + \ + rf'.*(<div id="post-{posts[2]}">)' + + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][0]}">)' + + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][1]}">)' + + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][2]}">)' + + rf'.*(<div id="post-{posts[1]}">)' + + rf'.*(<div id="post-{posts[0]}">)' + rf'.*(<div id="post-{posts[0]}-comment-{comments[0][0]}">)' ) @@ -1099,9 +1308,11 @@ class PostsViewTest(TestCase): self.assertContains( response, - '<span id="post-like-count-' + str(post.id) + '">' + str(post.like_count) + '</span>', + '<span id="post-like-count-' + + str(post.id) + '">' + str(post.like_count) + '</span>', ) + class TemplateLoaderTest(TestCase): def test_template_loader_url_exist(self): url = "/test-page.html" @@ -1154,13 +1365,13 @@ class UploadPageTest(TestCase): response = Client().get("/fake/") self.assertEqual(response.status_code, 404) - def test_upload_page_url_admin_doesnt_exist(self): + def test_upload_page_url_admin_doesnt_exist(self): self.client.login(email="admin@gov.id", password="admin") response = self.client.get("/unggah/") self.assertEqual(response.status_code, 403) - def test_upload_page_url_admin_cant_upload(self): + def test_upload_page_url_admin_cant_upload(self): self.client.login(email="admin@gov.id", password="admin") response = self.client.post("/unggah/") @@ -1280,7 +1491,9 @@ class UploadExcelPageTest(TestCase): file_path = os.path.join(settings.MEDIA_ROOT, 'dummy.xlsx') - writer = pd.ExcelWriter(file_path, engine='xlsxwriter') #pylint: disable=abstract-class-instantiated + # pylint: disable=abstract-class-instantiated + writer = pd.ExcelWriter( + file_path, engine='xlsxwriter') data_frame.to_excel(writer, index=0) writer.save() @@ -1291,14 +1504,15 @@ class UploadExcelPageTest(TestCase): password="kontributor") field_lengths = { - 'author':30, - 'publisher':30, + 'author': 30, + 'publisher': 30, } - file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths) + file_name, data_frame = self.create_dummy_excel( + field_lengths=field_lengths) with open(file_name, 'rb') as fp: response = self.client.post("/unggah_excel/", {'excel': fp}) - + messages = list(dj_messages.get_messages(response.wsgi_request)) msg_text = messages[0].message @@ -1309,33 +1523,34 @@ class UploadExcelPageTest(TestCase): password="kontributor") field_lengths = { - 'title':50, - 'publisher':30, + 'title': 50, + 'publisher': 30, } - file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths) + file_name, data_frame = self.create_dummy_excel( + field_lengths=field_lengths) with open(file_name, 'rb') as fp: response = self.client.post("/unggah_excel/", {'excel': fp}) - + messages = list(dj_messages.get_messages(response.wsgi_request)) msg_text = messages[0].message self.assertIn('Author', msg_text) - def test_upload_excel_upload_file_publisher_error(self): self.client.login(email="kontributor@gov.id", password="kontributor") field_lengths = { - 'title':50, - 'author':30, + 'title': 50, + 'author': 30, } - file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths) + file_name, data_frame = self.create_dummy_excel( + field_lengths=field_lengths) with open(file_name, 'rb') as fp: response = self.client.post("/unggah_excel/", {'excel': fp}) - + messages = list(dj_messages.get_messages(response.wsgi_request)) msg_text = messages[0].message @@ -1346,15 +1561,16 @@ class UploadExcelPageTest(TestCase): password="kontributor") field_lengths = { - 'title':50, - 'author':30, - 'publisher':30, + 'title': 50, + 'author': 30, + 'publisher': 30, } - file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths) + file_name, data_frame = self.create_dummy_excel( + field_lengths=field_lengths) with open(file_name, 'rb') as fp: response = self.client.post("/unggah_excel/", {'excel': fp}) - + messages = list(dj_messages.get_messages(response.wsgi_request)) msg_text = messages[0].message @@ -1369,37 +1585,38 @@ class UploadExcelPageTest(TestCase): Category(name='Deep Learning').save() field_lengths = { - 'title':50, - 'author':30, - 'publisher':30, + 'title': 50, + 'author': 30, + 'publisher': 30, } - categories = ['Computer Science','Machine Learning','Deep Learning'] - - file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths, categories=categories) + categories = ['Computer Science', 'Machine Learning', 'Deep Learning'] + + file_name, data_frame = self.create_dummy_excel( + field_lengths=field_lengths, categories=categories) with open(file_name, 'rb') as fp: self.client.post("/unggah_excel/", {'excel': fp}) - + title = data_frame['Title'][0] materi = Materi.objects.get(title=title) default_path = 'book-cover-placeholder.png' self.assertTrue(materi) self.assertEquals(materi.cover.name, default_path) - self.assertTrue(os.path.exists(os.path.join(settings.MEDIA_ROOT, default_path))) + self.assertTrue(os.path.exists( + os.path.join(settings.MEDIA_ROOT, default_path))) def test_upload_excel_download_template(self): self.client.login(email="kontributor@gov.id", password="kontributor") response = self.client.get("/unggah_excel/?template=1") - - self.assertEquals(response['Content-Type'],'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') - self.assertEquals(response['Content-Disposition'],'attachment; filename=template.xlsx') - - + self.assertEquals( + response['Content-Type'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + self.assertEquals(response['Content-Disposition'], + 'attachment; filename=template.xlsx') class DashboardKontributorViewTest(TestCase): @@ -1469,6 +1686,7 @@ class DashboardKontributorViewTest(TestCase): html = response.content.decode("utf-8") self.assertIn(ERROR_403_MESSAGE, html) + class DeleteMateriTest(TestCase): def setUp(self): self.client = Client() @@ -1494,7 +1712,8 @@ class DeleteMateriTest(TestCase): "content.txt", b"Test") self.cover = SimpleUploadedFile( "flower.jpg", b"Test file") - self.contributor = User.objects.create_contributor(**self.contributor_credential) + self.contributor = User.objects.create_contributor( + **self.contributor_credential) Materi(title="Materi 1", author="Agas", uploader=self.contributor, publisher="Kelas SC", descriptions="Deskripsi Materi 1", status="APPROVE", cover=self.cover, content=self.content).save() @@ -1505,14 +1724,14 @@ class DeleteMateriTest(TestCase): self.client.login(**self.contributor_credential) response = self.client.get(self.url) self.assertEqual(response.status_code, 302) - + def test_url_soft_delete_materi_is_success_as_admin(self): self.client.login(**self.admin_credential) response = self.client.get(self.url) - self.assertEqual(response.status_code, 302) + self.assertEqual(response.status_code, 302) self.materi1.refresh_from_db() self.assertNotEqual(self.materi1.deleted_at, None) - + def test_url_soft_delete_materi_is_success_as_superuser(self): self.client.login(**self.superuser_credential) response = self.client.get(self.url) @@ -1520,6 +1739,7 @@ class DeleteMateriTest(TestCase): self.materi1.refresh_from_db() self.assertNotEqual(self.materi1.deleted_at, None) + class ProfilViewTest(TestCase): @classmethod def setUpTestData(cls): @@ -1528,10 +1748,13 @@ class ProfilViewTest(TestCase): cls.template_name = "profil.html" cls.view = ProfilView - cls.contributor_credentials = {"email": "contributor@gov.id", "password": "justpass"} - cls.contributor = User.objects.create_contributor(**cls.contributor_credentials) + cls.contributor_credentials = { + "email": "contributor@gov.id", "password": "justpass"} + cls.contributor = User.objects.create_contributor( + **cls.contributor_credentials) - cls.admin_credentials = {"email": "admin@gov.id", "password": "justpass"} + cls.admin_credentials = { + "email": "admin@gov.id", "password": "justpass"} cls.admin = User.objects.create_admin(**cls.admin_credentials) def test_returns_correct_profile_view(self): @@ -1539,7 +1762,7 @@ class ProfilViewTest(TestCase): self.assertEqual(found.func.__name__, self.view.as_view().__name__) def _request_as_user(self, credentials): - self.client = Client() + self.client = Client() self.client.login(**credentials) return self.client.get(self.url) @@ -1570,10 +1793,13 @@ class SuntingProfilViewTest(TestCase): cls.template_name = "sunting.html" cls.view = SuntingProfilView - cls.contributor_credentials = {"email": "contributor@gov.id", "password": "justpass"} - cls.contributor = User.objects.create_contributor(**cls.contributor_credentials) + cls.contributor_credentials = { + "email": "contributor@gov.id", "password": "justpass"} + cls.contributor = User.objects.create_contributor( + **cls.contributor_credentials) - cls.admin_credentials = {"email": "admin@gov.id", "password": "justpass"} + cls.admin_credentials = { + "email": "admin@gov.id", "password": "justpass"} cls.admin = User.objects.create_admin(**cls.admin_credentials) def test_sunting_profile_view(self): @@ -1581,7 +1807,7 @@ class SuntingProfilViewTest(TestCase): self.assertEqual(found.func.__name__, self.view.as_view().__name__) def _request_as_user(self, credentials): - self.client = Client() + self.client = Client() self.client.login(**credentials) return self.client.get(self.url) @@ -1616,8 +1842,10 @@ class SuntingProfilViewTest(TestCase): } form = SuntingProfilForm(data=form_data) - self.assertEqual(form.fields["twitter"].widget.attrs.get("autofocus"), "") - self.assertEqual(form.fields["instagram"].widget.attrs.get("autofocus"), None) + self.assertEqual( + form.fields["twitter"].widget.attrs.get("autofocus"), "") + self.assertEqual( + form.fields["instagram"].widget.attrs.get("autofocus"), None) class SuksesLoginKontributorTest(TestCase): @@ -1962,7 +2190,7 @@ class RevisiMateriTest(TestCase): # Logout self.client.logout() - + class GenerateDummyCommandTest(TestCase): def setUp(self): @@ -1998,7 +2226,8 @@ class RemoveDummyCommandTest(TestCase): call_command("removedummy", stdout=stdout) - self.assertIn("Successfully remove all dummy object", stdout.getvalue()) + self.assertIn("Successfully remove all dummy object", + stdout.getvalue()) self.assertEqual(User.objects.count(), 0) self.assertEqual(Category.objects.count(), 0) self.assertEqual(Materi.objects.count(), 0) @@ -2023,8 +2252,10 @@ class RatingMateriTest(TestCase): self.contributor = get_user_model().objects.create_user( **self.contributor_credential, name="Kontributor", is_contributor=True ) - self.user_one = get_user_model().objects.create_user(**self.user_one_credential, name="User One") - self.user_two = get_user_model().objects.create_user(**self.user_two_credential, name="User Two") + self.user_one = get_user_model().objects.create_user( + **self.user_one_credential, name="User One") + self.user_two = get_user_model().objects.create_user( + **self.user_two_credential, name="User Two") self.cover = SimpleUploadedFile( "cover.jpg", b"Test file" @@ -2050,17 +2281,20 @@ class RatingMateriTest(TestCase): self.assertEqual(rating.materi, self.materi1) self.assertEqual(rating.user, self.user_one) self.assertTrue(0 < rating.score < 6) - self.assertEqual(rating.__str__(), "Material:Materi 1 | User:User One | Rating:5") + self.assertEqual(rating.__str__(), + "Material:Materi 1 | User:User One | Rating:5") def test_rating_model_should_not_be_created_with_rating_more_than_5(self): with self.assertRaises(ValidationError) as context: Rating(materi=self.materi1, user=self.user_one, score=6).save() - self.assertTrue('Rating score must be integer between 1-5' in str(context.exception)) + self.assertTrue( + 'Rating score must be integer between 1-5' in str(context.exception)) def test_rating_model_should_not_be_created_with_rating_less_than_1(self): with self.assertRaises(ValidationError) as context: Rating(materi=self.materi1, user=self.user_one, score=0).save() - self.assertTrue('Rating score must be integer between 1-5' in str(context.exception)) + self.assertTrue( + 'Rating score must be integer between 1-5' in str(context.exception)) def test_one_materi_should_be_able_to_be_related_to_multiple_rating(self): Rating(materi=self.materi1, user=self.user_one, score=1).save() @@ -2112,7 +2346,8 @@ class RatingMateriTest(TestCase): self.assertEqual(response.status_code, 403) def test_rating_materi_post_not_authenticated_should_return_403_forbidden(self): - response = self.client.post(self.url_rate, {'materi_id': 1, 'rating_score': 5}) + response = self.client.post( + self.url_rate, {'materi_id': 1, 'rating_score': 5}) response_json = json.loads(response.content) self.assertEqual(response_json.get("success", None), False) self.assertEqual(response_json.get("msg", None), "Forbidden") @@ -2137,52 +2372,65 @@ class RatingMateriTest(TestCase): def test_rating_materi_authenticated_materi_id_doesnt_exist_should_return_422(self): self.client.login(**self.user_one_credential) - response = self.client.post(self.url_rate, {'materi_id': 123456, 'rating_score': 5}) + response = self.client.post( + self.url_rate, {'materi_id': 123456, 'rating_score': 5}) response_json = json.loads(response.content) self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "Materi does not exist") + self.assertEqual(response_json.get( + "msg", None), "Materi does not exist") self.assertEqual(response.status_code, 422) def test_rating_materi_authenticated_param_wrong_data_type_should_return_422(self): self.client.login(**self.user_one_credential) - response = self.client.post(self.url_rate, {'materi_id': "STRING", 'rating_score': 5}) + response = self.client.post( + self.url_rate, {'materi_id': "STRING", 'rating_score': 5}) response_json = json.loads(response.content) self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "materi_id must be an integer") + self.assertEqual(response_json.get("msg", None), + "materi_id must be an integer") self.assertEqual(response.status_code, 422) - response = self.client.post(self.url_rate, {'materi_id': 1, 'rating_score': "STRING"}) + response = self.client.post( + self.url_rate, {'materi_id': 1, 'rating_score': "STRING"}) response_json = json.loads(response.content) self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "rating_score must be an integer") + self.assertEqual(response_json.get("msg", None), + "rating_score must be an integer") self.assertEqual(response.status_code, 422) def test_rating_score_should_be_between_1_and_5(self): self.client.login(**self.user_one_credential) for i in range(1, 6): Rating.objects.all().delete() - response = self.client.post(self.url_rate, {'materi_id': self.materi1.id, 'rating_score': i}) + response = self.client.post( + self.url_rate, {'materi_id': self.materi1.id, 'rating_score': i}) response_json = json.loads(response.content) # self.assertEqual(response_json.get("success", None), True) - self.assertEqual(response_json.get("msg", None), "Rating successfully created") + self.assertEqual(response_json.get("msg", None), + "Rating successfully created") self.assertEqual(response.status_code, 201) for i in [-100, -7, -6, -1, 0, 6, 7, 100]: Rating.objects.all().delete() - response = self.client.post(self.url_rate, {'materi_id': self.materi1.id, 'rating_score': i}) + response = self.client.post( + self.url_rate, {'materi_id': self.materi1.id, 'rating_score': i}) response_json = json.loads(response.content) # self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "Rating must be an integer from 1 to 5") + self.assertEqual(response_json.get("msg", None), + "Rating must be an integer from 1 to 5") self.assertEqual(response.status_code, 422) def test_user_should_not_able_to_rate_materi_twice(self): self.client.login(**self.user_one_credential) Rating.objects.all().delete() - self.client.post(self.url_rate, {'materi_id': self.materi1.id, 'rating_score': 1}) - response = self.client.post(self.url_rate, {'materi_id': self.materi1.id, 'rating_score': 2}) + self.client.post( + self.url_rate, {'materi_id': self.materi1.id, 'rating_score': 1}) + response = self.client.post( + self.url_rate, {'materi_id': self.materi1.id, 'rating_score': 2}) response_json = json.loads(response.content) # self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "Rating already exist") + self.assertEqual(response_json.get( + "msg", None), "Rating already exist") self.assertEqual(response.status_code, 409) def test_user_authenticated_visit_unrated_should_get_0_materi_rating_score_context(self): @@ -2200,11 +2448,12 @@ class RatingMateriTest(TestCase): response = self.client.get(self.url_materi) self.assertEqual(1, response.context.get('materi_rating_score')) + class FileManagementUtilTest(TestCase): def setUp(self): self.filename = "image_with_exif_data.gif" self.file_content = open(settings.BASE_DIR + "/app/test_files/" + - self.filename, "rb").read() + self.filename, "rb").read() def test_get_random_filename_isCorrect(self): generated_name = get_random_filename(self.filename) @@ -2227,6 +2476,7 @@ class FileManagementUtilTest(TestCase): self.assertTrue(len(sanitized_img) < len(self.file_content)) self.assertTrue(b'<exif:' not in sanitized_img) + class RequestMateriTest(TestCase): def setUp(self): self.client = Client() @@ -2278,15 +2528,16 @@ class RequestMateriTest(TestCase): self.client.login(**self.contributor_credential) response = self.client.post(self.url, - data={ - 'title': 'Requested Material' - }) + data={ + 'title': 'Requested Material' + }) self.assertEqual(ReqMaterial.objects.count(), 1) new_material_request = ReqMaterial.objects.first() self.assertEqual(new_material_request.title, 'Requested Material') - self.assertIn('Permintaan materi berhasil dikirimkan', response.content.decode()) + self.assertIn('Permintaan materi berhasil dikirimkan', + response.content.decode()) self.client.logout() def test_given_no_title_should_not_save_request_and_return_correct_response_message(self): @@ -2333,62 +2584,77 @@ class RatingContributorTest(TransactionTestCase): self.url = f"/profil/{self.contributor.email}/" def test_add_rating_contributor(self): - RatingContributor.objects.create(score=3, contributor=self.contributor, user=self.anonymous) + RatingContributor.objects.create( + score=3, contributor=self.contributor, user=self.anonymous) self.assertEqual(1, RatingContributor.objects.count()) def test_add_rating_contributor_should_failed_when_negative(self): with self.assertRaises(ValidationError): - RatingContributor.objects.create(score=-1, contributor=self.contributor, user=self.anonymous) + RatingContributor.objects.create( + score=-1, contributor=self.contributor, user=self.anonymous) self.assertEqual(0, RatingContributor.objects.count()) def test_add_rating_contributor_should_failed_when_bigger_than_five(self): with self.assertRaises(ValidationError): - RatingContributor.objects.create(score=6, contributor=self.contributor, user=self.anonymous) + RatingContributor.objects.create( + score=6, contributor=self.contributor, user=self.anonymous) self.assertEqual(0, RatingContributor.objects.count()) def test_submit_form_correct_rating_contributor_should_added(self): self.client.login(**self.anonymous_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 5}) - self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor.id).count()) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.anonymous.id, "score": 5}) + self.assertEqual(1, RatingContributor.objects.filter( + contributor=self.contributor.id).count()) self.client.logout() self.client.login(**self.admin_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": 1}) - self.assertEqual(2, RatingContributor.objects.filter(contributor=self.contributor.id).count()) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.admin.id, "score": 1}) + self.assertEqual(2, RatingContributor.objects.filter( + contributor=self.contributor.id).count()) def test_submit_form_not_correct_rating_contributor_should__not_added(self): self.client.login(**self.anonymous_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 6}) - self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor.id).count()) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.anonymous.id, "score": 6}) + self.assertEqual(0, RatingContributor.objects.filter( + contributor=self.contributor.id).count()) self.client.logout() self.client.login(**self.admin_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": 0}) - self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor.id).count()) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.admin.id, "score": 0}) + self.assertEqual(0, RatingContributor.objects.filter( + contributor=self.contributor.id).count()) def test_average_rating_score_empty(self): response = self.client.get(self.url) - self.assertTemplateUsed(response=response, template_name="app/katalog_kontri.html") + self.assertTemplateUsed( + response=response, template_name="app/katalog_kontri.html") self.assertContains(response=response, text="Rating: 0", count=1) self.assertContains(response=response, text="oleh: 0 orang", count=1) def test_average_rating_correct(self): self.client.login(**self.anonymous_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 5}) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.anonymous.id, "score": 5}) response = self.client.get(self.url) self.assertContains(response=response, text="Rating: 5", count=1) self.assertContains(response=response, text="oleh: 1 orang", count=1) def test_average_rating_form_incorrect_correct(self): self.client.login(**self.anonymous_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 6}) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.anonymous.id, "score": 6}) response = self.client.get(self.url) self.assertContains(response=response, text="Rating: 0", count=1) self.assertContains(response=response, text="oleh: 0 orang", count=1) self.client.logout() self.client.login(**self.admin_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": -1}) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.admin.id, "score": -1}) response = self.client.get(self.url) self.assertContains(response=response, text="Rating: 0", count=1) self.assertContains(response=response, text="oleh: 0 orang", count=1) @@ -2397,10 +2663,13 @@ class RatingContributorTest(TransactionTestCase): score = 5 avg = [score] self.client.login(**self.anonymous_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": score}) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.anonymous.id, "score": score}) response = self.client.get(self.url) - self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) - self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + self.assertContains(response=response, + text=f"Rating: {mean(avg)}", count=1) + self.assertContains(response=response, + text=f"oleh: {len(avg)} orang", count=1) self.client.logout() self.anonymous2 = get_user_model().objects.create_user( @@ -2409,10 +2678,13 @@ class RatingContributorTest(TransactionTestCase): score = 4 avg.append(score) self.client.login(email=self.anonymous2.email, password="test") - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous2.id, "score": score}) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.anonymous2.id, "score": score}) response = self.client.get(self.url) - self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) - self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + self.assertContains(response=response, + text=f"Rating: {mean(avg)}", count=1) + self.assertContains(response=response, + text=f"oleh: {len(avg)} orang", count=1) self.client.logout() self.anonymous3 = get_user_model().objects.create_user( @@ -2421,10 +2693,13 @@ class RatingContributorTest(TransactionTestCase): score = 3 avg.append(score) self.client.login(email=self.anonymous3.email, password="test") - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous3.id, "score": score}) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.anonymous3.id, "score": score}) response = self.client.get(self.url) - self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) - self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + self.assertContains(response=response, + text=f"Rating: {mean(avg)}", count=1) + self.assertContains(response=response, + text=f"oleh: {len(avg)} orang", count=1) self.client.logout() self.anonymous4 = get_user_model().objects.create_user( @@ -2433,120 +2708,131 @@ class RatingContributorTest(TransactionTestCase): score = 2 avg.append(score) self.client.login(email=self.anonymous4.email, password="test") - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous4.id, "score": score}) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.anonymous4.id, "score": score}) response = self.client.get(self.url) - self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) - self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + self.assertContains(response=response, + text=f"Rating: {mean(avg)}", count=1) + self.assertContains(response=response, + text=f"oleh: {len(avg)} orang", count=1) self.client.logout() score = 1 avg.append(score) self.client.login(**self.admin_credential) - self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": score}) + self.client.post(self.url, data={ + "contributor": self.contributor.id, "user": self.admin.id, "score": score}) response = self.client.get(self.url) - self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) - self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) - + self.assertContains(response=response, + text=f"Rating: {mean(avg)}", count=1) + self.assertContains(response=response, + text=f"oleh: {len(avg)} orang", count=1) + def test_not_authenticated_user_should_not_able_to_add_rating(self): response_post = self.client.post(self.url, data={ - "contributor":self.contributor.id, - "user":self.anonymous.id, - "score":3 + "contributor": self.contributor.id, + "user": self.anonymous.id, + "score": 3 }) response_get = self.client.get(self.url) self.assertEqual(response_post.status_code, 403) - self.assertContains(response_get, "Kamu harus login untuk memberi rating") - + self.assertContains( + response_get, "Kamu harus login untuk memberi rating") + def test_not_authenticated_user_should_not_able_to_delete_rating(self): - response_post = self.client.post(self.url, data={"delete":True}) + response_post = self.client.post(self.url, data={"delete": True}) self.assertEqual(response_post.status_code, 403) - + def test_authenticated_user_should_not_delete_rating_if_has_not_been_rated(self): self.client.login(**self.admin_credential) self.client.post(self.url, data={ - "contributor":self.contributor.id, - "user":self.admin.id, - "score":3 + "contributor": self.contributor.id, + "user": self.admin.id, + "score": 3 }) - - self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count()) + + self.assertEqual(1, RatingContributor.objects.filter( + contributor=self.contributor).count()) self.client.logout() self.client.login(**self.anonymous_credential) - self.client.post(self.url, data={"delete":True}) - - self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count()) - + self.client.post(self.url, data={"delete": True}) + + self.assertEqual(1, RatingContributor.objects.filter( + contributor=self.contributor).count()) + def test_authenticated_user_should_delete_rating_if_has_been_rated(self): self.client.login(**self.admin_credential) self.client.post(self.url, data={ - "contributor":self.contributor.id, - "user":self.admin.id, - "score":3 + "contributor": self.contributor.id, + "user": self.admin.id, + "score": 3 }) response = self.client.get(self.url) self.assertContains(response, 'Rating: 3') self.assertContains(response, 'oleh: 1 orang') - self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count()) + self.assertEqual(1, RatingContributor.objects.filter( + contributor=self.contributor).count()) - self.client.post(self.url, data={"delete":True}) + self.client.post(self.url, data={"delete": True}) response = self.client.get(self.url) self.assertContains(response, 'Rating: 0') self.assertContains(response, 'oleh: 0 orang') - self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor).count()) - + self.assertEqual(0, RatingContributor.objects.filter( + contributor=self.contributor).count()) + def test_average_still_be_correct_when_rating_was_deleted(self): - scores = [2,4] + scores = [2, 4] self.client.login(**self.admin_credential) self.client.post(self.url, data={ - "contributor":self.contributor.id, - "user":self.admin.id, - "score":scores[0] + "contributor": self.contributor.id, + "user": self.admin.id, + "score": scores[0] }) self.client.logout() self.client.login(**self.anonymous_credential) self.client.post(self.url, data={ - "contributor":self.contributor.id, - "user":self.anonymous.id, - "score":scores[1] + "contributor": self.contributor.id, + "user": self.anonymous.id, + "score": scores[1] }) response = self.client.get(self.url) self.assertContains(response, f'Rating: {mean(scores)}') self.assertContains(response, 'oleh: 2 orang') - self.client.post(self.url, data={"delete":True}) + self.client.post(self.url, data={"delete": True}) response = self.client.get(self.url) self.assertContains(response, f'Rating: {scores[0]}') self.assertContains(response, 'oleh: 1 orang') - + def test_authenticated_user_should_update_rating_if_has_been_rated(self): self.client.login(**self.anonymous_credential) self.client.post(self.url, data={ - "contributor":self.contributor.id, - "user":self.anonymous.id, - "score":5 + "contributor": self.contributor.id, + "user": self.anonymous.id, + "score": 5 }) response = self.client.get(self.url) self.assertContains(response, 'Rating: 5') self.assertContains(response, 'oleh: 1 orang') - self.client.post(self.url, data={"update":True, "score":3}) + self.client.post(self.url, data={"update": True, "score": 3}) response = self.client.get(self.url) self.assertContains(response, 'Rating: 3') self.assertContains(response, 'oleh: 1 orang') - + def test_authenticated_user_should_not_update_rating_if_has_not_been_rated(self): self.client.login(**self.anonymous_credential) response = self.client.get(self.url) self.assertContains(response, 'Rating: 0') self.assertContains(response, 'oleh: 0 orang') - self.client.post(self.url, data={"update":True, "score":3}) + self.client.post(self.url, data={"update": True, "score": 3}) response = self.client.get(self.url) self.assertContains(response, 'Rating: 0') self.assertContains(response, 'oleh: 0 orang') - + class UserDownloadHistoryTest(TestCase): def setUp(self): @@ -2585,26 +2871,30 @@ class UserDownloadHistoryTest(TestCase): 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() + 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() + + 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() + 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() + + 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) @@ -2613,19 +2903,19 @@ class UserDownloadHistoryTest(TestCase): 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() + pk__in=downloaded_materi).count() self.assertEqual(num_of_downloads, 1) def test_registered_user_multiple_download(self): @@ -2638,53 +2928,53 @@ class UserDownloadHistoryTest(TestCase): 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() + 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() + 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 + + # 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() + pk__in=downloaded_materi).count() self.assertEqual(user1_num_of_downloads, 1) self.assertEqual(guest_num_of_downloads, 1) - - # Login Anonym 2 + + # 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() @@ -2692,10 +2982,11 @@ class UserDownloadHistoryTest(TestCase): 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 = { @@ -2712,42 +3003,42 @@ class DownloadHistoryViewTest(TestCase): **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.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) + 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) - + 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 + # Forced Logout self.client.logout() - + response = self.client.get(self.history_url) self.assertEqual(response.status_code, 200) @@ -2759,14 +3050,14 @@ class DownloadHistoryViewTest(TestCase): 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 = pytz.timezone(TIME_ZONE) - + download_history = self.user_anonim.riwayat_unduh.all() response = self.client.get(self.history_url) resp_html = response.content.decode('utf8') @@ -2774,19 +3065,20 @@ class DownloadHistoryViewTest(TestCase): 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) - + 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 = pytz.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']: @@ -2794,19 +3086,20 @@ class DownloadHistoryViewTest(TestCase): 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) - + 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 = pytz.timezone(TIME_ZONE) - + download_history = self.user_anonim.riwayat_unduh.all() response = self.client.get(self.history_url) resp_html = response.content.decode('utf8') @@ -2814,25 +3107,26 @@ class DownloadHistoryViewTest(TestCase): 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) - + 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 + # download with 1 second interval to differ download time self.client.get(self.download_url1) sleep(1) self.client.get(self.download_url2) @@ -2840,23 +3134,25 @@ class DownloadHistoryViewTest(TestCase): 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>" + + 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) + 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) @@ -2865,9 +3161,9 @@ class DownloadHistoryViewTest(TestCase): class MateriModelTest(TestCase): - + def setUp(self): - self.contributor = User.objects.create( + self.contributor = User.objects.create( email="kontributor@gov.id", password="passwordtest", name="kontributor", @@ -2887,12 +3183,15 @@ class MateriModelTest(TestCase): self.assertEqual(0, self.materi.like_count) def test_like_count_return_right_value_when_there_is_like(self): - Like.objects.create(timestamp=timezone.now(), materi=self.materi, session_id="dummysessionid1") + Like.objects.create(timestamp=timezone.now(), + materi=self.materi, session_id="dummysessionid1") self.assertEqual(1, self.materi.like_count) - Like.objects.create(timestamp=timezone.now(), materi=self.materi, session_id="dummysessionid2") + Like.objects.create(timestamp=timezone.now(), + materi=self.materi, session_id="dummysessionid2") self.assertEqual(2, self.materi.like_count) + class MateriFavoriteTest(TestCase): @classmethod def setUpTestData(cls): @@ -2901,7 +3200,8 @@ class MateriFavoriteTest(TestCase): "email": "user@email.com", "password": "justpass" } - cls.user = User.objects.create_user(**cls.user_credentials, is_contributor=True) + cls.user = User.objects.create_user( + **cls.user_credentials, is_contributor=True) def _request_as_user(self): self.client.login(**self.user_credentials) @@ -2909,7 +3209,9 @@ class MateriFavoriteTest(TestCase): def test_url_resolves_to_favorite_view(self): found = resolve(self.url) - self.assertEqual(found.func.__name__, MateriFavorite.as_view().__name__) + self.assertEqual(found.func.__name__, + MateriFavorite.as_view().__name__) + class RandomizedMateriTest(TestCase): def setUp(self): @@ -2954,7 +3256,8 @@ class RandomizedMateriTest(TestCase): response = Client().get("/?random=1") self.assertIn("Materi 1", response.content.decode()) self.assertIn("Materi 2", response.content.decode()) - + + class YearChoicesTest(TestCase): def test_release_year_contains_the_right_current_year(self): now = dt.date.today().year @@ -2968,6 +3271,7 @@ class YearChoicesTest(TestCase): self.assertEqual((2000, 2000), choices[0]) + TEST_IMAGE = ''' iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI WXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAABfElEQVQ4y52TvUuCURTGf5Zg @@ -3007,25 +3311,26 @@ cpnR0WOUSiVEhLVKhbXXa7xcXqHyaoV6o0Hqd1MxUjqu7XYLMFkaNXtXYC09+R5UwbkYEcVaizFm P/LWGsLJydMs3VvCWkP3gzxK7OKu7Bl81/tEhKmpKVhYWNCJiQkNglDDMKdhLpf1/0AQhDo+Pq5z c3NKmqa6uLios7MXtFgsahRFGhUKHUS7KBQ0iiIdGhrS8+dndH5+XpMk0X8AMTVx/inpU4cAAAAl dEVYdGNyZWF0ZS1kYXRlADIwMTAtMTItMjZUMTQ6NDk6MjErMDk6MDAHHBB1AAAAJXRFWHRtb2Rp -ZnktZGF0ZQAyMDEwLTEyLTI2VDE0OjQ5OjIxKzA5OjAwWK1mQQAAAABJRU5ErkJggg== +ZnktZGF0ZQAyMDEwLTEyLTI2VDE0OjQ5OjIxKzA5OjAwWK1mQQAAAABJRU5ErkJggg == '''.strip() + + class YTUrlVideoTest(TestCase): def setUp(self): self.client = Client() - self.contributor_credential = {"email": "kontributor@gov.id", "password": "passwordtest"} + self.contributor_credential = { + "email": "kontributor@gov.id", "password": "passwordtest"} self.contributor = get_user_model().objects.create_user( **self.contributor_credential, name="Kontributor", is_contributor=True ) self.setUpImage() self.content = SimpleUploadedFile("ExampleFile221.pdf", b"Test file") - self.category = Category.objects.create(id="1", name="medis", description="kategori medis") - + self.category = Category.objects.create( + id="1", name="medis", description="kategori medis") + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) def setUpImage(self): - from io import BytesIO - - from django.core.files.uploadedfile import InMemoryUploadedFile self.cover = InMemoryUploadedFile( BytesIO(base64.b64decode(TEST_IMAGE)), field_name='tempfile', @@ -3048,23 +3353,23 @@ class YTUrlVideoTest(TestCase): def test_upload_materi_with_valid_yt_video_id(self): self.client.login(**self.contributor_credential) self.client.post( - "/unggah/", data={"title":"Materi 1", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", - "descriptions":"Deskripsi Materi 1", 'categories':"1", - "cover":self.cover, "content":self.content, - "yt_video_id":"jNwz4L9MGVY"} + "/unggah/", data={"title": "Materi 1", "author": "Agas", "publisher": "Kelas SC", "release_year": "2000", + "descriptions": "Deskripsi Materi 1", 'categories': "1", + "cover": self.cover, "content": self.content, + "yt_video_id": "jNwz4L9MGVY"} ) self.assertTrue(Materi.objects.get(title="Materi 1")) - + def test_upload_materi_with_invalid_yt_video_id(self): self.client.login(**self.contributor_credential) self.client.post( - "/unggah/", data={"title":"Materi 2", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", - "descriptions":"Deskripsi Materi 1", 'categories':"1", - "cover":self.cover, "content":self.content, - "yt_video_id":"randomId"} + "/unggah/", data={"title": "Materi 2", "author": "Agas", "publisher": "Kelas SC", "release_year": "2000", + "descriptions": "Deskripsi Materi 1", 'categories': "1", + "cover": self.cover, "content": self.content, + "yt_video_id": "randomId"} ) self.assertEqual(Materi.objects.filter(title="Materi 2").count(), 0) - + def test_detail_materi_has_video_if_yt_video_id_not_empty(self): self.test_upload_materi_with_valid_yt_video_id() pk = Materi.objects.get(title="Materi 1").pk @@ -3075,20 +3380,23 @@ class YTUrlVideoTest(TestCase): def test_detail_materi_has_no_video_if_yt_video_id_empty(self): self.client.login(**self.contributor_credential) self.client.post( - "/unggah/", data={"title":"Materi 2", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", - "descriptions":"Deskripsi Materi 1", 'categories':"1", - "cover":self.cover, "content":self.content,} + "/unggah/", data={"title": "Materi 2", "author": "Agas", "publisher": "Kelas SC", "release_year": "2000", + "descriptions": "Deskripsi Materi 1", 'categories': "1", + "cover": self.cover, "content": self.content, } ) pk = Materi.objects.get(title="Materi 2").pk response = self.client.get("/materi/"+str(pk)+"/") html = response.content.decode("utf-8") self.assertNotIn("Video", html) + class ChangePasswordTest(TestCase): def setUp(self): self.client = Client() - self.kontributor = User.objects.create_contributor(email="kontributor@gov.id", password="kontributor") - self.admin = User.objects.create_admin(email="admin@gov.id", password="admin") + self.kontributor = User.objects.create_contributor( + email="kontributor@gov.id", password="kontributor") + self.admin = User.objects.create_admin( + email="admin@gov.id", password="admin") self.url = "/change-password/" self.view = PasswordChangeViews self.template_name = "change-password.html" @@ -3115,7 +3423,7 @@ class ChangePasswordTest(TestCase): # Logout self.client.logout() - + class SeeRatedMateriByUser(TestCase): def setUp(self): self.client = Client() @@ -3127,7 +3435,8 @@ class SeeRatedMateriByUser(TestCase): "email": "admin@gov.id", "password": id_generator() } - self.kontributor = User.objects.create_contributor(**self.contributor_credential) + self.kontributor = User.objects.create_contributor( + **self.contributor_credential) self.admin = User.objects.create_admin(**self.admin_credential) self.given_rating_url = "/given-rating/" @@ -3152,13 +3461,17 @@ class SeeRatedMateriByUser(TestCase): self.materi2 = Materi.objects.all()[1] self.materi3 = Materi.objects.all()[2] time.sleep(5) - self.rating_test_1 = Rating(materi=self.materi1, user=self.kontributor, score=5) + self.rating_test_1 = Rating( + materi=self.materi1, user=self.kontributor, score=5) time.sleep(5) - self.rating_test_2 = Rating(materi=self.materi2, user=self.kontributor, score=4) + self.rating_test_2 = Rating( + materi=self.materi2, user=self.kontributor, score=4) time.sleep(5) - self.rating_test_3 = Rating(materi=self.materi3, user=self.kontributor, score=3) + self.rating_test_3 = Rating( + materi=self.materi3, user=self.kontributor, score=3) time.sleep(5) - self.rating_test_4 = Rating(materi=self.materi3, user=self.admin, score=3) + self.rating_test_4 = Rating( + materi=self.materi3, user=self.admin, score=3) self.rating_test_1.save() self.rating_test_2.save() @@ -3198,12 +3511,14 @@ class SeeRatedMateriByUser(TestCase): self.assertNotIn(self.rating_test_4, response.context['rating_list']) def test_given_rating_page_no_rating_should_display_message(self): - user_credential = {"email": "user@mail.com", "password": id_generator()} + user_credential = {"email": "user@mail.com", + "password": id_generator()} User.objects.create_contributor(**user_credential) self.client.login(**user_credential) response = self.client.get(self.given_rating_url) self.assertEqual(len(response.context['rating_list']), 0) - self.assertIn("Anda belum pernah memberikan rating ke materi", response.content.decode("utf-8")) + self.assertIn("Anda belum pernah memberikan rating ke materi", + response.content.decode("utf-8")) self.assertNotIn(self.materi1.title, response.content.decode("utf-8")) def test_given_rating_page_order_should_give_default_parameter(self): @@ -3219,24 +3534,28 @@ class SeeRatedMateriByUser(TestCase): self.assertEqual(response.context['order_by_key'], 'timestamp') # No order_by parameter, should default to asc - response = self.client.get(self.given_rating_url + '?order_by_key=score') + response = self.client.get( + self.given_rating_url + '?order_by_key=score') self.assertEqual(response.context['order_by'], 'dsc') self.assertEqual(response.context['order_by_key'], 'score') # INVALID PARAMETERS # Starts with negative - response = self.client.get(self.given_rating_url + '?order_by_key=-score') + response = self.client.get( + self.given_rating_url + '?order_by_key=-score') self.assertEqual(response.context['order_by'], 'dsc') self.assertEqual(response.context['order_by_key'], 'score') # Invalid params - response = self.client.get(self.given_rating_url + '?order_by_key=halohalohalo&order=haihaihaihai') + response = self.client.get( + self.given_rating_url + '?order_by_key=halohalohalo&order=haihaihaihai') self.assertEqual(response.context['order_by'], 'dsc') self.assertEqual(response.context['order_by_key'], 'timestamp') def test_given_rating_page_order_ascending_should_be_correct(self): self.client.login(**self.contributor_credential) - response = self.client.get(self.given_rating_url + '?order_by_key=score&order=asc') + response = self.client.get( + self.given_rating_url + '?order_by_key=score&order=asc') # From Low to High # order key score @@ -3245,13 +3564,15 @@ class SeeRatedMateriByUser(TestCase): self.assertEqual(list(response.context['rating_list']), [self.rating_test_3, self.rating_test_2, self.rating_test_1]) # order key timestamp - response = self.client.get(self.given_rating_url + '?order_by_key=timestamp&order=asc') + response = self.client.get( + self.given_rating_url + '?order_by_key=timestamp&order=asc') self.assertEqual(response.context['order_by'], 'asc') self.assertEqual(response.context['order_by_key'], 'timestamp') self.assertEqual(list(response.context['rating_list']), [self.rating_test_1, self.rating_test_2, self.rating_test_3]) # order key materi title - response = self.client.get(self.given_rating_url + '?order_by_key=materi__title&order=asc') + response = self.client.get( + self.given_rating_url + '?order_by_key=materi__title&order=asc') self.assertEqual(response.context['order_by'], 'asc') self.assertEqual(response.context['order_by_key'], 'materi__title') self.assertEqual(list(response.context['rating_list']), @@ -3261,26 +3582,30 @@ class SeeRatedMateriByUser(TestCase): self.client.login(**self.contributor_credential) # From High to Low # order key score - response = self.client.get(self.given_rating_url + '?order_by_key=score&order=dsc') + response = self.client.get( + self.given_rating_url + '?order_by_key=score&order=dsc') self.assertEqual(response.context['order_by'], 'dsc') self.assertEqual(response.context['order_by_key'], 'score') self.assertEqual(list(response.context['rating_list']), [self.rating_test_1, self.rating_test_2, self.rating_test_3]) # order key timestamp - response = self.client.get(self.given_rating_url + '?order_by_key=timestamp&order=dsc') + response = self.client.get( + self.given_rating_url + '?order_by_key=timestamp&order=dsc') self.assertEqual(response.context['order_by'], 'dsc') self.assertEqual(response.context['order_by_key'], 'timestamp') self.assertEqual(list(response.context['rating_list']), [self.rating_test_3, self.rating_test_2, self.rating_test_1]) # order key materi title - response = self.client.get(self.given_rating_url + '?order_by_key=materi__title&order=dsc') + response = self.client.get( + self.given_rating_url + '?order_by_key=materi__title&order=dsc') self.assertEqual(response.context['order_by'], 'dsc') self.assertEqual(response.context['order_by_key'], 'materi__title') self.assertEqual(list(response.context['rating_list']), [self.rating_test_2, self.rating_test_1, self.rating_test_3]) + class PasswordValidatorPolicyTest(TestCase): def setUp(self): self.password_no_lowercase = "PASSW0RD!" @@ -3290,28 +3615,34 @@ class PasswordValidatorPolicyTest(TestCase): self.password_length_lower_than_8 = "P4ss!" self.password_enforcing_policy = "Passw0rd!" self.validator = PasswordPolicyValidator() - + def test_using_password_no_lowercase(self): - self.assertRaises(ValidationError, self.validator.validate, self.password_no_lowercase) + self.assertRaises( + ValidationError, self.validator.validate, self.password_no_lowercase) def test_using_password_no_upprcase(self): - self.assertRaises(ValidationError, self.validator.validate, self.password_no_uppercase) - + self.assertRaises( + ValidationError, self.validator.validate, self.password_no_uppercase) + def test_using_password_no_digit(self): - self.assertRaises(ValidationError, self.validator.validate, self.password_no_digit) + self.assertRaises( + ValidationError, self.validator.validate, self.password_no_digit) def test_using_password_no_special_char(self): - self.assertRaises(ValidationError, self.validator.validate, self.password_no_special_char) - + self.assertRaises(ValidationError, self.validator.validate, + self.password_no_special_char) + def test_using_password_with_length_less_than_8(self): - self.assertRaises(ValidationError, self.validator.validate, self.password_length_lower_than_8) - + self.assertRaises(ValidationError, self.validator.validate, + self.password_length_lower_than_8) + def test_using_password_using_correct_policy(self): - self.assertEquals(self.validator.validate(self.password_enforcing_policy), None) - + self.assertEquals(self.validator.validate( + self.password_enforcing_policy), None) + class LandingPageNavbarTest(TestCase): - + def setUp(self): self.client = Client() self.contributor_credential = { @@ -3326,52 +3657,95 @@ class LandingPageNavbarTest(TestCase): "email": "public@gov.id", "password": id_generator() } - self.kontributor = User.objects.create_contributor(**self.contributor_credential) + self.kontributor = User.objects.create_contributor( + **self.contributor_credential) self.admin = User.objects.create_admin(**self.admin_credential) self.public = User.objects.create_user(**self.public_credential) def test_navbar_admin(self): self.client.login(**self.admin_credential) response = self.client.get('/') - self.assertContains(response, '<a class="nav-link" href="/">Home<span class="sr-only">(current)</span></a>') - self.assertContains(response, '<a class="nav-link" href="/forum">Forum</a>') - self.assertContains(response, '<a class="nav-link" href="/news/all">Berita</a>') - self.assertContains(response, '<a class="nav-link" href="/administration">Administrasi</a>') - self.assertContains(response, '<a class="nav-link" href="/profil">Profil</a>') - self.assertContains(response, '<a class="nav-link" href="/logout">Logout</a>') - self.assertNotContains(response, '<a class="nav-link" href="/logout">LogoutX</a>') + self.assertContains( + response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>') + self.assertContains( + response, '<a class="nav-link" href="/forum">Forum</a>') + self.assertContains( + response, '<a class="nav-link" href="/news/all">Berita</a>') + self.assertContains( + response, '<a class="nav-link" href="/profil">Profil</a>') + self.assertContains( + response, '<a class="nav-link" href="/logout">Logout</a>') + self.assertContains( + response, '<a class="nav-link" href="/administration">Administrasi</a>') + self.assertNotContains( + response, '<a class="nav-link" href="/dashboard">Dasbor</a>') def test_navbar_contributor(self): self.client.login(**self.contributor_credential) response = self.client.get('/') - self.assertContains(response, '<a class="nav-link" href="/">Home<span class="sr-only">(current)</span></a>') - self.assertContains(response, '<a class="nav-link" href="/forum">Forum</a>') - self.assertContains(response, '<a class="nav-link" href="/news/all">Berita</a>') - self.assertContains(response, '<a class="nav-link" href="/dashboard">Dasbor</a>') - self.assertContains(response, '<a class="nav-link" href="/profil">Profil</a>') - self.assertContains(response, '<a class="nav-link" href="/logout">Logout</a>') - self.assertNotContains(response, '<a class="nav-link" href="/logout">LogoutX</a>') + self.assertContains( + response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>') + self.assertContains( + response, '<a class="nav-link" href="/forum">Forum</a>') + self.assertContains( + response, '<a class="nav-link" href="/news/all">Berita</a>') + self.assertContains( + response, '<a class="nav-link" href="/profil">Profil</a>') + self.assertContains( + response, '<a class="nav-link" href="/logout">Logout</a>') + self.assertContains( + response, '<a class="nav-link" href="/dashboard">Dasbor</a>') + self.assertNotContains( + response, '<a class="nav-link" href="/administration">Administrasi</a>') def test_navbar_public(self): self.client.login(**self.public_credential) response = self.client.get('/') - self.assertContains(response, '<a class="nav-link" href="/">Home<span class="sr-only">(current)</span></a>') - self.assertContains(response, '<a class="nav-link" href="/forum">Forum</a>') - self.assertContains(response, '<a class="nav-link" href="/news/all">Berita</a>') - self.assertContains(response, '<a class="nav-link" href="/profil">Profil</a>') - self.assertContains(response, '<a class="nav-link" href="/logout">Logout</a>') - self.assertNotContains(response, '<a class="nav-link" href="/logout">LogoutX</a>') + self.assertContains( + response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>') + self.assertContains( + response, '<a class="nav-link" href="/forum">Forum</a>') + self.assertContains( + response, '<a class="nav-link" href="/news/all">Berita</a>') + self.assertContains( + response, '<a class="nav-link" href="/profil">Profil</a>') + self.assertContains( + response, '<a class="nav-link" href="/logout">Logout</a>') + self.assertNotContains( + response, '<a class="nav-link" href="/dashboard">Dasbor</a>') + self.assertNotContains( + response, '<a class="nav-link" href="/administration">Administrasi</a>') def test_navbar_anonymous(self): response = self.client.get('/') - self.assertContains(response, '<a class="nav-link" href="/">Home<span class="sr-only">(current)</span></a>') - self.assertContains(response, '<a class="nav-link" href="/forum">Forum</a>') - self.assertContains(response, '<a class="nav-link" href="/news/all">Berita</a>') - self.assertContains(response, '<a class="nav-link" href="/registrasi/umum">Registrasi Umum</a>') - self.assertContains(response, '<a class="nav-link" href="/registrasi">Registrasi Kontributor</a>') - 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>') + self.assertContains( + response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>') + self.assertContains( + response, '<a class="nav-link" href="/forum">Forum</a>') + self.assertContains( + response, '<a class="nav-link" href="/news/all">Berita</a>') + self.assertContains( + response, + '<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' + ) + self.assertContains(response, 'Registrasi</a>') + self.assertContains( + response, '<a class="dropdown-item" href="/registrasi/umum">Umum</a>') + self.assertContains( + response, '<a class="dropdown-item" href="/registrasi">Kontributor</a>') + self.assertContains(response, 'Login</a>') + self.assertContains( + response, '<a class="dropdown-item" href="/login">Kontributor</a>') + self.assertContains( + response, '<a class="dropdown-item" href="/login_admin">Admin</a>') + self.assertNotContains( + response, '<a class="nav-link" href="/profil">Profil</a>') + self.assertNotContains( + response, '<a class="nav-link" href="/logout">Logout</a>') + self.assertNotContains( + response, '<a class="nav-link" href="/dashboard">Dasbor</a>') + self.assertNotContains( + response, '<a class="nav-link" href="/administration">Administrasi</a>') class MateriRecommendationTest(TestCase): @@ -3431,7 +3805,8 @@ class MateriRecommendationTest(TestCase): ) response = Client().get("/?recommendation=1") - list = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", response.content.decode())] + 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): @@ -3468,9 +3843,201 @@ class MateriRecommendationTest(TestCase): 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())] + list = [int(id) for id in re.findall( + r"Materi\s(\d+)[^\d]", response.content.decode())] self.assertEqual(list, [1, 2]) - + + +class GuestBookTest(TestCase): + def test_form_name_input_has_placeholder_and_css_classes(self): + form = GuestBookForm() + self.assertIn('placeholder="Input your name"', form.as_p()) + self.assertIn('class="form-control input-lg"', form.as_p()) + + def test_form_job_input_has_placeholder_and_css_classes(self): + form = GuestBookForm() + self.assertIn('placeholder="Input your job"', form.as_p()) + self.assertIn('class="form-control input-lg"', form.as_p()) + + def test_form_gender_input_has_choices_and_css_classes(self): + form = GuestBookForm() + self.assertIn('option value="Male"', form.as_p()) + self.assertIn('option value="Female">', form.as_p()) + self.assertIn('class="form-control input-lg"', form.as_p()) + + def test_form_validation_for_blank_items(self): + form = GuestBookForm(data={'name': '', 'job': '', 'gender': ''}) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors['name'], + ['Name is required'] + ) + self.assertEqual( + form.errors['job'], + ['Job is required'] + ) + self.assertEqual( + form.errors['gender'], + ['Gender is required'] + ) + + def test_can_create_guest_book_instance(self): + guestBook = GuestBook(name='Selvy', job='Student', gender='Female') + guestBook.save() + self.assertEqual(GuestBook.objects.count(), 1) + + +class MateriSearchVectorTest(TestCase): + def setUp(self): + Materi.SEARCH_INDEX = (("title", "A"), ("author", "B")) + + def test_search_vector_constructed_on_create(self): + materi = Materi(title="Buku 1", author="Pembuat 1") + materi.save() + + search_vector_string = list( + Materi.objects.values_list("_search_vector", flat=True) + )[0] + + self.assertGreaterEqual(len(search_vector_string.split(",")), 1) + + def test_search_vector_based_on_indexed_attribute(self): + materi = Materi(title="Buku 1", author="Pembuat 1", + descriptions="Deskripsi 1") + materi.save() + + search_vector_string = list( + Materi.objects.values_list("_search_vector", flat=True) + )[0] + self.assertIn("buku", search_vector_string) + self.assertIn("pembuat", search_vector_string) + + def test_search_vector_not_based_on_unindexed_attribute(self): + materi = Materi(title="Buku 1", author="Pembuat 1", + descriptions="Deskripsi 1") + materi.save() + + search_vector_string = list( + Materi.objects.values_list("_search_vector", flat=True) + )[0] + + self.assertNotIn("deskripsi", search_vector_string) + + def test_search_vector_reconstructed_on_update_indexed_field(self): + materi = Materi(title="Sebelum reconstruct") + materi.save() + + search_vector = list(Materi.objects.values_list( + "_search_vector", flat=True))[0] + + materi.title = "Setelah reconstruct" + materi.save() + + search_vector = list(Materi.objects.values_list( + "_search_vector", flat=True))[0] + + self.assertIn("setelah", search_vector) + + def test_search_vector_not_reconstructed_on_update_unindexed_field(self): + materi = Materi(descriptions="sebelum reconstruct") + materi.save() + + search_vector = list(Materi.objects.values_list( + "_search_vector", flat=True))[0] + + materi.descriptions = "sebelum reconstruct" + materi.save() + + search_vector = list(Materi.objects.values_list( + "_search_vector", flat=True))[0] + + self.assertNotIn("setelah", search_vector) + + +class MateriSearchTest(TestCase): + def test_empty_result_on_empty_table(self): + search_query = "test" + + search_result = list(Materi.objects.search(search_query)) + expected_search_result = [] + + self.assertSequenceEqual(search_result, expected_search_result) + + def test_empty_result_on_unmatched_data(self): + Materi.SEARCH_INDEX = (("title", "A"), ("author", "B")) + + Materi(title="buku 1", author="bapak 1").save() + Materi(title="artikel 2", author="ibu 1").save() + + search_query = "majalah" + + search_result = list(Materi.objects.search(search_query)) + expected_search_result = [] + + self.assertSequenceEqual(search_result, expected_search_result) + + search_query = "kakak" + + search_result = list(Materi.objects.search(search_query)) + expected_search_result = [] + + self.assertSequenceEqual(search_result, expected_search_result) + + def test_correct_rank_on_result_tested_by_similiarity_words(self): + Materi.SEARCH_INDEX = (("descriptions", "A"),) + materi_2 = Materi(descriptions="ini lumayan cocok lumayan cocok") + materi_2.save() + + materi_1 = Materi( + descriptions="ini sangat cocok sangat cocok sangat cocok") + materi_1.save() + + materi_4 = Materi(descriptions="ini tidak") + materi_4.save() + + materi_3 = Materi(descriptions="ini sedikit cocok") + materi_3.save() + + search_query = "ini cocok" + + search_result = list(Materi.objects.search(search_query)) + expected_search_result = [materi_1, materi_2, materi_3] + + self.assertSequenceEqual(search_result, expected_search_result) + + def test_correct_rank_on_result_tested_by_weight(self): + Materi.SEARCH_INDEX = ( + ("title", "A"), + ("author", "C"), + ("descriptions", "B"), + ("publisher", "D"), + ) + + materi_title = Materi(title="cocok") + materi_title.save() + + materi_author = Materi(author="cocok cocok cocok") + materi_author.save() + + materi_descriptions = Materi(descriptions="cocok cocok") + materi_descriptions.save() + + materi_publisher = Materi(publisher="cocok cocok cocok cocok") + materi_publisher.save() + + search_query = "cocok" + + search_result = list(Materi.objects.search(search_query)) + expected_search_result = [ + materi_title, + materi_descriptions, + materi_author, + materi_publisher, + ] + + self.assertSequenceEqual(search_result, expected_search_result) + + class BacaNantiTest(TestCase): def setUp(self): self.contributor_credential = { @@ -3488,8 +4055,10 @@ class BacaNantiTest(TestCase): self.contributor = get_user_model().objects.create_user( **self.contributor_credential, name="Kontributor", is_contributor=True ) - self.user_one = get_user_model().objects.create_user(**self.user_one_credential, name="User One") - self.user_two = get_user_model().objects.create_user(**self.user_two_credential, name="User Two") + self.user_one = get_user_model().objects.create_user( + **self.user_one_credential, name="User One") + self.user_two = get_user_model().objects.create_user( + **self.user_two_credential, name="User Two") self.cover = SimpleUploadedFile( "cover.jpg", b"Test file" @@ -3525,7 +4094,7 @@ class BacaNantiTest(TestCase): self.assertEqual(read_later_one.user, self.user_one) self.assertEqual(read_later_two.materi, self.materi1) self.assertEqual(read_later_two.user, self.user_two) - + def test_readlater_user_must_not_unique(self): ReadLater(materi=self.materi1, user=self.user_one).save() ReadLater(materi=self.materi2, user=self.user_one).save() @@ -3549,7 +4118,7 @@ class BacaNantiTest(TestCase): def test_readlater_user_cant_null(self): with self.assertRaises(IntegrityError): ReadLater(materi=self.materi1).save() - + def test_readlater_profile_page_url_exist(self): self.client.login(**self.user_one_credential) response = self.client.get(self.url) @@ -3558,30 +4127,35 @@ class BacaNantiTest(TestCase): def test_readlater_profile_page_using_template(self): self.client.login(**self.user_one_credential) response = self.client.get(self.url) - self.assertTemplateUsed(response=response, template_name="baca-nanti.html") - + self.assertTemplateUsed( + response=response, template_name="baca-nanti.html") + def test_toggle_readlater_url_exist(self): self.client.login(**self.user_one_credential) - response = self.client.post(self.toggle_url, {'materi_id': self.materi1.id}) + response = self.client.post( + self.toggle_url, {'materi_id': self.materi1.id}) self.assertEqual(response.status_code, 200) def test_checking_readlater_in_materi_create_object(self): self.client.login(**self.user_one_credential) self.client.post(self.toggle_url, {'materi_id': self.materi1.id}) - read_later_exist = ReadLater.objects.filter(materi=self.materi1, user=self.user_one).exists() + read_later_exist = ReadLater.objects.filter( + materi=self.materi1, user=self.user_one).exists() self.assertEqual(read_later_exist, True) - + def test_unchecking_readlater_in_materi_delete_object(self): self.client.login(**self.user_one_credential) self.client.post(self.toggle_url, {'materi_id': self.materi1.id}) sleep(1) self.client.post(self.toggle_url, {'materi_id': self.materi1.id}) - read_later_exist = ReadLater.objects.filter(materi=self.materi1, user=self.user_one).exists() + read_later_exist = ReadLater.objects.filter( + materi=self.materi1, user=self.user_one).exists() self.assertEqual(read_later_exist, False) def test_checking_readlater_in_materi_with_complete_paramater_return_success(self): self.client.login(**self.user_one_credential) - response = self.client.post(self.toggle_url, {'materi_id': self.materi1.id}) + response = self.client.post( + self.toggle_url, {'materi_id': self.materi1.id}) self.assertJSONEqual( str(response.content, encoding='utf-8'), {"success": True, "read_later_checked": True} @@ -3591,20 +4165,22 @@ class BacaNantiTest(TestCase): self.client.login(**self.user_one_credential) self.client.post(self.toggle_url, {'materi_id': self.materi1.id}) sleep(1) - response = self.client.post(self.toggle_url, {'materi_id': self.materi1.id}) + response = self.client.post( + self.toggle_url, {'materi_id': self.materi1.id}) self.assertJSONEqual( str(response.content, encoding='utf-8'), {"success": True, "read_later_checked": False} ) - + def test_toggle_readlater_return_if_method_snot_post(self): self.client.login(**self.user_one_credential) - response = self.client.get(self.toggle_url, {'materi_id': self.materi1.id}) + response = self.client.get( + self.toggle_url, {'materi_id': self.materi1.id}) self.assertJSONEqual( str(response.content, encoding='utf-8'), {"success": False, "msg": "Unsuported method"} ) - + def test_toggle_readlater_return_if_paramater_materi_id_not_found(self): self.client.login(**self.user_one_credential) response = self.client.post(self.toggle_url) @@ -3613,19 +4189,21 @@ class BacaNantiTest(TestCase): {"success": False, "msg": "Missing parameter"} ) + class MateriStatsTest(TestCase): def setUp(self): self.credential = { - 'email':"kontributor@gov.id", - 'password':"P@ssw0rd", + 'email': "kontributor@gov.id", + 'password': "P@ssw0rd", } self.path = '/stats/' self.path_json = '/stats/?data=json' self.header = 'Summary Materi per Kategori' - self.contributor = User.objects.create_contributor(**self.credential, name="kontributor") + self.contributor = User.objects.create_contributor( + **self.credential, name="kontributor") self.client = Client() categories = [] @@ -3641,7 +4219,6 @@ class MateriStatsTest(TestCase): m.categories.add(categories[i]) m.save() - def test_stats_has_correct_template(self): self.client.login(**self.credential) response = self.client.get(self.path) @@ -3654,12 +4231,199 @@ class MateriStatsTest(TestCase): def test_stats_as_anonymous(self): response = self.client.get(self.path) - self.assertEqual(response.status_code, 302) #redirect + self.assertEqual(response.status_code, 302) # redirect response = self.client.get(self.path_json) - self.assertEqual(response.status_code, 302) #redirect + self.assertEqual(response.status_code, 302) # redirect def test_stats_api_correct_data(self): self.client.login(**self.credential) response = self.client.get(self.path_json) jobj = json.loads(response.content) self.assertEqual(len(jobj['labels']), 6) + +class UploadMateriTest(TestCase): + def setUp(self): + self.client = Client() + self.user = User.objects._create_user(email="kontributor@gov.id", + password="kontributor", is_contributor=True) + self.setUpImage() + self.content = SimpleUploadedFile("ExampleFile221.pdf", b"Test file") + self.category = Category.objects.create( + id="1", name="sains", description="kategori sains") + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + def setUpImage(self): + self.cover = InMemoryUploadedFile( + BytesIO(base64.b64decode(TEST_IMAGE)), + field_name='tempfile', + name='tempfile.png', + content_type='image/png', + size=len(TEST_IMAGE), + charset='utf-8', + ) + + def test_for_invalid_input_shows_validation_error(self): + self.client.login(email="kontributor@gov.id", + password="kontributor") + response = self.client.post("/unggah/", data={"title": ""}) + self.assertContains(response, "This field is required.") + + def test_data_form_upload_materi_success_save_to_db(self): + self.client.login(email="kontributor@gov.id", + password="kontributor") + + data = {"title": "Dunia Binatang", "author": "Parzival", "publisher": "Buku Asyik", + "release_year": "2015", "descriptions": "Buku dunia binatang seri 1", + 'categories': "1", "cover": self.cover, "content": self.content} + + self.client.post("/unggah/", data=data) + + self.assertEqual(Materi.objects.count(), 1) + +class NotifikasiKontributorTest(TestCase): + def setUp(self): + self.client = Client() + self.contributor_credential = {"email": "kontributor@gov.id", "password": "passwordtest"} + self.contributor = get_user_model().objects.create_user( + **self.contributor_credential, name="Kontributor", is_contributor=True + ) + + self.admin_credential = { + "email": "admin@gov.id", + "password": "passwordtest" + } + self.admin = get_user_model().objects.create_user( + **self.admin_credential, name="Admin", is_admin=True) + + self.setUpImage() + self.content = SimpleUploadedFile("ExampleFile221.pdf", b"Test file") + self.category = Category.objects.create(id="1", name="medis", description="kategori medis") + VerificationSetting.objects.create(title="Kriteria 1", description="memenuhi kriteria 1", archived=False, pk=1) + self.make_materi() + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + def setUpImage(self): + self.cover = InMemoryUploadedFile( + BytesIO(base64.b64decode(TEST_IMAGE)), + field_name='tempfile', + name='tempfile.png', + content_type='image/png', + size=len(TEST_IMAGE), + charset='utf-8', + ) + + def make_materi(self): + self.client.login(**self.contributor_credential) + self.client.post( + "/unggah/", data={"title":"Materi 1", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", + "descriptions":"Deskripsi Materi 1", 'categories':"1", + "cover":self.cover, "content":self.content, + "yt_video_id":"jNwz4L9MGVY"} + ) + self.client.logout() + + def admin_disapprove_materi(self, materi, pk_new_materi): + self.client.login(**self.admin_credential) + self.client.post( + "/administration/detail-verif/"+str(pk_new_materi)+"/", data={ + 'kriteria-1':'0','feedback':"a", 'action':"disapprove" + } + ) + self.client.logout() + + def test_no_notification(self): + self.client.login(**self.contributor_credential) + notifs = NotifikasiKontributor.objects.filter(user=self.contributor) + self.assertEqual(notifs.count(), 0) + self.client.logout() + + def test_notification(self): + materi = Materi.objects.get(title="Materi 1", uploader=self.contributor) + pk_new_materi = materi.id + self.admin_disapprove_materi(materi, pk_new_materi) + self.assertEqual(NotifikasiKontributor.objects.filter(materi=materi, user=self.contributor).count(), 1) + + # Client as Kontributor get notifications + self.client.login(**self.contributor_credential) + notif = NotifikasiKontributor.objects.get(materi=materi, user=self.contributor) + html = self.client.get("/dashboard/").content.decode("utf-8") + self.assertIn(str(notif), html) + + def test_notifications_reset_when_it_is_revised(self): + materi = Materi.objects.get(title="Materi 1", uploader=self.contributor) + pk_new_materi = materi.id + self.admin_disapprove_materi(materi, pk_new_materi) + self.assertEqual(NotifikasiKontributor.objects.filter(materi=materi, user=self.contributor).count(), 1) + + self.client.login(**self.contributor_credential) + notif = NotifikasiKontributor.objects.get(materi=materi, user=self.contributor) + + # Client as Kontributor check Materi detail, and the notification should be gone + response = self.client.post( + "/revisi/materi/"+str(pk_new_materi)+"/", + data={"title":"Materi 2", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", + "descriptions":"Deskripsi Materi 2", 'categories':"1", "cover":self.cover, "content":self.content, + "yt_video_id":"jNwz4L9MGVY"} + ) + self.assertEqual(NotifikasiKontributor.objects.filter(materi=materi, user=self.contributor).count(), 0) + + def test_notifications_reset_when_it_is_opened_in_detail(self): + materi = Materi.objects.get(title="Materi 1", uploader=self.contributor) + pk_new_materi = materi.id + self.admin_disapprove_materi(materi, pk_new_materi) + self.assertEqual(NotifikasiKontributor.objects.filter(materi=materi, user=self.contributor).count(), 1) + + self.client.login(**self.contributor_credential) + notif = NotifikasiKontributor.objects.get(materi=materi, user=self.contributor) + + # Client as Kontributor check Materi detail, and the notification should be gone + self.client.get("/materi/"+str(materi.id)+"/") + self.assertEqual(NotifikasiKontributor.objects.filter(materi=materi, user=self.contributor).count(), 0) + +class AdminNotificationTest(TestCase): + + def setUp(self): + self.client = Client() + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": id_generator() + } + self.admin_credential = { + "email": "admin@gov.id", + "password": id_generator() + } + self.public_credential = { + "email": "public@gov.id", + "password": id_generator() + } + self.kontributor = User.objects.create_contributor(**self.contributor_credential) + self.admin = User.objects.create_admin(**self.admin_credential) + self.public = User.objects.create_user(**self.public_credential) + self.setUpImage() + self.content = SimpleUploadedFile("ExampleFile221.pdf", b"Test file") + self.category = Category.objects.create(id="1", name="medis", description="kategori medis") + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + def setUpImage(self): + self.cover = InMemoryUploadedFile( + BytesIO(base64.b64decode(TEST_IMAGE)), + field_name='tempfile', + name='tempfile.png', + content_type='image/png', + size=len(TEST_IMAGE), + charset='utf-8', + ) + + def test_notification_admin(self): + self.client.login(**self.contributor_credential) + self.client.post( + "/unggah/", data={"title":"Materi 1", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", + "descriptions":"Deskripsi Materi 1", 'categories':"1", + "cover":self.cover, "content":self.content, + "yt_video_id":"jNwz4L9MGVY"} + ) + materi = Materi.objects.get(title="Materi 1") + self.assertTrue(AdminNotification.objects.get(materi=materi.id)) + + + diff --git a/app/urls.py b/app/urls.py index b48be941b1a77fcacd0cae3571b7360b26199c2f..fb1e0e927cfe413948fbf74502277c0c6d6859fe 100644 --- a/app/urls.py +++ b/app/urls.py @@ -5,8 +5,7 @@ from app import views from app.views import (DashboardKontributorView, ProfilView, StatisticsView, SuksesLoginAdminView, SuksesLoginKontributorView, DownloadHistoryView, SuntingProfilView, UploadMateriHTML, UploadMateriView, UploadMateriExcelView, PostsView, - ReqMateriView, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, password_success, - SubmitVisitorView, ReadLaterView, MostContributor) + ReqMateriView, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, password_success, SubmitVisitorView, GuestBookView, ReadLaterView, MostContributor) urlpatterns = [ @@ -15,14 +14,19 @@ urlpatterns = [ path("materi/like/", views.toggle_like, name="PostLikeToggle"), path("delete/<int:pk_materi>/<int:pk_comment>", views.delete_comment, name="delete-comment"), + path("review/delete/<int:pk_materi>/<int:pk_review>", + views.delete_review, name="delete-review"), path("comment/like/", views.toggle_like_comment, name="comment-like-toggle"), - path("comment/dislike/", views.toggle_dislike_comment, name="comment-dislike-toggle"), + path("comment/dislike/", views.toggle_dislike_comment, + name="comment-dislike-toggle"), path("materi/<int:pk>/delete", views.delete_materi, name="detele-materi"), 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("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"), path("profil/", ProfilView.as_view(), name="profil"), @@ -36,26 +40,33 @@ urlpatterns = [ path("profil/<str:email>/", KatalogPerKontributorView.as_view(), name="katalog-per-kontributor"), path("materi/rate/", views.add_rating_materi, name="rate-materi"), - path("materi/<int:pk>/save-to-gdrive/", views.save_to_gdrive, name="save-to-gdrive"), + path("materi/<int:pk>/save-to-gdrive/", + views.save_to_gdrive, name="save-to-gdrive"), path("favorite/", MateriFavorite.as_view(), name="favorite"), - path("change-password/", PasswordChangeViews.as_view(template_name='change-password.html')), + path("change-password/", + PasswordChangeViews.as_view(template_name='change-password.html')), path("password_success/", views.password_success, name="password_success"), path("given-rating/", views.see_given_rating, name="see_given_rating"), path("submit-visitor/", SubmitVisitorView.as_view(), name="submit-visitor"), path("baca-nanti/", ReadLaterView.as_view(), name="read-later"), path("baca-nanti-toggle/", views.toggle_readlater, name="toggle-read-later"), path("stats/", StatisticsView.as_view(), name="stats"), - path("reset_password/", - auth_views.PasswordResetView.as_view(template_name="password_reset.html"), - name="reset_password"), - path("reset_password_sent/", - auth_views.PasswordResetDoneView.as_view(template_name="password_reset_sent.html"), - name="password_reset_done"), - path("reset/<uidb64>/<token>/", - auth_views.PasswordResetConfirmView.as_view(template_name="password_reset_form.html"), - name="password_reset_confirm"), - path("reset_password_complete/", - auth_views.PasswordResetCompleteView.as_view(template_name="password_reset_done.html"), - name="password_reset_complete"), - path("most-contributor/", MostContributor.as_view(), name="most-contributor") + path("reset_password/", + auth_views.PasswordResetView.as_view( + template_name="password_reset.html"), + name="reset_password"), + path("reset_password_sent/", + auth_views.PasswordResetDoneView.as_view( + template_name="password_reset_sent.html"), + name="password_reset_done"), + path("reset/<uidb64>/<token>/", + auth_views.PasswordResetConfirmView.as_view( + template_name="password_reset_form.html"), + name="password_reset_confirm"), + path("reset_password_complete/", + auth_views.PasswordResetCompleteView.as_view( + template_name="password_reset_done.html"), + name="password_reset_complete"), + path("most-contributor/", MostContributor.as_view(), name="most-contributor"), + path("guest-book/", GuestBookView.as_view(), name="guest-book") ] diff --git a/app/views.py b/app/views.py index cd6be9c991ea2acdaaece401b16d1959f7a2a8db..63664613c38290ca9c5521d7f56ac4c8c4e89e73 100644 --- a/app/views.py +++ b/app/views.py @@ -24,16 +24,20 @@ from django.urls import reverse_lazy from django.views import defaults from django.views.generic import TemplateView -from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm +from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm, GuestBookForm from app.models import ( Category, Comment, Review, Materi, + LaporanMateri, ReqMaterial, Rating, RatingContributor, SubmitVisitor, - ReadLater + GuestBook, + ReadLater, + NotifikasiKontributor, + AdminNotification ) from authentication.models import User from .services import DafterKatalogService, DetailMateriService, LikeDislikeService, MateriFieldValidationHelperService, \ @@ -48,6 +52,7 @@ UNGGAH_URL = "/unggah/" UNGGAH_EXCEL_URL = "/unggah_excel/" LOGIN_URL = "/login/" + def permission_denied(request, exception, template_name="error_403.html"): return defaults.permission_denied(request, exception, template_name) @@ -63,11 +68,12 @@ class DaftarKatalog(TemplateView): context = self.get_context_data(**kwargs) context["kategori_list"] = Category.objects.all() - lst_materi = Materi.objects.filter(status="APPROVE").order_by("date_modified") + lst_materi = Materi.objects.filter( + status="APPROVE").order_by("date_modified") url = "" - lst_materi, url = DafterKatalogService.apply_options(lst_materi, request, url) - + lst_materi, url = DafterKatalogService.apply_options( + lst_materi, request, url) context["materi_list"] = lst_materi paginator = Paginator(context["materi_list"], 15) @@ -102,14 +108,16 @@ class KatalogPerKontributorView(TemplateView): contributor = context["contributor"] context["form_rating"] = RatingContributorForm(initial={ "contributor": contributor, - "user":request.user + "user": request.user }) context["avg_rating"] = User.objects.filter(email=kwargs["email"]) \ .annotate(avg_rating=Avg("contributor__score"))[0] - context["count_rating"] = RatingContributor.objects.filter(contributor=contributor).count() + context["count_rating"] = RatingContributor.objects.filter( + contributor=contributor).count() if request.user.is_authenticated: - has_rated = RatingContributor.objects.filter(user=request.user, contributor=contributor).exists() + has_rated = RatingContributor.objects.filter( + user=request.user, contributor=contributor).exists() context["has_rated"] = has_rated return self.render_to_response(context=context) @@ -123,7 +131,7 @@ class KatalogPerKontributorView(TemplateView): is_update = request.POST.get('update', None) if is_delete: rating_contributor = get_object_or_404( - RatingContributor, + RatingContributor, user=request.user, contributor=context["contributor"] ) @@ -134,7 +142,7 @@ class KatalogPerKontributorView(TemplateView): user=request.user, contributor=context["contributor"] ).first() - + if rating and score: rating.score = int(score) rating.save() @@ -142,9 +150,10 @@ class KatalogPerKontributorView(TemplateView): data = RatingContributorForm(request.POST) if data.is_valid(): data.save() - + return redirect("katalog-per-kontributor", email=kwargs["email"]) + class DetailMateri(TemplateView): template_name = "app/detail_materi.html" @@ -153,17 +162,21 @@ class DetailMateri(TemplateView): if not self.request.session or not self.request.session.session_key: self.request.session.save() materi = get_object_or_404(Materi, pk=kwargs["pk"]) - DetailMateriService.init_context_data(context, materi, self.request.session) + DetailMateriService.init_context_data( + context, materi, self.request.session) published_date = DetailMateriService.set_published_date(materi) - DetailMateriService.init_citation_and_materi_rating(context, materi, published_date, self.request) + DetailMateriService.init_citation_and_materi_rating( + context, materi, published_date, self.request) DetailMateriService.init_materi_download_count(context, materi) if self.request.user.is_authenticated: - materi_rating = Rating.objects.filter(materi=materi, user=self.request.user).first() + 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 - materi_read_later = ReadLater.objects.filter(materi=materi, user=self.request.user).first() + materi_read_later = ReadLater.objects.filter( + materi=materi, user=self.request.user).first() if materi_read_later is not None: context['is_in_read_later_list'] = True else: @@ -173,59 +186,72 @@ class DetailMateri(TemplateView): 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_review = Review.objects.filter(materi=context["materi_data"]) - has_disliked, has_liked = DetailMateriService.find_comment_like_dislike(query_set_for_comment, self.request.session) + query_set_for_comment = Comment.objects.filter( + materi=context["materi_data"]) + query_set_for_review = Review.objects.filter( + materi=context["materi_data"]) + has_disliked, has_liked = DetailMateriService.find_comment_like_dislike( + query_set_for_comment, self.request.session) context["comment_data"] = query_set_for_comment context["review_data"] = query_set_for_review context["has_liked_comment"] = has_liked context["has_disliked_comment"] = has_disliked + if request.user.is_authenticated and request.user.is_contributor: + opened_notif = NotifikasiKontributor.objects.filter(user=request.user, materi=context["materi_data"]) + opened_notif.delete() return self.render_to_response(context=context) - - def post(self, request, *args, **kwargs): comment_text = request.POST.get("comment", None) - review_text = request.POST.get("review", None) - if ((comment_text == None or comment_text == "" )and (review_text == None or review_text == "")): + review_text = request.POST.get("review", None) + report_text = request.POST.get("report", None) + if ((comment_text == None or comment_text == "") and + (review_text == None or review_text == "") and + (report_text == None or report_text == "")): 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 - query_set_for_review = Review.objects.filter(materi=context["materi_data"]) + query_set_for_review = Review.objects.filter( + materi=context["materi_data"]) context["review_data"] = query_set_for_review 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 if user_obj: - if (comment_text != None ): + if (comment_text != None): comment = Comment.objects.create( comment=comment_text, username=DetailMateriService.get_user_name(request), materi=materi, user=user_obj ) comment.save() materi_uploader = materi.uploader if materi_uploader.is_subscribing_to_material_comments and user_obj.email != materi_uploader.email: - email_content = f'User dengan email {user_obj.email} ' + \ + email_content = f'User dengan email {user_obj.email} ' + \ f'menambahkan komentar pada materi Anda dengan judul "{materi.title}".' + \ f'\nKomentar: "{comment.comment}".\n' + \ f'Silahkan akses halaman detail materi untuk berinteraksi lebih lanjut.' MailService.send( - subject = 'DIGIPUS: Komentar Baru pada Materi Anda', - message = email_content, - from_email = getattr(settings, 'EMAIL_HOST_USER'), - recipient_list = [materi_uploader.email,], - ) + subject='DIGIPUS: Komentar Baru pada Materi Anda', + message=email_content, + from_email=getattr(settings, 'EMAIL_HOST_USER'), + recipient_list=[materi_uploader.email, ], + ) elif (review_text != None): review = Review.objects.create( review=review_text, username=DetailMateriService.get_user_name(request), materi=materi, user=user_obj ) review.save() + elif (report_text != None): + laporan_materi = LaporanMateri.objects.create( + materi=materi, laporan=report_text, user=user_obj + ) + laporan_materi.save() return HttpResponseRedirect(request.path) @@ -248,6 +274,14 @@ def delete_comment(request, pk_materi, pk_comment): comment.delete() return HttpResponseRedirect(url) +def delete_review(request, pk_materi, pk_review): + if not request.user.is_authenticated or not request.user.is_admin: + raise PermissionDenied(request) + review = get_object_or_404(Review, pk=pk_review) + url_materi = "/materi/" + str(pk_materi) + "/" + review.delete() + return HttpResponseRedirect(url_materi) + def toggle_like_comment(request): comment_id = 0 if request.method == "POST": @@ -271,24 +305,22 @@ def toggle_dislike_comment(request): else: return JsonResponse({"success": False, "msg": UNSUPPORTED_MESSAGE, "comment_id": comment_id}) - def add_rating_materi(request): 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) - is_valid_params, materi_id, \ - rating_score, response, \ - status_code = MateriFieldValidationHelperService.\ - validate_materi_rating_params(materi_id,rating_score) + rating_score, response, \ + status_code = MateriFieldValidationHelperService.\ + validate_materi_rating_params(materi_id, rating_score) if not is_valid_params: return JsonResponse(response, status=status_code) is_valid_rating, materi, \ - response, status_code = MateriFieldValidationHelperService.\ + response, status_code = MateriFieldValidationHelperService.\ validate_materi_rating(materi_id, request.user) if not is_valid_rating: @@ -301,7 +333,6 @@ def add_rating_materi(request): return JsonResponse({"success": False, "msg": "Forbidden"}, status=403) - def download_materi(request, pk): materi = get_object_or_404(Materi, pk=pk) path = materi.content.path @@ -328,7 +359,8 @@ def view_materi(request, pk): try: with open(file_path, "rb") as fh: response = HttpResponse(fh.read(), content_type=mimetype[0]) - DownloadViewMateriHelperService.build_view_materi_response(file_path, materi, response) + DownloadViewMateriHelperService.build_view_materi_response( + file_path, materi, response) return response except Exception: raise Http404(FILE_NOT_FOUND_MESSAGE) @@ -344,6 +376,7 @@ def delete_materi(request, pk): materi.delete() return HttpResponseRedirect("/dashboard/") + class UploadMateriView(TemplateView): template_name = UNGGAH_HTML context = {} @@ -365,7 +398,8 @@ class UploadMateriView(TemplateView): if not UploadMateriService.validate_file_extension(konten, request, yt_url_id): return HttpResponseRedirect(UNGGAH_URL) UploadMateriService.upload_materi(form, materi) - messages.success(request, "Materi berhasil diunggah, periksa riwayat unggah anda") + messages.success( + request, "Materi berhasil diunggah, periksa riwayat unggah anda") return HttpResponseRedirect(UNGGAH_URL) else: context = self.get_context_data(**kwargs) @@ -373,8 +407,6 @@ class UploadMateriView(TemplateView): messages.error(request, "Terjadi kesalahan pada pengisian data") return self.render_to_response(context) - - def get(self, request, *args, **kwargs): if request.user.is_authenticated == False or not request.user.is_contributor: raise PermissionDenied(request) @@ -403,11 +435,14 @@ class UploadMateriExcelView(TemplateView): if "template" in self.request.GET: data_frame = pd.DataFrame( - {"Title": [], "Author": [], "Publisher": [], "Categories": [], "Description": [],} + {"Title": [], "Author": [], "Publisher": [], + "Categories": [], "Description": [], } ) + # pylint: disable=abstract-class-instantiated with BytesIO() as b: - writer = pd.ExcelWriter(b, engine="xlsxwriter") # pylint: disable=abstract-class-instantiated + writer = pd.ExcelWriter( + b, engine="xlsxwriter") data_frame.to_excel(writer, index=0) writer.save() response = HttpResponse( @@ -441,9 +476,11 @@ class UploadMateriExcelView(TemplateView): for i in range(row): # Validate Categories - message = UploadMateriService.validate_excel_categories(categories, excel, i, message) + message = UploadMateriService.validate_excel_categories( + categories, excel, i, message) - message = UploadMateriService.validate_excel_field_length(excel, field_length, i, message) + message = UploadMateriService.validate_excel_field_length( + excel, field_length, i, message) if message != None: break @@ -454,15 +491,14 @@ class UploadMateriExcelView(TemplateView): # Second pass, save data with django.db.transaction.atomic(): - UploadMateriService.upload_materi_excel(categories, excel, request, row) + UploadMateriService.upload_materi_excel( + categories, excel, request, row) messages.success(request, "Materi berhasil diunggah") return HttpResponseRedirect(UNGGAH_EXCEL_URL) - - class DashboardKontributorView(TemplateView): template_name = "dashboard.html" @@ -472,7 +508,8 @@ 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): @@ -519,13 +556,15 @@ 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: - EditProfileService.update_profile_picture(current_user, request) + EditProfileService.update_profile_picture( + current_user, request) else: form.save() return HttpResponseRedirect("/profil/") @@ -569,7 +608,8 @@ 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): @@ -584,6 +624,10 @@ class SuksesLoginKontributorView(TemplateView): class SuksesLoginAdminView(TemplateView): template_name = "sukses_admin.html" + def getAdminNotification(self): + notifications = AdminNotification.objects.all() + return notifications + def dispatch(self, request, *args, **kwargs): if not request.user.is_admin: raise PermissionDenied(request) @@ -599,8 +643,10 @@ class SuksesLoginAdminView(TemplateView): current_user = self.request.user context["user"] = current_user - return self.render_to_response(context) + notifications = self.getAdminNotification() + context["notifications"] = notifications + return self.render_to_response(context) class PostsView(TemplateView): @@ -616,9 +662,11 @@ 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) @@ -653,7 +701,13 @@ class RevisiMateriView(TemplateView): raise PermissionDenied(request) current_materi = get_object_or_404(Materi, pk=kwargs["pk"]) - form = UploadMateriForm(request.POST, request.FILES, instance=current_materi) + + if request.user.is_authenticated and request.user.is_contributor: + opened_notif = NotifikasiKontributor.objects.filter(user=request.user, materi=current_materi) + opened_notif.delete() + + form = UploadMateriForm( + request.POST, request.FILES, instance=current_materi) if form.is_valid(): RevisiMateriService.revisi_materi(form, request) return HttpResponseRedirect("/dashboard/") @@ -690,7 +744,8 @@ class DownloadHistoryView(TemplateView): context = self.get_context_data(**kwargs) if request.user.is_authenticated: current_user = self.request.user - DownloadHistoryService.init_data_authenticated_user(context, current_user) + DownloadHistoryService.init_data_authenticated_user( + context, current_user) else: DownloadHistoryService.init_data_guest_user(context, request) return self.render_to_response(context) @@ -707,6 +762,7 @@ def save_to_gdrive(request, pk): return HttpResponseRedirect(reverse('detail-materi', kwargs={'pk': pk})) + class MateriFavorite(TemplateView): template_name = "user_favorite_materi.html" @@ -721,7 +777,7 @@ class MateriFavorite(TemplateView): user = self.request.user materi = Materi.objects.filter(like=True) - likes_data = { mat.id: { "data": mat, "comments": [] } for mat in materi } + likes_data = {mat.id: {"data": mat, "comments": []} for mat in materi} comments = Comment.objects \ .filter(materi__id__in=likes_data.keys()) \ @@ -735,11 +791,13 @@ class MateriFavorite(TemplateView): return self.render_to_response(context=context) + class PasswordChangeViews(PasswordChangeView): from_class = PasswordChangeForm success_url = reverse_lazy('password_success') + def password_success(request): return render(request, 'password_success.html', {}) @@ -758,15 +816,18 @@ def see_given_rating(request): try: if order_by_key[0] == '-': order_by_key = order_by_key[1:] - rating_list = Rating.objects.filter(user=request.user).order_by(query_order + order_by_key) + rating_list = Rating.objects.filter( + user=request.user).order_by(query_order + order_by_key) except FieldError: order_by_key = 'timestamp' - rating_list = Rating.objects.filter(user=request.user).order_by(query_order + order_by_key) + rating_list = Rating.objects.filter( + user=request.user).order_by(query_order + order_by_key) return render(request, 'given-rating.html', context={'rating_list': rating_list, 'order_by_key': order_by_key, 'order_by': order_by}) return permission_denied(request, exception=None) + class SubmitVisitorView(TemplateView): template_name = "submit_visitor.html" @@ -792,6 +853,21 @@ class SubmitVisitorView(TemplateView): SubmitVisitor(msg=title, user_id=user_id, email=email).save() return JsonResponse({"success": True, "msg": "Buku tamu berhasil ditambahkan"}) + +class GuestBookView(TemplateView): + def get(self, request, *args, **kwargs): + form = GuestBookForm() + return render(request, 'guest_book.html', {'form': form}) + + def post(self, request, *args, **kwargs): + name = request.POST.get('name') + job = request.POST.get('job') + gender = request.POST.get('gender') + guestBook = GuestBook(name=name, job=job, gender=gender) + guestBook.save() + return redirect("daftar_katalog") + + class ReadLaterView(TemplateView): template_name = 'baca-nanti.html' @@ -807,19 +883,22 @@ class ReadLaterView(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) user = self.request.user - context["read_later_list"] = ReadLater.objects.filter(user=user).order_by('-timestamp') + context["read_later_list"] = ReadLater.objects.filter( + user=user).order_by('-timestamp') return self.render_to_response(context) + def toggle_readlater(request): if request.method == "POST": materi_id = request.POST.get("materi_id", None) if materi_id is None: return JsonResponse({"success": False, "msg": MISSING_PARAMETER_MESSAGE}) - + return JsonResponse(ReadLaterService.toggle_read_later(materi_id, request.user)) else: return JsonResponse({"success": False, "msg": UNSUPPORTED_MESSAGE}) + class StatisticsView(TemplateView): template_name = "statistik.html" @@ -840,29 +919,29 @@ class StatisticsView(TemplateView): result.append(e) chart_data = { - 'labels': [e.name for e in result], - 'datasets': [{ - 'label': 'Jumlah Materi per Kategori', - 'data': [e.num for e in result], - 'backgroundColor': [ - 'rgba(255, 99, 132, 0.2)', - 'rgba(54, 162, 235, 0.2)', - 'rgba(255, 206, 86, 0.2)', - 'rgba(75, 192, 192, 0.2)', - 'rgba(153, 102, 255, 0.2)', - 'rgba(255, 159, 64, 0.2)' - ], - 'borderColor': [ - 'rgba(255, 99, 132, 1)', - 'rgba(54, 162, 235, 1)', - 'rgba(255, 206, 86, 1)', - 'rgba(75, 192, 192, 1)', - 'rgba(153, 102, 255, 1)', - 'rgba(255, 159, 64, 1)' - ], - 'borderWidth': 1 - }] - } + 'labels': [e.name for e in result], + 'datasets': [{ + 'label': 'Jumlah Materi per Kategori', + 'data': [e.num for e in result], + 'backgroundColor': [ + 'rgba(255, 99, 132, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(255, 206, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(255, 159, 64, 0.2)' + ], + 'borderColor': [ + 'rgba(255, 99, 132, 1)', + 'rgba(54, 162, 235, 1)', + 'rgba(255, 206, 86, 1)', + 'rgba(75, 192, 192, 1)', + 'rgba(153, 102, 255, 1)', + 'rgba(255, 159, 64, 1)' + ], + 'borderWidth': 1 + }] + } return chart_data def get(self, request, *args, **kwargs): @@ -887,6 +966,6 @@ class MostContributor(TemplateView): def count_contributor(self): count_materi = Materi.objects.all.count() - content = {'count_materi':count_materi} + content = {'count_materi': count_materi} print(content) return content diff --git a/authentication/templates/login.html b/authentication/templates/login.html index e577a9969a799bef368c0deedc0146951c4a0bee..201288988c0b43337af6e000b8824353ebff8819 100644 --- a/authentication/templates/login.html +++ b/authentication/templates/login.html @@ -72,6 +72,10 @@ <a href="/registrasi" class="txt1"> Ingin jadi kontributor? klik di sini </a> + <br> + <a href="/" class="txt1"> + Kembali ke halaman beranda + </a> </div> </div> diff --git a/authentication/templates/login_admin.html b/authentication/templates/login_admin.html index 15a8d0f482d509c91edaa661e0484084720d29e1..501f7be80ae93ef39cc027e57de6b829708b0f29 100644 --- a/authentication/templates/login_admin.html +++ b/authentication/templates/login_admin.html @@ -81,6 +81,10 @@ <a href="/registrasi/admin/" class="txt1"> Belum mendaftar? klik di sini </a> + <br> + <a href="/" class="txt1"> + Kembali ke halaman beranda + </a> </div> </div> diff --git a/authentication/views.py b/authentication/views.py index c33e983e6562b3e40b242cdd54a447a0db57d7ff..852894bf99a187ed02f54d75d4014ef0929c09e9 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -77,3 +77,12 @@ class Login(TemplateView): return self.do_login(context, email, password, request, result) +from app.models import NotifikasiKontributor +def kontributor_notif_context(request): + if request.user.is_authenticated and request.user.is_contributor: + notifs = NotifikasiKontributor.objects.filter(user=request.user) + return { + 'kontributor_notif':notifs + } + else: + return {} \ No newline at end of file diff --git a/digipus/__pycache__/settings.cpython-36.pyc b/digipus/__pycache__/settings.cpython-36.pyc index 43d2d1c4169e7113ebfa287197b9840fd637a652..d738b3ddbb6740940b2b34c9c7651fa292ceac82 100644 Binary files a/digipus/__pycache__/settings.cpython-36.pyc and b/digipus/__pycache__/settings.cpython-36.pyc differ diff --git a/digipus/settings.py b/digipus/settings.py index 884c3e67cf7924e4165161d8d50a3ce04361a306..149fd47d1e7cffe34bb483d82aa92296322a6614 100644 --- a/digipus/settings.py +++ b/digipus/settings.py @@ -51,6 +51,7 @@ INSTALLED_APPS = [ "traffic_statistics", "forum", "rest_framework", + 'userguide' ] MIDDLEWARE = [ @@ -80,6 +81,8 @@ TEMPLATES = [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", + + "authentication.views.kontributor_notif_context" ], }, }, @@ -189,4 +192,4 @@ EMAIL_PORT = config('EMAIL_PORT', default=587) # use Google Mail SMTP as default EMAIL_HOST_USER = config('EMAIL_HOST_USER', default="pmplclass2020@gmail.com") EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default="pmpldigipusemail") EMAIL_USE_TLS = True -EMAIL_USE_SSL = False \ No newline at end of file +EMAIL_USE_SSL = False diff --git a/digipus/urls.py b/digipus/urls.py index a58c0fb1b8eb9156a99cf3b5c069d84ad380e40a..835b6c47cde1af45963d9f2f8cb79101b5520344 100644 --- a/digipus/urls.py +++ b/digipus/urls.py @@ -27,6 +27,7 @@ urlpatterns = [ path("", include("news.urls"), name="news"), path("statistics/", include("traffic_statistics.urls")), path("forum/", include("forum.urls")), + path("userguide/", include(("userguide.urls"))) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) handler403 = 'app.views.permission_denied' diff --git a/register/templates/register_base.html b/register/templates/register_base.html index 7befc8c4076485d785f3929f37ca764cffd90bde..f1b5701151b88979105febef60c05658539ce12c 100644 --- a/register/templates/register_base.html +++ b/register/templates/register_base.html @@ -1,4 +1,8 @@ -{% load static %} + + <br> + <a href="/" class="txt1"> + Kembali ke halaman beranda + </a>{% load static %} <!DOCTYPE html> <html lang="en"> @@ -103,6 +107,10 @@ <a href="/login/" class="txt1"> Kembali ke halaman login </a> + <br> + <a href="/" class="txt1"> + Kembali ke halaman beranda + </a> </div> </div> diff --git a/requirements.txt b/requirements.txt index 30fd8032d8b3be87ddcb85a8443175668af3dc5f..43d0d26f750678927c647708539ffbe7f4bd9589 100644 --- a/requirements.txt +++ b/requirements.txt @@ -101,4 +101,4 @@ whitenoise==5.0.1 wrapt==1.11.2 xlrd==1.2.0 XlsxWriter==1.3.6 -zipp==3.1.0 +zipp==3.1.0 \ No newline at end of file diff --git a/userguide/__init__.py b/userguide/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/userguide/admin.py b/userguide/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/userguide/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/userguide/apps.py b/userguide/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..986e41e1270a1f01b590414bfc87a1421f812d06 --- /dev/null +++ b/userguide/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UserguideConfig(AppConfig): + name = 'userguide' diff --git a/userguide/migrations/__init__.py b/userguide/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/userguide/models.py b/userguide/models.py new file mode 100644 index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91 --- /dev/null +++ b/userguide/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/userguide/static/css/home.css b/userguide/static/css/home.css new file mode 100644 index 0000000000000000000000000000000000000000..3961d49bc6c9edfbdf855ca87e198dcc3d8c23e9 --- /dev/null +++ b/userguide/static/css/home.css @@ -0,0 +1,37 @@ +.purple { + background-color : #615CFD !important; + color : #ffffff !important; +} + +.rounded { + border-radius: 5px; +} + +.padded { + height: 64px; + padding: 20px; +} + +li { + padding-top: 5px; + padding-bottom: 5px; +} + +#userGuideText { + font-size: 80px; + color: #615CFD; + padding-left: 10px; +} + +body { + background-color: #F8F8F8; +} + +#changedDiv { + background-color: #FFFFFF; + padding-top: 10px; +} + +h1 { + color: #615CFD; +} \ No newline at end of file diff --git a/userguide/static/js/home.js b/userguide/static/js/home.js new file mode 100644 index 0000000000000000000000000000000000000000..3cb314307d806922eb4272053ee9e086de6666dc --- /dev/null +++ b/userguide/static/js/home.js @@ -0,0 +1,85 @@ +$("#loginAdmin").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(10); +}) + +$("#approveRejectAdmin").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(11); +}) + +$("#configCategoryAdmin").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(12); +}) + +$("#manageContributorAdmin").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(13); +}) + +$("#manageAdminAdmin").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(14); +}) + +$("#editProfileAdmin, #editProfileContributor, #editProfileUser").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(15); +}) + +$("#loginContributor").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(21); +}) + +$("#uploadMaterialContributor").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(22); +}) + +$("#historyUploadsContributor").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(23); +}) + +$("#lookStatisticCommentsContributor").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(25); +}) + +$("#lookMaterialUser").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(31); +}) + +$("#giveCommentUser").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(32); +}) + +$("#downloadReadMaterials").click(function(e) { + e.preventDefault(); + ajaxCallToChangeTemplateBasedOnState(33); +}) + + +function ajaxCallToChangeTemplateBasedOnState(state){ + $.ajax({ + url: '/userguide/', + headers: {'X-CSRFToken': csrftoken}, + data :{ + 'state' : state + }, + dataType : 'html', + type : 'POST', + success : function(data) { + console.log(data) + $('#changedDiv').html(data) + }, + error: function(xhr, status, error) { + var err = eval("(" + xhr.responseText + ")"); + alert(err.Message); + } + }) +} \ No newline at end of file diff --git a/userguide/templates/changed.html b/userguide/templates/changed.html new file mode 100644 index 0000000000000000000000000000000000000000..43e5dc1294ac6036c17c589b79d66270e923834e --- /dev/null +++ b/userguide/templates/changed.html @@ -0,0 +1,108 @@ +{% if state == 0 %} +Digipus is a system application that can accommodate archiving educational material from regional +apparatus, academics, and practitioners, and organizing it properly so that it becomes material for +increasing knowledge and skills for the community at large. +{% elif state == 10 %} + <h1>Login as an admin</h1> + <ol> + <li>Click on the Login Admin button on the dashboard.</li> + <li>Type your credentials and solve the captcha problem.</li> + <li>Click “Login”</li> + </ol> + <h1>Register as an admin</h1> + <ol> + <li>Click on the Login Admin button on the dashboard.</li> + <li>Click on the “Belum mendaftar? klik di sini” Text</li> + <li>Type your registration details and click “Daftar”</li> + <li>Wait for our internal team to accept your admin account</li> + </ol> +{% elif state == 11 %} + <h1>Approve/Reject a Material</h1> + <ol> + <li>Click on "Administrasi" on the navigation bar</li> + <li>Click on "Verifikasi Materi" on the top left</li> + <li>Assuming that a material has been submitted, you can click on "Approve" or "Reject" on that particular material</li> + </ol> +{% elif state == 12 %} + <h1>Configuration Category and Criteria of Material</h1> + <ol> + <li>Click on "Administrasi" on the navigation bar</li> + <li>Click on "Pengaturan Kategori"</li> + <li>To add category, fill in the form and click "Tambahkan"</li> + <li>On the List of Categories, you can either edit or delete the category by clicking on “Edit” and “Hapus” respectively</li> + <li>You can view the list of categories and erased categories on the table on that page.</li> + </ol> +{% elif state == 13 %} + <h1>Manage contributor</h1> + <ol> + <li>Click on "Administrasi" on the navigation bar</li> + <li>Click on "Kelola Contributor"</li> + <li>On the “Tabel Daftar Kontributor” table, you can view all contributors. For each contributor, you can either look at the details or delete by pressing “Detail” or “Delete”</li> + </ol> +{% elif state == 14 %} + <h1>Manage Admin</h1> + <ol> + <li>Click on "Administrasi" on the navigation bar</li> + <li>Click on "Kelola Admin"</li> + <li>On the “Tabel Daftar Kontributor” table, you can view all contributors. For each contributor, you can either look at the details or delete by pressing “Detail” or “Delete”</li> + </ol> +{% elif state == 15 %} + <h1>Edit Profile</h1> + <ol> + <li>Click "Profil" on the top navigation bar</li> + <li>Configure your profile from there</li> + </ol> +{% elif state == 21 %} + <h1>Login as a kontributor</h1> + <ol> + <li>Click on the Login Kontributor button on the dashboard.</li> + <li>Type your credentials and solve the captcha problem.</li> + <li>Click “Login”</li> + </ol> + <h1>Register as an admin</h1> + <ol> + <li>Click on the Login Kontributor button on the dashboard.</li> + <li>Click on the “Belum mendaftar? klik di sini” Text</li> + <li>Type your registration details and click “Daftar”</li> + </ol> +{% elif state == 22 %} + <h1>Upload a material</h1> + <ol> + <li>Click on Dashboard on the Navigation Bar</li> + <li>Click on "Unggah Materi" on the top left</li> + <li>Fill in the form</li> + <li>Click "Simpan"</li> + <li>You can also upload through excel by clicking on “Unggah Materi (Excel)” instead of “Unggah Materi” The template for the excel can be downloaded from there</li> + </ol> +{% elif state == 23 %} + <h1>History of Uploads</h1> + <ol> + <li>Click on Dashboard on the Navigation Bar</li> + <li>Click on "Riwayat Unggah"</li> + <li>For each of the things on the table, you can either view the details or delete by clicking “Detail” or “Hapus” respectively</li> + </ol> +{% elif state == 24 %} + <h1>Edit Profile</h1> + <ol> + <li>Click "Profil" on the top navigation bar</li> + <li>Configure your profile from there</li> + </ol> +{% elif state == 25 %} + <h1>Look at statistic comments</h1> + This feature is under construction +{% elif state == 31 %} + <h1>Look on a material</h1> + <ol> + <li>Find the material that you want to look at</li> + <li>Click on "Detil"</li> + </ol> +{% elif state == 32 %} + <h1>Give Comment</h1> +This feature is under construction +{% elif state == 33 %} + <h1> Download or Read Materials </h1> + <ol> + <li>Find the material that you want to look at</li> + <li>Download or read by clicking on “Unduh” or “Baca” respectively</li> + </ol> +{% endif %} \ No newline at end of file diff --git a/userguide/templates/home.html b/userguide/templates/home.html new file mode 100644 index 0000000000000000000000000000000000000000..02009e8a951d3ee784ee248af18416f2c1302b06 --- /dev/null +++ b/userguide/templates/home.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% load static %} +{% block header %} + + +<!DOCTYPE html> +<html> + <head> + {% csrf_token %} + <link href="../../static/app/css/heroic-features.css" rel="stylesheet"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'css/util.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'css/home.css' %}"> + <script> + const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value; + </script> + {% endblock header %} + {% block content%} + </head> + + <div class="container"> + {% include 'app/includes/navbar_katalog_materi.html' %} + <div class="row"> + <div><span id = "userGuideText">USER GUIDE</span></div> + </div> + <div class="row"> + <div class="col-5"> + <a class="btn-block rounded padded purple" data-toggle="collapse" href="#collapseAdmin" role="button" aria-expanded="false" aria-controls="collapseAdmin"> + Admin + </a> + <div class="card"> + <div class="collapse" id="collapseAdmin"> + <ul> + <li><a href = "" id = "loginAdmin">Login/Register as Admin</a></li> + <li><a href = "" id = "approveRejectAdmin">Approve/Reject a material</a></li> + <li><a href = "" id = "configCategoryAdmin">Configuration Category and Criteria of material</a></li> + <li><a href = "" id = "manageContributorAdmin">Manage Contributor</a></li> + <li><a href = "" id = "manageAdminAdmin">Manage Admin</a></li> + <li><a href = "" id = "editProfileAdmin">Edit Profile</a></li> + </ul> + </div> + </div> + <a class="btn-block rounded padded purple" data-toggle="collapse" href="#collapseContributor" role="button" aria-expanded="false" aria-controls="collapseContributor"> + Contributor + </a> + <div class="card"> + <div class="collapse" id="collapseContributor"> + <ul> + <li><a href = "" id = "loginContributor">Login/Register as Contributor</a></li> + <li><a href = "" id = "uploadMaterialContributor">Upload a Material</a></li> + <li><a href = "" id = "historyUploadsContributor">History of Uploads</a></li> + <li><a href = "" id = "editProfileContributor">Edit Profile</a></li> + <li><a href = "" id = "lookStatisticCommentsContributor">Look at Statistic Comments</a></li> + </ul> + </div> + </div> + <a class="btn-block rounded padded purple" data-toggle="collapse" href="#collapseUser" role="button" aria-expanded="false" aria-controls="collapseContributor"> + User + </a> + <div class="card"> + <div class="collapse" id="collapseUser"> + <ul> + <li><a href = "" id = "lookMaterialUser">Look on a Material</a></li> + <li><a href = "" id = "giveCommentUser">Give Comment</a></li> + <li><a href = "" id = "downloadReadMaterials">Download or Read Materials</a></li> + <li><a href = "" id = "editProfileUser">Edit Profile</a></li> + </ul> + </div> + </div> + </div> + <div class = "col-7" id = "changedDiv"> + {% include "changed.html" %} + </div> + </div> + </div> +{% endblock content %} +{% block extra_scripts %} +<script src="../static/js/home.js"></script> +{% endblock extra_scripts%} +</html> \ No newline at end of file diff --git a/userguide/tests.py b/userguide/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..57a8e1dd2680343982dd38c5bdf38aea25442fce --- /dev/null +++ b/userguide/tests.py @@ -0,0 +1,35 @@ +from django.test import TestCase + + +# Create your tests here. +# Tests must start with the keyword test +# Relevant Resource: https://stackoverflow.com/questions/2037364/django-test-runner-not-finding-tests/4747444 + +class TestUserGuide(TestCase): + + def test_response_returns_correct_html(self): + c = self.client.get('/userguide/') + self.assertTemplateUsed(c, 'home.html') + + def test_response_returns_state_variable(self): + c = self.client.get('/userguide/') + self.assertIn('state', c.context) + + def test_state_variable_is_int(self): + c = self.client.get('/userguide/') + state = c.context['state'] + isinstance(state, int) + + def test_state_sends_request_to_controller_and_receives_state_variable_equal_to_sent_number(self): + for number in range(10,34): + c = self.client.post('/userguide/', data={'state': number}) + self.assertEquals(number, c.context['state']) + + def test_when_state_changed_html_also_changes(self): + state_before = 10 + state_after = 11 + c_before = self.client.post('/userguide/', data={'state': state_before}).content + c_after = self.client.post('/userguide/', data={'state': state_after}).content + self.assertNotEquals(c_before, c_after) + + \ No newline at end of file diff --git a/userguide/urls.py b/userguide/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..e58a15584c0f52781d3fd511b2a2660935a3d5ea --- /dev/null +++ b/userguide/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from userguide.views import UserGuideHome + +urlpatterns = [ + path("", UserGuideHome, name="user_guide_home"), +] diff --git a/userguide/views.py b/userguide/views.py new file mode 100644 index 0000000000000000000000000000000000000000..76008cd9837ddaea6d276a0122a428aff973fb9b --- /dev/null +++ b/userguide/views.py @@ -0,0 +1,13 @@ +from django.shortcuts import render +from django.views.decorators.csrf import ensure_csrf_cookie +from django.http import JsonResponse + + +# Create your views here. +@ensure_csrf_cookie +def UserGuideHome(request): + state = 0 + if (request.method == 'POST'): + state = int(request.POST['state']) + return render(request, 'changed.html', {'state' : state}) + return render(request, 'home.html', {'state': state}) \ No newline at end of file