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/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..25849b3e3a73b782054b3ea0157aa1f6ba389089 100644 --- a/administration/templates/laporan_materi.html +++ b/administration/templates/laporan_materi.html @@ -21,7 +21,7 @@ </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"> <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..300dbf45b0873615d1d1586a7dd788038eafc4d6 100644 --- a/app/services.py +++ b/app/services.py @@ -15,7 +15,7 @@ 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 + ViewStatistics, ReadLater from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata from digipus import settings import requests @@ -343,7 +343,6 @@ class UploadMateriService: @staticmethod def validate_yt_video_url(value): - is_yt_id_valid = True r = requests.get('http://www.youtube.com/watch?v='+value) if "\"playabilityStatus\":{\"status\":\"ERROR\"" in r.text: raise ValidationError("Invalid Youtube video ID") @@ -470,3 +469,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 \ No newline at end of file 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/detail_materi.html b/app/templates/app/detail_materi.html index 72d758c9e304adc8fbdae477fbbc9e3709fcd450..c083179bf1378998081c5f819bd78cf70b8c4816 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> @@ -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 224232c1a609e82a1004dd0183bd6ac83059cd48..4a06d4c5a4233e6766f70a3617cf61c9755260e2 100644 --- a/app/templates/app/katalog_materi.html +++ b/app/templates/app/katalog_materi.html @@ -160,7 +160,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/statistik.html b/app/templates/statistik.html new file mode 100644 index 0000000000000000000000000000000000000000..839895c7ddb791c8f492088381767aa6560d70de --- /dev/null +++ b/app/templates/statistik.html @@ -0,0 +1,72 @@ +{% extends 'app/base_dashboard.html' %} +{% load static %} + +{% block title %} +<title>Unggah Materi dari Excel | 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"> + <thead> + <th>Kategori</th> + <th>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..5b83a94176217c709516472a1584b0b800e4c7b6 100644 --- a/app/tests.py +++ b/app/tests.py @@ -3,6 +3,7 @@ import pandas as pd from io import StringIO import re import time +import random from django.test import override_settings from bs4 import BeautifulSoup @@ -41,6 +42,7 @@ from .models import ( ReqMaterial, RatingContributor, ViewStatistics, + ReadLater ) from .services import ( @@ -3443,3 +3445,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/urls.py b/app/urls.py index cb90cebb28e11f2fa069f0da848db7a581bc9d64..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,6 +42,9 @@ 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"), @@ -53,5 +57,4 @@ urlpatterns = [ 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..a31e0aaad64e0098b073338bc4bb6a67d1de504e 100644 --- a/app/utils/fileManagementUtil.py +++ b/app/utils/fileManagementUtil.py @@ -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..233bd53d4547d2f212c8db0629c8d3c85ff3e10c 100644 --- a/app/views.py +++ b/app/views.py @@ -12,7 +12,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 +31,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 +62,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 +162,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 @@ -219,10 +233,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 +253,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 +265,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 +312,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 +329,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,7 +344,7 @@ def delete_materi(request, pk): return HttpResponseRedirect("/dashboard/") class UploadMateriView(TemplateView): - template_name = "unggah.html" + template_name = UNGGAH_HTML context = {} def get_context_data(self, **kwargs): @@ -347,10 +361,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,12 +383,12 @@ 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" + if self.request.path == UNGGAH_URL: + template_name = UNGGAH_HTML return template_name @@ -383,7 +397,7 @@ class UploadMateriExcelView(TemplateView): 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 +453,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 +461,7 @@ class UploadMateriExcelView(TemplateView): messages.success(request, "Materi berhasil diunggah") - return HttpResponseRedirect("/unggah_excel/") + return HttpResponseRedirect(UNGGAH_EXCEL_URL) @@ -529,7 +543,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 +558,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 +676,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 +706,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 +775,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 +791,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 new file mode 100644 index 0000000000000000000000000000000000000000..adf7d380f2cb8ee84b633398fae73c1bf1ba332a --- /dev/null +++ b/authentication/migrations/0009_user_is_email_verified.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2020-10-31 02:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0008_user_is_subscribing_to_material_comments'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='is_email_verified', + field=models.BooleanField(default=False), + ), + ] diff --git a/digipus/__pycache__/settings.cpython-36.pyc b/digipus/__pycache__/settings.cpython-36.pyc index f90737f5493bb81ce1a0ac50e7dda3b442006d25..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 1e4289a53802e6f31bc9a08e28ed9bccc4374dc6..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 16:27 +# 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/templates/index.html b/register/templates/index.html index 59038d68afae38d6f70fe4e4d419237c5fcbe6ce..3870a89255f5a95712e4c74ab58f64d7a119a7e6 100644 --- a/register/templates/index.html +++ b/register/templates/index.html @@ -42,53 +42,45 @@ 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> diff --git a/register/templates/index_admin.html b/register/templates/index_admin.html index e1bfa08d23e59ead23670c8536ac12166057964d..aaf9eb6e344b81492f8af6b0fdda4da1c5a1053c 100644 --- a/register/templates/index_admin.html +++ b/register/templates/index_admin.html @@ -45,53 +45,45 @@ 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> diff --git a/register/templates/index_umum.html b/register/templates/index_umum.html index 792c676faf603769a6723620270260af2b5931fa..7aa60dcb4c5066ab2b54dfd42b7b6cb65bc56960 100644 --- a/register/templates/index_umum.html +++ b/register/templates/index_umum.html @@ -42,53 +42,45 @@ 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> 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' })