diff --git a/administration/templates/kelola_admin.html b/administration/templates/kelola_admin.html index aa6962d2841966cd2f89a88a34bcece51d564bc1..ac52797935b91de8ceea83a1a23035a73b44bf85 100644 --- a/administration/templates/kelola_admin.html +++ b/administration/templates/kelola_admin.html @@ -62,12 +62,21 @@ </button> </div> <div class="modal-body"> - <p>Sila konfirmasi penghapusan akun dengan tekan tombol hapus di bawah</p> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button> - <a href="/administration/hapus-admin/{{current.id}}/" type="button" class="btn btn-danger">Hapus</a> + <p>Anda akan menghapus akun admin dengan email + <span class="bg-danger pr-1 pl-1 text-white" style="text-transform: lowercase">{{current.email}}</span> + . Lakukan konfirmasi penghapusan dengan mengetik: </p> + <p class="bg-danger text-white p-1"> {{current.email}} </p> </div> + <form/> + {% csrf_token %} + <div class="form-group pl-3 pr-3"> + <input name="{{ current.email }}" class="form-control" id="{{ current.id }}"required pattern="{{current.email}}" oninput="checkValue(this)"> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button> + <a href="/administration/hapus-admin/{{current.id}}/" id="btn-hapus-{{current.id}}" type="button" class="btn btn-danger disabled">Hapus</a> + </div> + </form> </div> </div> </div> @@ -118,4 +127,14 @@ </div> </div> </div> +<script type="text/javascript"> + function checkValue(element){ + buttonElement = document.getElementById("btn-hapus-" + element.id) + if(element.value != element.name) { + buttonElement.classList.add("disabled"); + } else { + buttonElement.classList.remove("disabled"); + } +} +</script> {% endblock %} \ No newline at end of file diff --git a/app/static/app/css/katalog_kontri.css b/app/static/app/css/katalog_kontri.css new file mode 100644 index 0000000000000000000000000000000000000000..c6ff672311e4bfad5a4811ff2b0816fcb5c99e9c --- /dev/null +++ b/app/static/app/css/katalog_kontri.css @@ -0,0 +1,9 @@ +.img-profile { + width: 10vw; + height: 10vw; +} + +.profile-data { + color: #615CFD; + padding-right: 75px; +} \ No newline at end of file diff --git a/app/templates/app/detail_materi.html b/app/templates/app/detail_materi.html index 8802166b25015f71432ee88ed0d66b96fb10c49f..54b0bb090fa529d1cc3562e06369a7dd462536cd 100644 --- a/app/templates/app/detail_materi.html +++ b/app/templates/app/detail_materi.html @@ -5,7 +5,8 @@ <link href="{% static 'css/sb-admin-2.min.css' %}" rel="stylesheet"> <link rel="stylesheet" type="text/css" href="{% static 'app/css/detail_materi.css' %}"> <link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;1,100&display=swap" rel="stylesheet"> -<script async defer crossorigin="anonymous" src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v6.0"></script> +<script async defer crossorigin="anonymous" + src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v6.0"></script> <script src="{% static 'js/detail_materi.js'%}"></script> {% endblock header %} @@ -14,51 +15,53 @@ {% endblock title %} {% block content %} - {% 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"> +{% 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"> - <!-- Sidebar Toggle (Topbar) --> - <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> + <!-- Sidebar Toggle (Topbar) --> + <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> <i class="fa fa-bars" aria-hidden="true"></i> - </button> + </button> - <div class="sidebar-brand-text mx-3">Diskominfo Kota Depok</div> + <div class="sidebar-brand-text mx-3">Diskominfo Kota Depok</div> - <!-- Topbar Navbar --> - <ul class="navbar-nav ml-auto"> + <!-- Topbar Navbar --> + <ul class="navbar-nav ml-auto"> {% if request.user.is_contributor %} - {% if materi_data.status == "DISAPPROVE" %} - <li class="nav-item black-text"> - <a class="nav-link feedback" data-toggle="modal" data-target="#umpanBalikModal"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">Umpan Balik Materi</span> - </a> - <div class="modal fade" id="umpanBalikModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> + {% if materi_data.status == "DISAPPROVE" %} + <li class="nav-item black-text"> + <a class="nav-link feedback" data-toggle="modal" data-target="#umpanBalikModal"> + <span class="mr-2 d-none d-lg-inline text-gray-600 small">Umpan Balik Materi</span> + </a> + <div class="modal fade" id="umpanBalikModal" tabindex="-1" role="dialog" + aria-labelledby="exampleModalLabel" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> <div class="modal-header"> - <h5 class="modal-title black-text" id="exampleModalLabel">Umpan Balik Materi</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> + <h5 class="modal-title black-text" id="exampleModalLabel">Umpan Balik Materi</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="modal-body"> - {% for item in report %} + {% for item in report %} <p class="black-text">Alasan materi ditolak:</p> <p class="black-text">{{ item.report.feedback }}</p> - <p class="black-text">Untuk merevisi materi, sila tekan tombol revisi di halaman riwayat unggah.</p> - {% endfor %} + <p class="black-text">Untuk merevisi materi, sila tekan tombol revisi di halaman riwayat + unggah.</p> + {% endfor %} </div> <div class="modal-footer"> - <button type="button" class="btn btn-danger" data-dismiss="modal">Tutup</button> + <button type="button" class="btn btn-danger" data-dismiss="modal">Tutup</button> </div> - </div> </div> - </div> - </li> - {% endif %} + </div> + </div> + </li> + {% endif %} {% endif %} <li class="nav-item"> @@ -80,63 +83,106 @@ <!-- Nav Item - User Information --> {% if request.user.is_authenticated %} <li class="nav-item dropdown no-arrow"> - <a class="nav-link dropdown-toggle" href="/dashboard/"> - <span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ user.name }}</span> - {% if not user.default_profile_picture %} - <img class="img-profile rounded-circle" src="https://i.ibb.co/9wgPzyZ/default-image.png" alt="User profile picture"> - {% else %} - <img class="img-profile rounded-circle" src="{{ user.profile_picture.url }}" alt="User profile picture"> - {% endif %} - </a> + <a class="nav-link dropdown-toggle" href="/dashboard/"> + <span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ user.name }}</span> + {% if not user.default_profile_picture %} + <img class="img-profile rounded-circle" src="https://i.ibb.co/9wgPzyZ/default-image.png" + alt="User profile picture"> + {% else %} + <img class="img-profile rounded-circle" src="{{ user.profile_picture.url }}" + alt="User profile picture"> + {% endif %} + </a> </li> {% endif %} - </ul> + </ul> - </nav> - <div class="row materi-data mr-4 ml-4 p-3 shadow-sm rounded"> - <div class="col col-3 cover"> - <img src={{materi_data.cover.url}} alt="cover"> + </nav> + <div class="row materi-data mr-4 ml-4 p-3 shadow-sm rounded"> + <div class="col col-3 cover"> + <img src={{materi_data.cover.url}} alt="cover"> + </div> + <div class="col col-6 ml-3 book"> + <h2>{{materi_data.title}}</h2> + <div class="category-wrapper"> + {% for category in materi_data.categories.all %} + <span class="mr-1">#{{category.name}}</span> + {% endfor %} </div> - <div class="col col-6 ml-3 book"> - <h2>{{materi_data.title}}</h2> - <div class="category-wrapper"> - {% for category in materi_data.categories.all %} - <span class="mr-1">#{{category.name}}</span> - {% endfor %} + <div class="info-wrapper"> + <div class="info" id="1"> + <dl class="col col-4"> + <dt class="info-name">Penulis</dt> + </dl> + <dd> + <p class="info-content">{{materi_data.author}}</p> + </dd> </div> - <div class="info-wrapper"> - <div class="info" id="1"> - <dt class="col col-4"> - <p class="info-name">Penulis</p> - </dt> - <dd> - <p class="info-content">{{materi_data.author}}</p> - </dd> - </div> - <div class="info" id="1"> - <dt class="col col-4"> - <p class="info-name">Penerbit</p> - </dt> - <dd> - <p class="info-content">{{materi_data.publisher}}</p> - </dd> - </div> - <div class="info" id="1"> - <dt class="col col-4"> - <p class="info-name">Jumlah Halaman</p> - </dt> - <dd> - <p class="info-content">{{materi_data.pages}}</p> - </dd> - </div> - <div class="info" id="1"> - <dt class="col col-4"> - <p class="info-name">Ukuran File</p> - </dt> - <dd> - <p class="info-content">{{materi_data.content.size|filesizeformat}}</p> - </dd> + <div class="info" id="1"> + <dl class="col col-4"> + <dt class="info-name">Penerbit</dt> + </dl> + <dd> + <p class="info-content">{{materi_data.publisher}}</p> + </dd> + </div> + <div class="info" id="1"> + <dl class="col col-4"> + <dt class="info-name">Kontributor</dt> + </dl> + <dd> + <a class="info-content" + href="{% url 'katalog-per-kontributor' materi_data.uploader.email %}">{{materi_data.uploader.name}}</a> + </dd> + </div> + <div class="info" id="1"> + <dl class="col col-4"> + <dt class="info-name">Jumlah Halaman</dt> + </dl> + <dd> + <p class="info-content">{{materi_data.pages}}</p> + </dd> + </div> + <div class="info" id="1"> + <dl class="col col-4"> + <dt class="info-name">Ukuran File</dt> + </dl> + <dd> + <p class="info-content">{{materi_data.content.size|filesizeformat}}</p> + </dd> + </div> + </div> + <div class="buttons d-flex flex-row bd-highlight mb-1"> + <a href="{% url 'view-materi' materi_data.id %}" + class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded">Baca</a> + <a href="{% url 'download-materi' materi_data.id %}" + class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded">Unduh</a> + <div class="dropdown"> + <button class="btn dropdown-toggle btn-book shadow-sm p-2 mr-2 bg-white rounded" type="button" + id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Bagikan + </button> + <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"> + <div class="fb-share-button" data-href="{% url 'detail-materi' materi_data.id %}" + data-layout="button" data-size="small"> + <a target="_blank" + href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fdigipus-staging-2.herokuapp.com%2Fmateri%2F{{materi_data.id}}%2F&src=sdkpreparse" + class="dropdown-item fb-xfbml-parse-ignore">Facebook</a> + </div> + <a class="twitter-share-button dropdown-item" + href="https://twitter.com/intent/tweet?text=Cek%20materi%20ini%20yuk%20https%3A%2F%2Fdigipus-staging-2.herokuapp.com%2Fmateri%2F{{materi_data.id}}%2F" + target="_blank" data-size="large"> + Twitter + </a> + <a class="dropdown-item" + href="whatsapp://send?text=Cek materi ini yuk! https://digipus-staging-2.herokuapp.com{{request.path}}" + target="_blank">Whatsapp</a> + <a class="dropdown-item" + href="https://social-plugins.line.me/lineit/share?url=https%3A%2F%2Fdigipus-staging-2.herokuapp.com%2Fmateri%2F{{materi_data.id}}%2F" + target="_blank">Line</a> + <p id="url" style="display: none">https://digipus-staging-2.herokuapp.com{{request.path}}</p> + <button class="dropdown-item btn-book" onclick="copyToClipboard('#url')">Bagikan Tautan</button> </div> </div> <div class="buttons d-flex flex-row bd-highlight mb-1"> @@ -161,98 +207,198 @@ <a class="dropdown-item" href="https://social-plugins.line.me/lineit/share?url=https%3A%2F%2Fdigipus-staging-2.herokuapp.com%2Fmateri%2F{{materi_data.id}}%2F" target="_blank">Line</a> <p id="url" style="display: none">https://digipus-staging-2.herokuapp.com{{request.path}}</p> <button class="dropdown-item btn-book" onclick="copyToClipboard('#url')">Bagikan Tautan</button> + <form action="" method="POST"> + <input type="hidden" name="action" value="like"> + </form> + {% if has_liked %} + <button id="thumb" class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded"><i id="thumbIcon" + aria-hidden="true" class="fas fa-thumbs-up"></i> Disukai</button> + {% else %} + <button id="thumb" class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded"><i id="thumbIcon" + aria-hidden="true" class="far fa-thumbs-up"></i> Sukai</button> + {% endif %} + + {% if user.is_authenticated %} + <div class="dropdown"> + <button class="btn dropdown-toggle btn-book shadow-sm p-2 mr-2 bg-white rounded align-self-center" + type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <em id="button-rating-star-icon" class="align-self-center far fa-star"></em> + <span id="button-rating-text">Beri Rating</span> + </button> + <div id="star-dropdown" class="dropdown-menu" aria-labelledby="dropdownMenuButton"> + <div class="text-center btn-book"> + <em onclick="postAddRating(1)" id="star-1" class="far fa-star fa-lg"></em> + <em onclick="postAddRating(2)" id="star-2" class="far fa-star fa-lg"></em> + <em onclick="postAddRating(3)" id="star-3" class="far fa-star fa-lg"></em> + <em onclick="postAddRating(4)" id="star-4" class="far fa-star fa-lg"></em> + <em onclick="postAddRating(5)" id="star-5" class="far fa-star fa-lg"></em> </div> </div> - <form action="" method="POST"> - <input type="hidden" name="action" value="like"> - </form> - {% if has_liked %} - <button id="thumb" class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded"><i id="thumbIcon" aria-hidden="true" class="fas fa-thumbs-up"></i> Disukai</button> - {% else %} - <button id="thumb" class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded"><i id="thumbIcon" aria-hidden="true" class="far fa-thumbs-up"></i> Sukai</button> - {% endif %} </div> + {% 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> + {% endif %} </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"> - <ul class="navbar-nav"> - <li class="nav-item"> - <a class="nav-link" href="#deskripsi">Deskripsi</a> - </li> - {% if materi_data.status == "APPROVE" %} - <li class="nav-item"> - <a class="nav-link" href="#komentar">Komentar</a> - </li> - {% endif %} - </ul> - </nav> - <div id="deskripsi" class="container-fluid description-wrapper bg-white shadow-sm rounded p-3"> - <h1>Deskripsi</h1> - <div class="col col-8 description"> - <p>{{materi_data.descriptions}}</p> - </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"> + <ul class="navbar-nav"> + <li class="nav-item"> + <a class="nav-link" href="#deskripsi">Deskripsi</a> + </li> + {% if materi_data.status == "APPROVE" %} + <li class="nav-item"> + <a class="nav-link" href="#komentar">Komentar</a> + </li> + {% endif %} + </ul> + </nav> + <div id="deskripsi" class="container-fluid description-wrapper bg-white shadow-sm rounded p-3"> + <h1>Deskripsi</h1> + <div class="col col-8 description"> + <p>{{materi_data.descriptions}}</p> </div> - {% if materi_data.status == "APPROVE" %} - <div id="komentar" class="container-fluid comments-wrapper p-0"> - <div class="add-comments col col-8 bg-white shadow-sm rounded p-3 mb-3"> - <form method="POST"> - {% csrf_token %} - <h1>Komentar</h1> - <div class="form-group"> - <textarea placeholder="Beri komentar..." class="form-control" - id="exampleFormControlTextarea1" - rows="3" name="comment" required - ></textarea> - <button type="submit" class="btn btn-link btn-book shadow-sm p-2 mt-2 bg-white rounded">Kirim</button> - </div> - </form> + </div> + {% if materi_data.status == "APPROVE" %} + <div id="komentar" class="container-fluid comments-wrapper p-0"> + <div class="add-comments col col-8 bg-white shadow-sm rounded p-3 mb-3"> + <form method="POST"> + {% csrf_token %} + <h1>Komentar</h1> + <div class="form-group"> + <textarea placeholder="Beri komentar..." class="form-control" id="exampleFormControlTextarea1" + rows="3" name="comment" required></textarea> + <button type="submit" + class="btn btn-link btn-book shadow-sm p-2 mt-2 bg-white rounded">Kirim</button> </div> - {% 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"> - {% if comment.user != Null %} - {% if not user.default_profile_picture %} - <img class="profile" src="https://i.ibb.co/9wgPzyZ/default-image.png" alt="profile-picture"> - {% else %} - <img class="profile" src="{{ comment.user.profile_picture.url }}" alt="profile-picture"> - {% endif %} - {% 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> - {% if user.is_admin %} - <a class="ml-auto p-1 bd-highlight close" href="{% url 'delete-comment' materi_data.id comment.id %}"> - <span aria-hidden="true">×</span> - </a> - {% endif %} - </div> - <p class="text">{{comment.comment}}</p> - </div> - {% endfor %} + </form> + </div> + {% 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"> + {% if comment.user != Null %} + {% if not user.default_profile_picture %} + <img class="profile" src="https://i.ibb.co/9wgPzyZ/default-image.png" alt="profile-picture"> + {% else %} + <img class="profile" src="{{ comment.user.profile_picture.url }}" alt="profile-picture"> + {% endif %} + {% 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> + {% if user.is_admin %} + <a class="ml-auto p-1 bd-highlight close" + href="{% url 'delete-comment' materi_data.id comment.id %}"> + <span aria-hidden="true">×</span> + </a> + {% endif %} </div> - {% endif %} + <p class="text">{{comment.comment}}</p> + </div> + {% endfor %} </div> + {% endif %} </div> - <footer class="sticky-footer bg-white p-4"> - <div class="container my-auto"> - <div class="copyright text-center my-auto"> +</div> +<footer class="sticky-footer bg-white p-4"> + <div class="container my-auto"> + <div class="copyright text-center my-auto"> <span>Copyright © Diskominfo Kota Depok 2020</span> - </div> </div> - </footer> + </div> +</footer> + +<!-- Modal --> +<div class="modal fade" id="notLoggedInModal" tabindex="-1" role="dialog" aria-labelledby="notLoggedInModalLabel" + aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="notLoggedInModalLabel">Belum Login</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + Login untuk memberikan rating + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button> + <button type="button" class="btn btn-primary" onclick="window.location.href = '/login';">Login</button> + </div> + </div> + </div> +</div> {% endblock content %} {% block extra_scripts %} <script src="https://kit.fontawesome.com/bc2cedd6b2.js" crossorigin="anonymous"></script> <script type="text/javascript"> // using jQuery var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); + + let currentRating = {{ materi_rating_score }}; + fillRatingStar(currentRating) + + function changeButtonIntoRated() { + $('#button-rating-star-icon').addClass('fas'); + $('#button-rating-star-icon').removeClass('far'); + $('#button-rating-text').text("Rating Anda") + } + + if (currentRating > 0) { + changeButtonIntoRated() + } + + $("#star-dropdown").click(function (e) { + e.stopPropagation(); + }); + + function clearRatingStar() { + for (let i = 1; i <= 5; i++) { + $('#star-' + i).addClass('far'); + $('#star-' + i).removeClass('fas'); + } + } + + function fillRatingStar(ratingScore) { + for (let i = 1; i <= ratingScore; i++) { + $('#star-' + i).addClass('fas'); + $('#star-' + i).removeClass('far'); + } + } + + function makeHoverStar(starAmount) { + function hoverStar() { + clearRatingStar() + fillRatingStar(starAmount) + } + + return hoverStar + } + + function makeUnHoverStar(starAmount) { + function unHoverStar() { + clearRatingStar() + fillRatingStar(currentRating) + } + + return unHoverStar + } + + $('#star-1').hover(makeHoverStar(1), makeUnHoverStar(1)); + $('#star-2').hover(makeHoverStar(2), makeUnHoverStar(2)); + $('#star-3').hover(makeHoverStar(3), makeUnHoverStar(3)); + $('#star-4').hover(makeHoverStar(4), makeUnHoverStar(4)); + $('#star-5').hover(makeHoverStar(5), makeUnHoverStar(5)); </script> <script> function csrfSafeMethod(method) { @@ -261,37 +407,62 @@ } </script> <script> - $('#thumb').click(function () { + $('#thumb').click(function () { - $.ajaxSetup({ - beforeSend: function (xhr, settings) { - if (!csrfSafeMethod(settings.type) && !this.crossDomain) { - xhr.setRequestHeader("X-CSRFToken", csrftoken); - } + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); } - }); - $.ajax({ - type: 'POST', - url: "{% url 'PostLikeToggle' %}", - data: { - 'materi_id': "{{ materi_data.id }}", - 'session_id': "{{ session_id }}" - }, - success: LikePost, - dataType: 'html' - }); + } }); + $.ajax({ + type: 'POST', + url: "{% url 'PostLikeToggle' %}", + data: { + 'materi_id': "{{ materi_data.id }}", + 'session_id': "{{ session_id }}" + }, + success: LikePost, + dataType: 'html' + }); + }); - - function LikePost(data, jqXHR) { - var data = $.parseJSON(data) - if (data['liked']) { - $('#thumbIcon').removeClass("fas fa-thumbs-up").addClass('far fa-thumbs-up') - document.getElementById("thumb").firstChild.data = " Sukai" - } else { - $('#thumbIcon').removeClass("far fa-thumbs-up").addClass('fas fa-thumbs-up') - document.getElementById("thumb").firstChild.data = " Disukai" + function postAddRating(rating_score) { + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } } + }); + + $.ajax({ + type: 'POST', + url: "{% url 'rate-materi' %}", + data: { + 'materi_id': "{{ materi_data.id }}", + 'rating_score': rating_score + }, + success: function (data) { + const response = JSON.parse(data) + currentRating = response.rating_score + makeUnHoverStar(currentRating)() + changeButtonIntoRated() + + }, + dataType: 'html' + }); + } + + function LikePost(data, jqXHR) { + var data = $.parseJSON(data) + if (data['liked']) { + $('#thumbIcon').removeClass("fas fa-thumbs-up").addClass('far fa-thumbs-up') + document.getElementById("thumb").firstChild.data = " Sukai" + } else { + $('#thumbIcon').removeClass("far fa-thumbs-up").addClass('fas fa-thumbs-up') + document.getElementById("thumb").firstChild.data = " Disukai" } function CitateAPA(text){ alert('Hasil citasi : '+text); @@ -299,4 +470,4 @@ </script> -{% endblock extra_scripts %} +{% endblock extra_scripts %} \ No newline at end of file diff --git a/app/templates/app/katalog_kontri.html b/app/templates/app/katalog_kontri.html new file mode 100644 index 0000000000000000000000000000000000000000..3ddaf130261abbbb2ccc0c546f855e93b876f14d --- /dev/null +++ b/app/templates/app/katalog_kontri.html @@ -0,0 +1,135 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}Digipus - {% endblock %} +{% block header %} + +<!DOCTYPE html> +<html lang="en"> + +<head> + <title>Digipus Home</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <link href="../../static/app/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet"> + <link href="../../static/app/css/heroic-features.css" rel="stylesheet"> + + <link rel="stylesheet" type="text/css" href="{% static 'app/css/katalog_materi.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'app/css/katalog_kontri.css' %}"> + + <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'css/util.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}"> + + <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}"> + + <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> + + + {% endblock header %} + {% block content %} + +</head> + +<body style="background-color: #f8f8f8;"> + <div class="container main"> + <header class="jumbotron my-4" id="profile"> + <div class="container sub"> + <div class="row"> + <div class="col-lg-3"> + <div class="img-squared"> + {% if not contributor.default_profile_picture %} + <img class="img-profile rounded-circle" src="https://i.ibb.co/9wgPzyZ/default-image.png" + alt="Contributor profile picture"> + {% else %} + <img class="img-profile rounded-circle" src="{{ contributor.profile_picture.url }}" + alt="Contributor profile picture"> + {% endif %} + </div> + </div> + <div class="col-lg-9"> + + <table aria-describedby="profile"> + <th colspan="2" id="contributor-data"> + <h3 class="profile-data">{{ contributor.name }}</h3> + <br> + </th> + <tr> + <td class="profile-data">Instansi</td> + <td>{{ contributor.instansi }}</td> + </tr> + + <tr> + <td class="profile-data">LinkedIn</td> + <td><a href="https://linkedin.com">{{ contributor.linkedin }}</a></td> + </tr> + + <tr> + <td class="profile-data">Facebook</td> + <td><a href="https://facebook.com">{{ contributor.facebook }}</a></td> + </tr> + + <tr> + <td class="profile-data">Twitter</td> + <td><a href="https://twitter.com">{{ contributor.twitter }}</a></td> + </tr> + + <tr> + <td class="profile-data">Instagram</td> + <td><a href="https://instagram.com">{{ contributor.instagram }}</a></td> + </tr> + </table> + </div> + </div> + </div> + </header> + + <div class="container"> + <div class="row content"> + <div class="col books"> + {% for materi in materi_list %} + <div class="card book"> + <img src={{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">{{materi.title}}</h5> + <p class="card-text">{{materi.author}}</p> + <a href="{% url 'view-materi' materi.id %}" class="btn btn-book">Baca</a> + <a href="{% url 'detail-materi' materi.id %}" class="btn btn-book">Detail</a> + </div> + </div> + {% endfor %} + </div> + <div class="center"> + <div class="pagination"> + <span class="step-links"> + <span class="current"> + Page {{ materi_list.number }} of {{ materi_list.paginator.num_pages }} + </span> + <br> + {% if materi_list.has_previous %} + <a href="?page=1{{url}}">« first</a> + <a href="?page={{ materi_list.previous_page_number }}{{url}}">previous</a> + {% endif %} + + {% if materi_list.has_next %} + <a href="?page={{ materi_list.next_page_number }}{{url}}">next</a> + <a href="?page={{ materi_list.paginator.num_pages }}{{url}}">last »</a> + {% endif %} + </span> + </div> + </div> + + </div> + </div> + </div> +</body> + +</html> + +{% endblock %} \ No newline at end of file diff --git a/app/templates/app/katalog_materi.html b/app/templates/app/katalog_materi.html index 9782d19d98c75aafb5239947bcd5ffbfdb193cec..c4013e21b6549391bcea6a7281e3540f782a98d0 100644 --- a/app/templates/app/katalog_materi.html +++ b/app/templates/app/katalog_materi.html @@ -41,13 +41,13 @@ <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"> <!--===============================================================================================--> -{% endblock header %} -{% block content %} + {% endblock header %} + {% block content %} </head> <body style="background-color: #f8f8f8;"> -<!-- Page Content --> + <!-- Page Content --> <div class="container"> <header class="jumbotron my-4"> @@ -56,18 +56,20 @@ <div class="col"> <h2 class="pageTitle">Temukan Materi Yang Kamu Mau!</h2> <p class="description">Cari dengan judul buku, penerbit, atau penulis</p> - <form class="searchBar" action=''> + <form class="searchBar" action=''> <div class="col-6 form-group"> - <input type="text" name='search' class="form-control" placeholder="Tulis di sini" value='{{request.GET.search}}'> + <input type="text" name='search' class="form-control" placeholder="Tulis di sini" + value='{{request.GET.search}}'> </div> <button type="submit" class="btn btn-cari">Cari</button> </form> - <p class="pageTitle">Tidak menemukan materi yang kamu cari ? ajukan permintaan materi kami <a href="/req-materi">disini</a></p> + <p class="pageTitle">Tidak menemukan materi yang kamu cari ? ajukan permintaan materi kami <a + href="/req-materi">disini</a></p> </div> </div> - </div> + </div> </header> - + <div class="container"> <div class="row content"> <div class="col-3 sidebar"> @@ -75,26 +77,20 @@ <div class="card"> <div class="card-header" id="headingOne"> <h2 class="mb-0"> - <button class="btn collapsed" - type="button" - data-toggle="collapse" - data-target="#collapseOne" - aria-expanded="true" - aria-controls="collapseOne"> - Filter + <button class="btn collapsed" type="button" data-toggle="collapse" + data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne"> + Filter </button> </h2> </div> - - <div id="collapseOne" - class="collapse" - aria-labelledby="headingOne" - data-parent="#accordionExample"> + + <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> + <li> + <a href="?kategori={{itemKategori.pk}}">{{itemKategori.name}}</a> + </li> {% endfor %} </div> </div> @@ -102,33 +98,35 @@ <div class="card"> <div class="card-header" id="headingTwo"> - <h2 class="mb-0"> - <button class="btn collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"> - Sort - </button> - </h2> - </div> - <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> + <h2 class="mb-0"> + <button class="btn collapsed" type="button" data-toggle="collapse" + data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"> + Sort + </button> + </h2> </div> + <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> + </div> </div> </div> </div> @@ -137,11 +135,16 @@ <div class="col-9 books"> {% for item in materi_list %} <div class="card book"> - <img src={{item.cover.url}} class="card-img-top" alt="cover" + <img src={{item.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.title}}</h5> <p class="card-text">{{item.author}}</p> + <p class="card-text">Diunggah oleh + <a class="card-link" href="{% url 'katalog-per-kontributor' item.uploader.email %}"> + {{item.uploader.name}} + </a> + </p> <a href="{% url 'view-materi' item.id %}" class="btn btn-book">Baca</a> <a href="{% url 'detail-materi' item.id %}" class="btn btn-book">Detail</a> </div> @@ -158,14 +161,14 @@ <br> </br> - + {% if materi_list.has_previous %} - <a href="?page=1{{url}}" >« first</a> + <a href="?page=1{{url}}">« first</a> <a href="?page={{ materi_list.previous_page_number }}{{url}}">previous</a> {% endif %} - - - + + + {% if materi_list.has_next %} <a href="?page={{ materi_list.next_page_number }}{{url}}">next</a> <a href="?page={{ materi_list.paginator.num_pages }}{{url}}">last »</a> @@ -173,16 +176,14 @@ </span> </div> </div> - + </div> </div> </div> - -<!-- /.container --> + + <!-- /.container --> </body> </html> -{% endblock %} - - \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/app/templates/req_materi.html b/app/templates/req_materi.html index d0375d7169835ed89a4ac7a793783478a80bd35b..f4d071bab3f96663ae5575ecc911a502a5ad77ff 100644 --- a/app/templates/req_materi.html +++ b/app/templates/req_materi.html @@ -108,7 +108,7 @@ $('#btn_req_submit').click(function () { $.ajax({ type: 'POST', - url: "{% url 'post-req-materi' %}", + url: "{% url 'req-materi' %}", data: { 'title': title.value, }, diff --git a/app/test_files/image_with_exif_data.gif b/app/test_files/image_with_exif_data.gif new file mode 100644 index 0000000000000000000000000000000000000000..55609f4bf5f26b7569b7cab811489641cef6a882 Binary files /dev/null and b/app/test_files/image_with_exif_data.gif differ diff --git a/app/tests.py b/app/tests.py index 643dc7cea4f4a77ad586207d7a8a497475ba5dd7..adf63c619e866fda2bfc9044834a3dec7961a9d2 100644 --- a/app/tests.py +++ b/app/tests.py @@ -1,24 +1,27 @@ -import json +import json, tempfile, os from io import StringIO +from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied, ValidationError from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command -from django.test import Client, RequestFactory, TestCase from django.db import IntegrityError +from django.test import Client, TestCase from django.urls import resolve from administration.models import VerificationSetting, VerificationReport from administration.utils import id_generator -from app.views import UploadMateriView +from app.forms import SuntingProfilForm +from app.views import UploadMateriView, add_rating_materi from authentication.models import User -from .models import Category, Comment, Materi, Like, Rating +from .models import Category, Comment, Materi, Like, Rating, ReqMaterial from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri, ProfilKontributorView, SuksesLoginAdminView, SuksesLoginKontributorView, SuntingProfilView, - ProfilAdminView, PostsView, SuntingProfilAdminView, RevisiMateriView) + ProfilAdminView, PostsView, SuntingProfilAdminView, RevisiMateriView, ReqMateriView, KatalogPerKontributorView) from app.forms import SuntingProfilForm +from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata class DaftarKatalogTest(TestCase): @@ -45,6 +48,60 @@ class DaftarKatalogTest(TestCase): resp = Materi.objects.get(id=materi.id) self.assertEqual(resp, materi) +class DaftarKatalogPerKontributorTest(TestCase): + def setUp(self): + self.client = Client() + + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": "passwordtest" + } + + self.contributor_credential_2 = { + "email": "kontributor2@gov.id", + "password": "passwordtest" + } + + self.contributor = get_user_model().objects.create_user( + **self.contributor_credential, name="Kontributor 1", is_contributor=True) + self.contributor2 = get_user_model().objects.create_user( + **self.contributor_credential_2, name="Kontributor 2", is_contributor=True) + + self.cover = SimpleUploadedFile( + "Cherprang_Areekul40_nJM9dGt.jpg", b"Test file") + self.content = SimpleUploadedFile("Bahan_PA_RKK.pdf", b"Test file") + + Materi(title="Materi 1", author="Agas", uploader=self.contributor, + publisher="Kelas SC", descriptions="Deskripsi Materi 1", + status="APPROVE", cover=self.cover, content=self.content).save() + Materi(title="Materi 2", author="Agas", uploader=self.contributor, + publisher="Kelas SC", descriptions="Deskripsi Materi 2", + status="APPROVE", cover=self.cover, content=self.content).save() + Materi(title="Materi 3", author="Agas", uploader=self.contributor2, + publisher="Kelas SC", descriptions="Deskripsi Materi 3", + status="APPROVE", cover=self.cover, content=self.content).save() + + self.url = f"/profil/{self.contributor.email}/" + + def test_katalog_per_kontributor_url_exist(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + + def test_katalog_per_kontributor_using_katalog_kontri_template(self): + response = self.client.get(self.url) + self.assertTemplateUsed(response, "app/katalog_kontri.html") + + def test_katalog_per_kontributor_using_katalog_per_kontributor_func(self): + found = resolve(self.url) + self.assertEqual(found.func.__name__, KatalogPerKontributorView.as_view().__name__) + + def test_katalog_per_kontributor_show_daftar_materi_kontributor(self): + response = self.client.get(self.url) + + list_materi = Materi.objects.filter(uploader=self.contributor) + data = response.context_data['materi_list'] + self.assertEqual(len(list_materi), len(data)) + class DetailMateriTest(TestCase): def setUp(self): @@ -231,7 +288,7 @@ class PostsViewTest(TestCase): def test_url_resolves_to_posts_view(self): found = resolve(self.url) self.assertEqual(found.func.__name__, PostsView.as_view().__name__) - + def test_returns_200_on_authenticated_access(self): response = self._request_as_user() self.assertEqual(response.status_code, 200) @@ -267,14 +324,14 @@ class PostsViewTest(TestCase): } self.assertRegex( - str(response.content), + str(response.content), rf'.*(<div id="post-{posts[2]}">)' + \ - rf'.*(<div id="post-{posts[2]}-comment-{comments[2][0]}">)' + \ - rf'.*(<div id="post-{posts[2]}-comment-{comments[2][1]}">)' + \ - rf'.*(<div id="post-{posts[2]}-comment-{comments[2][2]}">)' + \ + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][0]}">)' + \ + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][1]}">)' + \ + rf'.*(<div id="post-{posts[2]}-comment-{comments[2][2]}">)' + \ rf'.*(<div id="post-{posts[1]}">)' + \ rf'.*(<div id="post-{posts[0]}">)' + \ - rf'.*(<div id="post-{posts[0]}-comment-{comments[0][0]}">)' + rf'.*(<div id="post-{posts[0]}-comment-{comments[0][0]}">)' ) @@ -358,6 +415,7 @@ class UploadPageTest(TestCase): # Negative tests self.assertNotContains(response, "anything") + class DashboardKontributorViewTest(TestCase): def setUp(self): self.client = Client() @@ -419,6 +477,7 @@ class DashboardKontributorViewTest(TestCase): response = self.client.get(self.url) self.assertEqual(response.status_code, 403) + class ProfilAdminTest(TestCase): def setUp(self): self.client = Client() @@ -457,6 +516,7 @@ class ProfilAdminTest(TestCase): # Logout self.client.logout() + class ProfilKontributorTest(TestCase): def setUp(self): self.client = Client() @@ -762,7 +822,7 @@ class LikeMateriTest(TestCase): def test_like_materi(self): # Verify that materi doesn't have any like to start with - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 0) # Like a materi @@ -774,12 +834,12 @@ class LikeMateriTest(TestCase): 'session_id': session_id } ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 1) def test_unlike_materi(self): # Verify that materi doesn't have any like to start with - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 0) # Like a materi @@ -791,7 +851,7 @@ class LikeMateriTest(TestCase): 'session_id': session_id } ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 1) # Unlike a materi @@ -803,12 +863,12 @@ class LikeMateriTest(TestCase): 'session_id': session_id } ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 0) def test_2_client_like_materi(self): # Verify that materi doesn't have any like to start with - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 0) # Client 1 like a materi @@ -820,7 +880,7 @@ class LikeMateriTest(TestCase): 'session_id': session_id } ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 1) # Client 2 like a materi @@ -832,12 +892,12 @@ class LikeMateriTest(TestCase): 'session_id': session_id } ajax_response = Client().post(self.url_like, payload) - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 2) def test_incomplete_like_parameter(self): # Verify that materi doesn't have any like to start with - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 0) # missing session id @@ -848,7 +908,7 @@ class LikeMateriTest(TestCase): } ajax_response = Client().post(self.url_like, payload) ajax_response = json.loads(ajax_response.content) - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 0) self.assertEqual(ajax_response.get("success", None), False) @@ -860,7 +920,7 @@ class LikeMateriTest(TestCase): } ajax_response = Client().post(self.url_like, payload) ajax_response = json.loads(ajax_response.content) - num_of_likes = Like.objects.filter(materi = self.materi1).count() + num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 0) self.assertEqual(ajax_response.get("success", None), False) @@ -906,6 +966,7 @@ class ViewMateriStatissticsTest(TestCase): num_of_views = self.materi1.baca.all().count() self.assertEqual(num_of_views, 2) + class DownloadMateriStatissticsTest(TestCase): def setUp(self): self.contributor_credential = { @@ -947,6 +1008,7 @@ class DownloadMateriStatissticsTest(TestCase): num_of_downloads = self.materi1.unduh.all().count() self.assertEqual(num_of_downloads, 2) + class RevisiMateriTest(TestCase): def setUp(self): self.client = Client() @@ -1019,7 +1081,7 @@ class GenerateDummyCommandTest(TestCase): for num_of_materi in self.material_numbers: call_command("generatedummy", num_of_materi, stdout=self.stdout) self.assertIn( - f"Successfully created {num_of_materi} materi\n", + f"Successfully created {num_of_materi} materi", self.stdout.getvalue() ) @@ -1043,7 +1105,7 @@ class RemoveDummyCommandTest(TestCase): call_command("removedummy", stdout=stdout) - self.assertEqual("Successfully remove all dummy object\n", stdout.getvalue()) + self.assertIn("Successfully remove all dummy object", stdout.getvalue()) self.assertEqual(User.objects.count(), 0) self.assertEqual(Category.objects.count(), 0) self.assertEqual(Materi.objects.count(), 0) @@ -1053,7 +1115,6 @@ class RemoveDummyCommandTest(TestCase): class RatingMateriTest(TestCase): def setUp(self): - self.url = '/administration/' self.contributor_credential = { "email": "kontributor@gov.id", "password": id_generator() @@ -1087,6 +1148,8 @@ class RatingMateriTest(TestCase): status="APPROVE", cover=self.cover, content=self.content).save() self.materi1 = Materi.objects.all()[0] self.materi2 = Materi.objects.all()[1] + self.url_rate = '/materi/rate/' + self.url_materi = '/materi/{}/'.format(self.materi1.id) def test_rating_model_can_be_created_with_proper_parameter(self): Rating(materi=self.materi1, user=self.user_one, score=5).save() @@ -1143,3 +1206,201 @@ class RatingMateriTest(TestCase): def test_score_in_rating_should_not_be_null(self): with self.assertRaises(TypeError): Rating(materi=self.materi1, user=self.user_one).save() + + def test_rating_materi_url_use_add_rating_materi_function(self): + found = resolve(self.url_rate) + self.assertEqual(found.func, add_rating_materi) + + def test_rating_materi_get_method_should_return_403_forbidden(self): + response = self.client.get(self.url_rate) + response_json = json.loads(response.content) + self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "Forbidden") + self.assertEqual(response.status_code, 403) + + def test_rating_materi_post_not_authenticated_should_return_403_forbidden(self): + response = self.client.post(self.url_rate, {'materi_id': 1, 'rating_score': 5}) + response_json = json.loads(response.content) + self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "Forbidden") + self.assertEqual(response.status_code, 403) + + def test_rating_materi_not_authenticated_post_wrong_param_should_return_403_forbidden(self): + for data in [{}, {'materi_id': 1}, {'rating_score': 1}, {'rating_score': 'STRING', 'materi_id': 'STRING'}]: + response = self.client.post(self.url_rate, data) + response_json = json.loads(response.content) + self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "Forbidden") + self.assertEqual(response.status_code, 403) + + def test_rating_materi_authenticated_post_missing_param(self): + self.client.login(**self.user_one_credential) + for data in [{'rating_score': 1}, {'materi_id': 1}, {}]: + response = self.client.post(self.url_rate, data) + response_json = json.loads(response.content) + self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "Missing param") + self.assertEqual(response.status_code, 422) + + def test_rating_materi_authenticated_materi_id_doesnt_exist_should_return_422(self): + self.client.login(**self.user_one_credential) + response = self.client.post(self.url_rate, {'materi_id': 123456, 'rating_score': 5}) + response_json = json.loads(response.content) + self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "Materi does not exist") + self.assertEqual(response.status_code, 422) + + def test_rating_materi_authenticated_param_wrong_data_type_should_return_422(self): + self.client.login(**self.user_one_credential) + response = self.client.post(self.url_rate, {'materi_id': "STRING", 'rating_score': 5}) + response_json = json.loads(response.content) + self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "materi_id must be an integer") + self.assertEqual(response.status_code, 422) + + response = self.client.post(self.url_rate, {'materi_id': 1, 'rating_score': "STRING"}) + response_json = json.loads(response.content) + self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "rating_score must be an integer") + self.assertEqual(response.status_code, 422) + + def test_rating_score_should_be_between_1_and_5(self): + self.client.login(**self.user_one_credential) + for i in range(1, 6): + Rating.objects.all().delete() + response = self.client.post(self.url_rate, {'materi_id': self.materi1.id, 'rating_score': i}) + response_json = json.loads(response.content) + # self.assertEqual(response_json.get("success", None), True) + self.assertEqual(response_json.get("msg", None), "Rating successfully created") + self.assertEqual(response.status_code, 201) + + for i in [-100, -7, -6, -1, 0, 6, 7, 100]: + Rating.objects.all().delete() + response = self.client.post(self.url_rate, {'materi_id': self.materi1.id, 'rating_score': i}) + response_json = json.loads(response.content) + # self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "Rating must be an integer from 1 to 5") + self.assertEqual(response.status_code, 422) + + def test_user_should_not_able_to_rate_materi_twice(self): + self.client.login(**self.user_one_credential) + Rating.objects.all().delete() + self.client.post(self.url_rate, {'materi_id': self.materi1.id, 'rating_score': 1}) + response = self.client.post(self.url_rate, {'materi_id': self.materi1.id, 'rating_score': 2}) + response_json = json.loads(response.content) + # self.assertEqual(response_json.get("success", None), False) + self.assertEqual(response_json.get("msg", None), "Rating already exist") + self.assertEqual(response.status_code, 409) + + def test_user_authenticated_visit_unrated_should_get_0_materi_rating_score_context(self): + self.client.login(**self.user_one_credential) + response = self.client.get(self.url_materi) + self.assertEqual(0, response.context.get('materi_rating_score')) + + def test_user_not_authenticated_visit_unrated_should_get_0_materi_rating_score_context(self): + response = self.client.get(self.url_materi) + self.assertEqual(0, response.context.get('materi_rating_score')) + + def test_user_authenticated_visit_rated_should_get_correct_materi_rating_score_context(self): + self.client.login(**self.user_one_credential) + Rating(materi=self.materi1, user=self.user_one, score=1).save() + response = self.client.get(self.url_materi) + self.assertEqual(1, response.context.get('materi_rating_score')) + +class fileManagementUtilTest(TestCase): + def setUp(self): + self.filename = "image_with_exif_data.gif" + self.file_content = open(settings.BASE_DIR + "/app/test_files/" + + self.filename, "rb").read() + + def test_get_random_filename_isCorrect(self): + generated_name = get_random_filename(self.filename) + + self.assertTrue(generated_name != self.filename) + # 40 from 36 expected name length + 4 from extension + self.assertEqual(len(generated_name), 40) + self.assertTrue(generated_name[-4:] == ".gif") + + def test_remove_image_exifdata_isCorrect(self): + with tempfile.TemporaryDirectory() as d: + image_with_exif_data_path = os.path.join(d, self.filename) + img = open(image_with_exif_data_path, "wb") + img.write(self.file_content) + img.close() + + remove_image_exifdata(image_with_exif_data_path) + sanitized_img = open(image_with_exif_data_path, "rb").read() + + self.assertTrue(len(sanitized_img) < len(self.file_content)) + self.assertTrue(b'<exif:' not in sanitized_img) + +class RequestMateriTest(TestCase): + def setUp(self): + self.client = Client() + self.admin_credential = { + "email": "admin@gov.id", + "password": "passwordtest" + } + self.contributor_credential = { + "email": "kontributor@gov.id", + "password": "passwordtest" + } + self.anonymous_credential = { + "email": "anonymous@gov.id", + "password": "passwordtest" + } + self.admin = get_user_model().objects.create_user( + **self.admin_credential, name="Admin", is_admin=True) + self.contributor = get_user_model().objects.create_user( + **self.contributor_credential, name="Kontributor", is_contributor=True) + self.anonymous = get_user_model().objects.create_user( + **self.anonymous_credential, name="Anonymous" + ) + self.url = "/req-materi/" + self.template_name = "req_materi.html" + + def test_req_materi_url_resolves_to_get_req_materi_view(self): + found = resolve(self.url) + self.assertEqual(found.func.__name__, ReqMateriView.as_view().__name__) + + def test_uses_req_material_template(self): + self.client.login(**self.contributor_credential) + response = self.client.get(self.url) + self.assertTemplateUsed(response, self.template_name) + self.client.logout() + + def test_redirect_to_login_page_is_not_authenticated(self): + response = self.client.get(self.url) + + self.assertEqual(response.status_code, 302) + self.assertEqual(response['location'], '/login/') + + def test_saving_and_retrieving_material_requests(self): + first_material_request = ReqMaterial(title="Material 1").save() + second_material_request = ReqMaterial(title="Material 2").save() + saved_material_request = ReqMaterial.objects.all() + self.assertEqual(saved_material_request.count(), 2) + + def test_can_save_a_POST_request_and_return_correct_response_message(self): + self.client.login(**self.contributor_credential) + + response = self.client.post(self.url, + data={ + 'title': 'Requested Material' + }) + self.assertEqual(ReqMaterial.objects.count(), 1) + + new_material_request = ReqMaterial.objects.first() + self.assertEqual(new_material_request.title, 'Requested Material') + + self.assertIn('Permintaan materi berhasil dikirimkan', response.content.decode()) + self.client.logout() + + def test_given_no_title_should_not_save_request_and_return_correct_response_message(self): + self.client.login(**self.contributor_credential) + + response = self.client.post(self.url) + self.assertEqual(ReqMaterial.objects.count(), 0) + + self.assertIn('Missing parameter', response.content.decode()) + self.client.logout() diff --git a/app/urls.py b/app/urls.py index ad34cef8f45adea6ebd29e63c176a3a5f73371a1..a63a2aabfc996ec656e35b5a86e242018275b464 100644 --- a/app/urls.py +++ b/app/urls.py @@ -4,7 +4,8 @@ from app import views from app.views import (DashboardKontributorView, ProfilKontributorView, SuksesLoginAdminView, SuksesLoginKontributorView, SuntingProfilView, UploadMateriHTML, UploadMateriView, - ProfilAdminView, PostsView, SuntingProfilAdminView, ReqMateriView) + ProfilAdminView, PostsView, SuntingProfilAdminView, + ReqMateriView, KatalogPerKontributorView) urlpatterns = [ path("", views.DaftarKatalog.as_view(), name="daftar_katalog"), @@ -27,5 +28,7 @@ urlpatterns = [ path("posts/", PostsView.as_view(), name='posts'), path("sunting-admin/", SuntingProfilAdminView.as_view(), name="sunting-admin"), path("req-materi/", ReqMateriView.as_view(), name="req-materi"), - path("post-req-materi/", views.post_req_materi, name="post-req-materi"), + path("profil/<str:email>/", KatalogPerKontributorView.as_view(), + name="katalog-per-kontributor"), + path("materi/rate/", views.add_rating_materi, name="rate-materi"), ] diff --git a/app/utils/fileManagementUtil.py b/app/utils/fileManagementUtil.py new file mode 100644 index 0000000000000000000000000000000000000000..d693b01fea0cbd035f0fa6e7ef347cdac1d0373b --- /dev/null +++ b/app/utils/fileManagementUtil.py @@ -0,0 +1,17 @@ +import tempfile, random, datetime, string, hashlib,os +import PIL.Image as Image + +def get_random_filename(f_name): + ext = f_name.split(".")[-1] + name = ''.join(random.choices(string.ascii_lowercase , k=4)) + name += hashlib.md5((datetime.datetime.now().isoformat() + f_name).encode()).hexdigest() + + name = name + "." + ext + return 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 96d6e80af05f1263b41c9962b35f177506ed8c6a..8a5ef310d506b74c73c38bfef6000f9e5b7a1302 100644 --- a/app/views.py +++ b/app/views.py @@ -2,23 +2,21 @@ import mimetypes import os from django.conf import settings -from django.contrib.auth.models import AnonymousUser from django.contrib import messages -from django.core import serializers +from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied, ValidationError -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.core.paginator import Paginator from django.db.models import Q, Count from django.http import (Http404, HttpResponse, HttpResponseRedirect, JsonResponse) -from django.shortcuts import get_object_or_404, redirect, render +from django.shortcuts import get_object_or_404 from django.template import loader -from django.urls import reverse -from django.views.generic import TemplateView, ListView -from .models import Category, Comment, Materi -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.views.generic import TemplateView + from administration.models import VerificationReport from app.forms import SuntingProfilForm, UploadMateriForm -from app.models import Category, Comment, Materi, Like, ViewStatistics, DownloadStatistics, ReqMaterial +from app.models import Category, Comment, Materi, Like, ViewStatistics, DownloadStatistics, ReqMaterial, Rating +from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata from authentication.models import User import django @@ -66,10 +64,10 @@ class DaftarKatalog(TemplateView): elif(getSort == "terbaru"): lstMateri = lstMateri.order_by('-date_created') elif(getSort == "terlama"): - lstMateri = lstMateri.order_by('date_created') + lstMateri = lstMateri.order_by('date_created') elif(getSort == "terpopuler"): lstMateri = lstMateri.annotate(count=Count('like__id')).order_by('-count') - + context["materi_list"] = lstMateri paginator = Paginator(context["materi_list"], 15) page_number = request.GET.get('page') @@ -79,6 +77,26 @@ class DaftarKatalog(TemplateView): context["url"] = url return self.render_to_response(context=context) +class KatalogPerKontributorView(TemplateView): + template_name = "app/katalog_kontri.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + contributor = get_object_or_404(User, email=kwargs["email"]) + context["contributor"] = contributor + return context + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + + materi_list = Materi.objects.filter(status="APPROVE", uploader=context["contributor"]).order_by("date_modified") + + paginator = Paginator(materi_list, 15) + page_number = request.GET.get('page') + materi_list_by_page = paginator.get_page(page_number) + context["materi_list"] = materi_list_by_page + + return self.render_to_response(context=context) class DetailMateri(TemplateView): template_name = "app/detail_materi.html" @@ -100,6 +118,13 @@ class DetailMateri(TemplateView): publishedDate = materi.published_date citationAPA = materi.author+' . (' + publishedDate +') . ' + materi.title +' . '+materi.publisher context["citationAPA"] = citationAPA + context['materi_rating_score'] = 0 + + if self.request.user.is_authenticated: + materi_rating = Rating.objects.filter(materi=materi, user=self.request.user).first() + if materi_rating is not None: + context['materi_rating_score'] = materi_rating.score + return context def get(self, request, *args, **kwargs): @@ -166,6 +191,41 @@ def delete_comment(request, pk_materi, pk_comment): return HttpResponseRedirect(url) +def add_rating_materi(request): + if request.method == 'POST' and request.user.is_authenticated: + + materi_id = request.POST.get('materi_id', None) + rating_score = request.POST.get('rating_score', None) + + if materi_id is None or rating_score is None: + return JsonResponse({"success": False, "msg": "Missing param"}, status=422) + try: + rating_score = int(rating_score) + except ValueError: + return JsonResponse({"success": False, "msg": "rating_score must be an integer"}, status=422) + + try: + materi_id = int(materi_id) + except ValueError: + return JsonResponse({"success": False, "msg": "materi_id must be an integer"}, status=422) + + if rating_score not in range(1, 6): + return JsonResponse({"success": False, "msg": "Rating must be an integer from 1 to 5"}, status=422) + + materi = Materi.objects.filter(pk=materi_id).first() + + if materi is None: + return JsonResponse({"success": False, "msg": "Materi does not exist"}, status=422) + + if Rating.objects.filter(materi=materi, user=request.user).first() is not None: + return JsonResponse({"success": False, "msg": "Rating already exist"}, status=409) + + Rating(materi=materi, user=request.user, score=rating_score).save() + return JsonResponse({"success": True, "msg": "Rating successfully created", "rating_score": rating_score}, + status=201) + return JsonResponse({"success": False, "msg": "Forbidden"}, status=403) + + def download_materi(request, pk): materi = get_object_or_404(Materi, pk=pk) path = materi.content.path @@ -244,7 +304,7 @@ class UploadMateriView(TemplateView): context = self.get_context_data(**kwargs) context["form"] = UploadMateriForm return self.render_to_response(context) - + def validate_file_extension(self, value): ext = os.path.splitext(value.name)[1] # [0] returns path+filename valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.png', '.xlsx', '.xls', '.mp4', '.mp3'] @@ -347,11 +407,26 @@ class SuntingProfilView(TemplateView): raise PermissionDenied(request) current_user = self.request.user + form = SuntingProfilForm( request.POST, request.FILES, instance=current_user) if form.is_valid(): current_user.default_profile_picture = True - form.save() + + # Removing exifdata from profile picture on upload + if request.FILES: + f_name = request.FILES['profile_picture'].name + f_name = get_random_filename(f_name) + f_path = settings.MEDIA_ROOT + "/" + f_name + request.FILES['profile_picture'].name = f_name + + form = SuntingProfilForm( + request.POST, request.FILES, instance=current_user) + form.save() + + remove_image_exifdata(f_path) + else: + form.save() return HttpResponseRedirect("/profil/") else: context = self.get_context_data(**kwargs) @@ -386,11 +461,26 @@ class SuntingProfilAdminView(TemplateView): raise PermissionDenied(request) current_user = self.request.user + form = SuntingProfilForm( request.POST, request.FILES, instance=current_user) if form.is_valid(): current_user.default_profile_picture = True - form.save() + + # Removing exifdata from profile picture on upload + if request.FILES: + f_name = request.FILES['profile_picture'].name + f_name = get_random_filename(f_name) + f_path = settings.MEDIA_ROOT + "/" + f_name + request.FILES['profile_picture'].name = f_name + + form = SuntingProfilForm( + request.POST, request.FILES, instance=current_user) + form.save() + + remove_image_exifdata(f_path) + else: + form.save() return HttpResponseRedirect("/profil-admin/") else: context = self.get_context_data(**kwargs) @@ -403,9 +493,6 @@ class ReqMateriView(TemplateView): def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated == False: return HttpResponseRedirect("/login/") - #raise PermissionDenied(request) - # else if not request.user.is_admin: - # raise PermissionDenied(request) return super(ReqMateriView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -415,39 +502,14 @@ class ReqMateriView(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) - - current_user = self.request.user - context["user"] = current_user - - context["form"] = SuntingProfilForm(instance=current_user) return self.render_to_response(context) def post(self, request, *args, **kwargs): - if request.user.is_authenticated == False: - raise PermissionDenied(request) - - current_user = self.request.user - form = SuntingProfilForm( - request.POST, request.FILES, instance=current_user) - if form.is_valid(): - current_user.default_profile_picture = True - form.save() - return HttpResponseRedirect("/profil-admin/") - else: - context = self.get_context_data(**kwargs) - context["form"] = form - return self.render_to_response(context) - -def post_req_materi(request): - if request.method == 'POST': - #return JsonResponse({"success": True, "msg": "Permintaan materi berhasil dikirimkan"}) title = request.POST.get('title', None) if title is None: return JsonResponse({"success": False, "msg": "Missing parameter"}) ReqMaterial(title=title).save() return JsonResponse({"success": True, "msg": "Permintaan materi berhasil dikirimkan"}) - else: - return JsonResponse({"success": False, "msg": "Unsuported method"}) class SuksesLoginKontributorView(TemplateView): @@ -525,7 +587,7 @@ class PostsView(TemplateView): class RevisiMateriView(TemplateView): template_name = "revisi.html" - def dispatch(self, request, *args, **kwargs): + def dispatch(self, request, *args, **kwargs): if not request.user.is_contributor: raise PermissionDenied(request) return super(RevisiMateriView, self).dispatch(request, *args, **kwargs) diff --git a/authentication/tests.py b/authentication/tests.py index 54d5cf9f8bcdbd05bf07fed0263ada910710ad77..c63f6f98e418fa4c3ee39eb0f1c186fd030fdda4 100644 --- a/authentication/tests.py +++ b/authentication/tests.py @@ -74,7 +74,7 @@ class UserModelTest(TestCase): class LoginPageContributorTest(TestCase): def setUp(self): self.client = Client() - self.admin = User.objects.create_contributor(email="kontributor@gov.id", + self.kontributor = User.objects.create_contributor(email="kontributor@gov.id", password="kontributor") self.url = "/login/" self.view = Login @@ -86,34 +86,34 @@ class LoginPageContributorTest(TestCase): "wrong_email_or_password": "Email atau Password anda salah.", } - def test_login_admin_view(self): + def test_login_kontributor_view(self): found = resolve(self.url) self.assertEqual(found.func.__name__, self.view.as_view().__name__) - def test_login_admin_template(self): + def test_login_kontributor_template(self): # Test response = self.client.get(self.url) self.assertTemplateUsed(response, self.template_name) - def test_login_admin_url(self): + def test_login_kontributor_url(self): # Test response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - def test_login_admin_title(self): + def test_login_kontributor_title(self): response = self.client.get(self.url) # Positive tests self.assertContains(response, "Halo, kontributor") - def test_login_admin_form_field(self): + def test_login_kontributor_form_field(self): response = self.client.get(self.url) # Positive tests self.assertContains(response, "Email") self.assertContains(response, "Kata Sandi") - def test_admin_login_missing_email_or_password(self): + def test_kontributor_login_missing_email_or_password(self): response = self.client.post(self.url, {"email": "kontributor@gov.id"}) self.assertIn("error_message", response.context_data) self.assertIn(self.error_message["empty_email_or_password"], @@ -123,7 +123,7 @@ class LoginPageContributorTest(TestCase): self.assertIn(self.error_message["empty_email_or_password"], response.context_data["error_message"]) - def test_admin_login_wrong_email_or_password(self): + def test_kontributor_login_wrong_email_or_password(self): # Wrong password response = self.client.post( self.url, {"email": "kontributor@gov.id", "pass": "kontributor1"}) @@ -143,7 +143,7 @@ class LoginPageContributorTest(TestCase): self.assertIn(self.error_message["wrong_email_or_password"], response.context_data["error_message"]) - def test_admin_login(self): + def test_kontributor_login(self): # 302 meaning successful login and redirected expected_redirect_url = "/sukses-kontributor/" response = self.client.post( @@ -151,6 +151,19 @@ class LoginPageContributorTest(TestCase): self.assertEqual(302, response.status_code) self.assertEqual(response.url, expected_redirect_url) + def test_kontributor_visit_login_after_auth(self): + # 302 meaning successful login and redirected + expected_redirect_url = "/sukses-kontributor/" + response = self.client.post( + self.url, self.login_credential) + self.assertEqual(302, response.status_code) + self.assertEqual(response.url, expected_redirect_url) + response = self.client.get( + self.url + ) + self.assertEqual(302, response.status_code) + self.assertEqual(response.url, expected_redirect_url) + class LoginPageAdminTest(TestCase): def setUp(self): @@ -230,3 +243,17 @@ class LoginPageAdminTest(TestCase): self.url, self.login_credential) 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/" + response = self.client.post( + self.url, self.login_credential) + self.assertEqual(302, response.status_code) + self.assertEqual(response.url, expected_redirect_url) + response = self.client.get( + self.url + ) + self.assertEqual(302, response.status_code) + self.assertEqual(response.url, expected_redirect_url) diff --git a/authentication/views.py b/authentication/views.py index 6f67ccb35e12ab7371f48a33df5569fdf999da3c..f5e7d82c2f80b0a0f57d4fbc83819f5fddcf60e6 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -4,6 +4,16 @@ from django.views.generic import TemplateView class Login(TemplateView): + + def dispatch(self, request, *args, **kwargs): + if request.user.is_authenticated: + if request.user.is_admin: + redirect_to = "/sukses-admin/" + elif request.user.is_contributor: + redirect_to = "/sukses-kontributor/" + return HttpResponseRedirect(redirect_to) + return super(Login, self).dispatch(request, *args, **kwargs) + def get_template_names(self): if self.request.path == "/login_admin/": template_name = "login_admin.html"