diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 40af2c49879743f0f0fee25367178727bb23dd1b..2407079397556a6ed3efa0f28c2c01e0ddc8a13d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -100,8 +100,11 @@ Dev Docker Image: - .docker-image - .development allow_failure: true + when: manual Prod Docker Image: extends: - .docker-image - - .production \ No newline at end of file + - .production + when: manual + allow_failure: true \ No newline at end of file diff --git a/administration/choices.py b/administration/choices.py index b4b352705d0da75888e1d33100826f0a2955e2d0..5b87902fa0272d14adf1d2a03e9deda56a0ad733 100644 --- a/administration/choices.py +++ b/administration/choices.py @@ -18,7 +18,7 @@ BASE_PERIODE = [ ] -def genereatePeriodeChoices(): +def generate_periode_choices(): periode = BASE_PERIODE now = timezone.now() # Month diff --git a/administration/forms.py b/administration/forms.py index 3c350d456e8a335027f923edb5c008e01af802bc..3d6c08fb34a406bd3f64066fd06917ac3e39b5cd 100644 --- a/administration/forms.py +++ b/administration/forms.py @@ -3,7 +3,7 @@ from crispy_forms.layout import Layout, Submit, Row, Column, Reset, ButtonHolder from crispy_forms.bootstrap import InlineCheckboxes from django import forms -from administration.choices import genereatePeriodeChoices +from administration.choices import generate_periode_choices from administration.models import VerificationSetting from app.models import Category from authentication.models import User @@ -88,7 +88,7 @@ class RegistrasiAdminForm(forms.ModelForm): class PeriodForm(forms.Form): period = forms.ChoiceField( - choices=genereatePeriodeChoices(), label="Periode", required=False) + choices=generate_periode_choices(), label="Periode", required=False) start_date = forms.DateField( widget=DateInput, label="Waktu mulai", required=False) end_date = forms.DateField( @@ -102,14 +102,16 @@ class PeriodForm(forms.Form): for field_name, field in self.fields.items(): field.widget.attrs['class'] = 'form-control' + period_css_class = 'form-group col-md-4 mb-0' + self.helper = FormHelper() self.helper.attrs["name"] = "filter-form" self.helper.form_method = 'get' self.helper.layout = Layout( Row( - Column('period', css_class='form-group col-md-4 mb-0'), - Column('start_date', css_class='form-group col-md-4 mb-0'), - Column('end_date', css_class='form-group col-md-4 mb-0'), + Column('period', css_class=period_css_class), + Column('start_date', css_class=period_css_class), + Column('end_date', css_class=period_css_class), css_class='form-row' ), InlineCheckboxes('categories'), @@ -130,10 +132,9 @@ class PeriodForm(forms.Form): if start_date is None and end_date is not None: self.add_error("start_date", "masukan waktu mulai") - if start_date is not None and end_date is not None: - if start_date > end_date: - self.add_error("end_date", - "waktu selesai sebelum waktu mulai") + if start_date is not None and end_date is not None and start_date > end_date: + self.add_error("end_date", + "waktu selesai sebelum waktu mulai") class EditAdminStatusForm(forms.ModelForm): diff --git a/administration/templates/administration/data_statistik.html b/administration/templates/administration/data_statistik.html index 17df72575d35c2a457fe0ae1500c4af991e0abdd..6550ab09f0da645981f302b133620c4e5a691c02 100644 --- a/administration/templates/administration/data_statistik.html +++ b/administration/templates/administration/data_statistik.html @@ -97,20 +97,6 @@ <!-- Card Header - Dropdown --> <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between"> <h6 class="m-0 font-weight-bold text-primary">Statistik</h6> - <!-- <div class="dropdown no-arrow"> - <a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" - aria-haspopup="true" aria-expanded="false"> - <em class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></em> - </a> - <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" - aria-labelledby="dropdownMenuLink"> - <div class="dropdown-header">Dropdown Header:</div> - <a class="dropdown-item" href="#">Action</a> - <a class="dropdown-item" href="#">Another action</a> - <div class="dropdown-divider"></div> - <a class="dropdown-item" href="#">Something else here</a> - </div> - </div> --> </div> <!-- Card Body --> <div class="card-body" style="overflow:auto!important; display:inline-block!important; height: 750px;"> @@ -128,7 +114,7 @@ </div> <div class="card-body"> <div class="table-responsive"> - <table class="table table-bordered" id="dataTableRiwayat" width="100%" cellspacing="0" + <table class="table table-bordered" id="dataTableRiwayat" aria-describedby="titleTabelRiwayat"> <thead> <tr> diff --git a/administration/templates/detail_verif.html b/administration/templates/detail_verif.html index a5755d3980c9120fdf5c6eec648162d29205b6df..e51612098dfe17190e2181f2a9563ac1682a0a7e 100644 --- a/administration/templates/detail_verif.html +++ b/administration/templates/detail_verif.html @@ -52,25 +52,31 @@ Pratinjau Materi {% if riwayat %} <div class="info-wrapper"> <div class="info" id="1"> - <dt class="col col-4"> - <p class="info-name"><strong>Verifikatur</strong></p> - </dt> + <dl> + <dt class="col col-4"> + <p class="info-name"><strong>Verifikatur</strong></p> + </dt> + </dl> <dd> <p class="info-content">{{verification_report.user.name}}</p> </dd> </div> <div class="info" id="1"> - <dt class="col col-4"> - <p class="info-name"><strong>Waktu Verifikasi</strong></p> - </dt> + <dl> + <dt class="col col-4"> + <p class="info-name"><strong>Waktu Verifikasi</strong></p> + </dt> + </dl> <dd> <p class="info-content">{{verification_report.timestamp}}</p> </dd> </div> <div class="info" id="1"> - <dt class="col col-4"> - <p class="info-name"><strong>Status Materi</strong></p> - </dt> + <dl> + <dt class="col col-4"> + <p class="info-name"><strong>Status Materi</strong></p> + </dt> + </dl> <dd> <p class="info-content">{{verification_report.status}}</p> </dd> diff --git a/administration/templates/kelola_kontributor.html b/administration/templates/kelola_kontributor.html index 9fee87a117739e6a0c9b1c6f06b6976a87d7a621..2732d032cea4b89f97c85ff29ee83efe4982f2a0 100644 --- a/administration/templates/kelola_kontributor.html +++ b/administration/templates/kelola_kontributor.html @@ -17,7 +17,7 @@ </div> <div class="card-body"> <div class="table-responsive"> - <table aria-describedby="table-description" class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> + <table aria-describedby="table-description" class="table table-bordered" id="dataTable"> <thead> <tr> <th scope="col">Nama</th> diff --git a/administration/templates/laporan_materi.html b/administration/templates/laporan_materi.html index 33e489890c0eaa9979f3fd06567f559e8df7ebdb..023241df9a2162fb0b1797674716b5492e839c8e 100644 --- a/administration/templates/laporan_materi.html +++ b/administration/templates/laporan_materi.html @@ -21,7 +21,8 @@ </div> <div class="card-body"> <div class="table-responsive"> - <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> + <table class="table table-bordered" id="dataTable"> + <caption>Materi yang Dilaporkan</caption> <thead> <tr> <th scope="col">Judul</th> diff --git a/administration/templates/setting_verifikasi.html b/administration/templates/setting_verifikasi.html index 754365bfcad93c8b93214cc6ffcd34cf33006243..8e39eb3e0369693d472622c0bd6718dbb8a17580 100644 --- a/administration/templates/setting_verifikasi.html +++ b/administration/templates/setting_verifikasi.html @@ -186,7 +186,7 @@ </div> <div class="card-body"> <div class="table-responsive"> - <table aria-describedby="table-description" class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> + <table aria-describedby="table-description" class="table table-bordered" id="dataTable"> <thead> <tr> <th scope="col">Judul Verifikasi</th> @@ -235,29 +235,6 @@ <em class="fas fa-angle-up"></em> </a> - <!-- <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> - - - <script src="https://code.jquery.com/jquery-3.5.0.min.js" - integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script> - - - <script src="{% static 'js/sb-admin-2.min.js' %}"></script> - - <script src="{% static 'vendor/datatables/jquery.dataTables.min.js' %}"></script> - <script src="{% static 'vendor/datatables/dataTables.bootstrap4.min.js' %}"></script> - - - <script src="{% static 'js/demo/datatables-demo.js' %}"></script> --> - </body> </html> \ No newline at end of file diff --git a/administration/templates/verif.html b/administration/templates/verif.html index ebbbd1dd4127eedbb697cd895906bb638ecd69c2..6b1a6688024b5356c4a31b3bb72cdc1872024555 100644 --- a/administration/templates/verif.html +++ b/administration/templates/verif.html @@ -61,7 +61,7 @@ </div> <div class="card-body"> <div class="table-responsive"> - <table class="table table-bordered" id="dataTableRiwayat" width="100%" cellspacing="0" aria-describedby="titleTabelRiwayat"> + <table class="table table-bordered" id="dataTableRiwayat" aria-describedby="titleTabelRiwayat"> <thead> <tr> <th scope="col">Judul Materi</th> diff --git a/administration/tests.py b/administration/tests.py index 6ba9e50b788c568dd4ce4661922f25283005647b..cc7b72df30e9b727df09ed8e055755dd5751577f 100644 --- a/administration/tests.py +++ b/administration/tests.py @@ -10,7 +10,9 @@ from app.models import Category, Materi, LaporanMateri from authentication.models import User from bs4 import BeautifulSoup -from datetime import datetime +from datetime import datetime, timedelta + +from .forms import PeriodForm EDIT_ENDPOINT = "/edit" ERROR_403_MESSAGE = 'Kamu harus login untuk mengakses halaman ini' @@ -1659,3 +1661,50 @@ class EditKontributorStatusTests(TestCase): self.assertEqual(response.status_code, 403) +class PeriodFormTests(TestCase): + + def test_validation_error_when_start_date_is_none(self): + data = { + 'start_date': None, + 'end_date': datetime.now(), + } + form = PeriodForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual( + form["start_date"].errors, + ['masukan waktu mulai'] + ) + + def test_validation_error_when_end_date_is_none(self): + data = { + 'start_date': datetime.now(), + 'end_date': None, + } + form = PeriodForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual( + form["end_date"].errors, + ['masukan waktu selesai'] + ) + + def test_validation_error_when_start_date_greater_than_end_date(self): + current_time = datetime.now() + data = { + 'start_date': current_time + timedelta(days = 1), + 'end_date': current_time, + } + form = PeriodForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual( + form["end_date"].errors, + ['waktu selesai sebelum waktu mulai'] + ) + + def test_form_valid_when_data_valid(self): + current_time = datetime.now() + data = { + 'start_date': current_time - timedelta(days = 1), + 'end_date': current_time, + } + form = PeriodForm(data) + self.assertTrue(form.is_valid()) diff --git a/administration/views.py b/administration/views.py index 65519deb8353157a6cce53d405d1936f1c78dc72..43a42b877c2b712001de03d85c77c362a8fab0b7 100644 --- a/administration/views.py +++ b/administration/views.py @@ -19,6 +19,8 @@ from django.core import management ADMINISTRATION_MANAGEMENT = "/administration/kelola-admin/" ADMINISTRATION_REPORT = "/administration/laporan-materi/" +ADMINISTRATION_VERIFICATION_SETTING = "/administration/setting/verification/" +ADMINISTRATION_CATEGORY_SETTING = "/administration/setting/category/" class VerificationView(TemplateView): @@ -115,7 +117,7 @@ class VerificationSettingView(TemplateView): form = VerificationSettingForm(request.POST) if form.is_valid(): form.save() - return HttpResponseRedirect("/administration/setting/verification/") + return HttpResponseRedirect(ADMINISTRATION_VERIFICATION_SETTING) else: context = self.get_context_data(**kwargs) context["form"] = form @@ -146,7 +148,7 @@ class CategorySettingView(TemplateView): form = CategoryForm(request.POST) if form.is_valid(): form.save() - return HttpResponseRedirect("/administration/setting/category/") + return HttpResponseRedirect(ADMINISTRATION_CATEGORY_SETTING) else: context = self.get_context_data(**kwargs) context["form"] = form @@ -351,7 +353,7 @@ class EditVerificationView(TemplateView): request.POST, instance=verification_object) if form.is_valid(): form.save() - return HttpResponseRedirect("/administration/setting/verification/") + return HttpResponseRedirect(ADMINISTRATION_VERIFICATION_SETTING) else: context = self.get_context_data(**kwargs) context["form"] = form @@ -383,7 +385,7 @@ class EditCategoryView(TemplateView): request.POST, instance=category_object) if form.is_valid(): form.save() - return HttpResponseRedirect("/administration/setting/category/") + return HttpResponseRedirect(ADMINISTRATION_CATEGORY_SETTING) else: context = self.get_context_data(**kwargs) context["form"] = form @@ -483,15 +485,15 @@ def delete_contributor(request, *args, **kwargs): def delete_verification(request, *args, **kwargs): if not request.user.is_authenticated or not request.user.is_admin: raise PermissionDenied(request) - queryObject = get_object_or_404(VerificationSetting, + query_object = get_object_or_404(VerificationSetting, pk=kwargs["pk_verification"]) - queryObject.archived = True - queryObject.description = "Telah dihapus pada " + \ + query_object.archived = True + query_object.description = "Telah dihapus pada " + \ str(timezone.now().strftime("%m/%d/%Y, %H:%M:%S")) + " WIB" - queryObject.archived_by = request.user - queryObject.save() + query_object.archived_by = request.user + query_object.save() messages.success(request, "Point verifikasi berhasil dihapus") - return HttpResponseRedirect("/administration/setting/verification/") + return HttpResponseRedirect(ADMINISTRATION_VERIFICATION_SETTING) def delete_category(request, *args, **kwargs): @@ -499,16 +501,16 @@ def delete_category(request, *args, **kwargs): raise PermissionDenied(request) category_name = get_object_or_404(Category, pk=kwargs["pk_category"]).name - queryObject = get_object_or_404(Category, + query_object = get_object_or_404(Category, pk=kwargs["pk_category"]) - queryObject.archived = True - queryObject.description = "Telah dihapus pada " + \ + query_object.archived = True + query_object.description = "Telah dihapus pada " + \ str(timezone.now().strftime("%m/%d/%Y, %H:%M:%S")) + " WIB" - queryObject.archived_by = request.user - queryObject.save() + query_object.archived_by = request.user + query_object.save() messages.success(request, "Kategori " + category_name + " berhasil dihapus") - return HttpResponseRedirect("/administration/setting/category/") + return HttpResponseRedirect(ADMINISTRATION_CATEGORY_SETTING) def generatedummy(request): if request.user.is_authenticated is False or request.user.is_admin is False: diff --git a/app/forms.py b/app/forms.py index 5b8b3438c71bbe7efcb9dbee7746662563efb42b..266720e86f912083e2c1335923114cc5c21c4bd3 100644 --- a/app/forms.py +++ b/app/forms.py @@ -10,7 +10,6 @@ def year_choices(): class UploadMateriForm(forms.ModelForm): categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all(),widget=forms.CheckboxSelectMultiple(attrs={'style' : 'column-count:2'}),required=True) - #categories.widget.attrs["style"] = "column-count:2" 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>\ diff --git a/app/management/commands/generatedummy.py b/app/management/commands/generatedummy.py index 3137d034c75d656ffafdea6d91999cf8f7eb1329..4cd4af451e71b2acfb47c525a28651a7ddd11fc0 100644 --- a/app/management/commands/generatedummy.py +++ b/app/management/commands/generatedummy.py @@ -24,7 +24,7 @@ class Command(BaseCommand): counter = 0 category_name = f"Kategori dummy {counter}" dummy_category = [] - for i in range(num_of_category): + for _ in range(num_of_category): while (Category.objects.filter(name=category_name).exists()): counter += 1 category_name = f"Kategori dummy {counter}" @@ -38,7 +38,7 @@ class Command(BaseCommand): counter = 0 criterion_name = f"Kriteria dummy {counter}" dummy_criteria = [] - for i in range(num_of_criteria): + for _ in range(num_of_criteria): while (VerificationSetting.objects.filter(title=criterion_name).exists()): counter += 1 criterion_name = f"Kriteria dummy {counter}" @@ -67,8 +67,7 @@ class Command(BaseCommand): counter = 0 dummy_user = [] email = f"admin-dummy-{counter}@email.com" - end_date = timezone.now() - for i in range(num_of_user): + for _ in range(num_of_user): while (User.objects.filter(email=email).exists()): counter += 1 email = f"admin-dummy-{counter}@email.com" @@ -140,8 +139,6 @@ class Command(BaseCommand): verif_report = VerificationReport( report=report_field, materi=materi, user=admin, status=materi.get_status_display(), timestamp=timestamp) verif_report.save() - else: - pass return materi diff --git a/app/management/commands/generatetraffic.py b/app/management/commands/generatetraffic.py index f0c26950815807e2dd3339df8da9c4ea738a971c..6ca5cdba2a4f283e71e7277ab8cfe96fe766cb24 100644 --- a/app/management/commands/generatetraffic.py +++ b/app/management/commands/generatetraffic.py @@ -8,7 +8,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.utils import timezone from app.models import Materi, Comment, Like, DownloadStatistics, ViewStatistics, DummyLike, DummyViewStatistics, DummyDownloadStatistics, DummyComment -from app.management.commands.utils import SECONDS_IN_DAY, get_time_before, get_random_datetime, generate_list_of_random_datetime, generate_random_string, getRandomColor, getLoremWithLength +from app.management.commands.utils import SECONDS_IN_DAY, get_time_before, get_random_datetime, generate_list_of_random_datetime, generate_random_string, get_random_color, get_lorem_with_length class Command(BaseCommand): @@ -50,7 +50,7 @@ class Command(BaseCommand): def _comment_materi(self, timestamp, materi): item = Comment(materi=materi, timestamp=timestamp, - profile=getRandomColor(), comment=getLoremWithLength(240)) + profile=get_random_color(), comment=get_lorem_with_length(240)) item.save() DummyComment(item=item).save() @@ -64,16 +64,7 @@ class Command(BaseCommand): s_date = materi_published_date[0] s_date = s_date.replace(day=s_date.day+1, hour=0, minute=0, second=0, microsecond=0) - # reports = [] while (s_date < today): - # report = { - # "s_date": s_date, - # "visit": 0, - # "view": 0, - # "download": 0, - # "like": 0, - # "comment": 0, - # } today_materi = [i for i in materi if i.published_date < s_date] visiting_user = options["baseline"] visiting_user += int(options["coef_time"] * r_day) + \ @@ -82,8 +73,6 @@ class Command(BaseCommand): (1 + uniform(-options["coef_visit_range"], options["coef_visit_range"]))) active_user = int(visiting_user * options["coef_read"]) - # report["visit"] = visiting_user - # report["view"] = active_user times = generate_list_of_random_datetime( s_date, s_date+timedelta(days=1), active_user) for timestamp in times: @@ -91,23 +80,9 @@ class Command(BaseCommand): self._view_materi(timestamp, selected_materi) if random() < options["coef_download"]: self._download_materi(timestamp, selected_materi) - # report["download"] += 1 if random() < options["coef_like"]: self._like_materi(timestamp, selected_materi) - # report["like"] += 1 if random() < options["coef_comment"]: self._comment_materi(timestamp, selected_materi) - # report["comment"] += 1 s_date = s_date + timedelta(days=1) r_day += 1 - # reports.append(report) - # for i in range - # for i in reports: - # self.stdout.write(self.style.SUCCESS(f"Today is {i['s_date']}")) - # self.stdout.write(self.style.SUCCESS(f"User visit {i['visit']}")) - # self.stdout.write(self.style.SUCCESS(f"User view {i['view']}")) - # self.stdout.write(self.style.SUCCESS( - # f"User download {i['download']}")) - # self.stdout.write(self.style.SUCCESS(f"User like {i['like']}")) - # self.stdout.write(self.style.SUCCESS( - # f"User comment {i['comment']}")) diff --git a/app/management/commands/removetraffic.py b/app/management/commands/removetraffic.py index 73afceda4a06d41f29892df66ad8c20d97a86db5..06f7bc73ceb7bf60a1d54a7d27cc288cbdd987aa 100644 --- a/app/management/commands/removetraffic.py +++ b/app/management/commands/removetraffic.py @@ -8,7 +8,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.utils import timezone, lorem_ipsum from app.models import DummyLike, DummyViewStatistics, DummyDownloadStatistics, DummyComment -from app.management.commands.utils import SECONDS_IN_DAY, get_time_before, get_random_datetime, generate_list_of_random_datetime, generate_random_string, getRandomColor +from app.management.commands.utils import SECONDS_IN_DAY, get_time_before, get_random_datetime, generate_list_of_random_datetime, generate_random_string, get_random_color class Command(BaseCommand): diff --git a/app/management/commands/utils.py b/app/management/commands/utils.py index 35884ddbeac236a264a0ab4e7fe96c32f2ea0833..9a394e2c0b1052f551d2e34cde12de9fca5a8bde 100644 --- a/app/management/commands/utils.py +++ b/app/management/commands/utils.py @@ -9,12 +9,12 @@ from django.utils import timezone, lorem_ipsum SECONDS_IN_DAY = 86400 -def getRandomColor(): +def get_random_color(): color = "%06x" % randint(0, 0xFFFFFF) return color -def getLoremWithLength(n): +def get_lorem_with_length(n): while True: s = lorem_ipsum.sentence() if len(s) < n: @@ -43,7 +43,7 @@ def get_random_datetime(start_date, end_date, max_delta_seconds=None, min_delta_ def generate_list_of_random_datetime(start, end, n): res = [] - for i in range(n): + for _ in range(n): res.append(get_random_datetime(start, end)) res.sort() return res @@ -56,4 +56,4 @@ def get_last_year(): def generate_random_string(n): - return(''.join(choice(ascii_letters) for i in range(n))) + return(''.join(choice(ascii_letters) for _ in range(n))) diff --git a/app/migrations/0003_auto_20200509_2108.py b/app/migrations/0003_auto_20200509_2108.py index 844d9d85ad27f5f5b3102597c4e8e8ef6173f8fd..4b47af81379e4fb8d8d7aefb1801016621a76828 100644 --- a/app/migrations/0003_auto_20200509_2108.py +++ b/app/migrations/0003_auto_20200509_2108.py @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='comment', name='profile', - field=models.CharField(default=app.models.getRandomColor, max_length=100), + field=models.CharField(default=app.models.get_random_color, max_length=100), ), ] diff --git a/app/migrations/0025_review.py b/app/migrations/0025_review.py index 79b7b89a4f655cf6851c7265539222047eef56e1..c57943a977e74551909a596ea4277cb656d905f9 100644 --- a/app/migrations/0025_review.py +++ b/app/migrations/0025_review.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('username', models.CharField(max_length=100)), - ('profile', models.CharField(default=app.models.getRandomColor, max_length=100)), + ('profile', models.CharField(default=app.models.get_random_color, max_length=100)), ('review', models.TextField(default='review')), ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), ('materi', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.materi')), diff --git a/app/migrations/0027_readlater.py b/app/migrations/0027_readlater.py new file mode 100644 index 0000000000000000000000000000000000000000..4652422241846b94a23e930332c0a3736173c890 --- /dev/null +++ b/app/migrations/0027_readlater.py @@ -0,0 +1,29 @@ +# Generated by Django 3.1 on 2020-10-30 13:26 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('app', '0027_auto_20201030_1648'), + ] + + operations = [ + migrations.CreateModel( + name='ReadLater', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), + ('materi', models.ForeignKey(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)), + ], + options={ + 'unique_together': {('materi', 'user')}, + }, + ), + ] diff --git a/app/models.py b/app/models.py index e531ad121c188ab50d73c93dfbedd3eafb464389..fb932f7546c54b0f22af0e02c9e1212f123b51f6 100644 --- a/app/models.py +++ b/app/models.py @@ -21,7 +21,7 @@ VERIFICATION_STATUS = [ # Create your models here. -def getRandomColor(): +def get_random_color(): color = "%06x" % random.randint(0, 0xFFFFFF) return color @@ -140,11 +140,12 @@ class Materi(SoftDeleteModel): like = False if Like.objects.filter(materi=self).exists(): like = True + return like class Comment(models.Model): username = models.CharField(max_length=100) - profile = models.CharField(max_length=100, default=getRandomColor) + 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) @@ -165,7 +166,7 @@ class Comment(models.Model): class Review(models.Model): username = models.CharField(max_length=100) - profile = models.CharField(max_length=100, default=getRandomColor) + 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) @@ -272,3 +273,11 @@ class LaporanMateri(models.Model): laporan = models.TextField(validators=[MinValueValidator(30), MaxValueValidator(120)], default="") timestamp = models.DateTimeField(default=timezone.now) is_rejected = models.BooleanField(default=False) + +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 diff --git a/app/services.py b/app/services.py index ab9d56301e62f2fd921bed9e76d4bf0a8c8aea0d..4ae99b55b1349e03cdcf79d9ef244944592b64b9 100644 --- a/app/services.py +++ b/app/services.py @@ -4,21 +4,24 @@ import random import shutil from functools import cmp_to_key +import requests +from administration.models import VerificationReport +from digipus import settings from django.contrib import messages from django.contrib.auth.models import AnonymousUser from django.core.exceptions import ValidationError -from django.db.models import Case, When, Count, Q +from django.db.models import Case, Count, Q, When from django.shortcuts import get_object_or_404 +from django.utils import timezone from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive -from administration.models import VerificationReport from app.forms import SuntingProfilForm -from app.models import Category, Like, LikeComment, DislikeComment, Materi, Comment, Rating, DownloadStatistics, \ - ViewStatistics -from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata -from digipus import settings -import requests +from app.models import (Category, Comment, DislikeComment, DownloadStatistics, + Like, LikeComment, Materi, Rating, ReadLater, + ViewStatistics) +from app.utils.fileManagementUtil import (get_random_filename, + remove_image_exifdata) class DafterKatalogService: @@ -64,6 +67,8 @@ class DafterKatalogService: lst_materi = lst_materi.annotate(count=Count('unduh__id')).order_by('-count') elif (get_sort == "jumlah_tampilan"): lst_materi = lst_materi.annotate(count=Count('baca__id')).order_by('-count') + elif (get_sort == "jumlah_komentar"): + lst_materi = lst_materi.annotate(count=Count('comment__id')).order_by('-count') return lst_materi, url @staticmethod @@ -160,7 +165,7 @@ class CitationService: @staticmethod def get_citation_ieee(request, materi): - current_date = datetime.datetime.now() + current_date = timezone.now() current_day = str(current_date.day) current_month = current_date.strftime("%b") current_year = str(current_date.year) @@ -343,8 +348,7 @@ class UploadMateriService: @staticmethod def validate_yt_video_url(value): - is_yt_id_valid = True - r = requests.get('http://www.youtube.com/watch?v='+value) + r = requests.get('https://www.youtube.com/watch?v='+value) if "\"playabilityStatus\":{\"status\":\"ERROR\"" in r.text: raise ValidationError("Invalid Youtube video ID") @@ -470,3 +474,18 @@ class GoogleDriveUploadService: file1["title"] = title print("title: %s, mimeType: %s" % (file1["title"], file1["mimeType"])) file1.Upload() + +class ReadLaterService: + + @staticmethod + def toggle_read_later(materi_id, current_user): + materi = get_object_or_404(Materi, pk=materi_id) + read_later_item_exist = ReadLater.objects.filter(materi=materi, user=current_user).exists() + if read_later_item_exist: + read_later_item = get_object_or_404(ReadLater, materi=materi, user=current_user) + read_later_item.delete() + response = {"success": True, "read_later_checked": False} + else: + ReadLater(materi=materi, user=current_user).save() + response = {"success": True, "read_later_checked": True} + return response diff --git a/app/templates/app/base_admin.html b/app/templates/app/base_admin.html index 7c8b851b015df98ead395e4bc31c80b72531268e..ff080f96ee2d739e2d7837189a7f9df131341030 100644 --- a/app/templates/app/base_admin.html +++ b/app/templates/app/base_admin.html @@ -55,13 +55,6 @@ <span>Sunting Profil</span></a> </li> - <!-- - <li class="nav-item"> - <a class="nav-link" href="/sunting/"> - <span>Sunting Profil</span></a> - </li> - --> - </ul> <!-- End of Sidebar --> diff --git a/app/templates/app/base_reset.html b/app/templates/app/base_reset.html new file mode 100644 index 0000000000000000000000000000000000000000..3fc86b803b6179ffa59b0a7aaa05f6b756c03bb6 --- /dev/null +++ b/app/templates/app/base_reset.html @@ -0,0 +1,130 @@ +{% 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 --> + + + --> + + </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 --> + + </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/detail_materi.html b/app/templates/app/detail_materi.html index 72d758c9e304adc8fbdae477fbbc9e3709fcd450..c489843d40314a994c7a4e39dba192316ed30e46 100644 --- a/app/templates/app/detail_materi.html +++ b/app/templates/app/detail_materi.html @@ -22,7 +22,8 @@ div.review { {% block verification %} {% endblock verification %} <div id="fb-root"></div> <div class="container-fluid p-0 bg detail-materi-color"> - <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow"> + <span class="modal-title black-text" id="navbarLabel" style="display: none;">Navbar</span> + <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow" aria-labelledby="navbarLabel"> <!-- Sidebar Toggle (Topbar) --> <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> @@ -106,7 +107,7 @@ div.review { <div class="col col-3 cover"> <img src={{materi_data.cover.url}} alt="cover"> </div> - <div class="col col-6 ml-3 book"> + <div class="col col-8 ml-3 book"> <h2>{{materi_data.title}}</h2> <div class="category-wrapper"> {% for category in materi_data.categories.all %} @@ -256,18 +257,39 @@ div.review { </div> </div> </div> + + {% if is_in_read_later_list %} + <button class="btn btn-book shadow-sm p-2 mr-2 bg-primary text-white rounded align-self-center" + type="button" id="readLaterButton" aria-haspopup="true" aria-expanded="false" + onclick="postToggleReadLater()"> + <em class="align-self-center far fa-check-square" id="readLaterText"></em> Baca Nanti + </button> + {% else %} + <button class="btn btn-book shadow-sm p-2 mr-2 bg-white text-primary rounded align-self-center" + type="button" id="readLaterButton" aria-haspopup="true" aria-expanded="false" + onclick="postToggleReadLater()"> + <em class="align-self-center far fa-square" id="readLaterText"></em> Baca Nanti + </button> + {% endif %} {% else %} <button class="btn dropdown-toggle btn-book shadow-sm p-2 mr-2 bg-white rounded align-self-center" type="button" id="dropdownMenuButton" aria-haspopup="true" aria-expanded="false" data-toggle="modal" data-target="#notLoggedInModal"> <em class="align-self-center far fa-star"></em> Beri Rating </button> + + <button class="btn btn-book shadow-sm p-2 mr-2 bg-white text-primary rounded align-self-center" + type="button" id="readLaterButton" aria-haspopup="true" aria-expanded="false" + onclick="postToggleReadLater()"> + <em class="align-self-center far fa-square" id="readLaterText"></em> Baca Nanti + </button> {% endif %} </div> </div> </div> <div class="row menu-wrapper mr-4 ml-4 p-3"> - <nav class="navbar navbar-expand-sm border-top border-bottom p-0 mt-3 mb-3"> + <span class="modal-title black-text" id="navbarLabelTwo" style="display: none;">Navbar</span> + <nav class="navbar navbar-expand-sm border-top border-bottom p-0 mt-3 mb-3" aria-labelledby="navbarLabelTwo"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="#deskripsi">Deskripsi</a> @@ -290,7 +312,7 @@ div.review { <div style="border-top:1px solid #d4d4d4;text-align:center"> <h3>Video</h3> <iframe src="https://www.youtube.com/embed/{{ materi_data.yt_video_id }}" - allowfullscreen="allowfullscreen" + title="Video materi" allowfullscreen="allowfullscreen" width="564" height="300"> invalid video url </iframe> @@ -450,7 +472,7 @@ div.review { {% endblock content %} {% block extra_scripts %} <script src="https://kit.fontawesome.com/bc2cedd6b2.js" crossorigin="anonymous"></script> -<script type="text/javascript"> +<script> // using jQuery var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); @@ -603,6 +625,37 @@ div.review { }); } + function postToggleReadLater() { + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + + $.ajax({ + type: 'POST', + url: "{% url 'toggle-read-later' %}", + data: { + 'materi_id': "{{ materi_data.id }}", + }, + success: changeReadLaterButton, + dataType: 'html' + }); + } + + function changeReadLaterButton(data, jqXHR) { + var data = $.parseJSON(data) + if (data['read_later_checked']) { + $('#readLaterButton').removeClass("bg-white text-primary").addClass("bg-primary text-white") + $('#readLaterText').removeClass("fa-square").addClass("fa-check-square") + } else { + $('#readLaterButton').removeClass("bg-primary text-white").addClass("bg-white text-primary") + $('#readLaterText').removeClass("fa-check-square").addClass("fa-square") + } + } + function LikePost(data, jqXHR) { var data = $.parseJSON(data) var likeCount = parseInt($('.info-content')[6].textContent) diff --git a/app/templates/app/includes/sidebar.html b/app/templates/app/includes/sidebar.html index ff0bb3adb420af54f491adcdc994bd422f1991d6..69425f3e24b473e9953c78d53353d3d128c23dda 100644 --- a/app/templates/app/includes/sidebar.html +++ b/app/templates/app/includes/sidebar.html @@ -27,7 +27,7 @@ </li> <li class="nav-item"> - <a class="nav-link" href="#"> + <a class="nav-link" href="{% url 'stats' %}"> <span>Statisik Materi</span> </a> </li> diff --git a/app/templates/app/includes/sidebar_profile.html b/app/templates/app/includes/sidebar_profile.html index 69c80bba4e59efd2d2a0496a690ebe3e5a555966..d28a4c58f129d2221169186cec9927b3c1e203c9 100644 --- a/app/templates/app/includes/sidebar_profile.html +++ b/app/templates/app/includes/sidebar_profile.html @@ -27,4 +27,9 @@ <a class="nav-link" href="/given-rating/"> <span>Rating Diberikan</span></a> </li> + + <li class="nav-item"> + <a class="nav-link" href="/baca-nanti"> + <span>Baca Nanti</span></a> + </li> </ul> \ No newline at end of file diff --git a/app/templates/app/katalog_materi.html b/app/templates/app/katalog_materi.html index ca965e071c07384bae59661ee8a42e139e210946..c2e2d31861593bc920134708c63da1205e6ca261 100644 --- a/app/templates/app/katalog_materi.html +++ b/app/templates/app/katalog_materi.html @@ -150,6 +150,9 @@ <li> <a href="?sort=jumlah_tampilan">jumlah tampilan</a> </li> + <li> + <a href="?sort=jumlah_komentar">jumlah komentar</a> + </li> </ul> </div> </div> @@ -161,7 +164,7 @@ {% for item in materi_list %} <div class="card book"> <img src={{item.cover.url}} class="card-img-top" alt="cover" - style="height:200px; widows: 200px;; overflow: hidden;"></img> + style="height:200px; widows: 200px; overflow: hidden;"></img> <div class="card-body"> <h5 class="card-title">{{item.title}}</h5> <p class="card-text">{{item.author}}</p> diff --git a/app/templates/baca-nanti.html b/app/templates/baca-nanti.html new file mode 100644 index 0000000000000000000000000000000000000000..969728371ec78c7c24241afa335c070f8b3553f7 --- /dev/null +++ b/app/templates/baca-nanti.html @@ -0,0 +1,84 @@ +{% extends 'app/base_profile.html' %} +{% load static %} + +{% block title %} + <title>Baca Nanti | Digipus</title> +{% endblock %} +{% block stylesheets %} + <link rel="stylesheet" type="text/css" href="{% static 'app/css/katalog_materi.css' %}"> + <!-- Optional JavaScript --> + <!-- jQuery first, then Popper.js, then Bootstrap JS --> + <script src="https://code.jquery.com/jquery-3.5.1.min.js" + integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" 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> + + <!-- Bootstrap core CSS --> + <link href="../../static/app/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet"> + + <!-- Custom styles for this template --> + <link href="../../static/app/css/heroic-features.css" rel="stylesheet"> + + + <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> + + + <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}"> + + + <link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}"> + + <link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}"> + + <link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}"> + + <link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}"> + + <link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}"> + + <link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'css/util.css' %}"> + +{% endblock %} + +{% block content %} + <div class="container"> + <div class="col-20"> + <h1 class="mt-2">Daftar Materi Yang Belum Dibaca</h1> + <hr class="mt-0 mb-4"> + + {% if read_later_list %} + <div class="container row content"> + <div class="col-20 books"> + {% for item in read_later_list %} + <div class="card book"> + <img src={{item.materi.cover.url}} class="card-img-top" alt="cover" + style="height:200px; widows: 200px; overflow: hidden;"></img> + <div class="card-body"> + <h5 class="card-title">{{item.materi.title}}</h5> + <p class="card-text">{{item.materi.author}}</p> + <p class="card-text">Diunggah oleh + <a class="card-link" href="{% url 'katalog-per-kontributor' item.materi.uploader.email %}"> + {{item.materi.uploader.name}} + </a> + </p> + <a href="{% url 'view-materi' item.materi.id %}" class="btn btn-book">Baca</a> + <a href="{% url 'detail-materi' item.materi.id %}" class="btn btn-book">Detail</a> + </div> + </div> + {% endfor %} + </div> + </div> + {% else %} + <h1>Anda Tidak Memiliki Daftar Baca Nanti</h1> + {% endif %} + </div> + </div> + + +{% endblock %} + diff --git a/app/templates/password_reset.html b/app/templates/password_reset.html new file mode 100644 index 0000000000000000000000000000000000000000..148908564a188bfaf8b1a2639bbc8162ea16a58e --- /dev/null +++ b/app/templates/password_reset.html @@ -0,0 +1,18 @@ +{% extends 'app/base_reset.html' %} +{% block title %}Reset Password{% endblock%} +{% block content %} + + +<h1> Reset Password </h1> +<br/><br/> +<p>Forgotten your password ? Enter email address below, and we'll email instructions for setting a new one.</p> + +<div class = "from-group"> + <form method="POST"> + {% csrf_token %} + {{ form.as_p }} + <button class="btn btn-secondary"> Submit</button> + </form> +</div> + +{% endblock %} \ No newline at end of file diff --git a/app/templates/password_reset_done.html b/app/templates/password_reset_done.html new file mode 100644 index 0000000000000000000000000000000000000000..d021c573d3b9f6794c12d754fa85ab818d6a2cf2 --- /dev/null +++ b/app/templates/password_reset_done.html @@ -0,0 +1,14 @@ + +{% extends 'app/base_reset.html' %} +{% block title %}Password reset complete{% endblock%} +{% block content %} + + +<h1> Password reset complete </h1> +<br/><br/> +<p>Your password has been set. You may go ahead and log in now.</p> + +<a href='/login'>Log in</a> + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/password_reset_form.html b/app/templates/password_reset_form.html new file mode 100644 index 0000000000000000000000000000000000000000..73e68f57f6785e250468678e82d7d7a2a87e4f16 --- /dev/null +++ b/app/templates/password_reset_form.html @@ -0,0 +1,17 @@ +{% extends 'app/base_reset.html' %} +{% block title %}Enter new password{% endblock%} +{% block content %} + + +<h1> Enter new password </h1> +<br/><br/> + +<div class = "from-group"> + <form method="POST"> + {% csrf_token %} + {{ form.as_p }} + <button class="btn btn-secondary"> Update Password</button> + </form> +</div> + +{% endblock %} \ No newline at end of file diff --git a/app/templates/password_reset_sent.html b/app/templates/password_reset_sent.html new file mode 100644 index 0000000000000000000000000000000000000000..12542b5a335e1b0a400c28411f4e9a1c79c809b3 --- /dev/null +++ b/app/templates/password_reset_sent.html @@ -0,0 +1,14 @@ + +{% extends 'app/base_reset.html' %} +{% block title %}Password reset sent{% endblock%} +{% block content %} + + +<h1> Password reset sent </h1> +<br/><br/> +<p>We’ve emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.</p> +<br/> +<p>If you don’t receive an email, please make sure you’ve entered the address you registered with, and check your spam folder.</p> + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/statistik.html b/app/templates/statistik.html new file mode 100644 index 0000000000000000000000000000000000000000..4065f65f4288f6ad040ef795c8f988229f703442 --- /dev/null +++ b/app/templates/statistik.html @@ -0,0 +1,73 @@ +{% extends 'app/base_dashboard.html' %} +{% load static %} + +{% block title %} +<title>Statistik | Digipus</title> +{% endblock %} + +{% block stylesheets %} +<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js" + integrity="sha512-d9xgZrVZpmmQlfonhQUvTR7lMPtO7NkZMkA0ABN3PHCbKA5nqylQ/yWlFAyY6hYgdF1Qh6nYiuADWwKB4C2WSw==" + crossorigin="anonymous"></script> +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.css" + integrity="sha512-/zs32ZEJh+/EO2N1b0PEdoA10JkdC3zJ8L5FTiQu82LR9S/rOQNfQN7U59U9BC12swNeRAz3HSzIL2vpp4fv3w==" + crossorigin="anonymous" /> +{% endblock %} + +{% block content %} + + +<div class="container"> + + <h1 class="h3 mb-2 text-gray-800">Summary Materi per Kategori</h1> + + <div class="card-body"> + <div class="table-responsive"> + <table class="table table-bordered"> + <caption>Summary Materi per Kategori</caption> + <thead> + <th scope="col">Kategori</th> + <th scope="col">Jumlah Materi</th> + </thead> + <tbody> + {% for s in stats %} + <tr> + <td>{{s.name}}</td> + <td>{{s.num}}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + + <div class="col-lg-6 mx-auto"> + <canvas id="myChart" width="400" height="400"></canvas> + </div> + +</div> + +<script type="text/javascript"> + + function displayChart(data) { + var ctx = document.getElementById('myChart').getContext('2d'); + + var myChart = new Chart(ctx, { + type: 'pie', + data: data + }); + } + + + document.addEventListener("DOMContentLoaded", function(){ + fetch('/stats?data=json') + .then(function(response) { + return response.json() + }) + .then(function(json) { + displayChart(json) + }) + }); + +</script> +{% endblock %} \ No newline at end of file diff --git a/app/tests.py b/app/tests.py index 9f10c73f27f528bd814fb834b1084391f60514d9..a487c2b693a76393a635a91271a64ed492855618 100644 --- a/app/tests.py +++ b/app/tests.py @@ -1,85 +1,65 @@ -import json, tempfile, os, mock, base64 -import pandas as pd -from io import StringIO +import base64 +import datetime as dt +import json +import os +import random import re +import tempfile import time -from django.test import override_settings +from datetime import datetime +from io import StringIO +from time import sleep +import mock +import pandas as pd +import pytz +from administration.models import VerificationReport, VerificationSetting +from administration.utils import id_generator +from authentication.models import User from bs4 import BeautifulSoup -from datetime import datetime +from digipus.settings import TIME_ZONE from django.conf import settings from django.contrib import messages as dj_messages from django.contrib.auth import get_user_model from django.core import mail, serializers -from django.core.files import File from django.core.exceptions import PermissionDenied, ValidationError +from django.core.files import File from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command -from django.test import Client, TestCase, TransactionTestCase -from django.urls import resolve, reverse from django.db.utils import IntegrityError -from django.test import Client, RequestFactory, TestCase, TransactionTestCase -from pytz import timezone -from time import sleep -import datetime as dt +from django.test import (Client, RequestFactory, TestCase, TransactionTestCase, + override_settings) +from django.urls import resolve, reverse +from django.utils import timezone -from administration.models import VerificationSetting, VerificationReport -from administration.utils import id_generator -from app.views import UploadMateriHTML, add_rating_materi -from authentication.models import User -from digipus.settings import TIME_ZONE -from .models import ( - Category, - Comment, - Review, - DislikeComment, - DownloadStatistics, - Materi, - Like, - LikeComment, - Rating, - ReqMaterial, - RatingContributor, - ViewStatistics, -) - -from .services import ( - DetailMateriService, -) - -from .views import ( - DaftarKatalog, - DashboardKontributorView, - DetailMateri, - ProfilView, - SuntingProfilView, - SuksesLoginAdminView, - SuksesLoginKontributorView, - PostsView, - RevisiMateriView, - ReqMateriView, - KatalogPerKontributorView, - UploadMateriView, - UploadMateriExcelView, - PasswordChangeViews, - password_success, - MateriFavorite, -) from app.forms import SuntingProfilForm, year_choices -from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata +from app.utils.fileManagementUtil import (get_random_filename, + remove_image_exifdata) from app.utils.PasswordValidator import PasswordPolicyValidator +from app.views import UploadMateriHTML, add_rating_materi + +from .models import (Category, Comment, DislikeComment, DownloadStatistics, + Like, LikeComment, Materi, Rating, RatingContributor, + ReadLater, ReqMaterial, Review, ViewStatistics) +from .services import DetailMateriService +from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri, + KatalogPerKontributorView, MateriFavorite, + PasswordChangeViews, PostsView, ProfilView, ReqMateriView, + RevisiMateriView, SuksesLoginAdminView, + 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 -from selenium.common.exceptions import NoSuchElementException -import requests -from statistics import mean - class DaftarKatalogTest(TestCase): @@ -226,7 +206,48 @@ class DaftarKatalogSortingByJumlahTampilanTest(TestCase): self.client.get(self.url) response = self.client.get("/?sort=jumlah_tampilan") self.assertRegex(str(response.content), rf'.*Materi 2.*Materi 1.*') - + +class DaftarKatalogSortingByJumlahKomentarTest(TestCase): + def setUp(self): + self.client = Client() + + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": "passwordtest" + } + + self.contributor_credential_2 = { + "email": "kontributor2@gov.id", + "password": "passwordtest" + } + + self.contributor = get_user_model().objects.create_user( + **self.contributor_credential, name="Kontributor 1", is_contributor=True) + self.contributor2 = get_user_model().objects.create_user( + **self.contributor_credential_2, name="Kontributor 2", is_contributor=True) + + self.cover = SimpleUploadedFile( + "Cherprang_Areekul40_nJM9dGt.jpg", b"Test file") + self.content = SimpleUploadedFile("Bahan_PA_RKK.pdf", b"Test file") + + Materi(title="Materi 1", author="Agas", uploader=self.contributor, + publisher="Kelas SC", descriptions="Deskripsi Materi 1", + status="APPROVE", cover=self.cover, content=self.content).save() + time.sleep(1) + Materi(title="Materi 2", author="Agas", uploader=self.contributor, + publisher="Kelas SC", descriptions="Deskripsi Materi 2", + status="APPROVE", cover=self.cover, content=self.content).save() + + self.last_uploaded_material = Materi.objects.last() + + 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"}) + + 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() @@ -341,7 +362,7 @@ class DetailMateriTest(TestCase): 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=datetime.now(), date_created=datetime.now()) + 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}]}', \ @@ -756,7 +777,7 @@ class DetailMateriTest(TestCase): def test_citation_IEEE_materi_has_no_published_date(self): response = self.client.get(self.url) - current_date = datetime.now() + current_date = timezone.now() current_day = str(current_date.day) current_month = current_date.strftime("%b") current_year = str(current_date.year) @@ -779,7 +800,7 @@ class DetailMateriTest(TestCase): def test_citation_IEEE_materi_has_published_date(self): response = self.client.get(self.materi_with_published_date_url) - current_date = datetime.now() + current_date = timezone.now() current_day = str(current_date.day) current_month = current_date.strftime("%b") current_year = str(current_date.year) @@ -981,7 +1002,7 @@ class PostsViewTest(TestCase): for j in range (LIKES_COUNT_PER_POST[i]): Like.objects.create( - timestamp=datetime.now(), + timestamp=timezone.now(), materi=post, session_id=("dummysession-" + str(i) + '-' + str(j)) ) @@ -1048,6 +1069,8 @@ class PostsViewTest(TestCase): response = self._request_as_user() posts = list(self.data.keys()) + posts.sort() + comments = { i: [comment.id for comment in self.data[post_id]["comments"]] for i, post_id in enumerate(posts) @@ -2738,7 +2761,7 @@ class DownloadHistoryViewTest(TestCase): self.client.get(self.download_url2) self.client.get(self.download_url1) - jkt_timezone = timezone(TIME_ZONE) + jkt_timezone = pytz.timezone(TIME_ZONE) download_history = self.user_anonim.riwayat_unduh.all() response = self.client.get(self.history_url) @@ -2758,7 +2781,7 @@ class DownloadHistoryViewTest(TestCase): self.client.get(self.download_url2) self.client.get(self.download_url1) - jkt_timezone = timezone(TIME_ZONE) + jkt_timezone = pytz.timezone(TIME_ZONE) response = self.client.get(self.history_url) resp_html = response.content.decode('utf8') @@ -2778,7 +2801,7 @@ class DownloadHistoryViewTest(TestCase): self.client.logout() - jkt_timezone = timezone(TIME_ZONE) + jkt_timezone = pytz.timezone(TIME_ZONE) download_history = self.user_anonim.riwayat_unduh.all() response = self.client.get(self.history_url) @@ -2854,16 +2877,16 @@ class MateriModelTest(TestCase): self.materi = 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=datetime.now(), date_created=datetime.now()) + date_modified=timezone.now(), date_created=timezone.now()) def test_like_count_return_zero_when_there_is_no_like(self): self.assertEqual(0, self.materi.like_count) def test_like_count_return_right_value_when_there_is_like(self): - Like.objects.create(timestamp=datetime.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=datetime.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): @@ -2902,8 +2925,8 @@ class RandomizedMateriTest(TestCase): status="APPROVE", cover=self.cover, content=self.content, - date_modified=datetime.now(), - date_created=datetime.now(), + date_modified=timezone.now(), + date_created=timezone.now(), ) self.materi = Materi.objects.create( @@ -2915,8 +2938,8 @@ class RandomizedMateriTest(TestCase): status="APPROVE", cover=self.cover, content=self.content, - date_modified=datetime.now(), - date_created=datetime.now(), + date_modified=timezone.now(), + date_created=timezone.now(), ) def test_randomized_materi_returns_200(self): @@ -2996,8 +3019,9 @@ class YTUrlVideoTest(TestCase): @override_settings(MEDIA_ROOT=tempfile.gettempdir()) def setUpImage(self): - from django.core.files.uploadedfile import InMemoryUploadedFile from io import BytesIO + + from django.core.files.uploadedfile import InMemoryUploadedFile self.cover = InMemoryUploadedFile( BytesIO(base64.b64decode(TEST_IMAGE)), field_name='tempfile', @@ -3365,8 +3389,8 @@ class MateriRecommendationTest(TestCase): status="APPROVE", cover=self.cover, content=self.content, - date_modified=datetime.now(), - date_created=datetime.now(), + date_modified=timezone.now(), + date_created=timezone.now(), ) Like.objects.create(materi=materi1) @@ -3381,8 +3405,8 @@ class MateriRecommendationTest(TestCase): status="APPROVE", cover=self.cover, content=self.content, - date_modified=datetime.now(), - date_created=datetime.now(), + date_modified=timezone.now(), + date_created=timezone.now(), ) Like.objects.create(materi=materi2) @@ -3398,8 +3422,8 @@ class MateriRecommendationTest(TestCase): status="APPROVE", cover=self.cover, content=self.content, - date_modified=datetime.now(), - date_created=datetime.now(), + date_modified=timezone.now(), + date_created=timezone.now(), ) response = Client().get("/?recommendation=1") @@ -3416,8 +3440,8 @@ class MateriRecommendationTest(TestCase): status="APPROVE", cover=self.cover, content=self.content, - date_modified=datetime.now(), - date_created=datetime.now(), + date_modified=timezone.now(), + date_created=timezone.now(), ) Like.objects.create(materi=materi2) @@ -3432,8 +3456,8 @@ class MateriRecommendationTest(TestCase): status="APPROVE", cover=self.cover, content=self.content, - date_modified=datetime.now(), - date_created=datetime.now(), + date_modified=timezone.now(), + date_created=timezone.now(), ) Like.objects.create(materi=materi1) @@ -3443,3 +3467,195 @@ class MateriRecommendationTest(TestCase): list = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", response.content.decode())] self.assertEqual(list, [1, 2]) +class BacaNantiTest(TestCase): + def setUp(self): + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": id_generator() + } + self.user_one_credential = { + "email": "user_one@user.id", + "password": id_generator() + } + self.user_two_credential = { + "email": "user_two@user.id", + "password": id_generator() + } + 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.cover = SimpleUploadedFile( + "cover.jpg", + b"Test file" + ) + self.content = SimpleUploadedFile( + "content.txt", + b"Test file" + ) + Materi(title="Materi 1", author="Agas", uploader=self.contributor, + publisher="Kelas SC", descriptions="Deskripsi Materi 1", + status="APPROVE", cover=self.cover, content=self.content).save() + Materi(title="Materi Dua", author="Author", uploader=self.contributor, + publisher="Publisher", descriptions="Deskripsi Materi Dua", + status="APPROVE", cover=self.cover, content=self.content).save() + self.materi1 = Materi.objects.filter(title='Materi 1').get() + self.materi2 = Materi.objects.filter(title='Materi Dua').get() + self.url = '/baca-nanti/' + self.toggle_url = '/baca-nanti-toggle/' + self.url_materi = '/materi/{}/'.format(self.materi1.id) + + def test_readlater_object_can_be_created(self): + ReadLater(materi=self.materi1, user=self.user_one).save() + read_later = ReadLater.objects.first() + self.assertEqual(read_later.materi, self.materi1) + self.assertEqual(read_later.user, self.user_one) + + def test_readlater_materi_must_not_unique(self): + ReadLater(materi=self.materi1, user=self.user_one).save() + ReadLater(materi=self.materi1, user=self.user_two).save() + read_later_one = ReadLater.objects.get(user=self.user_one) + read_later_two = ReadLater.objects.get(user=self.user_two) + self.assertEqual(read_later_one.materi, self.materi1) + 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() + read_later_one = ReadLater.objects.get(materi=self.materi1) + read_later_two = ReadLater.objects.get(materi=self.materi2) + self.assertEqual(read_later_one.materi, self.materi1) + self.assertEqual(read_later_one.user, self.user_one) + self.assertEqual(read_later_two.materi, self.materi2) + self.assertEqual(read_later_two.user, self.user_one) + + def test_readlater_materi_combined_with_user_must_be_unique(self): + with self.assertRaises(IntegrityError) as context: + ReadLater(materi=self.materi1, user=self.user_one).save() + ReadLater(materi=self.materi1, user=self.user_one).save() + self.assertTrue('already exists' in str(context.exception)) + + def test_readlater_materi_cant_null(self): + with self.assertRaises(IntegrityError): + ReadLater(user=self.user_one).save() + + 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) + self.assertEqual(response.status_code, 200) + + 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") + + 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}) + 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() + 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() + 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}) + self.assertJSONEqual( + str(response.content, encoding='utf-8'), + {"success": True, "read_later_checked": True} + ) + + def test_unchecking_readlater_in_materi_with_complete_paramater_return_success(self): + 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}) + 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}) + 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) + self.assertJSONEqual( + str(response.content, encoding='utf-8'), + {"success": False, "msg": "Missing parameter"} + ) + +class MateriStatsTest(TestCase): + + def setUp(self): + self.credential = { + '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.client = Client() + + categories = [] + for i in range(10): + cat = Category(name=f'Cat{i}', description=f'Cat{i} description') + cat.save() + categories.append(cat) + + for i in range(10): + for j in range(random.randint(1, 10)): + m = Materi(title=f'Title{i}-{j}') + m.save() + 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) + self.assertTemplateUsed(response, 'statistik.html') + + def test_stats_as_authenticated(self): + self.client.login(**self.credential) + response = self.client.get(self.path) + self.assertContains(response, self.header) + + def test_stats_as_anonymous(self): + response = self.client.get(self.path) + self.assertEqual(response.status_code, 302) #redirect + response = self.client.get(self.path_json) + 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) diff --git a/app/tests.py.56b44c7bd7ec54dbc681b2c3905ccac9.tmp b/app/tests.py.56b44c7bd7ec54dbc681b2c3905ccac9.tmp deleted file mode 100644 index a5a4db8e0ff4e2768425b836bee6e0d3b9996f77..0000000000000000000000000000000000000000 --- a/app/tests.py.56b44c7bd7ec54dbc681b2c3905ccac9.tmp +++ /dev/null @@ -1,2108 +0,0 @@ -import json, tempfile, os, mock -import pandas as pd -from io import StringIO - -from bs4 import BeautifulSoup -from datetime import datetime -from django.conf import settings -from django.contrib import messages as dj_messages -from django.contrib.auth import get_user_model -from django.core import serializers -from django.core.files import File -from django.core.exceptions import PermissionDenied, ValidationError -from django.core.files.uploadedfile import SimpleUploadedFile -from django.core.management import call_command -from django.urls import resolve, reverse -from django.db.utils import IntegrityError -from django.test import Client, RequestFactory, TestCase, TransactionTestCase -from pytz import timezone -from time import sleep - -from administration.models import VerificationSetting, VerificationReport -from administration.utils import id_generator -from app.views import UploadMateriHTML, add_rating_materi -from authentication.models import User -from digipus.settings import TIME_ZONE -from .models import ( - Category, - Comment, - DownloadStatistics, - Materi, - Like, - Rating, - ReqMaterial, - RatingContributor, - ViewStatistics, -) - -from .views import ( - DaftarKatalog, - DashboardKontributorView, - DetailMateri, - ProfilKontributorView, - SuksesLoginAdminView, - SuksesLoginKontributorView, - SuntingProfilView, - ProfilAdminView, - PostsView, - SuntingProfilAdminView, - RevisiMateriView, - ReqMateriView, - KatalogPerKontributorView, - UploadMateriView, - UploadMateriExcelView, -) -from app.forms import SuntingProfilForm -from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata - -ERROR_403_MESSAGE = "Kamu harus login untuk mengakses halaman ini" - -class DaftarKatalogTest(TestCase): - def test_daftar_katalog_url_exist(self): - url = "/" - response = Client().get(f"{url}") - self.assertEqual(response.status_code, 200) - - def test_daftar_katalog_using_daftar_katalog_template(self): - response = Client().get("/") - self.assertTemplateUsed(response, "app/katalog_materi.html") - - def test_daftar_katalog_using_daftar_katalog_func(self): - found = resolve("/") - self.assertEqual(found.func.__name__, DaftarKatalog.as_view().__name__) - - def test_fields(self): - materi = Materi() - materi.title = "tes" - materi.cover = "https://cache.umusic.com/_sites/billieeilish/v2/images/pic-red.jpg" - materi.author = "input" - materi.save() - - 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 DaftarKatalogPerKontributorTest(TestCase): - def setUp(self): - self.client = Client() - - self.contributor_credential = {"email": "kontributor@gov.id", "password": "passwordtest"} - - self.contributor_credential_2 = {"email": "kontributor2@gov.id", "password": "passwordtest"} - - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor 1", is_contributor=True - ) - self.contributor2 = get_user_model().objects.create_user( - **self.contributor_credential_2, name="Kontributor 2", is_contributor=True - ) - - self.cover = SimpleUploadedFile("Cherprang_Areekul40_nJM9dGt.jpg", b"Test file") - self.content = SimpleUploadedFile("Bahan_PA_RKK.pdf", b"Test file") - - Materi( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="APPROVE", - cover=self.cover, - content=self.content, - ).save() - Materi( - title="Materi 2", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 2", - status="APPROVE", - cover=self.cover, - content=self.content, - ).save() - Materi( - title="Materi 3", - author="Agas", - uploader=self.contributor2, - publisher="Kelas SC", - descriptions="Deskripsi Materi 3", - status="APPROVE", - cover=self.cover, - content=self.content, - ).save() - - self.url = f"/profil/{self.contributor.email}/" - - def test_katalog_per_kontributor_url_exist(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - - def test_katalog_per_kontributor_using_katalog_kontri_template(self): - response = self.client.get(self.url) - self.assertTemplateUsed(response, "app/katalog_kontri.html") - - def test_katalog_per_kontributor_using_katalog_per_kontributor_func(self): - found = resolve(self.url) - 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) - - list_materi = Materi.objects.filter(uploader=self.contributor) - data = response.context_data["materi_list"] - self.assertEqual(len(list_materi), len(data)) - - -class DetailMateriTest(TestCase): - def setUp(self): - self.client = Client() - self.admin_credential = {"email": "admin@gov.id", "password": "passwordtest"} - self.contributor_credential = {"email": "kontributor@gov.id", "password": "passwordtest"} - self.anonymous_credential = {"email": "anonymous@gov.id", "password": "passwordtest"} - self.admin = get_user_model().objects.create_user(**self.admin_credential, name="Admin", is_admin=True) - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - self.anonymous = get_user_model().objects.create_user(**self.anonymous_credential, name="Anonymous") - self.cover = SimpleUploadedFile("ExampleCover921.jpg", b"Test file") - self.content = SimpleUploadedFile("ExampleFile921.pdf", b"Test file") - - Materi( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="APPROVE", - cover=self.cover, - content=self.content, - ).save() - self.materi1 = Materi.objects.first() - self.url = "/materi/" + str(self.materi1.id) + "/" - - 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=datetime.now(), - date_created=datetime.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) - self.assertEqual(response.status_code, 200) - self.assertNotEqual(response.status_code, 404) - - def test_detail_materi_using_detail_materi_template(self): - response = Client().get(self.url) - self.assertTemplateUsed(response, "app/detail_materi.html") - - def test_detail_materi_using_detail_materi_func(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, DetailMateri.as_view().__name__) - - def test_category_models_can_create_new_object(self): - test = Category.objects.create(id="1", name="medis", description="kategori medis") - countData = Category.objects.all().count() - self.assertEqual(1, countData) - self.assertNotEqual(0, countData) - self.assertEqual(test.__str__(), "medis") - self.assertNotEqual(test.__str__(), "saul") - - def test_comment_models_can_create_new_object(self): - test = Comment.objects.create(username="saul", profile="121212", comment="232323") - countData = Comment.objects.all().count() - self.assertEqual(1, countData) - self.assertNotEqual(0, countData) - self.assertEqual(test.__str__(), "saul") - self.assertNotEqual(test.__str__(), "userlain") - - def test_post_blank_comment(self): - url = self.url - self.client.login(**self.anonymous_credential) - response = self.client.post(url, {"comment": ""}) - 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) - self.client.post(url, {"comment": "This is my new comment"}) - response = Client().get(url) - self.assertContains(response, "This is my new comment") - - def test_comment_by_admin(self): - url = self.url - self.client.login(**self.admin_credential) - self.client.post(url, {"comment": "This is new comment by Admin"}) - response = self.client.get(url) - self.assertContains(response, "Admin") - - def test_comment_by_kontributor(self): - url = self.url - self.client.login(**self.contributor_credential) - self.client.post(url, {"comment": "This is new comment by Contributor"}) - response = self.client.get(url) - self.assertContains(response, "Kontributor") - - def test_comment_by_anonymous(self): - url = self.url - self.client.get("/logout/") - self.client.login(**self.anonymous_credential) - self.client.post(url, {"comment": "This is new comment by Anonymous"}) - response = self.client.get(url) - self.assertContains(response, "Anonymous") - - def test_detail_materi_contains_form_comment(self): - response = self.client.get(self.url) - self.assertContains(response, "Beri komentar...") - - def test_delete_comments_by_admin(self): - url = self.url - self.client.post(url, {"comment": "This is new comment by Anonymous"}) - deleteURL = ( - "/delete/" - + str(self.materi1.id) - + "/" - + str(Comment.objects.get(comment="This is new comment by Anonymous").id) - ) - self.client.get(deleteURL) - self.assertEqual(Comment.objects.all().filter(comment="This is new comment by Anonymous").count(), 0) - - def test_tombol_citasiAPA(self): - response = self.client.get(self.url) - self.assertContains(response, "Citate APA") - - 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 - 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 - ) - self.assertIn(expected, response.context["citationAPA"]) - - def test_citation_IEEE_button(self): - response = self.client.get(self.url) - self.assertContains(response, "Citate IEEE") - - def test_citation_IEEE_materi_has_no_published_date(self): - response = self.client.get(self.url) - current_date = datetime.now() - current_day = str(current_date.day) - 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 - ) - self.assertIn(expected, response.context["citationIEEE"]) - - def test_citation_IEEE_materi_has_published_date(self): - response = self.client.get(self.materi_with_published_date_url) - current_date = datetime.now() - 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") - - expected = ( - "Agas, " - + "Materi 1. " - + "Kelas SC, " - + published_date - + ". " - + "Accessed on: " - + current_month - + ". " - + current_day - + ", " - + current_year - + ". [Online]. " - + "Available: http://testserver" - + self.materi_with_published_date_url - ) - self.assertIn(expected, response.context["citationIEEE"]) - - def test_tombol_bagikan_google_drive(self): - response = Client().get(self.url) - self.assertContains(response, "Google Drive") - - @mock.patch("app.views.upload_to_gdrive") - def test_save_to_gdrive_with_nonexistent_materi(self, mock_upload_to_gdrive): - response = self.client.get("/materi/%s/save-to-gdrive/" % 0) - mock_upload_to_gdrive.assert_not_called() - self.assertEqual(response.status_code, 404) - - @mock.patch("app.views.upload_to_gdrive") - def test_save_to_gdrive_with_valid_materi(self, mock_upload_to_gdrive): - response = self.client.get("/materi/%s/save-to-gdrive/" % self.materi1.id, follow=True) - last_url, status_code = response.redirect_chain[-1] - mock_upload_to_gdrive.assert_called_once() - self.assertEqual(last_url, "/materi/%d/" % self.materi1.id) - self.assertEqual(status_code, 302) - - -class PostsViewTest(TestCase): - @classmethod - def generate_posts_data(cls, user): - POST_COUNT = 3 - COMMENTS_COUNT_PER_POST = [1, 0, 3] - LIKES_COUNT_PER_POST = [5, 4, 2] - - assert POST_COUNT == len(COMMENTS_COUNT_PER_POST) - - sample_file = SimpleUploadedFile("Test.jpg", b"Test file") - sample_category = Category.objects.create(name="Test Category") - - post_comment_group_dict = {} - for i in range(POST_COUNT): - post = Materi.objects.create(uploader=user, cover=sample_file, content=sample_file,) - post.categories.add(sample_category) - - post_comment_group_dict[post.id] = { - "data": post, - "comments": [], - } - - for j in range(LIKES_COUNT_PER_POST[i]): - Like.objects.create( - timestamp=datetime.now(), materi=post, session_id=("dummysession-" + str(i) + "-" + str(j)) - ) - - for i, post_id in enumerate(post_comment_group_dict): - post = post_comment_group_dict[post_id]["data"] - - for _ in range(COMMENTS_COUNT_PER_POST[i]): - comment = Comment.objects.create(materi=post) - post_comment_group_dict[post_id]["comments"].append(comment) - - # order by latest (-timestamp) - post_comment_group_dict[post_id]["comments"].reverse() - - return post_comment_group_dict - - @classmethod - def setUpTestData(cls): - cls.url = "/posts/" - cls.user_credentials = {"email": "user@email.com", "password": "justpass"} - 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): - self.client.login(**self.user_credentials) - return self.client.get(self.url) - - def test_url_resolves_to_posts_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, PostsView.as_view().__name__) - - def test_returns_200_on_authenticated_access(self): - response = self._request_as_user() - self.assertEqual(response.status_code, 200) - - def test_returns_403_on_unauthenticated_access(self): - response = self.client.get(self.url) - self.assertRaises(PermissionDenied) - self.assertEqual(response.status_code, 403) - - html = response.content.decode("utf-8") - self.assertIn(ERROR_403_MESSAGE, html) - - def test_returns_correct_template(self): - response = self._request_as_user() - self.assertTemplateUsed(response, "user_uploaded_posts.html") - - def test_success_returns_correct_comment_post_groupings_by_context(self): - post_comment_group_dict = self.data - - response = self._request_as_user() - - response_user = response.context_data["user"] - self.assertEqual(response_user, self.user) - - response_data = response.context_data["posts"] - actual_data = post_comment_group_dict - self.assertDictEqual(response_data, actual_data) - - def test_html_contains_grouped_posts_and_comments(self): - response = self._request_as_user() - - posts = list(self.data.keys()) - comments = {i: [comment.id for comment in self.data[post_id]["comments"]] for i, post_id in enumerate(posts)} - - self.assertRegex( - str(response.content), - rf'.*(<div id="post-{posts[2]}">)' - + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][0]}">)' - + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][1]}">)' - + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][2]}">)' - + rf'.*(<div id="post-{posts[1]}">)' - + rf'.*(<div id="post-{posts[0]}">)' - + rf'.*(<div id="post-{posts[0]}-comment-{comments[0][0]}">)', - ) - - def test_like_count_written_correctly_on_template(self): - response = self._request_as_user() - - for _, post_id in enumerate(self.data): - post = self.data[post_id]["data"] - - self.assertContains( - response, '<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" - response = Client().get(url) - self.assertEqual(response.status_code, 200) - - def test_template_loader_using_template_loader_template(self): - url = "/test-page.html" - response = Client().get(url) - expected_template_name = "test-page.html" - self.assertTemplateUsed(response, expected_template_name) - - def test_template_loader_using_template_loader_func(self): - url = "/test-page.html" - found = resolve(url) - expected_view_name = "pages" - self.assertEqual(found.func.__name__, expected_view_name) - - def test_template_loader_handle_non_existent_html(self): - url = "/test.html" - expected_template_name = "error-404.html" - response = Client().get(url) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, expected_template_name) - - -class UploadPageTest(TestCase): - def setUp(self): - self.client = Client() - self.user = User.objects._create_user(email="kontributor@gov.id", password="kontributor", is_contributor=True) - self.admin = User.objects.create_admin(email="admin@gov.id", password="admin") - - def test_upload_page_using_login_func(self): - found = resolve("/unggah/") - self.assertEqual(found.func.__name__, UploadMateriView.as_view().__name__) - - def test_upload_page_url_is_exist(self): - # Positive test - self.client.login(email="kontributor@gov.id", password="kontributor") - response = self.client.get("/unggah/") - self.assertEqual(response.status_code, 200) - - # Negative tests - self.client.login(email="kontributor@gov.id", password="kontributor") - response = Client().get("/fake/") - self.assertEqual(response.status_code, 404) - - 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): - self.client.login(email="admin@gov.id", password="admin") - response = self.client.post("/unggah/") - self.assertEqual(response.status_code, 403) - - def test_upload_page_template(self): - url = "/unggah/" - self.client.login(email="kontributor@gov.id", password="kontributor") - response = self.client.get(url) - expected_template_name = "unggah.html" - self.assertTemplateUsed(response, expected_template_name) - - def test_upload_page_title(self): - self.client.login(email="kontributor@gov.id", password="kontributor") - response = self.client.get("/unggah/") - - # Positive tests - self.assertContains(response, "Unggah Materi") - - # Negative tests - self.assertNotContains(response, "Anything") - - def test_upload_page_form_field(self): - self.client.login(email="kontributor@gov.id", password="kontributor") - response = self.client.get("/unggah/") - - # Positive tests - self.assertContains(response, "title") - - # Negative tests - self.assertNotContains(response, "anything") - - -class UploadExcelPageTest(TestCase): - def setUp(self): - self.client = Client() - self.user = User.objects._create_user(email="kontributor@gov.id", password="kontributor", is_contributor=True) - - def test_upload_excel_page_using_login_func(self): - found = resolve("/unggah_excel/") - self.assertEqual(found.func.__name__, UploadMateriExcelView.as_view().__name__) - - def test_uplaod_excel_page_url_is_exist(self): - # Positive test - self.client.login(email="kontributor@gov.id", password="kontributor") - response = self.client.get("/unggah_excel/") - self.assertEqual(response.status_code, 200) - - # Negative tests - self.client.login(email="kontributor@gov.id", password="kontributor") - response = Client().get("/fake/") - self.assertEqual(response.status_code, 404) - - def test_upload_excel_page_template(self): - url = "/unggah_excel/" - self.client.login(email="kontributor@gov.id", password="kontributor") - response = self.client.get(url) - expected_template_name = "unggah_excel.html" - self.assertTemplateUsed(response, expected_template_name) - - def test_upload_excel_page_title(self): - self.client.login(email="kontributor@gov.id", password="kontributor") - response = self.client.get("/unggah_excel/") - - # Positive tests - self.assertContains(response, "Unggah Materi dari Excel") - - # Negative tests - self.assertNotContains(response, "Anything") - - def test_upload_excel_page_form_field(self): - self.client.login(email="kontributor@gov.id", password="kontributor") - response = self.client.get("/unggah_excel/") - - # Positive tests - self.assertContains(response, "File (*.xlsx)") - - # Negative tests - self.assertNotContains(response, "anything") - - def create_dummy_excel(self, field_lengths={}, categories=[]): - title1 = "Hands-On Machine Learning with Scikit-Learn and TensorFlow: Concepts, Tools, and Techniques to Build Intelligent Systems" - author1 = "Aurelien Geron, Aurelien Geron, Aurelien Geron" - publisher1 = "O'Reilly Media, O'Reilly Media, O'Reilly Media" - categories1 = "Machine Learning,Deep Learning,Computer Science" - description1 = "A series of Deep Learning breakthroughs have boosted the whole field of machine learning over the last decade. Now that machine learning is thriving, even programmers who know close to nothing about this technology can use simple, efficient tools to implement programs capable of learning from data. This practical book shows you how." - - if "title" in field_lengths: - title1 = title1[: field_lengths["title"]] - - if "author" in field_lengths: - author1 = author1[: field_lengths["author"]] - - if "publisher" in field_lengths: - publisher1 = publisher1[: field_lengths["publisher"]] - - if len(categories) > 0: - categories1 = ",".join(categories) - - data_frame = pd.DataFrame( - { - "Title": [title1], - "Author": [author1], - "Publisher": [publisher1], - "Categories": [categories1], - "Description": [description1], - } - ) - - file_path = os.path.join(settings.MEDIA_ROOT, "dummy.xlsx") - - writer = pd.ExcelWriter(file_path, engine="xlsxwriter") # pylint: disable=abstract-class-instantiated - data_frame.to_excel(writer, index=0) - writer.save() - - return file_path, data_frame - - def test_upload_excel_upload_file_title_error(self): - self.client.login(email="kontributor@gov.id", password="kontributor") - - field_lengths = { - "author": 30, - "publisher": 30, - } - 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("Title", msg_text) - - def test_upload_excel_upload_file_author_error(self): - self.client.login(email="kontributor@gov.id", password="kontributor") - - field_lengths = { - "title": 50, - "publisher": 30, - } - 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, - } - 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("Publisher", msg_text) - - def test_upload_excel_upload_file_categories_error(self): - self.client.login(email="kontributor@gov.id", password="kontributor") - - field_lengths = { - "title": 50, - "author": 30, - "publisher": 30, - } - 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("Kategori", msg_text) - - def test_upload_excel_upload_file_success(self): - self.client.login(email="kontributor@gov.id", password="kontributor") - - Category(name="Computer Science").save() - Category(name="Machine Learning").save() - Category(name="Deep Learning").save() - - field_lengths = { - "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) - - with open(file_name, "rb") as fp: - response = self.client.post("/unggah_excel/", {"excel": fp}) - - title = data_frame["Title"][0] - self.assertTrue(Materi.objects.get(title=title)) - - 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") - - -class DashboardKontributorViewTest(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.url = "/dashboard/" - self.view = DashboardKontributorView - self.template_name = "dashboard.html" - - def test_dashboard_kontributor_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, self.view.as_view().__name__) - - def test_dashboard_kontributor_template(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - # Logout - self.client.logout() - - def test_dashboard_kontributor_url(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_dashboard_kontributor_access(self): - # Kontributor - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - # Admin - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 403) - - html = response.content.decode("utf-8") - self.assertIn(ERROR_403_MESSAGE, html) - # Logout - self.client.logout() - - # Anonim - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 403) - - html = response.content.decode("utf-8") - self.assertIn(ERROR_403_MESSAGE, html) - - -class ProfilAdminTest(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.url = "/profil-admin/" - self.view = ProfilAdminView - self.template_name = "profil-admin.html" - - def test_profil_Admin_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, self.view.as_view().__name__) - - def test_profil_admin_template(self): - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - # Logout - self.client.logout() - - def test_profil_admin_url(self): - # Login - response = self.client.get(self.url) - self.assertNotEqual(response.status_code, 200) - - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - -class ProfilKontributorTest(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.url = "/profil/" - self.view = ProfilKontributorView - self.template_name = "profil.html" - - def test_profil_kontributor_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, self.view.as_view().__name__) - - def test_profil_kontributor_template(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - # Logout - self.client.logout() - - def test_profil_kontributor_url(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_profil_kontributor_access(self): - # Kontributor - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - # Admin - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 403) - - html = response.content.decode("utf-8") - self.assertIn(ERROR_403_MESSAGE, html) - # Logout - self.client.logout() - - # Anonim - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 403) - - html = response.content.decode("utf-8") - self.assertIn(ERROR_403_MESSAGE, html) - - -class SuntingProfilTest(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.url = "/sunting/" - self.view = SuntingProfilView - self.template_name = "sunting.html" - - def test_sunting_profile_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, self.view.as_view().__name__) - - def test_sunting_profile_template(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - # Logout - self.client.logout() - - def test_sunting_profile_url(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_sunting_profile_access(self): - # Kontributor - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_sunting_profile_access_anonymous(self): - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 403) - - def test_sunting_profile_autofocus(self): - form_data = { - "email": "kontributor@gov.id", - "name": "kontributor", - "instansi": "instansi", - "nik": "nik", - "alamat": "alamat", - "nomor_telpon": "123456789", - "twitter": "Twit", - "instagram": "https://instagram.com/test_ig", - } - form = SuntingProfilForm(data=form_data) - - # Test - self.assertEqual(form.fields["twitter"].widget.attrs.get("autofocus"), "") - self.assertEqual(form.fields["instagram"].widget.attrs.get("autofocus"), None) - - -class SuntingProfilAdminTest(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.url = "/sunting-admin/" - self.view = SuntingProfilAdminView - self.template_name = "sunting_admin.html" - - def test_sunting_profile_admin_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, self.view.as_view().__name__) - - def test_sunting_profile_admin_template(self): - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - # Logout - self.client.logout() - - def test_sunting_profile_admin_url(self): - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_sunting_profile_admin_access(self): - # Kontributor - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - -class SuksesLoginKontributorTest(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.url = "/sukses-kontributor/" - self.view = SuksesLoginKontributorView - self.template_name = "sukses_kontri.html" - - def test_sukses_login_kontributor_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, self.view.as_view().__name__) - - def test_sukses_login_kontributor_template(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - # Logout - self.client.logout() - - def test_sukses_login_kontributor_url(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_sukses_login_kontributor_access(self): - # Kontributor - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - -class SuksesLoginAdminTest(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.url = "/sukses-admin/" - self.view = SuksesLoginAdminView - self.template_name = "sukses_admin.html" - - def test_sukses_login_admin_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, self.view.as_view().__name__) - - def test_sukses_login_admin_template(self): - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - # Logout - self.client.logout() - - def test_sukses_login_admin_url(self): - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_sukses_login_admin_access(self): - # Kontributor - # Login - self.client.login(email="admin@gov.id", password="admin") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - -class LikeMateriTest(TestCase): - def setUp(self): - self.contributor_credential = {"email": "kontributor@gov.id", "password": id_generator()} - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - self.client = Client() - self.url_like = "/materi/like/" - content = b"Test file" - self.cover = SimpleUploadedFile("cover.jpg", content) - self.content = SimpleUploadedFile("content.txt", content) - Materi( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="PENDING", - cover=self.cover, - content=self.content, - ).save() - self.materi1 = Materi.objects.first() - self.url_materi = f"/materi/{self.materi1.id}/" - - def test_get_method(self): - response = self.client.get(self.url_like) - response = json.loads(response.content) - self.assertEqual(response.get("success", None), False) - - def test_like_materi(self): - # Verify that materi doesn't have any like to start with - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 0) - - # Like a materi - response = self.client.get(self.url_materi) - session_id = response.context["session_id"] - materi_id = response.context["materi_data"].id - payload = {"materi_id": materi_id, "session_id": session_id} - ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 1) - - def test_unlike_materi(self): - # Verify that materi doesn't have any like to start with - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 0) - - # Like a materi - response = self.client.get(self.url_materi) - session_id = response.context["session_id"] - materi_id = response.context["materi_data"].id - payload = {"materi_id": materi_id, "session_id": session_id} - ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 1) - - # Unlike a materi - response = self.client.get(self.url_materi) - session_id = response.context["session_id"] - materi_id = response.context["materi_data"].id - payload = {"materi_id": materi_id, "session_id": session_id} - ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 0) - - def test_2_client_like_materi(self): - # Verify that materi doesn't have any like to start with - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 0) - - # Client 1 like a materi - response = self.client.get(self.url_materi) - session_id = response.context["session_id"] - materi_id = response.context["materi_data"].id - payload = {"materi_id": materi_id, "session_id": session_id} - ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 1) - - # Client 2 like a materi - response = Client().get(self.url_materi) - session_id = response.context["session_id"] - materi_id = response.context["materi_data"].id - payload = {"materi_id": materi_id, "session_id": session_id} - ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 2) - - def test_incomplete_like_parameter(self): - # Verify that materi doesn't have any like to start with - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 0) - - # missing session id - response = self.client.get(self.url_materi) - materi_id = response.context["materi_data"].id - payload = { - "materi_id": materi_id, - } - ajax_response = Client().post(self.url_like, payload) - ajax_response = json.loads(ajax_response.content) - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 0) - self.assertEqual(ajax_response.get("success", None), False) - - # missing materi id - response = self.client.get(self.url_materi) - session_id = response.context["session_id"] - payload = {"session_id": session_id} - ajax_response = Client().post(self.url_like, payload) - ajax_response = json.loads(ajax_response.content) - num_of_likes = Like.objects.filter(materi=self.materi1).count() - self.assertEqual(num_of_likes, 0) - self.assertEqual(ajax_response.get("success", None), False) - - -class ViewMateriStatissticsTest(TestCase): - def setUp(self): - self.contributor_credential = {"email": "kontributor@gov.id", "password": id_generator()} - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - self.client = Client() - content = b"Test file" - self.cover = SimpleUploadedFile("cover.jpg", content) - self.content = SimpleUploadedFile("content.txt", content) - Materi( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="PENDING", - cover=self.cover, - content=self.content, - ).save() - self.materi1 = Materi.objects.first() - self.url = f"/materi/{self.materi1.id}/view" - - # Test single view - def test_count_one_materi_view(self): - response = self.client.get(self.url) - num_of_views = self.materi1.baca.all().count() - self.assertEqual(num_of_views, 1) - - # Test more than one view - def test_count_more_than_one_materi_view(self): - response = self.client.get(self.url) - num_of_views = self.materi1.baca.all().count() - self.assertEqual(num_of_views, 1) - - response = Client().get(self.url) - num_of_views = self.materi1.baca.all().count() - self.assertEqual(num_of_views, 2) - - -class DownloadMateriStatissticsTest(TestCase): - def setUp(self): - self.contributor_credential = {"email": "kontributor@gov.id", "password": id_generator()} - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - self.client = Client() - content = b"Test file" - self.cover = SimpleUploadedFile("cover.jpg", content) - self.content = SimpleUploadedFile("content.txt", content) - Materi( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="PENDING", - cover=self.cover, - content=self.content, - ).save() - self.materi1 = Materi.objects.first() - self.url = f"/materi/{self.materi1.id}/unduh" - - # Test single download - def test_count_one_materi_download(self): - response = self.client.get(self.url) - num_of_downloads = self.materi1.unduh.all().count() - self.assertEqual(num_of_downloads, 1) - - # Test more than one download - def test_count_more_than_one_materi_download(self): - response = self.client.get(self.url) - num_of_downloads = self.materi1.unduh.all().count() - self.assertEqual(num_of_downloads, 1) - - response = Client().get(self.url) - num_of_downloads = self.materi1.unduh.all().count() - self.assertEqual(num_of_downloads, 2) - - -class RevisiMateriTest(TestCase): - def setUp(self): - self.client = Client() - self.view = RevisiMateriView - self.template_name = "revisi.html" - - self.contributor_credential = {"email": "kontributor@gov.id", "password": id_generator()} - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - self.cover = SimpleUploadedFile("cover.jpg", b"Test file") - self.content = SimpleUploadedFile("content.txt", b"Test file") - Materi( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="REVISION", - cover=self.cover, - content=self.content, - ).save() - - self.materi1 = Materi.objects.first() - self.url = "/revisi/materi/" + str(self.materi1.id) + "/" - - def test_revisi_materi_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, self.view.as_view().__name__) - - def test_revisi_materi_template(self): - # Login - self.client.login(**self.contributor_credential) - # Test - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - # Logout - self.client.logout() - - def test_revisi_materi_url(self): - # Login - self.client.login(**self.contributor_credential) - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_revisi_materi_access(self): - # Kontributor - # Login - self.client.login(**self.contributor_credential) - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - -class GenerateDummyCommandTest(TestCase): - def setUp(self): - self.material_numbers = [5, 10, 25, 100] - self.invalid_material_numbers = [-100, -10, -1, 0, 1, 2, 3, 4] - self.stdout = StringIO() - - def test_command_output_with_given_num_of_materi(self): - for num_of_materi in self.material_numbers: - call_command("generatedummy", num_of_materi, stdout=self.stdout) - self.assertIn(f"Successfully created {num_of_materi} materi", self.stdout.getvalue()) - - def test_command_should_generate_materi_objects(self): - for num_of_materi in self.material_numbers: - call_command("generatedummy", num_of_materi, stdout=self.stdout) - self.assertEqual(Materi.objects.count(), num_of_materi) - Materi.objects.all().delete() - - def test_command_should_raise_exception_if_invalid_values_are_given(self): - with self.assertRaises(IndexError): - for num_of_materi in self.invalid_material_numbers: - call_command("generatedummy", num_of_materi) - - -class RemoveDummyCommandTest(TestCase): - def test_calling_remove_dummy_command_should_remove_generated_dummy_objects(self): - stdout = StringIO() - call_command("generatedummy", 50) - - call_command("removedummy", stdout=stdout) - - 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) - self.assertEqual(VerificationSetting.objects.count(), 0) - self.assertEqual(VerificationReport.objects.count(), 0) - - -class RatingMateriTest(TestCase): - def setUp(self): - self.contributor_credential = {"email": "kontributor@gov.id", "password": id_generator()} - self.user_one_credential = {"email": "user_one@user.id", "password": id_generator()} - self.user_two_credential = {"email": "user_two@user.id", "password": id_generator()} - 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.cover = SimpleUploadedFile("cover.jpg", b"Test file") - self.content = SimpleUploadedFile("content.txt", b"Test file") - Materi( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="PENDING", - cover=self.cover, - content=self.content, - ).save() - Materi( - title="Materi Dua", - author="Author", - uploader=self.contributor, - publisher="Publisher", - descriptions="Deskripsi Materi Dua", - status="APPROVE", - cover=self.cover, - content=self.content, - ).save() - self.materi1 = Materi.objects.all()[0] - self.materi2 = Materi.objects.all()[1] - self.url_rate = "/materi/rate/" - self.url_materi = "/materi/{}/".format(self.materi1.id) - - def test_rating_model_can_be_created_with_proper_parameter(self): - Rating(materi=self.materi1, user=self.user_one, score=5).save() - rating = Rating.objects.first() - 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") - - 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)) - - 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)) - - 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() - Rating(materi=self.materi1, user=self.user_two, score=2).save() - rating_one = Rating.objects.get(user=self.user_one) - rating_two = Rating.objects.get(user=self.user_two) - self.assertEqual(rating_one.materi, self.materi1) - self.assertEqual(rating_two.materi, self.materi1) - self.assertEqual(rating_one.user, self.user_one) - self.assertEqual(rating_two.user, self.user_two) - - def test_one_user_should_be_able_to_be_related_to_two_rating(self): - Rating(materi=self.materi1, user=self.user_one, score=3).save() - Rating(materi=self.materi2, user=self.user_one, score=3).save() - rating_one = Rating.objects.filter(materi=self.materi1).first() - rating_two = Rating.objects.filter(materi=self.materi2).first() - self.assertEqual(rating_one.materi, self.materi1) - self.assertEqual(rating_two.materi, self.materi2) - self.assertEqual(rating_one.user, self.user_one) - self.assertEqual(rating_two.user, self.user_one) - - def test_two_rating_should_not_have_same_user_and_materi(self): - with self.assertRaises(IntegrityError) as context: - Rating(materi=self.materi1, user=self.user_one, score=1).save() - Rating(materi=self.materi1, user=self.user_one, score=2).save() - self.assertTrue("already exists" in str(context.exception)) - - def test_materi_in_rating_should_not_be_null(self): - with self.assertRaises(IntegrityError): - Rating(user=self.user_one, score=1).save() - - def test_user_in_rating_should_not_be_null(self): - with self.assertRaises(IntegrityError): - Rating(materi=self.materi1, score=1).save() - - def test_score_in_rating_should_not_be_null(self): - with self.assertRaises(TypeError): - Rating(materi=self.materi1, user=self.user_one).save() - - def test_rating_materi_url_use_add_rating_materi_function(self): - found = resolve(self.url_rate) - self.assertEqual(found.func, add_rating_materi) - - def test_rating_materi_get_method_should_return_403_forbidden(self): - response = self.client.get(self.url_rate) - response_json = json.loads(response.content) - self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "Forbidden") - 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_json = json.loads(response.content) - self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "Forbidden") - self.assertEqual(response.status_code, 403) - - def test_rating_materi_not_authenticated_post_wrong_param_should_return_403_forbidden(self): - for data in [{}, {"materi_id": 1}, {"rating_score": 1}, {"rating_score": "STRING", "materi_id": "STRING"}]: - response = self.client.post(self.url_rate, data) - response_json = json.loads(response.content) - self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "Forbidden") - self.assertEqual(response.status_code, 403) - - def test_rating_materi_authenticated_post_missing_param(self): - self.client.login(**self.user_one_credential) - for data in [{"rating_score": 1}, {"materi_id": 1}, {}]: - response = self.client.post(self.url_rate, data) - response_json = json.loads(response.content) - self.assertEqual(response_json.get("success", None), False) - self.assertEqual(response_json.get("msg", None), "Missing param") - self.assertEqual(response.status_code, 422) - - 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_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.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_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.status_code, 422) - - 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.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_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.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_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.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}) - 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.status_code, 409) - - def test_user_authenticated_visit_unrated_should_get_0_materi_rating_score_context(self): - self.client.login(**self.user_one_credential) - response = self.client.get(self.url_materi) - self.assertEqual(0, response.context.get("materi_rating_score")) - - def test_user_not_authenticated_visit_unrated_should_get_0_materi_rating_score_context(self): - response = self.client.get(self.url_materi) - self.assertEqual(0, response.context.get("materi_rating_score")) - - def test_user_authenticated_visit_rated_should_get_correct_materi_rating_score_context(self): - self.client.login(**self.user_one_credential) - Rating(materi=self.materi1, user=self.user_one, score=1).save() - 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() - - def test_get_random_filename_isCorrect(self): - generated_name = get_random_filename(self.filename) - - self.assertTrue(generated_name != self.filename) - # 40 from 36 expected name length + 4 from extension - self.assertEqual(len(generated_name), 40) - self.assertTrue(generated_name[-4:] == ".gif") - - def test_remove_image_exifdata_isCorrect(self): - with tempfile.TemporaryDirectory() as d: - image_with_exif_data_path = os.path.join(d, self.filename) - img = open(image_with_exif_data_path, "wb") - img.write(self.file_content) - img.close() - - remove_image_exifdata(image_with_exif_data_path) - sanitized_img = open(image_with_exif_data_path, "rb").read() - - 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() - self.admin_credential = {"email": "admin@gov.id", "password": "passwordtest"} - self.contributor_credential = {"email": "kontributor@gov.id", "password": "passwordtest"} - self.anonymous_credential = {"email": "anonymous@gov.id", "password": "passwordtest"} - self.admin = get_user_model().objects.create_user(**self.admin_credential, name="Admin", is_admin=True) - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - self.anonymous = get_user_model().objects.create_user(**self.anonymous_credential, name="Anonymous") - self.url = "/req-materi/" - self.template_name = "req_materi.html" - - def test_req_materi_url_resolves_to_get_req_materi_view(self): - found = resolve(self.url) - self.assertEqual(found.func.__name__, ReqMateriView.as_view().__name__) - - def test_uses_req_material_template(self): - self.client.login(**self.contributor_credential) - response = self.client.get(self.url) - self.assertTemplateUsed(response, self.template_name) - self.client.logout() - - def test_redirect_to_login_page_is_not_authenticated(self): - response = self.client.get(self.url) - - self.assertEqual(response.status_code, 302) - self.assertEqual(response["location"], "/login/") - - def test_saving_and_retrieving_material_requests(self): - first_material_request = ReqMaterial(title="Material 1").save() - second_material_request = ReqMaterial(title="Material 2").save() - saved_material_request = ReqMaterial.objects.all() - self.assertEqual(saved_material_request.count(), 2) - - def test_can_save_a_POST_request_and_return_correct_response_message(self): - self.client.login(**self.contributor_credential) - - response = self.client.post(self.url, 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.client.logout() - - def test_given_no_title_should_not_save_request_and_return_correct_response_message(self): - self.client.login(**self.contributor_credential) - - response = self.client.post(self.url) - self.assertEqual(ReqMaterial.objects.count(), 0) - - self.assertIn("Missing parameter", response.content.decode()) - self.client.logout() - - -class RatingContributorTest(TransactionTestCase): - def setUp(self): - self.contributor_credential = {"email": "kontributor@gov.id", "password": id_generator()} - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - - def test_add_rating_contributor(self): - RatingContributor.objects.create(score=3, user=self.contributor) - 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, user=self.contributor) - 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, user=self.contributor) - self.assertEqual(0, RatingContributor.objects.count()) - - def test_submit_form_correct_rating_contributor_should_added(self): - url = f"/profil/{self.contributor.email}/" - self.client.post(url, data={"user": self.contributor.id, "score": 5}) - self.assertEqual(1, RatingContributor.objects.filter(user=self.contributor.id).count()) - self.client.post(url, data={"user": self.contributor.id, "score": 1}) - self.assertEqual(2, RatingContributor.objects.filter(user=self.contributor.id).count()) - - def test_submit_form_not_correct_rating_contributor_should__not_added(self): - url = f"/profil/{self.contributor.email}/" - self.client.post(url, data={"user": self.contributor.id, "score": 6}) - self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count()) - self.client.post(url, data={"user": self.contributor.id, "score": 0}) - self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count()) - - -class UserDownloadHistoryTest(TestCase): - def setUp(self): - self.user1_credential = {"email": "anonim1@gov.id", "password": id_generator()} - self.user1_anonim = get_user_model().objects.create_user(**self.user1_credential, name="Anonim1") - self.user2_credential = {"email": "anonim2@gov.id", "password": id_generator()} - self.user2_anonim = get_user_model().objects.create_user(**self.user2_credential, name="Anonim2") - self.contributor_credential = {"email": "kontributor@gov.id", "password": id_generator()} - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - self.client = Client() - content = b"Test file" - self.cover = SimpleUploadedFile("cover.jpg", content) - self.content = SimpleUploadedFile("content.txt", content) - Materi( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="PENDING", - cover=self.cover, - content=self.content, - ).save() - self.materi1 = Materi.objects.first() - self.download_url = f"/materi/{self.materi1.id}/unduh" - self.history_url = "/download-history/" - - def test_multiple_insert_download_statistic_with_user(self): - DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save() - num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - self.assertEqual(num_of_downloads, 1) - - DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save() - num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - self.assertEqual(num_of_downloads, 2) - - def test_download_statistics_bound_to_specific_user(self): - DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save() - num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - self.assertEqual(num_of_downloads, 1) - - DownloadStatistics(materi=self.materi1).save() - num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - self.assertEqual(num_of_downloads, 1) - - DownloadStatistics(materi=self.materi1, downloader=self.user2_anonim).save() - user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - user2_num_of_downloads = self.user2_anonim.riwayat_unduh.all().count() - self.assertEqual(user1_num_of_downloads, 1) - self.assertEqual(user2_num_of_downloads, 1) - - def test_registered_user_download(self): - # Login - self.client.login(**self.user1_credential) - - self.client.get(self.download_url) - num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - self.assertEqual(num_of_downloads, 1) - - # Logout - self.client.logout() - - def test_unregistered_user_download(self): - self.client.get(self.download_url) - downloaded_materi = self.client.session["downloaded_materi"] - num_of_downloads = DownloadStatistics.objects.filter(pk__in=downloaded_materi).count() - self.assertEqual(num_of_downloads, 1) - - def test_registered_user_multiple_download(self): - # Login - self.client.login(**self.user1_credential) - self.client.get(self.download_url) - num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - self.assertEqual(num_of_downloads, 1) - - self.client.get(self.download_url) - num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - self.assertEqual(num_of_downloads, 2) - - # Logout - self.client.logout() - - def test_unregistered_user_multiple_download(self): - self.client.get(self.download_url) - downloaded_materi = self.client.session["downloaded_materi"] - num_of_downloads = DownloadStatistics.objects.filter(pk__in=downloaded_materi).count() - self.assertEqual(num_of_downloads, 1) - - self.client.get(self.download_url) - downloaded_materi = self.client.session["downloaded_materi"] - num_of_downloads = DownloadStatistics.objects.filter(pk__in=downloaded_materi).count() - self.assertEqual(num_of_downloads, 2) - - def test_registered_user_doesnt_use_session_when_download(self): - # Login - self.client.login(**self.user1_credential) - - self.client.get(self.download_url) - self.assertFalse("downloaded_materi" in self.client.session) - - # Logout - self.client.logout() - - def test_download_history_bound_to_specific_user(self): - # Login Anonym 1 - self.client.login(**self.user1_credential) - self.client.get(self.download_url) - num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - self.assertEqual(num_of_downloads, 1) - - # Logout Anonym 1 - self.client.logout() - - # Unregistered User download - self.client.get(self.download_url) - user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - downloaded_materi = self.client.session["downloaded_materi"] - guest_num_of_downloads = DownloadStatistics.objects.filter(pk__in=downloaded_materi).count() - self.assertEqual(user1_num_of_downloads, 1) - self.assertEqual(guest_num_of_downloads, 1) - - # Login Anonym 2 - self.client.login(**self.user2_credential) - self.client.get(self.download_url) - user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count() - user2_num_of_downloads = self.user2_anonim.riwayat_unduh.all().count() - self.assertEqual(user1_num_of_downloads, 1) - self.assertEqual(guest_num_of_downloads, 1) - self.assertEqual(user2_num_of_downloads, 1) - - # Logout Anonym 2 - self.client.logout() - - -class DownloadHistoryViewTest(TestCase): - def setUp(self): - self.user_credential = {"email": "anonim1@gov.id", "password": id_generator()} - self.user_anonim = get_user_model().objects.create_user(**self.user_credential, name="Anonim") - self.contributor_credential = {"email": "kontributor@gov.id", "password": id_generator()} - self.contributor = get_user_model().objects.create_user( - **self.contributor_credential, name="Kontributor", is_contributor=True - ) - self.client = Client() - - content1 = b"Test file" - content2 = b"File Test" - - self.cover1 = SimpleUploadedFile("cover1.jpg", content1) - self.content1 = SimpleUploadedFile("content1.txt", content1) - - self.cover2 = SimpleUploadedFile("cover2.jpg", content2) - self.content2 = SimpleUploadedFile("content2.txt", content2) - - self.materi1 = Materi.objects.create( - title="Materi 1", - author="Agas", - uploader=self.contributor, - publisher="Kelas SC", - descriptions="Deskripsi Materi 1", - status="PENDING", - cover=self.cover1, - content=self.content1, - ) - self.materi2 = Materi.objects.create( - title="Materi 2", - author="Danin", - uploader=self.contributor, - publisher="Kelas DDP", - descriptions="Deskripsi Materi 2", - status="PENDING", - cover=self.cover2, - content=self.content2, - ) - - self.download_url1 = f"/materi/{self.materi1.id}/unduh" - self.download_url2 = f"/materi/{self.materi2.id}/unduh" - self.history_url = "/download-history/" - - # Login - self.client.login(**self.user_credential) - - def tearDown(self): - # Logout - self.client.logout() - - def test_allow_registered_user(self): - response = self.client.get(self.history_url) - self.assertEqual(response.status_code, 200) - - def test_allow_unregistered_user(self): - # Forced Logout - self.client.logout() - - response = self.client.get(self.history_url) - self.assertEqual(response.status_code, 200) - - def test_download_history_using_correct_template(self): - response = self.client.get(self.history_url) - self.assertTemplateUsed(response, "download_history.html") - - def test_download_history_has_user_name(self): - response = self.client.get(self.history_url) - resp_html = response.content.decode("utf8") - self.assertIn(self.user_anonim.name, resp_html) - - def test_registered_user_download_history_correctly_displayed(self): - self.client.get(self.download_url1) - self.client.get(self.download_url2) - self.client.get(self.download_url1) - - jkt_timezone = timezone(TIME_ZONE) - - download_history = self.user_anonim.riwayat_unduh.all() - response = self.client.get(self.history_url) - resp_html = response.content.decode("utf8") - for riwayat in download_history: - downloaded_materi = riwayat.materi - self.assertIn(downloaded_materi.title, resp_html) - self.assertIn(downloaded_materi.author, resp_html) - - jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone) - self.assertIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html) - - def test_unregistered_user_download_history_correctly_displayed(self): - self.client.logout() - - self.client.get(self.download_url1) - self.client.get(self.download_url2) - self.client.get(self.download_url1) - - jkt_timezone = timezone(TIME_ZONE) - - response = self.client.get(self.history_url) - resp_html = response.content.decode("utf8") - for riwayat_id in self.client.session["downloaded_materi"]: - riwayat = DownloadStatistics.objects.get(pk=riwayat_id) - downloaded_materi = riwayat.materi - self.assertIn(downloaded_materi.title, resp_html) - self.assertIn(downloaded_materi.author, resp_html) - - jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone) - self.assertIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html) - - def test_download_history_not_display_if_user_changed(self): - self.client.get(self.download_url1) - self.client.get(self.download_url2) - self.client.get(self.download_url1) - - self.client.logout() - - jkt_timezone = timezone(TIME_ZONE) - - download_history = self.user_anonim.riwayat_unduh.all() - response = self.client.get(self.history_url) - resp_html = response.content.decode("utf8") - for riwayat in download_history: - downloaded_materi = riwayat.materi - self.assertNotIn(downloaded_materi.title, resp_html) - self.assertNotIn(downloaded_materi.author, resp_html) - - jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone) - self.assertNotIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html) - - def test_unregistered_user_download_history_wont_be_saved_if_user_changes(self): - self.client.logout() - - self.client.get(self.download_url1) - self.client.get(self.download_url2) - self.client.get(self.download_url1) - - self.client.get(self.history_url) - - self.client.login(**self.user_credential) - self.client.logout() - self.assertFalse("downloaded_materi" in self.client.session) - - def test_download_history_sorted_by_download_time(self): - # download with 1 second interval to differ download time - self.client.get(self.download_url1) - sleep(1) - self.client.get(self.download_url2) - sleep(1) - self.client.get(self.download_url1) - sleep(1) - self.client.get(self.download_url2) - - response = self.client.get(self.history_url) - resp_html = response.content.decode("utf8") - - table_html = ("<table" + resp_html.split("<table")[1]).split("</table>")[0] + "</table>" - soup = BeautifulSoup(table_html, "html.parser") - histories_html = soup.find("tbody").find_all("tr") - prev_timestamp = None - - for riwayat_html in histories_html: - materi_data = riwayat_html.find_all("td") - date_format = "%d %B %Y %H:%M:%S" - materi_timestamp = datetime.strptime(materi_data[2].get_text(), date_format) - if prev_timestamp: - self.assertTrue(prev_timestamp > materi_timestamp) - prev_timestamp = materi_timestamp - - def test_no_history_display_message(self): - no_history_msg = "Anda belum mengunduh materi. Silahkan unduh materi yang anda butuhkan" - response = self.client.get(self.history_url) - resp_html = response.content.decode("utf8") - self.assertIn(no_history_msg, resp_html) - - -class MateriModelTest(TestCase): - def setUp(self): - self.contributor = User.objects.create( - email="kontributor@gov.id", password="passwordtest", name="kontributor", is_contributor=True - ) - - self.cover = SimpleUploadedFile("ExampleCover221.jpg", b"Test file") - self.content = SimpleUploadedFile("ExampleFile221.pdf", b"Test file") - - self.materi = 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=datetime.now(), - date_created=datetime.now(), - ) - - def test_like_count_return_zero_when_there_is_no_like(self): - self.assertEqual(0, self.materi.like_count) - - def test_like_count_return_right_value_when_there_is_like(self): - Like.objects.create(timestamp=datetime.now(), materi=self.materi, session_id="dummysessionid1") - self.assertEqual(1, self.materi.like_count) - - Like.objects.create(timestamp=datetime.now(), materi=self.materi, session_id="dummysessionid2") - self.assertEqual(2, self.materi.like_count) diff --git a/app/urls.py b/app/urls.py index a8c7281cbdd6821bf7eace5f939520e099f53ad5..9df11eb5fc440636480622cbb9bcf0dcd129adb3 100644 --- a/app/urls.py +++ b/app/urls.py @@ -2,10 +2,11 @@ from django.contrib.auth import views as auth_views from django.urls import path, re_path from app import views -from app.views import (DashboardKontributorView, ProfilView, +from app.views import (DashboardKontributorView, ProfilView, StatisticsView, SuksesLoginAdminView, SuksesLoginKontributorView, DownloadHistoryView, SuntingProfilView, UploadMateriHTML, UploadMateriView, UploadMateriExcelView, PostsView, - ReqMateriView, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, password_success, SubmitVisitorView) + ReqMateriView, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, password_success, + SubmitVisitorView, ReadLaterView) urlpatterns = [ @@ -41,4 +42,19 @@ urlpatterns = [ 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"), ] diff --git a/app/utils/fileManagementUtil.py b/app/utils/fileManagementUtil.py index d693b01fea0cbd035f0fa6e7ef347cdac1d0373b..a3961f9438261a374655c4f5aab31e55d0fe243a 100644 --- a/app/utils/fileManagementUtil.py +++ b/app/utils/fileManagementUtil.py @@ -4,7 +4,7 @@ import PIL.Image as Image def get_random_filename(f_name): ext = f_name.split(".")[-1] name = ''.join(random.choices(string.ascii_lowercase , k=4)) - name += hashlib.md5((datetime.datetime.now().isoformat() + f_name).encode()).hexdigest() + name += hashlib.md5((datetime.datetime.now().isoformat() + f_name).encode()).hexdigest() # Sensitive name = name + "." + ext return name @@ -12,6 +12,5 @@ def get_random_filename(f_name): def remove_image_exifdata(f_path): img = Image.open(f_path) img.save(f_path) - return diff --git a/app/views.py b/app/views.py index a0062ed070c2065cc77c3e37757c749b70f470d1..cb0be086fc60e5a57b44fb13c1ad36651f930e98 100644 --- a/app/views.py +++ b/app/views.py @@ -1,6 +1,7 @@ import mimetypes import os from io import BytesIO +from register.services import MailService import django from decouple import config @@ -12,7 +13,7 @@ from django.contrib.auth.views import PasswordChangeView from django.core.exceptions import PermissionDenied, FieldError from django.core.mail import send_mail from django.core.paginator import Paginator -from django.db.models import Q, Avg +from django.db.models import Q, Avg, Count from django.http import (Http404, HttpResponse, HttpResponseRedirect, JsonResponse) from django.shortcuts import get_object_or_404, redirect @@ -31,13 +32,21 @@ from app.models import ( Materi, ReqMaterial, Rating, RatingContributor, - SubmitVisitor + SubmitVisitor, + ReadLater ) from authentication.models import User from .services import DafterKatalogService, DetailMateriService, LikeDislikeService, MateriFieldValidationHelperService, \ DownloadViewMateriHelperService, UploadMateriService, EditProfileService, RevisiMateriService, \ - DownloadHistoryService, GoogleDriveUploadService + DownloadHistoryService, GoogleDriveUploadService, ReadLaterService +MISSING_PARAMETER_MESSAGE = "Missing parameter" +UNSUPPORTED_MESSAGE = "Unsuported method" +FILE_NOT_FOUND_MESSAGE = "File tidak dapat ditemukan." +UNGGAH_HTML = "unggah.html" +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) @@ -54,13 +63,13 @@ class DaftarKatalog(TemplateView): context = self.get_context_data(**kwargs) context["kategori_list"] = Category.objects.all() - lstMateri = Materi.objects.filter(status="APPROVE").order_by("date_modified") + lst_materi = Materi.objects.filter(status="APPROVE").order_by("date_modified") url = "" - lstMateri, url = DafterKatalogService.apply_options(lstMateri, request, url) + lst_materi, url = DafterKatalogService.apply_options(lst_materi, request, url) - context["materi_list"] = lstMateri + context["materi_list"] = lst_materi paginator = Paginator(context["materi_list"], 15) page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) @@ -154,6 +163,12 @@ class DetailMateri(TemplateView): 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() + if materi_read_later is not None: + context['is_in_read_later_list'] = True + else: + context['is_in_read_later_list'] = False + context['is_authenticated'] = self.request.user.is_authenticated return context @@ -199,12 +214,12 @@ class DetailMateri(TemplateView): 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.' - send_mail( + + MailService.send( subject = 'DIGIPUS: Komentar Baru pada Materi Anda', message = email_content, from_email = getattr(settings, 'EMAIL_HOST_USER'), recipient_list = [materi_uploader.email,], - fail_silently = False, ) elif (review_text != None): review = Review.objects.create( @@ -219,10 +234,10 @@ def toggle_like(request): materi_id = request.POST.get("materi_id", None) session_id = request.POST.get("session_id", None) if materi_id is None or session_id is None: - return JsonResponse({"success": False, "msg": "Missing parameter"}) + return JsonResponse({"success": False, "msg": MISSING_PARAMETER_MESSAGE}) return JsonResponse(LikeDislikeService.apply_like_materi(materi_id, session_id)) else: - return JsonResponse({"success": False, "msg": "Unsuported method"}) + return JsonResponse({"success": False, "msg": UNSUPPORTED_MESSAGE}) def delete_comment(request, pk_materi, pk_comment): @@ -239,10 +254,10 @@ def toggle_like_comment(request): comment_id = request.POST.get("comment_id", None) session_id = request.POST.get("session_id", None) if comment_id is None or session_id is None: - return JsonResponse({"success": False, "msg": "Missing parameter", "comment_id": comment_id}) + return JsonResponse({"success": False, "msg": MISSING_PARAMETER_MESSAGE, "comment_id": comment_id}) return JsonResponse(LikeDislikeService.apply_comment_like(comment_id, session_id)) else: - return JsonResponse({"success": False, "msg": "Unsuported method", "comment_id": comment_id}) + return JsonResponse({"success": False, "msg": UNSUPPORTED_MESSAGE, "comment_id": comment_id}) def toggle_dislike_comment(request): @@ -251,10 +266,10 @@ def toggle_dislike_comment(request): comment_id = request.POST.get("comment_id", None) session_id = request.POST.get("session_id", None) if comment_id is None or session_id is None: - return JsonResponse({"success": False, "msg": "Missing parameter", "comment_id": comment_id}) + return JsonResponse({"success": False, "msg": MISSING_PARAMETER_MESSAGE, "comment_id": comment_id}) return JsonResponse(LikeDislikeService.apply_comment_dislike(comment_id, session_id)) else: - return JsonResponse({"success": False, "msg": "Unsuported method", "comment_id": comment_id}) + return JsonResponse({"success": False, "msg": UNSUPPORTED_MESSAGE, "comment_id": comment_id}) def add_rating_materi(request): @@ -298,10 +313,10 @@ def download_materi(request, pk): return DownloadViewMateriHelperService.build_materi_response(fh, file_path, materi, mimetype, request, HttpResponse(fh.read(), content_type=mimetype[0])) - except Exception as e: - raise Http404("File tidak dapat ditemukan.") + except Exception: + raise Http404(FILE_NOT_FOUND_MESSAGE) else: - raise Http404("File tidak dapat ditemukan.") + raise Http404(FILE_NOT_FOUND_MESSAGE) def view_materi(request, pk): @@ -315,10 +330,10 @@ def view_materi(request, pk): response = HttpResponse(fh.read(), content_type=mimetype[0]) DownloadViewMateriHelperService.build_view_materi_response(file_path, materi, response) return response - except Exception as e: - raise Http404("File tidak dapat ditemukan.") + except Exception: + raise Http404(FILE_NOT_FOUND_MESSAGE) else: - raise Http404("File tidak dapat ditemukan.") + raise Http404(FILE_NOT_FOUND_MESSAGE) def delete_materi(request, pk): @@ -330,8 +345,9 @@ def delete_materi(request, pk): return HttpResponseRedirect("/dashboard/") class UploadMateriView(TemplateView): - template_name = "unggah.html" + template_name = UNGGAH_HTML context = {} + redirect_path = "/unggah/" def get_context_data(self, **kwargs): context = super(UploadMateriView, self).get_context_data(**kwargs) @@ -347,10 +363,10 @@ class UploadMateriView(TemplateView): konten = form.cleaned_data["content"] yt_url_id = form.cleaned_data['yt_video_id'] if not UploadMateriService.validate_file_extension(konten, request, yt_url_id): - return HttpResponseRedirect("/unggah/") + return HttpResponseRedirect(UNGGAH_URL) UploadMateriService.upload_materi(form, materi) messages.success(request, "Materi berhasil diunggah, periksa riwayat unggah anda") - return HttpResponseRedirect("/unggah/") + return HttpResponseRedirect(UNGGAH_URL) else: context = self.get_context_data(**kwargs) context["form"] = form @@ -369,21 +385,16 @@ class UploadMateriView(TemplateView): class UploadMateriHTML(TemplateView): - template_name = "unggah.html" + template_name = UNGGAH_HTML context = {} - def get_template_names(self): - if self.request.path == "/unggah/": - template_name = "unggah.html" - return template_name - class UploadMateriExcelView(TemplateView): template_name = "unggah_excel.html" context = {} def get_template_names(self): - if self.request.path == "/unggah_excel/": + if self.request.path == UNGGAH_EXCEL_URL: template_name = "unggah_excel.html" return template_name @@ -439,7 +450,7 @@ class UploadMateriExcelView(TemplateView): if message != None: messages.error(request, message) - return HttpResponseRedirect("/unggah_excel/") + return HttpResponseRedirect(UNGGAH_EXCEL_URL) # Second pass, save data with django.db.transaction.atomic(): @@ -447,7 +458,7 @@ class UploadMateriExcelView(TemplateView): messages.success(request, "Materi berhasil diunggah") - return HttpResponseRedirect("/unggah_excel/") + return HttpResponseRedirect(UNGGAH_EXCEL_URL) @@ -529,7 +540,7 @@ class ReqMateriView(TemplateView): def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated == False: - return HttpResponseRedirect("/login/") + return HttpResponseRedirect(LOGIN_URL) return super(ReqMateriView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -544,7 +555,7 @@ class ReqMateriView(TemplateView): def post(self, request, *args, **kwargs): title = request.POST.get("title", None) if title is None: - return JsonResponse({"success": False, "msg": "Missing parameter"}) + return JsonResponse({"success": False, "msg": MISSING_PARAMETER_MESSAGE}) ReqMaterial(title=title).save() return JsonResponse({"success": True, "msg": "Permintaan materi berhasil dikirimkan"}) @@ -662,7 +673,7 @@ def pages(request): template = loader.get_template(load_template) return HttpResponse(template.render(context, request)) - except Exception as e: + except Exception: template = loader.get_template("error-404.html") return HttpResponse(template.render(context, request)) @@ -692,7 +703,7 @@ def save_to_gdrive(request, pk): if os.path.exists(file_path): GoogleDriveUploadService.upload_to_gdrive(file_path, materi.title) else: - raise Http404("File tidak dapat ditemukan.") + raise Http404(FILE_NOT_FOUND_MESSAGE) return HttpResponseRedirect(reverse('detail-materi', kwargs={'pk': pk})) @@ -761,7 +772,7 @@ class SubmitVisitorView(TemplateView): def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated == False: - return HttpResponseRedirect("/login/") + return HttpResponseRedirect(LOGIN_URL) return super(SubmitVisitorView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -777,6 +788,91 @@ class SubmitVisitorView(TemplateView): email = request.POST.get("email", None) user_id = request.POST.get("user_id", None) if title is None: - return JsonResponse({"success": False, "msg": "Missing parameter"}) + return JsonResponse({"success": False, "msg": MISSING_PARAMETER_MESSAGE}) SubmitVisitor(msg=title, user_id=user_id, email=email).save() return JsonResponse({"success": True, "msg": "Buku tamu berhasil ditambahkan"}) + +class ReadLaterView(TemplateView): + template_name = 'baca-nanti.html' + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + raise PermissionDenied(request) + return super(ReadLaterView, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(ReadLaterView, self).get_context_data(**kwargs) + return context + + 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') + 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" + + def dispatch(self, request, *args, **kwargs): + if request.user.is_authenticated == False: + return HttpResponseRedirect(LOGIN_URL) + return super(StatisticsView, self).dispatch(request, *args, **kwargs) + + def get_stat_json(self): + query = Category.objects.annotate(num=Count('materi')).order_by('-num') + + # Take maximum 10 Category + result = [] + for e in query: + if len(result) >= 6: + break + else: + 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 + }] + } + return chart_data + + def get(self, request, *args, **kwargs): + if request.GET.get('data') == 'json': + return JsonResponse(self.get_stat_json()) + + else: + context = self.get_context_data(**kwargs) + + query = Category.objects.annotate(num=Count('materi')) + context['stats'] = query + + return self.render_to_response(context) diff --git a/authentication/migrations/0009_user_is_email_verified.py b/authentication/migrations/0009_user_is_email_verified.py index 27dc440919467aced7b5aeb3ff5dfda3316860be..adf7d380f2cb8ee84b633398fae73c1bf1ba332a 100644 --- a/authentication/migrations/0009_user_is_email_verified.py +++ b/authentication/migrations/0009_user_is_email_verified.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1 on 2020-10-30 21:57 +# Generated by Django 3.1 on 2020-10-31 02:00 from django.db import migrations, models diff --git a/authentication/templates/login.html b/authentication/templates/login.html index 9fac8e20b8966e810ae5b4b87eb687e0ab88a7cc..e577a9969a799bef368c0deedc0146951c4a0bee 100644 --- a/authentication/templates/login.html +++ b/authentication/templates/login.html @@ -65,6 +65,10 @@ Belum mendaftar? klik di sini </a> <br> + <a href="/reset_password" class="txt1"> + Lupa password? klik di sini + </a> + <br> <a href="/registrasi" class="txt1"> Ingin jadi kontributor? klik di sini </a> diff --git a/digipus/__pycache__/settings.cpython-36.pyc b/digipus/__pycache__/settings.cpython-36.pyc index 1cf78ac5dc695e4dca1c09a4b10db034aec54534..43d2d1c4169e7113ebfa287197b9840fd637a652 100644 Binary files a/digipus/__pycache__/settings.cpython-36.pyc and b/digipus/__pycache__/settings.cpython-36.pyc differ diff --git a/docker-compose.yml b/docker-compose.yml index a595488cc40c670433ef120484d40fb3b8627b70..4e5ad6dd70b2ccf2e693243aca890fac65884131 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,15 +6,15 @@ services: env_file: - .env ports: - - 8000:8000 + - 8000:8000 depends_on: - db db: image: postgres:12 volumes: - - postgres_data:/var/lib/postgresql/data/ - - static_volume:/home/digipus/staticfiles - - media_volume:/home/digipus/mediafiles + - postgres_data:/var/lib/postgresql/data/ + - static_volume:/home/digipus/staticfiles + - media_volume:/home/digipus/mediafiles env_file: - .env.db diff --git a/formating.sh b/formating.sh index 573630b66894365044f164ee4d3dbcdf802480f4..73d7ed9afaf80894229017fb1c3d4c3430d093eb 100644 --- a/formating.sh +++ b/formating.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # Run autopep8 git ls-files | grep -v 'migrations' | grep -v 'settings.py' | grep -v 'manage.py' | grep -E '.py$' | xargs autopep8 --in-place --recursive # Run pylint diff --git a/register/forms.py b/register/forms.py index 1b3b5ba717ffba57f45ea4d4c1f995be00ae2651..cf038b1c2867bd1d155932d8eeccc4e70ec8c192 100644 --- a/register/forms.py +++ b/register/forms.py @@ -46,9 +46,9 @@ class UserForm(forms.ModelForm): nomor_telpon = self.cleaned_data.get("nomor_telpon") if not User.objects.filter(nomor_telpon=nomor_telpon).exists(): try: - no_telp = int(nomor_telpon) + int(nomor_telpon) return nomor_telpon - except: + except ValueError: raise forms.ValidationError("Hanya masukkan angka") raise forms.ValidationError("Nomor telepon sudah digunakan untuk mendaftar akun") diff --git a/register/migrations/0001_initial.py b/register/migrations/0001_initial.py index 6fa85b70a622da9ad8f58823313b16de69be7ec1..b0aac4fa68c6c22d7c6dca72ea91c42504361238 100644 --- a/register/migrations/0001_initial.py +++ b/register/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1 on 2020-10-30 21:57 +# Generated by Django 3.1 on 2020-10-31 02:00 from django.conf import settings from django.db import migrations, models diff --git a/register/services.py b/register/services.py index a668e0426d20c444831f43d430fffe01deddcdc2..da0f9b0255f9803a19aa4d677b44b7e7297dbc8c 100644 --- a/register/services.py +++ b/register/services.py @@ -93,9 +93,20 @@ class RegistrationService: Mohon verifikasi email Anda dengan klik pada link berikut: {url} """ - send_mail( + MailService.send( subject = 'DIGIPUS: Verifikasi Alamat Email', message = email_content, from_email = getattr(settings, 'EMAIL_HOST_USER'), - recipient_list = [user.email], - fail_silently = False) + recipient_list = [user.email]) + +class MailService: + + @staticmethod + def send(subject, message, from_email, recipient_list): + send_mail( + subject, + message, + from_email, + recipient_list, + fail_silently = False) # Sensitive + diff --git a/register/templates/index.html b/register/templates/index.html index 59038d68afae38d6f70fe4e4d419237c5fcbe6ce..10e5205420ec25774c8e6093e7ef26920c295312 100644 --- a/register/templates/index.html +++ b/register/templates/index.html @@ -1,145 +1,11 @@ -{% load static %} +{% extends 'register_base.html' %} -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Registrasi Kontributor</title> - <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/bootstrap/css/bootstrap.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}"> - <!--===============================================================================================--> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}"> - <!--===============================================================================================--> - <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 href="https://fonts.googleapis.com/css2?family=Montserrat:ital@1&display=swap" rel="stylesheet"> - <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet"> -</head> -<body style="background-color: #666666;"> - - <main> - <div class="limiter"> - <div class="container-login100"> - <div class="wrap-login100"> - <form class="login100-form validate-form" method="POST" action=""> - {% csrf_token %} - <div class="login100-form-title p-b-43"> - Registrasi Kontributor - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.name }} - <span class="focus-input100"></span> - <span class="label-input100">Nama</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Password is required"> - <!-- <input class="input100" type="text" name="pekerjaan"> --> - {{ form.instansi }} - <span class="focus-input100"></span> - <span class="label-input100">Instansi/Pekerjaan</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.nik.errors }} - {{ form.nik }} - <span class="focus-input100"></span> - <span class="label-input100">NIK</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.alamat }} - <span class="focus-input100"></span> - <span class="label-input100">Alamat</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.email.errors }} - {{ form.email }} - <span class="focus-input100"></span> - <span class="label-input100">Email</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.nomor_telpon.errors}} - {{ form.nomor_telpon }} - <span class="focus-input100"></span> - <span class="label-input100">Nomor Telepon</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.password.errors }} - {{ form.password }} - <span class="focus-input100"></span> - <span class="label-input100">Kata Sandi</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.password2 }} - <span class="focus-input100"></span> - <span class="label-input100">Ketik Ulang Kata Sandi</span> - </div> +{% block title %} Registrasi Kontributor {% endblock %} +{% block form_title %} Registrasi Kontributor {% endblock %} - - <div class="container-login100-form-btn"> - <button class="login100-form-btn"> - Daftar - </button> - </div> - - <div class="flex-sb-m w-full p-t-3 p-b-32"> - - <div> - <a href="/login/" class="txt1"> - Kembali ke halaman login - </a> - </div> - - </div> - - {% if message %} - <div class="text-success txt1" id="registrasi">{{ message }}</div> - <div class="txt1">Kembali ke <a href="/" class="txt1">halaman utama</a></div> - {% endif %} - - </form> - <div class="login100-more" style="background-image: url('../static/images/bg-03.jpg'); z-index: 0;"> - </div> - </div> - </div> - </div> - </main> - <!--===============================================================================================--> - <script src="../static/vendor/jquery/jquery-3.2.1.min.js"></script> - <!--===============================================================================================--> - <script src="../static/vendor/animsition/js/animsition.min.js"></script> - <!--===============================================================================================--> - <script src="../static/vendor/bootstrap/js/popper.js"></script> - <script - src="../static/../static/../static/../static/../static/../static/vendor/bootstrap/js/bootstrap.min.js"></script> - <!--===============================================================================================--> - <script src="../static/../static/../static/../static/../static/vendor/select2/select2.min.js"></script> - <!--===============================================================================================--> - <script src="../static/../static/../static/../static/vendor/daterangepicker/moment.min.js"></script> - <script src="../static/../static/../static/vendor/daterangepicker/daterangepicker.js"></script> - <!--===============================================================================================--> - <script src="../static/../static/vendor/countdowntime/countdowntime.js"></script> - <!--===============================================================================================--> - <script src="../static/js/login.js"></script> - <script src="../static/js/navbar.js"></script> -</body> -</html> \ No newline at end of file +{% block footer %} + {% if message %} + <div class="text-success txt1" id="registrasi">{{ message }}</div> + <div class="txt1">Kembali ke <a href="/" class="txt1">halaman utama</a></div> + {% endif %} +{% endblock %} diff --git a/register/templates/index_admin.html b/register/templates/index_admin.html index e1bfa08d23e59ead23670c8536ac12166057964d..8e666d18cfca8720121966501664fcf181b395e1 100644 --- a/register/templates/index_admin.html +++ b/register/templates/index_admin.html @@ -1,125 +1,10 @@ -{% load static %} +{% extends 'register_base.html' %} -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Registrasi Admin</title> - <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/bootstrap/css/bootstrap.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}"> - <!--===============================================================================================--> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}"> - <!--===============================================================================================--> - <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 href="https://fonts.googleapis.com/css2?family=Montserrat:ital@1&display=swap" rel="stylesheet"> - <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet"> -</head> -<body style="background-color: #666666;"> - - <main> - <div class="limiter"> - {% if message %} - <h1>{{ message }}</h1> - {% endif %} - <div class="container-login100"> - <div class="wrap-login100"> - <form class="login100-form validate-form" method="POST" action=""> - {% csrf_token %} - <div class="login100-form-title p-b-43"> - Registrasi Admin - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.name }} - <span class="focus-input100"></span> - <span class="label-input100">Nama</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Password is required"> - <!-- <input class="input100" type="text" name="pekerjaan"> --> - {{ form.instansi }} - <span class="focus-input100"></span> - <span class="label-input100">Instansi/Pekerjaan</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.nik.errors }} - {{ form.nik }} - <span class="focus-input100"></span> - <span class="label-input100">NIK</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.alamat }} - <span class="focus-input100"></span> - <span class="label-input100">Alamat</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.email.errors }} - {{ form.email }} - <span class="focus-input100"></span> - <span class="label-input100">Email</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.nomor_telpon.errors }} - {{ form.nomor_telpon }} - <span class="focus-input100"></span> - <span class="label-input100">Nomor Telepon</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.password.errors }} - {{ form.password }} - <span class="focus-input100"></span> - <span class="label-input100">Kata Sandi</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.password2 }} - <span class="focus-input100"></span> - <span class="label-input100">Ketik Ulang Kata Sandi</span> - </div> +{% block title %} Registrasi Admin {% endblock %} +{% block form_title %} Registrasi Admin {% endblock %} - - <div class="container-login100-form-btn"> - <button class="login100-form-btn"> - Daftar - </button> - </div> - - <div class="flex-sb-m w-full p-t-3 p-b-32"> - - <div> - <a href="/login/" class="txt1"> - Kembali ke halaman login - </a> - </div> - - </div> - - </form> - <div class="login100-more" style="background-image: url('../static/images/bg-03.jpg'); z-index: 0;"> - </div> - </div> - </div> - </div> - </main> -</body> -</html> \ No newline at end of file +{% block header %} + {% if message %} + <h1>{{ message }}</h1> + {% endif %} +{% endblock %} diff --git a/register/templates/index_umum.html b/register/templates/index_umum.html index 792c676faf603769a6723620270260af2b5931fa..6992a991102cb63fbca0526a770e8f6ef6f4caaf 100644 --- a/register/templates/index_umum.html +++ b/register/templates/index_umum.html @@ -1,137 +1,4 @@ -{% load static %} +{% extends 'register_base.html' %} -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Registrasi Umum</title> - <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/bootstrap/css/bootstrap.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}"> - <!--===============================================================================================--> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}"> - <!--===============================================================================================--> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}"> - <!--===============================================================================================--> - <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 href="https://fonts.googleapis.com/css2?family=Montserrat:ital@1&display=swap" rel="stylesheet"> - <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet"> -</head> -<body style="background-color: #666666;"> - - <main> - <div class="limiter"> - <div class="container-login100"> - <div class="wrap-login100"> - <form class="login100-form validate-form" method="POST" action=""> - {% csrf_token %} - <div class="login100-form-title p-b-43"> - Registrasi Umum - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.name }} - <span class="focus-input100"></span> - <span class="label-input100">Nama</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Password is required"> - <!-- <input class="input100" type="text" name="pekerjaan"> --> - {{ form.instansi }} - <span class="focus-input100"></span> - <span class="label-input100">Instansi/Pekerjaan</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.nik.errors }} - {{ form.nik }} - <span class="focus-input100"></span> - <span class="label-input100">NIK</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.alamat }} - <span class="focus-input100"></span> - <span class="label-input100">Alamat</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.email.errors }} - {{ form.email }} - <span class="focus-input100"></span> - <span class="label-input100">Email</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.nomor_telpon.errors}} - {{ form.nomor_telpon }} - <span class="focus-input100"></span> - <span class="label-input100">Nomor Telepon</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.password.errors }} - {{ form.password }} - <span class="focus-input100"></span> - <span class="label-input100">Kata Sandi</span> - </div> - <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> - <!-- <input class="input100" type="text" name="nama"> --> - {{ form.password2 }} - <span class="focus-input100"></span> - <span class="label-input100">Ketik Ulang Kata Sandi</span> - </div> - - - <div class="container-login100-form-btn"> - <button class="login100-form-btn"> - Daftar - </button> - </div> - - <div class="flex-sb-m w-full p-t-3 p-b-32"> - - <div> - <a href="/login/" class="txt1"> - Kembali ke halaman login - </a> - </div> - - </div> - - </form> - <div class="login100-more" style="background-image: url('{% static 'images/bg-03.jpg' %}'); z-index: 0;"> - </div> - </div> - </div> - </div> - </main> - <!--===============================================================================================--> - <script src="{% static 'vendor/jquery/jquery-3.2.1.min.js' %}"></script> - <!--===============================================================================================--> - <script src="{% static 'vendor/animsition/js/animsition.min.js' %}"></script> - <!--===============================================================================================--> - <script src="{% static 'vendor/bootstrap/js/popper.js' %}"></script> - <script - src="{% static 'vendor/bootstrap/js/bootstrap.min.js' %}"></script> - <!--===============================================================================================--> - <script src="{% static 'vendor/select2/select2.min.js' %}"></script> - <!--===============================================================================================--> - <script src="{% static 'vendor/daterangepicker/moment.min.js' %}"></script> - <script src="{% static 'vendor/daterangepicker/daterangepicker.js' %}"></script> - <!--===============================================================================================--> - <script src="{% static 'vendor/countdowntime/countdowntime.js' %}"></script> -</body> -</html> \ No newline at end of file +{% block title %} Registrasi Umum {% endblock %} +{% block form_title %} Registrasi Umum {% endblock %} diff --git a/register/templates/register_base.html b/register/templates/register_base.html new file mode 100644 index 0000000000000000000000000000000000000000..7befc8c4076485d785f3929f37ca764cffd90bde --- /dev/null +++ b/register/templates/register_base.html @@ -0,0 +1,133 @@ +{% load static %} + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title> + {% block title %}{% endblock %} + </title> + <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/bootstrap/css/bootstrap.min.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}"> + <!--===============================================================================================--> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}"> + <!--===============================================================================================--> + <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 href="https://fonts.googleapis.com/css2?family=Montserrat:ital@1&display=swap" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet"> +</head> +<body style="background-color: #666666;"> + + <main> + <div class="limiter"> + + {% block header %}{% endblock %} + + <div class="container-login100"> + <div class="wrap-login100"> + <form class="login100-form validate-form" method="POST" action=""> + {% csrf_token %} + <div class="login100-form-title p-b-43"> + {% block form_title %}{% endblock %} + </div> + <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> + {{ form.name }} + <span class="focus-input100"></span> + <span class="label-input100">Nama</span> + </div> + <div class="wrap-input100 validate-input" data-validate="Password is required"> + {{ form.instansi }} + <span class="focus-input100"></span> + <span class="label-input100">Instansi/Pekerjaan</span> + </div> + <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> + {{ form.nik.errors }} + {{ form.nik }} + <span class="focus-input100"></span> + <span class="label-input100">NIK</span> + </div> + <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> + {{ form.alamat }} + <span class="focus-input100"></span> + <span class="label-input100">Alamat</span> + </div> + <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> + {{ form.email.errors }} + {{ form.email }} + <span class="focus-input100"></span> + <span class="label-input100">Email</span> + </div> + <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> + {{ form.nomor_telpon.errors}} + {{ form.nomor_telpon }} + <span class="focus-input100"></span> + <span class="label-input100">Nomor Telepon</span> + </div> + <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> + {{ form.password.errors }} + {{ form.password }} + <span class="focus-input100"></span> + <span class="label-input100">Kata Sandi</span> + </div> + <div class="wrap-input100 validate-input" data-validate="Valid email is required: ex@abc.xyz"> + {{ form.password2 }} + <span class="focus-input100"></span> + <span class="label-input100">Ketik Ulang Kata Sandi</span> + </div> + + + <div class="container-login100-form-btn"> + <button class="login100-form-btn"> + Daftar + </button> + </div> + + <div class="flex-sb-m w-full p-t-3 p-b-32"> + <div> + <a href="/login/" class="txt1"> + Kembali ke halaman login + </a> + </div> + </div> + + {% block footer %}{% endblock %} + + </form> + <div class="login100-more" style="background-image: url('{% static 'images/bg-03.jpg' %}'); z-index: 0;"> + </div> + </div> + </div> + </div> + </main> + <!--===============================================================================================--> + <script src="{% static 'vendor/jquery/jquery-3.2.1.min.js' %}"></script> + <!--===============================================================================================--> + <script src="{% static 'vendor/animsition/js/animsition.min.js' %}"></script> + <!--===============================================================================================--> + <script src="{% static 'vendor/bootstrap/js/popper.js' %}"></script> + <script src="{% static 'vendor/bootstrap/js/bootstrap.min.js' %}"></script> + <!--===============================================================================================--> + <script src="{% static 'vendor/select2/select2.min.js' %}"></script> + <!--===============================================================================================--> + <script src="{% static 'vendor/daterangepicker/moment.min.js' %}"></script> + <script src="{% static 'vendor/daterangepicker/daterangepicker.js' %}"></script> + <!--===============================================================================================--> + <script src="{% static 'vendor/countdowntime/countdowntime.js' %}"></script> +</body> +</html> \ No newline at end of file diff --git a/register/tests.py b/register/tests.py index 799c0d2ea8e8610447e7397ca152b6a7f64e0b07..8aec57933bf62f767fe961bc83dab1c980912225 100644 --- a/register/tests.py +++ b/register/tests.py @@ -29,11 +29,11 @@ class RegisterPageTest(TestCase): def test_status_using_index_func(self): # Positive tests found = resolve("/registrasi/") - self.assertEqual(found.func.__name__, views.index.__name__) + self.assertEqual(found.func.__name__, views.Index.__name__) # Negative tests found = resolve("/admin/") - self.assertNotEqual(found.func, views.index) + self.assertNotEqual(found.func, views.Index) def test_register_title(self): response = Client().get("/registrasi/") @@ -628,7 +628,7 @@ class RegisterPublicTest(TestCase): # Negative tests found = resolve("/admin/") - self.assertNotEqual(found.func, views.index) + self.assertNotEqual(found.func, views.Index) def test_register_title(self): response = Client().get(self.reg_path) diff --git a/register/urls.py b/register/urls.py index 53d6a6ed6a4629ab084d3bac362718086b6126f6..d3d7b0a6a1e75151b4f7e962289d4d1a22747188 100644 --- a/register/urls.py +++ b/register/urls.py @@ -5,7 +5,7 @@ from . import views app_name = "register" urlpatterns = [ - path("", views.index.as_view()), + path("", views.Index.as_view()), path("umum/", views.RegistrasiUmum.as_view()), path("admin/", views.RegistrasiAdmin.as_view()), path("verify-email/<str:token>", views.verify_email, name='verify-email'), diff --git a/register/views.py b/register/views.py index 91952d6907cb91b74dfd2a6e5e451f0c0a0f26c4..c409342524aea3ddec0ffde708083df02e41bd3f 100644 --- a/register/views.py +++ b/register/views.py @@ -9,12 +9,14 @@ from django.db import models from django.http import HttpResponseRedirect from django.views.generic import TemplateView from django.core.exceptions import PermissionDenied +from django.core.exceptions import ValidationError from register.forms import UserForm from register.services import RegistrationService -class index(TemplateView): + +class Index(TemplateView): template_name = "index.html" def get_context_data(self, **kwargs): @@ -134,7 +136,7 @@ def verify_email(request, token): return render(request, template_name, { 'message': f'Link verifikasi telah expire' }) - except: + except (User.DoesNotExist, ValidationError): return render(request, template_name, { 'message': f'Email gagal diverifikasi' })