diff --git a/.gitignore b/.gitignore index c0739ce4ef0b8293ec8d34f428377bf9fcf5af8d..7e6811adf6066c047eb2fca79f1f49238d300bc2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ media /static/ media/ .coverage +virtualenv # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ # in your Git repository. Update and uncomment the following line accordingly. diff --git a/administration/templates/administration/base_administrasi.html b/administration/templates/administration/base_administrasi.html index 4e8acf203c9f51e8a259f7a65a475bc57450ab56..e92e0277c2ab9096266b43ea3670c0fb41e39ec0 100644 --- a/administration/templates/administration/base_administrasi.html +++ b/administration/templates/administration/base_administrasi.html @@ -68,7 +68,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> diff --git a/administration/templates/administration/base_administrasi2.html b/administration/templates/administration/base_administrasi2.html index cf48a013a723adf31ceaea1039ed8c1e76aae3da..a731025ecefb52cbc43d5499d45074907923029c 100644 --- a/administration/templates/administration/base_administrasi2.html +++ b/administration/templates/administration/base_administrasi2.html @@ -4,6 +4,7 @@ <html lang="en"> <!-- Static assets --> <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"> @@ -67,7 +68,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> diff --git a/administration/templates/administration/data_statistik.html b/administration/templates/administration/data_statistik.html index 2aad8e549f34095ff42972a7c2c5ad91691fa478..17df72575d35c2a457fe0ae1500c4af991e0abdd 100644 --- a/administration/templates/administration/data_statistik.html +++ b/administration/templates/administration/data_statistik.html @@ -28,7 +28,7 @@ <div class="h5 mb-0 font-weight-bold text-gray-800">{{chart_data.total.0}}</div> </div> <div class="col-auto"> - <i class="fas fa-eye" aria-hidden="true"></i> + <em class="fas fa-eye" aria-hidden="true"></em> </div> </div> </div> @@ -45,7 +45,7 @@ <div class="h5 mb-0 font-weight-bold text-gray-800">{{chart_data.total.1}}</div> </div> <div class="col-auto"> - <i class="fas fa-download" aria-hidden="true"></i> + <em class="fas fa-download" aria-hidden="true"></em> </div> </div> </div> @@ -62,7 +62,7 @@ <div class="h5 mb-0 font-weight-bold text-gray-800">{{chart_data.total.2}}</div> </div> <div class="col-auto"> - <i class="fas fa-thumbs-up" aria-hidden="true"></i> + <em class="fas fa-thumbs-up" aria-hidden="true"></em> </div> </div> </div> @@ -79,7 +79,7 @@ <div class="h5 mb-0 font-weight-bold text-gray-800">{{chart_data.total.3}}</div> </div> <div class="col-auto"> - <i class="fas fa-comments fa-2x text-gray-300"></i> + <em class="fas fa-comments fa-2x text-gray-300"></em> </div> </div> </div> @@ -100,7 +100,7 @@ <!-- <div class="dropdown no-arrow"> <a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i> + <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"> diff --git a/administration/templates/detail_admin.html b/administration/templates/detail_admin.html index 2f86f7006c304af25c06382f4433ed408ffabf14..5105c7706cce8755a67c0c411a24579ef161d9db 100644 --- a/administration/templates/detail_admin.html +++ b/administration/templates/detail_admin.html @@ -19,31 +19,46 @@ <h4>{{ user.email }}</h4> <h4>{{ user.biography }}</h4> <div class="profile-margin"></div> - <table> - <tr> - <td class="profile-data">Instansi</td> - <td>{{ user.instansi }}</td> - </tr> - - <td class="profile-data">LinkedIn</td> - <td><a href="https://linkedin.com">{{ user.linkedin }}</a></td> - </tr> - - <tr> - <td class="profile-data">Facebook</td> - <td><a href="https://facebook.com">{{ user.facebook }}</a></td> - </tr> - - <tr> - <td class="profile-data">Twitter</td> - <td><a href="https://twitter.com">{{ user.twitter }}</a></td> - </tr> - - <tr> - <td class="profile-data">Instagram</td> - <td><a href="https://instagram.com">{{ user.instagram }}</a></td> - </tr> - </table> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + instansi + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + {{user.instansi}} + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + LinkedIn + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://linkedin.com">{{ user.linkedin }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Facebook + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://facebook.com">{{ user.facebook }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Twitter + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://twitter.com">{{ user.twitter }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Instagram + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://instagram.com">{{ user.instagram }}</a> + </div> + </div> <a class="btn btn-primary btn-admin" href="/administration/kelola-admin/">Kembali ke Kelola Admin</a> </div> </div> diff --git a/administration/templates/detail_kontri_admin.html b/administration/templates/detail_kontri_admin.html index fb425130b9478304df78d175dcc1dccec0946072..ef4ff27d8bb210b497fc24fa18160e3ae3273d81 100644 --- a/administration/templates/detail_kontri_admin.html +++ b/administration/templates/detail_kontri_admin.html @@ -18,31 +18,46 @@ <h4>{{ user.email }}</h4> <h4>{{ user.biography }}</h4> <div class="profile-margin"></div> - <table> - <tr> - <td class="profile-data">Instansi</td> - <td>{{ user.instansi }}</td> - </tr> - - <td class="profile-data">LinkedIn</td> - <td><a href="https://linkedin.com">{{ user.linkedin }}</a></td> - </tr> - - <tr> - <td class="profile-data">Facebook</td> - <td><a href="https://facebook.com">{{ user.facebook }}</a></td> - </tr> - - <tr> - <td class="profile-data">Twitter</td> - <td><a href="https://twitter.com">{{ user.twitter }}</a></td> - </tr> - - <tr> - <td class="profile-data">Instagram</td> - <td><a href="https://instagram.com">{{ user.instagram }}</a></td> - </tr> - </table> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + instansi + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + {{user.instansi}} + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + LinkedIn + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://linkedin.com">{{ user.linkedin }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Facebook + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://facebook.com">{{ user.facebook }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Twitter + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://twitter.com">{{ user.twitter }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Instagram + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://instagram.com">{{ user.instagram }}</a> + </div> + </div> <a class="btn btn-primary btn-admin" href="/administration/kelola-kontributor/">Kembali ke Kelola Kontributor</a> </div> </div> diff --git a/administration/templates/detail_verif.html b/administration/templates/detail_verif.html index 18b1751c9bec8649249ece282e2e92f14117303d..a5755d3980c9120fdf5c6eec648162d29205b6df 100644 --- a/administration/templates/detail_verif.html +++ b/administration/templates/detail_verif.html @@ -8,7 +8,7 @@ Pratinjau Materi <!-- Sidebar Toggle (Topbar) --> <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> - <i class="fa fa-bars"></i> + <em class="fa fa-bars"></em> </button> <div class="sidebar-brand-text mx-3">Pratinjau Materi</div> @@ -53,7 +53,7 @@ Pratinjau Materi <div class="info-wrapper"> <div class="info" id="1"> <dt class="col col-4"> - <p class="info-name">Verifikatur</p> + <p class="info-name"><strong>Verifikatur</strong></p> </dt> <dd> <p class="info-content">{{verification_report.user.name}}</p> @@ -61,7 +61,7 @@ Pratinjau Materi </div> <div class="info" id="1"> <dt class="col col-4"> - <p class="info-name">Waktu Verifikasi</p> + <p class="info-name"><strong>Waktu Verifikasi</strong></p> </dt> <dd> <p class="info-content">{{verification_report.timestamp}}</p> @@ -69,7 +69,7 @@ Pratinjau Materi </div> <div class="info" id="1"> <dt class="col col-4"> - <p class="info-name">Status Materi</p> + <p class="info-name"><strong>Status Materi</strong></p> </dt> <dd> <p class="info-content">{{verification_report.status}}</p> diff --git a/administration/templates/kelola_admin.html b/administration/templates/kelola_admin.html index 1af6884bf2e244ff5cc323b2f94dfce561f236b9..dd020c9771ca6b73161182317499809216bf6e16 100644 --- a/administration/templates/kelola_admin.html +++ b/administration/templates/kelola_admin.html @@ -15,7 +15,7 @@ <div class="card-header py-3"> <div class="d-flex"> <div class="mr-auto p-2"> - <h6 class="m-0 font-weight-bold text-primary">Tabel Daftar Admin</h6> + <h6 id="table-description" class="m-0 font-weight-bold text-primary">Tabel Daftar Admin</h6> </div> <div class="p-2"> <a href="/administration/kelola-admin/tambah/" class="accept-button button-decoration button-header">Buat Akun Baru</a> @@ -24,7 +24,7 @@ </div> <div class="card-body"> <div class="table-responsive"> - <table 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/kelola_kontributor.html b/administration/templates/kelola_kontributor.html index 0fdfe526aeb6b32b9e3c57643beb2472a965a75f..21b94fc5bc769c604ed0e78dddcc854b05f8dc8b 100644 --- a/administration/templates/kelola_kontributor.html +++ b/administration/templates/kelola_kontributor.html @@ -13,25 +13,25 @@ <!-- DataTales Example --> <div class="card shadow mb-4"> <div class="card-header py-3"> - <h6 class="m-0 font-weight-bold text-primary">Tabel Daftar Kontributor</h6> + <h6 id="table-description" class="m-0 font-weight-bold text-primary">Tabel Daftar Kontributor</h6> </div> <div class="card-body"> <div class="table-responsive"> - <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> + <table aria-describedby="table-description" class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> <thead> <tr> - <th>Nama</th> - <th>NIK</th> - <th>Instansi</th> - <th>Detail</th> + <th scope="col">Nama</th> + <th scope="col">NIK</th> + <th scope="col">Instansi</th> + <th scope="col">Detail</th> </tr> </thead> <tfoot> <tr> - <th>Nama</th> - <th>NIK</th> - <th>Instansi</th> - <th>Detail</th> + <th scope="col">Nama</th> + <th scope="col">NIK</th> + <th scope="col">Instansi</th> + <th scope="col">Detail</th> </tr> </tr> </tfoot> diff --git a/administration/templates/laporan_materi.html b/administration/templates/laporan_materi.html index 5b92f17dce51124d0ff32ba034ac282131d2954e..33e489890c0eaa9979f3fd06567f559e8df7ebdb 100644 --- a/administration/templates/laporan_materi.html +++ b/administration/templates/laporan_materi.html @@ -76,11 +76,11 @@ </div> <div class="card shadow mb-4"> <div class="card-header py-3"> - <h6 class="m-0 font-weight-bold text-primary" id="titleTabelPending">Materi yang Diblokir</h6> + <h6 id="table-description" class="m-0 font-weight-bold text-primary" id="titleTabelPending">Materi yang Diblokir</h6> </div> <div class="card-body"> <div class="table-responsive"> - <table class="table table-bordered" id="dataTablePending" aria-describedby="titleTabelPending"> + <table aria-describedby="table-description" class="table table-bordered" id="dataTablePending" aria-describedby="titleTabelPending"> {% if not materi_diblokir %} Tidak ada materi yang diblokir {% else %} diff --git a/administration/templates/registrasi_admin.html b/administration/templates/registrasi_admin.html index 7d3a0151bb82231d953cbcbc96034ec44a3a8710..8da8fc6f3271dd7dd02a0e2374c1cfb3b4d50499 100644 --- a/administration/templates/registrasi_admin.html +++ b/administration/templates/registrasi_admin.html @@ -103,7 +103,7 @@ <!-- Sidebar Toggle (Topbar) --> <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> - <i class="fa fa-bars"></i> + <em class="fa fa-bars"></em> </button> <div class="sidebar-brand-text mx-3">Diskominfo Kota Depok</div> @@ -257,7 +257,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> <!-- Bootstrap core JavaScript--> diff --git a/administration/templates/setting_verifikasi.html b/administration/templates/setting_verifikasi.html index f21254ef032ac3fdae82641d1827a99e6c5403fd..aa5fbdf0f5462035f49f610aca5144a48c29d88c 100644 --- a/administration/templates/setting_verifikasi.html +++ b/administration/templates/setting_verifikasi.html @@ -112,7 +112,7 @@ <!-- Sidebar Toggle (Topbar) --> <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> - <i class="fa fa-bars"></i> + <em class="fa fa-bars"></em> </button> <div class="sidebar-brand-text mx-3">Diskominfo Kota Depok</div> @@ -172,7 +172,7 @@ <label>Deskripsi :</label>{{ form.description }}<br> <br> <div class="row"> - <button class="primary_btn save" style="border-radius:20px;" type="submit"><i class="far fa-save"></i> + <button class="primary_btn save" style="border-radius:20px;" type="submit"><em class="far fa-save"></em> Simpan</button> </div> </form> @@ -182,24 +182,24 @@ <!-- DataTales Example --> <div class="card shadow mb-4"> <div class="card-header py-3"> - <h6 class="m-0 font-weight-bold text-primary">Point Verifikasi Materi</h6> + <h6 id="table-description" class="m-0 font-weight-bold text-primary">Point Verifikasi Materi</h6> </div> <div class="card-body"> <div class="table-responsive"> - <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> + <table aria-describedby="table-description" class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> <thead> <tr> - <th>Judul Verifikasi</th> - <th>Deskripsi</th> - <th></th> + <th scope="col">Judul Verifikasi</th> + <th scope="col">Deskripsi</th> + <th scope="col"></th> </tr> </thead> <tbody> {% for item in verification_settings %} <tr> - <th>{{item.title}}</th> - <th>{{item.description}}</th> - <th> </th> + <th scope="col">{{item.title}}</th> + <th scope="col">{{item.description}}</th> + <th scope="col"> </th> </tr> {% endfor %} </tbody> @@ -232,7 +232,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> <!-- <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" diff --git a/administration/templates/settings.html b/administration/templates/settings.html index 6381a4e088abdaa87f41c4d531d01183de455015..3f43715589699c8aa4ee113e4989679e8f5b29cd 100644 --- a/administration/templates/settings.html +++ b/administration/templates/settings.html @@ -281,9 +281,9 @@ <tbody> {% for item in items %} <tr> - <th>{{item.title}} {{item.name}}</th> - <th>{{item.description}}</th> - <th class="verif-buttons"> + <th scope="col">{{item.title}} {{item.name}}</th> + <th scope="col">{{item.description}}</th> + <th scope="col" class="verif-buttons"> {% if item.name is None %} <a href="/administration/setting/verification/{{ item.id }}/edit" @@ -377,9 +377,9 @@ <tbody> {% for item in items_archived %} <tr> - <th>{{item.title}} {{item.name}}</th> - <th>{{item.description}}</th> - <th>{{item.archived_by.name}}</th> + <th scope="col">{{item.title}} {{item.name}}</th> + <th scope="col">{{item.description}}</th> + <th scope="col">{{item.archived_by.name}}</th> </tr> {% endfor %} </tbody> diff --git a/app/forms.py b/app/forms.py index 84facd1f2e25e18920754b7cd44a6396e7e1f4fe..54358428322ab389abb06d3b51ba3b265c38faf6 100644 --- a/app/forms.py +++ b/app/forms.py @@ -2,16 +2,20 @@ from django import forms from app.models import Materi, Category, RatingContributor from authentication.models import User +import datetime +def year_choices(): + return[(r,r) for r in range(2000, datetime.date.today().year+1)] class UploadMateriForm(forms.ModelForm): categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all(),widget=forms.CheckboxSelectMultiple(attrs={'style' : 'column-count:2'}),required=True) #categories.widget.attrs["style"] = "column-count:2" + release_year = forms.TypedChoiceField(coerce=int, choices=year_choices, initial=datetime.date.today().year) class Meta: model = Materi - fields = ["title", "author", "publisher", + fields = ["title", "author", "publisher", "release_year", "categories", "descriptions", "cover", "content"] def __init__(self, *args, **kwargs): diff --git a/app/migrations/0021_dislikecomment_likecomment.py b/app/migrations/0021_dislikecomment_likecomment.py new file mode 100644 index 0000000000000000000000000000000000000000..1ce9111268da53f8e2a88fdf877c4250fb354695 --- /dev/null +++ b/app/migrations/0021_dislikecomment_likecomment.py @@ -0,0 +1,33 @@ +# Generated by Django 3.1 on 2020-10-09 16:19 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0020_merge_20201009_2039'), + ] + + operations = [ + migrations.CreateModel( + name='LikeComment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), + ('session_id', models.CharField(max_length=32)), + ('comment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.comment')), + ], + ), + migrations.CreateModel( + name='DislikeComment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), + ('session_id', models.CharField(max_length=32)), + ('comment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.comment')), + ], + ), + ] diff --git a/app/migrations/0021_materi_release_year.py b/app/migrations/0021_materi_release_year.py new file mode 100644 index 0000000000000000000000000000000000000000..8294e44629dc026ddff721b329e610946de3a7d5 --- /dev/null +++ b/app/migrations/0021_materi_release_year.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1 on 2020-10-09 16:13 + +import app.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0020_merge_20201009_2039'), + ] + + operations = [ + migrations.AddField( + model_name='materi', + name='release_year', + field=models.IntegerField(default=app.models.current_year), + ), + ] diff --git a/app/migrations/0022_merge_20201011_1122.py b/app/migrations/0022_merge_20201011_1122.py new file mode 100644 index 0000000000000000000000000000000000000000..4b0d5ebf263822713c41cced80792494eb749a21 --- /dev/null +++ b/app/migrations/0022_merge_20201011_1122.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1 on 2020-10-11 04:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0021_dislikecomment_likecomment'), + ('app', '0021_materi_release_year'), + ] + + operations = [ + ] diff --git a/app/models.py b/app/models.py index aa54208425b22632be3f49ce1214851651e4359d..54c7f628b2c86b816f4063aaa38fe8a773588ba8 100644 --- a/app/models.py +++ b/app/models.py @@ -1,4 +1,5 @@ import random +import datetime from django.contrib.postgres import search from django.core.exceptions import ValidationError @@ -24,6 +25,8 @@ def getRandomColor(): color = "%06x" % random.randint(0, 0xFFFFFF) return color +def current_year(): + return datetime.date.today().year class Category(models.Model): name = models.CharField(max_length=20) @@ -56,6 +59,7 @@ class Materi(models.Model): author = models.CharField(max_length=30, default="Penyusun") uploader = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) publisher = models.CharField(max_length=30, default="Penerbit") + release_year = models.IntegerField(default=current_year) pages = models.IntegerField(default=0) descriptions = models.TextField(default="Deskripsi") status = models.CharField(max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0]) @@ -108,6 +112,28 @@ class Comment(models.Model): def __str__(self): return self.username + @property + def like_count(self): + count = LikeComment.objects.filter(comment=self).count() + return count + + @property + def dislike_count(self): + count = DislikeComment.objects.filter(comment=self).count() + return count + + +class LikeComment(models.Model): + comment = models.ForeignKey(Comment, models.SET_NULL, null=True) + timestamp = models.DateTimeField(default=timezone.now) + session_id = models.CharField(max_length=32, blank=False) + + +class DislikeComment(models.Model): + comment = models.ForeignKey(Comment, models.SET_NULL, null=True) + timestamp = models.DateTimeField(default=timezone.now) + session_id = models.CharField(max_length=32, blank=False) + class Like(models.Model): materi = models.ForeignKey(Materi, models.SET_NULL, null=True) diff --git a/app/templates/app/base_admin.html b/app/templates/app/base_admin.html index d8b93163621bbb7494764c66f0216ff1217ef447..73465184f28526320515eb1de9dda37b81dc887b 100644 --- a/app/templates/app/base_admin.html +++ b/app/templates/app/base_admin.html @@ -4,7 +4,7 @@ <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"> @@ -75,7 +75,7 @@ <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"> - <i class="fa fa-bars"></i> + <em class="fa fa-bars"></em> </button> <div class="sidebar-brand-text mx-3">Diskominfo Kota Depok</div> <!-- Topbar Navbar --> @@ -139,7 +139,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> <!-- Bootstrap core JavaScript--> diff --git a/app/templates/app/base_dashboard.html b/app/templates/app/base_dashboard.html index 0d5b034e451e352d040882b9d41e5dce595088c6..b8281931e7b8fae10fe76dd0477fee586206fcb2 100644 --- a/app/templates/app/base_dashboard.html +++ b/app/templates/app/base_dashboard.html @@ -4,6 +4,7 @@ <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"> @@ -64,7 +65,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> </body> diff --git a/app/templates/app/base_profile.html b/app/templates/app/base_profile.html index e21cb34dcfe7c051f68daedbb888b84c7ea22b49..6dc27646954f8d5b97e55725285ec708edfe6233 100644 --- a/app/templates/app/base_profile.html +++ b/app/templates/app/base_profile.html @@ -4,7 +4,7 @@ <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"> @@ -66,7 +66,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> </body> diff --git a/app/templates/app/detail_materi.html b/app/templates/app/detail_materi.html index e15f5f9fa4cda0a0c915adec2200c784710696fb..577e65bfb1f077a306962335a64b9202c1b90254 100644 --- a/app/templates/app/detail_materi.html +++ b/app/templates/app/detail_materi.html @@ -127,6 +127,14 @@ <p class="info-content">{{materi_data.publisher}}</p> </dd> </div> + <div class="info" id="1"> + <dl class="col col-4"> + <dt class="info-name">Tahun Terbit</dt> + </dl> + <dd> + <p class="info-content">{{materi_data.release_year}}</p> + </dd> + </div> <div class="info" id="1"> <dl class="col col-4"> <dt class="info-name">Kontributor</dt> @@ -260,6 +268,7 @@ </div> {% if materi_data.status == "APPROVE" %} <div id="komentar" class="container-fluid comments-wrapper p-0"> + {% if is_authenticated %} <div class="add-comments col col-8 bg-white shadow-sm rounded p-3 mb-3"> <form method="POST"> {% csrf_token %} @@ -272,6 +281,9 @@ </div> </form> </div> + {% else %} + <h3>Login terlebih dahulu untuk berkomentar</h3> + {% endif %} {% for comment in comment_data %} <div class="col col-8 comment shadow-sm p-3 mb-1 bg-white rounded"> <div class="d-flex bd-highlight mb-3 align-items-center user"> @@ -284,11 +296,35 @@ {% else %} <span style="background-color: #{{comment.profile}}" class="profile p-1 bd-highligh"></span> {% endif %} - <p class="p-1 bd-highligh m-0"><b>{{comment.user.name}}</b></p> - <p class="p-1 bd-highligh m-0">•</p> - <p class="timestamp p-1 bd-highligh m-0 text-muted"> - {{ comment.timestamp|naturaltime }} - </p> + <div class="d-flex flex-row justify-content-end"> + <p class="p-1 bd-highligh m-0"><strong>{{comment.user.name}}</strong></p> + <p class="p-1 bd-highligh m-0">•</p> + <p class="timestamp p-1 bd-highligh m-0 text-muted"> + {{ comment.timestamp|naturaltime }} + </p> + <div> + <button id="thumb-like-comment-{{ comment.id }}" class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded" onClick="postLikeComment({{ comment.id }})"> + <div class="d-flex flex-row"> + {% if has_liked.comment.id %} + <i id="thumb-like-comment-icon-{{ comment.id }}" aria-hidden="true" class="fas fa-thumbs-up"></i> + {% else %} + <i id="thumb-like-comment-icon-{{ comment.id }}" aria-hidden="true" class="far fa-thumbs-up"></i> + {% endif %} + <div id="like-comment-{{ comment.id }}">{{ comment.like_count }}</div> + </div + </button> + <button id="thumb-dislike-comment-{{ comment.id }}" class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded" onClick="postDislikeComment({{ comment.id }})"> + <div class="d-flex flex-row"> + {% if has_disliked.comment.id %} + <i id="thumb-dislike-comment-icon-{{ comment.id }}" aria-hidden="true" class="fas fa-thumbs-down"></i> + {% else %} + <i id="thumb-dislike-comment-icon-{{ comment.id }}" aria-hidden="true" class="far fa-thumbs-down"></i> + {% endif %} + <div id="dislike-comment-{{ comment.id }}">{{ comment.dislike_count }}</div> + </div> + </button> + </div> + </div> {% if user.is_admin %} <a class="ml-auto p-1 bd-highlight close" href="{% url 'delete-comment' materi_data.id comment.id %}"> @@ -422,6 +458,46 @@ }); }); + function postLikeComment(comment_id) { + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + $.ajax({ + type: 'POST', + url: "{% url 'comment-like-toggle' %}", + data: { + 'comment_id': comment_id, + 'session_id': "{{ session_id }}" + }, + success: likeComment, + dataType: 'html' + }); + } + + function postDislikeComment(comment_id) { + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + $.ajax({ + type: 'POST', + url: "{% url 'comment-dislike-toggle' %}", + data: { + 'comment_id': comment_id, + 'session_id': "{{ session_id }}" + }, + success: dislikeComment, + dataType: 'html' + }); + } + function postAddRating(rating_score) { $.ajaxSetup({ beforeSend: function (xhr, settings) { @@ -459,6 +535,32 @@ document.getElementById("thumb").firstChild.data = " Disukai" } } + + function likeComment(data, jqXHR) { + var data = $.parseJSON(data) + likeIcon = $('#thumb-like-comment-icon-' + data['comment_id']) + likeValue = $('#like-comment-' + data['comment_id']) + if (data['liked']) { + likeIcon.removeClass("fas fa-thumbs-up").addClass('far fa-thumbs-up') + likeValue.text(parseInt(likeValue.text())-1) + } else { + likeIcon.removeClass("far fa-thumbs-up").addClass('fas fa-thumbs-up') + likeValue.text(parseInt(likeValue.text())+1) + } + } + + function dislikeComment(data, jqXHR) { + var data = $.parseJSON(data) + dislikeIcon = $('#thumb-dislike-comment-icon-' + data['comment_id']) + dislikeValue = $('#dislike-comment-' + data['comment_id']) + if (data['disliked']) { + dislikeIcon.removeClass("fas fa-thumbs-down").addClass('far fa-thumbs-down') + dislikeValue.text(parseInt(dislikeValue.text())-1) + } else { + dislikeIcon.removeClass("far fa-thumbs-down").addClass('fas fa-thumbs-down') + dislikeValue.text(parseInt(dislikeValue.text())+1) + } + } function getCitation(text){ var $temp = $("<input>"); diff --git a/app/templates/app/includes/navbar_katalog_materi.html b/app/templates/app/includes/navbar_katalog_materi.html index 14db671ecb64fadb5319600b914023baa852bdad..c747e1cfb734c93be052b0c037b007144fc5112e 100644 --- a/app/templates/app/includes/navbar_katalog_materi.html +++ b/app/templates/app/includes/navbar_katalog_materi.html @@ -1,4 +1,48 @@ - <nav class="navbar navbar-dark static-top shadow katalog-navbar"> <!-- Sidebar Toggle (Topbar) --> - <div class="sidebar-brand-text">Digipus</div> + <nav class="navbar navbar-expand-lg navbar-light static-top shadow katalog-navbar"> <!-- Sidebar Toggle (Topbar) --> + <div class="sidebar-brand-text navbar-brand">Digipus</div> + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + + <div class="collapse navbar-collapse" id="navbarContent"> + <ul class="navbar-nav ml-auto"> + <li class="nav-item active"> + <a class="nav-link" href="/">Home<span class="sr-only">(current)</span></a> + </li> + <li class="nav-item"> + <a class="nav-link" href="/forum">Forum</a> + </li> + {% if not request.user.is_authenticated %} + <li class="nav-item"> + <a class="nav-link" href="/registrasi">Registrasi</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="/login">Login Kontributor</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="/login_admin">Login Admin</a> + </li> + {% else %} + <li class="nav-item"> + <a class="nav-link" href="/dashboard">Dasbor</a> + </li> + {% if request.user.is_admin %} + <li class="nav-item"> + <a class="nav-link" href="/administration">Administrasi</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="/profil-admin">Profil</a> + </li> + {% else %} + <li class="nav-item"> + <a class="nav-link" href="/profil">Profil</a> + </li> + {% endif %} + <li class="nav-item"> + <a class="nav-link" href="/logout">Logout</a> + </li> + {% endif %} + </ul> + </div> </nav> \ No newline at end of file diff --git a/app/templates/app/includes/navigation.html b/app/templates/app/includes/navigation.html index 73360a92eab1c0560b32aa5e508c9a10ed194575..37988b70b41417630adc6d1376da77a8ee73f9d6 100644 --- a/app/templates/app/includes/navigation.html +++ b/app/templates/app/includes/navigation.html @@ -2,7 +2,7 @@ <!-- Sidebar Toggle (Topbar) --> <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> - <i class="fa fa-bars"></i> + <em class="fa fa-bars"></em> </button> <div class="sidebar-brand-text mx-3">Diskominfo Kota Depok</div> diff --git a/app/templates/app/katalog_materi.html b/app/templates/app/katalog_materi.html index 1b11195bd2b77ee6bb9d20cd2dacdb9f63c85146..bd49b2d93fc8ba0d0416e2088640618e17fc414e 100644 --- a/app/templates/app/katalog_materi.html +++ b/app/templates/app/katalog_materi.html @@ -49,10 +49,10 @@ <body style="background-color: #f8f8f8;"> - {% include 'app/includes/navbar_katalog_materi.html' %} <!-- Page Content --> <div class="container"> + {% include 'app/includes/navbar_katalog_materi.html' %} <header class="jumbotron my-4"> <div class="container"> <div class="row header"> @@ -96,11 +96,13 @@ <div id="collapseOne" class="collapse" aria-labelledby="headingOne" data-parent="#accordionExample"> <div class="card-body"> - {% for itemKategori in kategori_list %} - <li> - <a href="?kategori={{itemKategori.pk}}">{{itemKategori.name}}</a> - </li> - {% endfor %} + <ul> + {% for itemKategori in kategori_list %} + <li> + <a href="?kategori={{itemKategori.pk}}">{{itemKategori.name}}</a> + </li> + {% endfor %} + </ul> </div> </div> </div> @@ -117,24 +119,29 @@ <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample"> <div class="card-body"> - <li> - <a href="?sort=terbaru">terbaru</a> - </li> - <li> - <a href="?sort=terlama">terlama</a> - </li> - <li> - <a href="?sort=terpopuler">terpopuler</a> - </li> - <li> - <a href="?sort=judul">judul</a> - </li> - <li> - <a href="?sort=penulis">penulis</a> - </li> - <li> - <a href="?sort=pengunggah">pengunggah</a> - </li> + <ul> + <li> + <a href="?sort=terbaru">terbaru</a> + </li> + <li> + <a href="?sort=terlama">terlama</a> + </li> + <li> + <a href="?sort=terpopuler">terpopuler</a> + </li> + <li> + <a href="?sort=judul">judul</a> + </li> + <li> + <a href="?sort=penulis">penulis</a> + </li> + <li> + <a href="?sort=pengunggah">pengunggah</a> + </li> + <li> + <a href="?sort=jumlah_unduh">jumlah unduh</a> + </li> + </ul> </div> </div> </div> diff --git a/app/templates/comments.html b/app/templates/comments.html index 96f4a9c097f101ebab3d74c877ad6d92c2566ba8..6a1180d2a5f6b61643dd13a45577ddf206b00af3 100644 --- a/app/templates/comments.html +++ b/app/templates/comments.html @@ -48,7 +48,7 @@ <span style="background-color: #{{comment.profile}}" class="profile p-1 bd-highlight"></span> {% endif %} <p class="p-1 bd-highligh m-0"> - <b>{{comment.user.name}}</b> + <strong>{{comment.user.name}}</strong> </p> <p class="p-1 bd-highligh m-0">•</p> <p class="timestamp p-1 bd-highligh m-0"> diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 95ec1ee58a534f65270862f791cd32f71eaecce2..076ad12d1cc465adb079a69e16e3e5e5feff1d11 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -3,7 +3,7 @@ {% block title %} <title>Riwayat Unggah | Digipus</title> -{% endblock %} +{% endblock %} {% block content %} <!-- Page Heading --> @@ -13,25 +13,25 @@ <!-- DataTales Example --> <div class="card shadow mb-4"> <div class="card-header py-3"> - <h6 class="m-0 font-weight-bold text-primary">Tabel Riwayat Unggah</h6> + <h6 id="table-description" class="m-0 font-weight-bold text-primary">Tabel Riwayat Unggah</h6> </div> <div class="card-body"> <div class="table-responsive"> - <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> + <table aria-describedby="table-description" class="table table-bordered" id="dataTable"> <thead> <tr> - <th>Judul Materi</th> - <th>Pembuat Materi</th> - <th>Status</th> - <th>Detail</th> + <th scope="col">Judul Materi</th> + <th scope="col">Pembuat Materi</th> + <th scope="col">Status</th> + <th scope="col">Detail</th> </tr> </thead> <tfoot> <tr> - <th>Judul Materi</th> - <th>Pembuat Materi</th> - <th>Status</th> - <th>Detail</th> + <th scope="col">Judul Materi</th> + <th scope="col">Pembuat Materi</th> + <th scope="col">Status</th> + <th scope="col">Detail</th> </tr> </tfoot> <tbody> @@ -43,10 +43,12 @@ <td class="verif-buttons"> <span> <a href="/materi/{{materi.id}}/" class="accept-button button-decoration" - style="background-color:#4e73df">Detail</a> + style="background-color:#4e73df">Detail</a> {% if materi.get_status_display == "Ditolak" or materi.get_status_display == "Perbaikan" %} <a href="/revisi/materi/{{materi.id}}/" class="reject-button button-decoration">Revisi</a> {% endif %} + <a type="button" href="/materi/{{materi.id}}/delete" class="reject-button button-decoration" + style="background-color:red">Hapus</a> </span> </td> </tr> diff --git a/app/templates/profil-admin.html b/app/templates/profil-admin.html index 58ad43884d115f874cf1abba499d67572341b5fa..a2a566f12071edfd2907fe1ff2b3c5380f051ef6 100644 --- a/app/templates/profil-admin.html +++ b/app/templates/profil-admin.html @@ -18,31 +18,46 @@ <h4>{{ user.email }}</h4> <h4>{{ user.biography }}</h4> <div class="profile-margin"></div> - <table> - <tr> - <td class="profile-data">Instansi</td> - <td>{{ user.instansi }}</td> - </tr> - - <td class="profile-data">LinkedIn</td> - <td><a href="https://linkedin.com">{{ user.linkedin }}</a></td> - </tr> - - <tr> - <td class="profile-data">Facebook</td> - <td><a href="https://facebook.com">{{ user.facebook }}</a></td> - </tr> - - <tr> - <td class="profile-data">Twitter</td> - <td><a href="https://twitter.com">{{ user.twitter }}</a></td> - </tr> - - <tr> - <td class="profile-data">Instagram</td> - <td><a href="https://instagram.com">{{ user.instagram }}</a></td> - </tr> - </table> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + instansi + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + {{user.instansi}} + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + LinkedIn + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://linkedin.com">{{ user.linkedin }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Facebook + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://facebook.com">{{ user.facebook }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Twitter + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://twitter.com">{{ user.twitter }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Instagram + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://instagram.com">{{ user.instagram }}</a> + </div> + </div> </div> </div> {% endblock %} \ No newline at end of file diff --git a/app/templates/profil.html b/app/templates/profil.html index 13ad5403e9873c1a8efaf626397e322c35bdea06..3145ab41746a6ff532e8fe8ef2fc7dcee333370d 100644 --- a/app/templates/profil.html +++ b/app/templates/profil.html @@ -22,31 +22,46 @@ <h4>{{ user.email }}</h4> <h4>{{ user.biography }}</h4> <div class="profile-margin"></div> - <table> - <tr> - <td class="profile-data">Instansi</td> - <td>{{ user.instansi }}</td> - </tr> - - <td class="profile-data">LinkedIn</td> - <td><a href="https://linkedin.com">{{ user.linkedin }}</a></td> - </tr> - - <tr> - <td class="profile-data">Facebook</td> - <td><a href="https://facebook.com">{{ user.facebook }}</a></td> - </tr> - - <tr> - <td class="profile-data">Twitter</td> - <td><a href="https://twitter.com">{{ user.twitter }}</a></td> - </tr> - - <tr> - <td class="profile-data">Instagram</td> - <td><a href="https://instagram.com">{{ user.instagram }}</a></td> - </tr> - </table> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + instansi + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + {{user.instansi}} + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + LinkedIn + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://linkedin.com">{{ user.linkedin }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Facebook + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://facebook.com">{{ user.facebook }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Twitter + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://twitter.com">{{ user.twitter }}</a> + </div> + </div> + <div class="row"> + <div class="col-md-6 my-auto" style="font-size: 2rem;"> + Instagram + </div> + <div class="col-md-6 my-auto" style="font-size: 1.2rem;"> + <a href="https://instagram.com">{{ user.instagram }}</a> + </div> + </div> </div> </div> {% endblock %} \ No newline at end of file diff --git a/app/templates/sukses_admin.html b/app/templates/sukses_admin.html index 313d93ce805937c12d3728278727de36237fe194..40692fdb1abd598551b99f2a644c22352e9f63d1 100644 --- a/app/templates/sukses_admin.html +++ b/app/templates/sukses_admin.html @@ -73,7 +73,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> <!-- Bootstrap core JavaScript--> diff --git a/app/templates/sukses_kontri.html b/app/templates/sukses_kontri.html index af4ee3bf90ee2b19102a8717da5b519c5bcab9ba..0aaf4a33bbe8bbe7cb3560ae4ed7ffca5a7178fe 100644 --- a/app/templates/sukses_kontri.html +++ b/app/templates/sukses_kontri.html @@ -79,7 +79,7 @@ <!-- Scroll to Top Button--> <a class="scroll-to-top rounded" href="#page-top"> - <i class="fas fa-angle-up"></i> + <em class="fas fa-angle-up"></em> </a> <!-- Bootstrap core JavaScript--> diff --git a/app/tests.py b/app/tests.py index 717f6315315d66f30767c932a274be1fe1d04c39..303ed2861d9c2761193e344b0a297854bdb9d2f2 100644 --- a/app/tests.py +++ b/app/tests.py @@ -1,6 +1,7 @@ import json, tempfile, os, mock import pandas as pd from io import StringIO +import time from bs4 import BeautifulSoup from datetime import datetime @@ -12,11 +13,13 @@ 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.test import Client, TestCase, TransactionTestCase +from django.urls import resolve 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 administration.models import VerificationSetting, VerificationReport from administration.utils import id_generator @@ -26,9 +29,11 @@ from digipus.settings import TIME_ZONE from .models import ( Category, Comment, + DislikeComment, DownloadStatistics, Materi, Like, + LikeComment, Rating, ReqMaterial, RatingContributor, @@ -52,7 +57,7 @@ from .views import ( UploadMateriView, UploadMateriExcelView, ) -from app.forms import SuntingProfilForm +from app.forms import SuntingProfilForm, year_choices from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata ERROR_403_MESSAGE = "Kamu harus login untuk mengakses halaman ini" @@ -136,6 +141,45 @@ class DaftarKatalogTest(TestCase): self.assertSequenceEqual(search_result, expected_search_result) +class DaftarKatalogSortingByJumlahUnduhTest(TestCase): + def setUp(self): + self.client = Client() + + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": "passwordtest" + } + + 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() + + material_unduh_url = f"/materi/{self.last_uploaded_material.id}/unduh" + self.client.get(material_unduh_url) + + def test_sorting_by_jumlah_unduh(self): + response = self.client.get("/?sort=jumlah_unduh") + self.assertRegex(str(response.content), rf'.*Materi 2.*Materi 1.*') class DaftarKatalogPerKontributorTest(TestCase): def setUp(self): @@ -322,11 +366,108 @@ class DetailMateriTest(TestCase): response = self.client.get(url) self.assertContains(response, "Anonymous") + def test_comment_disliked_by_anonymous(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get(comment="This is new comment by Anonymous").id + response = self.client.get(url_materi) + session_id = response.context["session_id"] + + payload = {"comment": comment, "session_id": session_id} + ajax_response = self.client.post("/comment/dislike/", payload) + num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment).count() + self.assertEqual(num_of_comment_dislikes, 0) + + def test_comment_liked_by_anonymous(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get(comment="This is new comment by Anonymous").id + response = self.client.get(url_materi) + session_id = response.context["session_id"] + + payload = {"comment": comment, "session_id": session_id} + ajax_response = self.client.post("/comment/like/", payload) + num_of_comment_likes = LikeComment.objects.filter(comment=comment).count() + self.assertEqual(num_of_comment_likes, 0) + + def test_comment_undisliked_by_anonymous(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get(comment="This is new comment by Anonymous") + response = self.client.get(url_materi) + session_id = response.context["session_id"] + + payload = {"comment": comment, "session_id": session_id} + ajax_response = self.client.post("/comment/dislike/", payload) + + ajax_response = self.client.post("/comment/dislike/", payload) + num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment, session_id=session_id).count() + self.assertEqual(num_of_comment_dislikes, 0) + + def test_comment_unliked_by_anonymous(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + comment = Comment.objects.get(comment="This is new comment by Anonymous") + response = self.client.get(url_materi) + session_id = response.context["session_id"] + + payload = {"comment": comment, "session_id": session_id} + ajax_response = self.client.post("/comment/like/", payload) + + ajax_response = self.client.post("/comment/like/", payload) + num_of_comment_likes = LikeComment.objects.filter(comment=comment, session_id=session_id).count() + self.assertEqual(num_of_comment_likes, 0) + + def test_comment_new_does_not_have_dislike(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + response = self.client.get(url_materi) + session_id = response.context["session_id"] + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + + comment = Comment.objects.get(comment="This is new comment by Anonymous") + comment_dislike_counter = DislikeComment.objects.filter(comment=comment, session_id=session_id).count() + self.assertEqual(comment_dislike_counter, 0) + + def test_comment_new_does_not_have_like(self): + url_materi = self.url + self.client.get("/logout/") + self.client.login(**self.anonymous_credential) + + response = self.client.get(url_materi) + session_id = response.context["session_id"] + self.client.post(url_materi, {"comment": "This is new comment by Anonymous"}) + + comment = Comment.objects.get(comment="This is new comment by Anonymous") + comment_like_counter = LikeComment.objects.filter(comment=comment, session_id=session_id).count() + self.assertEqual(comment_like_counter, 0) + def test_detail_materi_contains_form_comment(self): + self.client.login(**self.contributor_credential) response = self.client.get(self.url) self.assertContains(response, "Beri komentar...") + def test_detail_materi_not_contains_form_comment(self): + response = self.client.get(self.url) + self.assertNotContains(response, "Beri komentar...") + self.assertContains(response, "Login terlebih dahulu untuk berkomentar") + def test_delete_comments_by_admin(self): + self.client.login(**self.contributor_credential) url = self.url self.client.post(url, {"comment": "This is new comment by Anonymous"}) deleteURL = ( @@ -432,7 +573,6 @@ class DetailMateriTest(TestCase): self.assertEqual(last_url, "/materi/%d/" % self.materi1.id) self.assertEqual(status_code, 302) - class PostsViewTest(TestCase): @classmethod def generate_posts_data(cls, user): @@ -877,6 +1017,25 @@ class DashboardKontributorViewTest(TestCase): self.assertIn(ERROR_403_MESSAGE, html) +class DeleteMateriTest(TestCase): + def setUp(self): + self.client = Client() + self.content = SimpleUploadedFile( + "content.txt", b"Test") + self.cover = SimpleUploadedFile( + "flower.jpg", b"Test file") + self.contributor = User.objects.create_contributor(email="kontributor@gov.id", + password="kontributor") + 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) + "/delete" + + def test_url_delete_materi_is_success(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 302) + class ProfilAdminTest(TestCase): def setUp(self): self.client = Client() @@ -2158,3 +2317,16 @@ class RandomizedMateriTest(TestCase): response = Client().get("/?random=1") self.assertIn("Materi 1", response.content.decode()) self.assertIn("Materi 2", response.content.decode()) + +class YearChoicesTest(TestCase): + def test_release_year_contains_the_right_current_year(self): + now = dt.date.today().year + + choices = year_choices() + + self.assertEqual((now, now), choices[-1]) + + def test_min_release_year_is_2000(self): + choices = year_choices() + + self.assertEqual((2000, 2000), choices[0]) \ No newline at end of file diff --git a/app/urls.py b/app/urls.py index 7ccc70c3a5dc1c82dbcb59772f950b9a04e9c2da..baa6bccb6630849347f02cd47363536d7cc68d83 100644 --- a/app/urls.py +++ b/app/urls.py @@ -13,6 +13,9 @@ urlpatterns = [ path("materi/like/", views.toggle_like, name="PostLikeToggle"), path("delete/<int:pk_materi>/<int:pk_comment>", views.delete_comment, name="delete-comment"), + path("comment/like/", views.toggle_like_comment, name="comment-like-toggle"), + path("comment/dislike/", views.toggle_dislike_comment, name="comment-dislike-toggle"), + path("materi/<int:pk>/delete", views.delete_materi, name="detele-materi"), path("materi/<int:pk>/unduh", views.download_materi, name="download-materi"), path("materi/<int:pk>/view", views.view_materi, name="view-materi"), path("dashboard/", DashboardKontributorView.as_view(), name="dashboard"), diff --git a/app/views.py b/app/views.py index deeb1b9acba6eb5348b11b1b4221bfe0fbdff6d3..66b96f602abe4c0033ad5efdb02ec8cd51f90950 100644 --- a/app/views.py +++ b/app/views.py @@ -7,7 +7,8 @@ from django.contrib import messages from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied, ValidationError from django.db.models import Q, Count -from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse +from django.http import (Http404, HttpResponse, HttpResponseRedirect, + JsonResponse) from django.urls import reverse from django.shortcuts import get_object_or_404, redirect from django.template import loader @@ -19,8 +20,10 @@ from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm from app.models import ( Category, Comment, + DislikeComment, Materi, Like, + LikeComment, ViewStatistics, DownloadStatistics, ReqMaterial, @@ -32,12 +35,12 @@ from authentication.models import User import django import pandas as pd from io import BytesIO +from django.contrib import messages from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive from pydrive.auth import AuthenticationRejected import random - def permission_denied(request, exception, template_name="error_403.html"): return defaults.permission_denied(request, exception, template_name) @@ -79,19 +82,21 @@ class DaftarKatalog(TemplateView): getSort = request.GET.get("sort") if getSort: url = url + "&sort={0}".format(getSort) - if getSort == "judul": - lstMateri = lstMateri.order_by("title") - elif getSort == "penulis": - lstMateri = lstMateri.order_by("author") - elif getSort == "pengunggah": - lstMateri = lstMateri.order_by("uploader") - elif getSort == "terbaru": - lstMateri = lstMateri.order_by("-date_created") - elif getSort == "terlama": - lstMateri = lstMateri.order_by("date_created") - elif getSort == "terpopuler": - lstMateri = lstMateri.annotate(count=Count("like__id")).order_by("-count") - + if(getSort == "judul"): + lstMateri = lstMateri.order_by('title') + elif(getSort == "penulis"): + lstMateri = lstMateri.order_by('author') + elif(getSort == "pengunggah"): + lstMateri = lstMateri.order_by('uploader') + elif(getSort == "terbaru"): + lstMateri = lstMateri.order_by('-date_created') + elif(getSort == "terlama"): + lstMateri = lstMateri.order_by('date_created') + elif(getSort == "terpopuler"): + lstMateri = lstMateri.annotate(count=Count('like__id')).order_by('-count') + elif(getSort == "jumlah_unduh"): + lstMateri = lstMateri.annotate(count=Count('unduh__id')).order_by('-count') + should_random = bool(request.GET.get("random")) if should_random: lstMateri = random.sample(list(lstMateri), len(lstMateri)) @@ -162,14 +167,23 @@ class DetailMateri(TemplateView): if self.request.user.is_authenticated: materi_rating = Rating.objects.filter(materi=materi, user=self.request.user).first() if materi_rating is not None: - context["materi_rating_score"] = materi_rating.score + context['materi_rating_score'] = materi_rating.score + + context['is_authenticated'] = self.request.user.is_authenticated return context def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) query_set_for_comment = Comment.objects.filter(materi=context["materi_data"]) + has_liked = {} + has_disliked = {} + for comment in query_set_for_comment: + has_liked[comment.id] = LikeComment.objects.filter(comment=comment, session_id=self.request.session.session_key).exists() + has_disliked[comment.id] = DislikeComment.objects.filter(comment=comment, session_id=self.request.session.session_key).exists() context["comment_data"] = query_set_for_comment + context["has_liked"] = has_liked + context["has_disliked"] = has_disliked return self.render_to_response(context=context) def get_user_name(self, request): @@ -194,10 +208,11 @@ class DetailMateri(TemplateView): materi = get_object_or_404(Materi, pk=kwargs["pk"]) user_obj = request.user if request.user.is_authenticated else None - comment = Comment.objects.create( - comment=commentText, username=self.get_user_name(request), materi=materi, user=user_obj - ) - comment.save() + if user_obj: + comment = Comment.objects.create( + comment=commentText, username=self.get_user_name(request), materi=materi, user=user_obj + ) + comment.save() return HttpResponseRedirect(request.path) @@ -226,6 +241,41 @@ def delete_comment(request, pk_materi, pk_comment): comment.delete() return HttpResponseRedirect(url) +def toggle_like_comment(request): + if request.method == "POST": + 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}) + comment = get_object_or_404(Comment, pk=comment_id) + has_liked = LikeComment.objects.filter(comment=comment, session_id=session_id).exists() + if has_liked: + like = get_object_or_404(LikeComment, comment=comment, session_id=session_id) + like.delete() + return JsonResponse({"success": True, "liked": True, "comment_id": comment_id}) + else: + LikeComment(comment=comment, session_id=session_id).save() + return JsonResponse({"success": True, "liked": False, "comment_id": comment_id}) + else: + return JsonResponse({"success": False, "msg": "Unsuported method", "comment_id": comment_id}) + +def toggle_dislike_comment(request): + if request.method == "POST": + 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}) + comment = get_object_or_404(Comment, pk=comment_id) + has_disliked = DislikeComment.objects.filter(comment=comment, session_id=session_id).exists() + if has_disliked: + dislike = get_object_or_404(DislikeComment, comment=comment, session_id=session_id) + dislike.delete() + return JsonResponse({"success": True, "disliked": True, "comment_id": comment_id}) + else: + DislikeComment(comment=comment, session_id=session_id).save() + return JsonResponse({"success": True, "disliked": False, "comment_id": comment_id}) + else: + return JsonResponse({"success": False, "msg": "Unsuported method", "comment_id": comment_id}) def get_citation_ieee(request, materi): current_date = datetime.datetime.now() @@ -351,6 +401,11 @@ def view_materi(request, pk): raise Http404("File tidak dapat ditemukan.") +def delete_materi(request, pk): + materi = get_object_or_404(Materi, pk=pk) + materi.delete() + return HttpResponseRedirect("/dashboard/") + class UploadMateriView(TemplateView): template_name = "unggah.html" context = {} @@ -853,10 +908,8 @@ def save_to_gdrive(request, pk): path = materi.content.path file_path = os.path.join(settings.MEDIA_ROOT, path) if os.path.exists(file_path): - with open(file_path, "rb") as fh: - upload_to_gdrive(file_path, materi.title) + upload_to_gdrive(file_path, materi.title) else: raise Http404("File tidak dapat ditemukan.") - return HttpResponseRedirect(reverse("detail-materi", kwargs={"pk": pk})) - + return HttpResponseRedirect(reverse('detail-materi', kwargs={'pk': pk})) \ No newline at end of file diff --git a/authentication/templates/login.html b/authentication/templates/login.html index 0dfae589ed5969411d1930b21e24b16e1fc0267b..68859550dc8a8c72519d2ab9f4a7a6a3ec0e80c1 100644 --- a/authentication/templates/login.html +++ b/authentication/templates/login.html @@ -69,7 +69,7 @@ </div> - + <div class="g-recaptcha" data-sitekey={{captcha_site_key}}></div> <div class="container-login100-form-btn"> <button class="login100-form-btn" type="submit"> Login @@ -100,6 +100,8 @@ <script src="../static/../static/vendor/countdowntime/countdowntime.js"></script> <!--===============================================================================================--> <script src="../static/js/login.js"></script> + <!--===============================================================================================--> + <script src='https://www.google.com/recaptcha/api.js'></script> </body> diff --git a/authentication/templates/login_admin.html b/authentication/templates/login_admin.html index ca2cad7d4f40dea55792e3d7eac14320972b4b61..fca44f4cf9d0bd8344d9cbca7dae0e8189a75cd1 100644 --- a/authentication/templates/login_admin.html +++ b/authentication/templates/login_admin.html @@ -54,7 +54,7 @@ <div class="work_info"> <form class="login100-form validate-form" method="POST"> {% csrf_token %} - + <input type="hidden" name="source" value="admin" /> <div class="login100-form-title p-b-43"> Halo, Admin </div> @@ -84,7 +84,7 @@ </div> </div> - + <div class="g-recaptcha" data-sitekey="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"></div> <div class="container-login100-form-btn"> <button class="login100-form-btn" type="submit"> Login @@ -116,6 +116,9 @@ <script src="../static/../static/vendor/countdowntime/countdowntime.js"></script> <!--===============================================================================================--> <script src="../static/js/login.js"></script> + <!--===============================================================================================--> + <script src='https://www.google.com/recaptcha/api.js'></script> + </body> diff --git a/authentication/tests.py b/authentication/tests.py index 85d09f60d9b428681a141669ae2267cf842a18f5..ae7a2f85b99d7f095ed7b0ceaae84984113965fc 100644 --- a/authentication/tests.py +++ b/authentication/tests.py @@ -1,5 +1,6 @@ from django.test import Client, TestCase from django.urls import resolve +from rest_framework_simplejwt import views as jwt_views from authentication.models import User from authentication.views import Login @@ -75,12 +76,12 @@ class LoginPageContributorTest(TestCase): def setUp(self): self.client = Client() self.kontributor = User.objects.create_contributor(email="kontributor@gov.id", - password="kontributor") + password="kontributor") self.url = "/login/" self.view = Login self.template_name = "login.html" self.login_credential = { - "email": "kontributor@gov.id", "pass": "kontributor"} + "email": "kontributor@gov.id", "pass": "kontributor", "g-recaptcha-response" : "testcaptcha"} self.error_message = { "empty_email_or_password": "Email atau Password anda kosong.", "wrong_email_or_password": "Email atau Password anda salah.", @@ -114,11 +115,11 @@ class LoginPageContributorTest(TestCase): self.assertContains(response, "Kata Sandi") def test_kontributor_login_missing_email_or_password(self): - response = self.client.post(self.url, {"email": "kontributor@gov.id"}) + response = self.client.post(self.url, {"email": "kontributor@gov.id", "g-recaptcha-response" : "testcaptcha"}) self.assertIn("error_message", response.context_data) self.assertIn(self.error_message["empty_email_or_password"], response.context_data["error_message"]) - response = self.client.post(self.url, {"pass": "kontributor"}) + response = self.client.post(self.url, {"pass": "kontributor", "g-recaptcha-response" : "testcaptcha"}) self.assertIn("error_message", response.context_data) self.assertIn(self.error_message["empty_email_or_password"], response.context_data["error_message"]) @@ -126,19 +127,19 @@ class LoginPageContributorTest(TestCase): def test_kontributor_login_wrong_email_or_password(self): # Wrong password response = self.client.post( - self.url, {"email": "kontributor@gov.id", "pass": "kontributor1"}) + self.url, {"email": "kontributor@gov.id", "pass": "kontributor1", "g-recaptcha-response" : "testcaptcha"}) self.assertIn("error_message", response.context_data) self.assertIn(self.error_message["wrong_email_or_password"], response.context_data["error_message"]) # Wrong email response = self.client.post( - self.url, {"email": "kontributor1@gov.id", "pass": "kontributor"}) + self.url, {"email": "kontributor1@gov.id", "pass": "kontributor", "g-recaptcha-response" : "testcaptcha"}) self.assertIn("error_message", response.context_data) self.assertIn(self.error_message["wrong_email_or_password"], response.context_data["error_message"]) # Wrong email and password response = self.client.post( - self.url, {"email": "kontributor1@gov.id", "pass": "kontributor1"}) + self.url, {"email": "kontributor1@gov.id", "pass": "kontributor1", "g-recaptcha-response" : "testcaptcha"}) self.assertIn("error_message", response.context_data) self.assertIn(self.error_message["wrong_email_or_password"], response.context_data["error_message"]) @@ -250,7 +251,6 @@ class LoginPageAdminTest(TestCase): self.assertEqual(302, response.status_code) self.assertEqual(response.url, expected_redirect_url) - def test_admin_visit_login_after_auth(self): # 302 meaning successful login and redirected expected_redirect_url = "/sukses-admin/" @@ -263,3 +263,67 @@ class LoginPageAdminTest(TestCase): ) self.assertEqual(302, response.status_code) self.assertEqual(response.url, expected_redirect_url) + + +class TokenLoginTest(TestCase): + def setUp(self): + self.client = Client() + self.admin = User.objects.create_admin(email="admin@gov.id", + password="admin") + self.url = "/api/token/" + self.view = jwt_views.TokenObtainPairView + self.login_credential = {"email": "admin@gov.id", "password": "admin"} + + def test_token_login_view(self): + found = resolve(self.url) + self.assertEqual(found.func.cls, self.view) + + def test_token_login_get_method(self): + # Test + response = self.client.get(self.url) + self.assertEqual(response.status_code, 405) + + def test_token_login_valid_data(self): + # Test + response = self.client.post(self.url, data=self.login_credential) + self.assertEqual(response.status_code, 200) + + def test_token_login_invalid_data(self): + # Test + response = self.client.post( + self.url, data={"email": "admin@gov.id", "password": "admin1"}) + self.assertEqual(response.status_code, 401) + + +class TokenRefreshTest(TestCase): + def setUp(self): + self.client = Client() + self.admin = User.objects.create_admin(email="admin@gov.id", + password="admin") + self.token_url = "/api/token/" + self.refresh_url = "/api/token/refresh/" + self.view = jwt_views.TokenRefreshView + self.login_credential = {"email": "admin@gov.id", "password": "admin"} + + def test_token_refresh_view(self): + found = resolve(self.refresh_url) + self.assertEqual(found.func.cls, self.view) + + def test_token_refresh_get_method(self): + # Test + response = self.client.get(self.refresh_url) + self.assertEqual(response.status_code, 405) + + def test_token_refresh_valid_data(self): + # Test + response = self.client.post(self.token_url, data=self.login_credential) + refresh_token = response.json()["refresh"] + response = self.client.post( + self.refresh_url, data={"refresh": refresh_token}) + self.assertEqual(response.status_code, 200) + + def test_token_refresh_invalid_data(self): + # Test + response = self.client.post( + self.refresh_url, data={"refresh": "aaaAAAA.BBbbbb.cCcCcCc"}) + self.assertEqual(response.status_code, 401) diff --git a/authentication/urls.py b/authentication/urls.py index c4d31dc4ad8d010fd1e02105561c9c67edb78432..590a95ee49d2980fd96c5f3d7357f1123f78f090 100644 --- a/authentication/urls.py +++ b/authentication/urls.py @@ -1,5 +1,6 @@ from django.contrib.auth.views import LogoutView from django.urls import path +from rest_framework_simplejwt import views as jwt_views from authentication.views import Login @@ -7,4 +8,6 @@ urlpatterns = [ path("login/", Login.as_view(), name="login"), path("login_admin/", Login.as_view(), name="login_admin"), path("logout/", LogoutView.as_view()), + path('api/token/', jwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/token/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'), ] diff --git a/authentication/views.py b/authentication/views.py index 8e50ca975d598bb7b506a76150576191abbc6e71..03408cbaf9d5204e6bf89900b327cc6f1e564691 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -1,7 +1,10 @@ from django.contrib.auth import authenticate, login from django.http import HttpResponseRedirect, QueryDict from django.views.generic import TemplateView - +from django.conf import settings +from django.contrib import messages +import urllib +import json class Login(TemplateView): @@ -27,30 +30,46 @@ class Login(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) + context['captcha_site_key'] = settings.GOOGLE_RECAPTCHA_SITE_KEY return self.render_to_response(context=context) def post(self, request, *args, **kwargs): email = request.POST.get("email", None) password = request.POST.get("pass", None) + context = self.get_context_data(*args, **kwargs) if email is None or password is None: context = self.get_context_data(*args, **kwargs) context["error_message"] = "Email atau Password anda kosong." return self.render_to_response(context=context) - else: - user = authenticate(email=email, password=password) - if user is not None: - login(request, user) - redirect_to = "/" - querystring = QueryDict(request.META['QUERY_STRING']) - if request.user.is_admin: - redirect_to = "/sukses-admin/" - elif request.user.is_contributor: - redirect_to = "/sukses-kontributor/" - if 'next' in querystring: - redirect_to = querystring['next'] - return HttpResponseRedirect(redirect_to) + else: + recaptcha_response = request.POST.get('g-recaptcha-response') + url = 'https://www.google.com/recaptcha/api/siteverify' + values = { + 'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY, + 'response': recaptcha_response + } + data = urllib.parse.urlencode(values).encode() + req = urllib.request.Request(url, data=data) + response = urllib.request.urlopen(req) + result = json.loads(response.read().decode()) + + if result['success']: + user = authenticate(email=email, password=password) + if user is not None: + login(request, user) + redirect_to = "/" + querystring = QueryDict(request.META['QUERY_STRING']) + if request.user.is_admin: + redirect_to = "/sukses-admin/" + elif request.user.is_contributor: + redirect_to = "/sukses-kontributor/" + if 'next' in querystring: + redirect_to = querystring['next'] + return HttpResponseRedirect(redirect_to) + else: + context["error_message"] = "Email atau Password anda salah." + return self.render_to_response(context=context) else: - context = self.get_context_data(*args, **kwargs) - context["error_message"] = "Email atau Password anda salah." - return self.render_to_response(context=context) + messages.error(request, 'Invalid reCAPTCHA. Please try again.') + return self.render_to_response(context=context) diff --git a/digipus/__pycache__/settings.cpython-36.pyc b/digipus/__pycache__/settings.cpython-36.pyc index a2dfadd532b5c69b61e9c0a2768ccc1ee6376edf..bbd092036ce26932962c8c236860e7899ca31f6f 100644 Binary files a/digipus/__pycache__/settings.cpython-36.pyc and b/digipus/__pycache__/settings.cpython-36.pyc differ diff --git a/digipus/settings.py b/digipus/settings.py index a1c930660339988f60d34dad5d86c7f561ce7473..6f3add23133069c7552d793518bc22cee7e4e116 100644 --- a/digipus/settings.py +++ b/digipus/settings.py @@ -14,6 +14,7 @@ import os import dj_database_url from decouple import config +from django.contrib.messages import constants as messages # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -49,6 +50,7 @@ INSTALLED_APPS = [ "news.apps.NewsConfig", "traffic_statistics", "forum", + "rest_framework", ] MIDDLEWARE = [ @@ -145,6 +147,16 @@ USE_L10N = True USE_TZ = True +MESSAGE_TAGS = { + messages.DEBUG: 'alert-info', + messages.INFO: 'alert-info', + messages.SUCCESS: 'alert-success', + messages.WARNING: 'alert-warning', + messages.ERROR: 'alert-danger', +} + +GOOGLE_RECAPTCHA_SECRET_KEY = config('GOOGLE_RECHAPTCHA', default= "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") +GOOGLE_RECAPTCHA_SITE_KEY = config('CLIENT_RECHAPTCHA', default= "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ @@ -164,3 +176,9 @@ MEDIA_URL = "/media/" LOGOUT_REDIRECT_URL = "/" CRISPY_TEMPLATE_PACK = 'bootstrap4' + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ], +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f3b2e12e332740426925ee03b625d0e64be99f14..30fd8032d8b3be87ddcb85a8443175668af3dc5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,6 +25,9 @@ Django==3.1 django-crispy-forms==1.9.1 django-heroku==0.3.1 django-storages==1.9.1 +djangorestframework==3.12.1 +djangorestframework-jwt==1.11.0 +djangorestframework-simplejwt==4.4.0 entrypoints==0.3 filelock==3.0.12 flake8==3.7.9 @@ -67,6 +70,7 @@ pycodestyle==2.5.0 pycparser==2.20 PyDrive==1.3.1 pyflakes==2.1.1 +PyJWT==1.7.1 pylint==2.4.4 pylint-django==2.0.15 pylint-plugin-utils==0.6