diff --git a/app/forms.py b/app/forms.py index aafd9baf3412973d7942c2e17c1a1aecd54a87d2..5b8b3438c71bbe7efcb9dbee7746662563efb42b 100644 --- a/app/forms.py +++ b/app/forms.py @@ -28,6 +28,8 @@ class UploadMateriForm(forms.ModelForm): if (field_name == "categories"): continue field.widget.attrs["class"] = "form-control" + field.widget.attrs["placeholder"] = field.initial + field.initial = "" self.fields['categories'].widget.attrs.update({'class' : "native"}) @@ -57,7 +59,7 @@ class SuntingProfilForm(forms.ModelForm): class RatingContributorForm(forms.ModelForm): class Meta: model = RatingContributor - fields = ['score', 'user'] + fields = ['score', 'user', 'contributor'] SCORE_CHOICE = ( ('', 'Select score'), ('1', '1'), # First one is the value of select option and second is the displayed value in option @@ -67,6 +69,7 @@ class RatingContributorForm(forms.ModelForm): ('5', '5'), ) widgets = { - 'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control'},), - 'user': forms.HiddenInput() + 'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control', 'id':'form-rating'},), + 'user': forms.HiddenInput(), + 'contributor': forms.HiddenInput() } diff --git a/app/migrations/0027_auto_20201030_1648.py b/app/migrations/0027_auto_20201030_1648.py new file mode 100644 index 0000000000000000000000000000000000000000..2c3a8b0a4d27b16db4d83a5dbd283d5556565f13 --- /dev/null +++ b/app/migrations/0027_auto_20201030_1648.py @@ -0,0 +1,31 @@ +# Generated by Django 3.1 on 2020-10-30 09:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('app', '0026_submitvisitor'), + ] + + operations = [ + migrations.AddField( + model_name='ratingcontributor', + name='contributor', + field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, related_name='contributor', to='authentication.user'), + preserve_default=False, + ), + migrations.AlterField( + model_name='ratingcontributor', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterUniqueTogether( + name='ratingcontributor', + unique_together={('contributor', 'user')}, + ), + ] diff --git a/app/models.py b/app/models.py index d68d767677ad268dc006753aab227c960966f8c2..661cde6be8d78d8b5d785ab31b34c8764c7ecd18 100644 --- a/app/models.py +++ b/app/models.py @@ -253,13 +253,17 @@ class Rating(models.Model): class RatingContributor(models.Model): timestamp = models.DateTimeField(auto_now=True) score = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)]) - user = models.ForeignKey(User, on_delete=models.CASCADE) + contributor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='contributor') + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user') def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if 1 <= self.score <= 5: super().save(force_insert, force_update, using, update_fields) else: raise ValidationError("Rating score must be integer between 1-5") + + class Meta: + unique_together = ["contributor", "user"] class SubscribeModel(models.Model): sys_id = models.AutoField(primary_key=True, null=False, blank=True) diff --git a/app/templates/app/base_dashboard.html b/app/templates/app/base_dashboard.html index b8281931e7b8fae10fe76dd0477fee586206fcb2..c74bd589eb745d75b89cb9f1560428d76b24e4fb 100644 --- a/app/templates/app/base_dashboard.html +++ b/app/templates/app/base_dashboard.html @@ -24,6 +24,7 @@ <!-- Custom styles for this page --> <link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet"> {% block stylesheets %}{% endblock %} + {% block scripts %}{% endblock %} </head> <body id="page-top" style="font-family: 'Poppins', sans-serif;"> diff --git a/app/templates/app/base_profile.html b/app/templates/app/base_profile.html index 6dc27646954f8d5b97e55725285ec708edfe6233..081dc2ef7b2c7c987d31d20c31301d3e81849d8b 100644 --- a/app/templates/app/base_profile.html +++ b/app/templates/app/base_profile.html @@ -24,6 +24,8 @@ <!-- Custom styles for this page --> <link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet"> {% block stylesheets %}{% endblock %} + + <script type="text/javascript" src="{% static 'vendor/jquery/jquery-3.2.1.min.js' %}"></script> </head> <body id="page-top" style="font-family: 'Poppins', sans-serif;"> diff --git a/app/templates/app/detail_materi.html b/app/templates/app/detail_materi.html index 278c5decd9fd975e8bdac221ee5b7f70777737fa..72d758c9e304adc8fbdae477fbbc9e3709fcd450 100644 --- a/app/templates/app/detail_materi.html +++ b/app/templates/app/detail_materi.html @@ -33,7 +33,6 @@ div.review { <!-- Topbar Navbar --> <ul class="navbar-nav ml-auto"> - {% if request.user.is_contributor %} {% if materi_data.status == "DISAPPROVE" %} <li class="nav-item black-text"> @@ -164,6 +163,14 @@ div.review { <p class="info-content">{{materi_data.content.size|filesizeformat}}</p> </dd> </div> + <div class="info" id="1"> + <dl class="col col-4"> + <dt class="info-name">Jumlah Like</dt> + </dl> + <dd> + <p class="info-content">{{materi_data.like_count}}</p> + </dd> + </div> <div class="info" id="1"> <dl class="col col-4"> <dt class="info-name">Jumlah Download</dt> @@ -225,11 +232,11 @@ div.review { <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> + <button id="thumb" class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded" onClick="postLike()"><i id="thumbIcon" + aria-hidden="true" class="fas fa-thumbs-up"></i><span> Disukai</span></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> + <button id="thumb" class="btn btn-link btn-book shadow-sm p-2 mr-2 bg-white rounded" onClick="postLike()"><i id="thumbIcon" + aria-hidden="true" class="far fa-thumbs-up"></i><span> Sukai</span></button> {% endif %} {% if user.is_authenticated %} @@ -331,7 +338,7 @@ div.review { <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 %} + {% if has_liked_comment.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> @@ -341,7 +348,7 @@ div.review { </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 %} + {% if has_disliked_comment.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> @@ -509,8 +516,7 @@ div.review { } </script> <script> - $('#thumb').click(function () { - + function postLike() { $.ajaxSetup({ beforeSend: function (xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { @@ -528,7 +534,7 @@ div.review { success: LikePost, dataType: 'html' }); - }); + } function postLikeComment(comment_id) { $.ajaxSetup({ @@ -599,12 +605,15 @@ div.review { function LikePost(data, jqXHR) { var data = $.parseJSON(data) + var likeCount = parseInt($('.info-content')[6].textContent) if (data['liked']) { $('#thumbIcon').removeClass("fas fa-thumbs-up").addClass('far fa-thumbs-up') - document.getElementById("thumb").firstChild.data = " Sukai" + $('#thumb>span').text(" Sukai") + $('.info-content')[6].textContent = likeCount - 1 } else { $('#thumbIcon').removeClass("far fa-thumbs-up").addClass('fas fa-thumbs-up') - document.getElementById("thumb").firstChild.data = " Disukai" + $('#thumb>span').text(" Disukai") + $('.info-content')[6].textContent = likeCount + 1 } } diff --git a/app/templates/app/katalog_kontri.html b/app/templates/app/katalog_kontri.html index 3dc7915a72348cc02da5cf53aff3dc4143963b70..38d7e95fe9db70860a996bdacc5c1cec0ece7e44 100644 --- a/app/templates/app/katalog_kontri.html +++ b/app/templates/app/katalog_kontri.html @@ -1,41 +1,33 @@ {% extends "base.html" %} {% load static %} {% load humanize %} -{% block title %}Digipus - {% endblock %} +{% block title %}Digipus - {{ contributor.name }}'s' Profile{% endblock %} {% block header %} +<meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> -<!DOCTYPE html> -<html lang="en"> +<link href="../../static/app/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet"> +<link href="../../static/app/css/heroic-features.css" rel="stylesheet"> -<head> - <title>Digipus Home</title> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> +<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 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 '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 '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 '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="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="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> - <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}"> - <link rel="stylesheet" type="text/css" href="{% static 'vendor/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' %}"> +{% endblock header %} - <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> - - - {% endblock header %} - {% block content %} - -</head> +{% block content %} <body style="background-color: #f8f8f8;"> <div class="container main"> @@ -87,10 +79,28 @@ </table> </div> </div> - <h4 id="rating">Rating: {% if avg_rating.avg_rating %}{{ avg_rating.avg_rating|floatformat:"2"|intcomma }}{% else %}0{% endif %}</h4> - <h6>oleh: {{ count_rating }} orang</h6> - <div class="row"><form method="post">{{ form_rating }} {% csrf_token %} - <button type="submit" class="form-control" style="margin-top: 1rem">Submit</button></form> </div> + <div class="row mx-5 mt-3"> + <h4 id="rating">Rating: {% if avg_rating.avg_rating %}{{ avg_rating.avg_rating|floatformat:"2"|intcomma }}{% else %}0{% endif %} + </h4> + </div> + <div class="row mx-5 mt-2"> + <h6>oleh: {{ count_rating }} orang</h6> + </div> + <div class="row mx-5 mt-3"> + {% if not user.is_authenticated %} + <h4><a href="{% url 'login' %}">Kamu harus login untuk memberi rating</a></h4> + {% else %} + <form method="post" class="form-inline">{{ form_rating }} {% csrf_token %} + {% if has_rated %} + <button class="form-control mx-3" id="updateButton">Update</button> + <button class="btn btn-danger" id="deleteButton">Delete Rating</button> + {% else %} + <button type="submit" class="form-control mx-3">Submit</button> + {% endif %} + </form> + {% endif %} + + </div> </div> </header> @@ -134,7 +144,73 @@ </div> </div> </body> - -</html> - +{% endblock %} +{% block extra_scripts %} +<script type="text/javascript"> + // using jQuery + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + var csrftoken = getCookie('csrftoken'); + + function csrfSafeMethod(method) { + // these HTTP methods do not require CSRF protection + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); + } + + function reload () { + window.location = '' + }; + + $('#deleteButton').click(function () { + console.log("anjing") + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + $.ajax({ + type: 'POST', + url: "{% url 'katalog-per-kontributor' contributor.email%}", + data: { + 'delete': true + }, + success: reload, + dataType: 'html' + }); + }); + + $('#updateButton').click(function () { + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + $.ajax({ + type: 'POST', + url: "{% url 'katalog-per-kontributor' contributor.email%}", + data: { + 'update': true, + 'score': $('#form-rating').val() + }, + success: reload, + dataType: 'html' + }); + }); +</script> {% endblock %} \ No newline at end of file diff --git a/app/templates/app/katalog_materi.html b/app/templates/app/katalog_materi.html index 4745480bd96eef5ba9a4faea35a02495c272bcc2..93c719b560ad93d582913d20205b0d125f799565 100644 --- a/app/templates/app/katalog_materi.html +++ b/app/templates/app/katalog_materi.html @@ -57,10 +57,9 @@ <div class="container"> <div class="row header"> <div class="col"> - <a href= "/subscribeform"><img src="{% static 'images/sub-btn.png' %}" style="height:100px"></a> <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 }}'> @@ -125,6 +124,32 @@ <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample"> <div class="card-body"> + <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> + <li> + <a href="?sort=jumlah_tampilan">jumlah tampilan</a> + </li> + </ul> </div> </div> </div> diff --git a/app/templates/req_materi.html b/app/templates/req_materi.html index f4d071bab3f96663ae5575ecc911a502a5ad77ff..9f2eed0e638eebd49c18321ff37a263adfda4337 100644 --- a/app/templates/req_materi.html +++ b/app/templates/req_materi.html @@ -72,8 +72,26 @@ </div> </div> </header> - - + {% if requested_material %} + <table class="table" aria-describedby="Requested Material"> + <thead> + <tr> + <th scope="col">No</th> + <th scope="col">Title</th> + <th scope="col">Date</th> + </tr> + </thead> + <tbody> + {% for material in requested_material %} + <tr> + <td>{{ forloop.counter }}</td> + <td>{{ material.title }}</td> + <td>{{ material.timestamp }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} </div> <!-- /.container --> diff --git a/app/templates/sunting.html b/app/templates/sunting.html index 62171b1654cd60c9c9a1b262ccba0289578b65c4..02dfee9e0c555e4348098396e4b15ba87fdfb6a5 100644 --- a/app/templates/sunting.html +++ b/app/templates/sunting.html @@ -3,18 +3,18 @@ {% block title %} <title>Sunting Profil | Digipus</title> -{% endblock %} +{% endblock %} {% block content %} <div class="container"> <div class="col-20"> <h1 class="mt-2"> {% if user.is_admin %} - Sunting Profil Admin + Sunting Profil Admin {% elif user.is_contributor %} - Sunting Profil Kontributor + Sunting Profil Kontributor {% else %} - Sunting Profil User + Sunting Profil User {% endif %} </h1> <hr class="mt-0 mb-4"> @@ -24,6 +24,26 @@ <div class="col-md-6"> <div class="fieldWrapper"> {{ field.label_tag }} {{ field }} + + {% if field.name == 'email' %} + <div class="my-3"> + + {% if user.is_email_verified %} + <span class="text-success"> + Email Status: Verified + </span> + {% else %} + <span class="text-danger"> + Email Status: Unverified + <button class="btn btn-success" id='verify-button' + style="background-color: #615CFD; border-color: #615CFD;"> + Verify + </button> + </span> + {% endif %} + </div> + {% endif %} + {% if field.errors %} <span class="text-danger"> {{ field.errors }} @@ -33,15 +53,41 @@ <p class="help">{{ field.help_text|safe }}</p> {% endif %} </div> + </div> {% endfor %} <div class="form-margin"></div> <div class="col-md-6"> <div class="fieldWrapper"> - <button type="submit" class="btn btn-success btn-edit" style="background-color: #615CFD; border-color: #615CFD;">Simpan</button> + <button type="submit" class="btn btn-success btn-edit" + style="background-color: #615CFD; border-color: #615CFD;">Simpan</button> </div> </div> </form> </div> </div> + +<script type="text/javascript"> + $(document).ready(function () { + const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value; + $('#verify-button').click(function () { + $.ajax({ + url: '/registrasi/send-verify-email', + headers: { + 'X-CSRFToken': csrftoken + }, + method: 'POST', + data: { + email: $('#id_email').val() + }, + success: function (data, status) { + alert(data) + }, + }) + + return false; + }); + }); +</script> + {% endblock %} \ No newline at end of file diff --git a/app/templates/unggah.html b/app/templates/unggah.html index 5f9a0c1da2c7b2303e9c869fe6704d35bb22e4d7..b6668bb76ef6c9b73303b640bff5fb20d1168478 100644 --- a/app/templates/unggah.html +++ b/app/templates/unggah.html @@ -5,6 +5,10 @@ <title>Unggah Materi | Digipus</title> {% endblock %} +{% block scripts %} +<script src="{% static 'js/upload-preview.js' %}"></script> +{% endblock %} + {% block content %} <div class="container"> <div class="col-20"> @@ -36,6 +40,17 @@ {% if field.help_text %} <p class="help">{{ field.help_text|safe }}</p> {% endif %} + {% if field.name == 'cover' %} + <div hidden id="frame_cover_parent" class="my-3"> + <div class="mb-2">Preview Cover:</div> + <img id="frame_cover" class="constraint-frame" alt="Cover Preview" /> + </div> + {% elif field.name == 'content' %} + <div hidden id="frame_content_parent" class="my-3"> + <div class="mb-2">Preview Content:</div> + <embed id="frame_content" width="550px" height="550px" /> + </div> + {% endif %} </div> </div> {% endfor %} diff --git a/app/tests.py b/app/tests.py index 2123295afe447e5fa077dc7871e1b80030d685e9..2a0a6e069a9d9297915b0907ee1b1b932c5b6e6a 100644 --- a/app/tests.py +++ b/app/tests.py @@ -337,7 +337,9 @@ class DetailMateriTest(TestCase): self.download_url1 = self.url + "unduh" self.url2 = "/materi/" + str(self.materi2.id) + "/" self.download_url2 = self.url2 + "unduh" + self.like_url = '/materi/like/' self.dcount_info_name = "Jumlah Download" + self.lcount_info_name = "Jumlah Like" self.materi_with_published_date = Materi.objects.create(title="Materi 1", author="Agas", uploader=self.contributor, publisher="Kelas SC", descriptions="Deskripsi Materi 1", @@ -429,7 +431,7 @@ class DetailMateriTest(TestCase): session_id = response.context["session_id"] payload = {"comment": comment, "session_id": session_id} - ajax_response = self.client.post("/comment/dislike/", payload) + self.client.post("/comment/dislike/", payload) num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment).count() self.assertEqual(num_of_comment_dislikes, 0) @@ -444,7 +446,7 @@ class DetailMateriTest(TestCase): session_id = response.context["session_id"] payload = {"comment": comment, "session_id": session_id} - ajax_response = self.client.post("/comment/like/", payload) + self.client.post("/comment/like/", payload) num_of_comment_likes = LikeComment.objects.filter(comment=comment).count() self.assertEqual(num_of_comment_likes, 0) @@ -459,9 +461,9 @@ class DetailMateriTest(TestCase): session_id = response.context["session_id"] payload = {"comment": comment, "session_id": session_id} - ajax_response = self.client.post("/comment/dislike/", payload) + self.client.post("/comment/dislike/", payload) - ajax_response = self.client.post("/comment/dislike/", payload) + self.client.post("/comment/dislike/", payload) num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment, session_id=session_id).count() self.assertEqual(num_of_comment_dislikes, 0) @@ -476,9 +478,9 @@ class DetailMateriTest(TestCase): session_id = response.context["session_id"] payload = {"comment": comment, "session_id": session_id} - ajax_response = self.client.post("/comment/like/", payload) + self.client.post("/comment/like/", payload) - ajax_response = self.client.post("/comment/like/", payload) + self.client.post("/comment/like/", payload) num_of_comment_likes = LikeComment.objects.filter(comment=comment, session_id=session_id).count() self.assertEqual(num_of_comment_likes, 0) @@ -620,7 +622,7 @@ class DetailMateriTest(TestCase): url = self.url self.client.login(**self.admin_credential) self.client.post(url, {"comment": "This is new comment by Anonymous"}) - deleteURL = "/delete/" + str(self.materi1.id) + "/" + str( + delete_url = "/delete/" + str(self.materi1.id) + "/" + str( Comment.objects.get(comment="This is new comment by Anonymous").id) if is_admin: self.client.login(**self.admin_credential) @@ -628,7 +630,7 @@ class DetailMateriTest(TestCase): self.client.login(**self.contributor_credential) if not is_admin and not is_contributor: self.client.login(**self.anonymous_credential) - response = self.client.get(deleteURL) + response = self.client.get(delete_url) return response def test_delete_comments_by_admin(self): @@ -903,6 +905,56 @@ class DetailMateriTest(TestCase): self.check_materi_info_in_html(self.dcount_info_name, dcount_materi2, html2) self.check_materi_info_not_in_html(self.dcount_info_name, dcount_materi1, html2) + def test_like_count_displayed_on_template_when_no_like(self): + response = self.client.get(self.url) + html = response.content.decode("utf-8") + self.check_materi_info_in_html(self.lcount_info_name, 0, html) + + def test_like_count_displayed_on_template_when_single_like(self): + payload = { + 'materi_id': self.materi1.id, + 'session_id': "dummysession" + } + self.client.post(self.like_url, payload) + + response = self.client.get(self.url) + html = response.content.decode("utf-8") + self.check_materi_info_in_html(self.lcount_info_name, 1, html) + + def test_like_count_displayed_on_template_when_multiple_like(self): + payload1 = { + 'materi_id': self.materi1.id, + 'session_id': "dummysession1" + } + payload2 = { + 'materi_id': self.materi1.id, + 'session_id': "dummysession2" + } + self.client.post(self.like_url, payload1) + self.client.post(self.like_url, payload2) + + response = self.client.get(self.url) + html = response.content.decode("utf-8") + self.check_materi_info_in_html(self.lcount_info_name, 2, html) + + def test_like_count_displayed_on_template_when_unlike(self): + payload = { + 'materi_id': self.materi1.id, + 'session_id': "dummysession" + } + + # Like materi once + self.client.post(self.like_url, payload) + response = self.client.get(self.url) + html = response.content.decode("utf-8") + self.check_materi_info_in_html(self.lcount_info_name, 1, html) + + # Like it again, resulting in unlike + self.client.post(self.like_url, payload) + response = self.client.get(self.url) + html = response.content.decode("utf-8") + self.check_materi_info_in_html(self.lcount_info_name, 0, html) + class PostsViewTest(TestCase): @classmethod @@ -1303,7 +1355,7 @@ class UploadExcelPageTest(TestCase): file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths, categories=categories) with open(file_name, 'rb') as fp: - response = self.client.post("/unggah_excel/", {'excel': fp}) + self.client.post("/unggah_excel/", {'excel': fp}) title = data_frame['Title'][0] materi = Materi.objects.get(title=title) @@ -1569,18 +1621,7 @@ class SuksesLoginKontributorTest(TestCase): # Logout self.client.logout() - def test_sukses_login_kontributor_url(self): - # Login - self.client.login(email="kontributor@gov.id", - password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_sukses_login_kontributor_access(self): - # Kontributor + def test_sukses_login_kontributor_url_access(self): # Login self.client.login(email="kontributor@gov.id", password="kontributor") @@ -1616,18 +1657,7 @@ class SuksesLoginAdminTest(TestCase): # Logout self.client.logout() - def test_sukses_login_admin_url(self): - # Login - self.client.login(email="admin@gov.id", - password="admin") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_sukses_login_admin_access(self): - # Kontributor + def test_sukses_login_admin_url_access(self): # Login self.client.login(email="admin@gov.id", password="admin") @@ -1682,7 +1712,7 @@ class LikeMateriTest(TestCase): 'materi_id': materi_id, 'session_id': session_id } - ajax_response = Client().post(self.url_like, payload) + Client().post(self.url_like, payload) num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 1) @@ -1699,7 +1729,7 @@ class LikeMateriTest(TestCase): 'materi_id': materi_id, 'session_id': session_id } - ajax_response = Client().post(self.url_like, payload) + Client().post(self.url_like, payload) num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 1) @@ -1711,7 +1741,7 @@ class LikeMateriTest(TestCase): 'materi_id': materi_id, 'session_id': session_id } - ajax_response = Client().post(self.url_like, payload) + Client().post(self.url_like, payload) num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 0) @@ -1728,7 +1758,7 @@ class LikeMateriTest(TestCase): 'materi_id': materi_id, 'session_id': session_id } - ajax_response = Client().post(self.url_like, payload) + Client().post(self.url_like, payload) num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 1) @@ -1740,7 +1770,7 @@ class LikeMateriTest(TestCase): 'materi_id': materi_id, 'session_id': session_id } - ajax_response = Client().post(self.url_like, payload) + Client().post(self.url_like, payload) num_of_likes = Like.objects.filter(materi=self.materi1).count() self.assertEqual(num_of_likes, 2) @@ -1801,17 +1831,17 @@ class ViewMateriStatissticsTest(TestCase): # Test single view def test_count_one_materi_view(self): - response = self.client.get(self.url) + self.client.get(self.url) num_of_views = self.materi1.baca.all().count() self.assertEqual(num_of_views, 1) # Test more than one view def test_count_more_than_one_materi_view(self): - response = self.client.get(self.url) + self.client.get(self.url) num_of_views = self.materi1.baca.all().count() self.assertEqual(num_of_views, 1) - response = Client().get(self.url) + Client().get(self.url) num_of_views = self.materi1.baca.all().count() self.assertEqual(num_of_views, 2) @@ -1843,17 +1873,17 @@ class DownloadMateriStatissticsTest(TestCase): # Test single download def test_count_one_materi_download(self): - response = self.client.get(self.url) + self.client.get(self.url) num_of_downloads = self.materi1.unduh.all().count() self.assertEqual(num_of_downloads, 1) # Test more than one download def test_count_more_than_one_materi_download(self): - response = self.client.get(self.url) + self.client.get(self.url) num_of_downloads = self.materi1.unduh.all().count() self.assertEqual(num_of_downloads, 1) - response = Client().get(self.url) + Client().get(self.url) num_of_downloads = self.materi1.unduh.all().count() self.assertEqual(num_of_downloads, 2) @@ -1899,7 +1929,7 @@ class RevisiMateriTest(TestCase): # Logout self.client.logout() - def test_revisi_materi_url(self): + def test_revisi_materi_url_access(self): # Login self.client.login(**self.contributor_credential) # Test @@ -1908,15 +1938,6 @@ class RevisiMateriTest(TestCase): # Logout self.client.logout() - def test_revisi_materi_access(self): - # Kontributor - # Login - self.client.login(**self.contributor_credential) - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() class GenerateDummyCommandTest(TestCase): @@ -2155,7 +2176,7 @@ class RatingMateriTest(TestCase): response = self.client.get(self.url_materi) self.assertEqual(1, response.context.get('materi_rating_score')) -class fileManagementUtilTest(TestCase): +class FileManagementUtilTest(TestCase): def setUp(self): self.filename = "image_with_exif_data.gif" self.file_content = open(settings.BASE_DIR + "/app/test_files/" + @@ -2224,8 +2245,8 @@ class RequestMateriTest(TestCase): 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() + ReqMaterial(title="Material 1").save() + ReqMaterial(title="Material 2").save() saved_material_request = ReqMaterial.objects.all() self.assertEqual(saved_material_request.count(), 2) @@ -2253,118 +2274,255 @@ class RequestMateriTest(TestCase): self.assertIn('Missing parameter', response.content.decode()) self.client.logout() + def test_displays_all_requested_material(self): + self.client.login(**self.contributor_credential) + ReqMaterial(title="Material 1").save() + ReqMaterial(title="Material 2").save() + response = self.client.get(self.url) + self.assertContains(response, 'Material 1') + self.assertContains(response, 'Material 2') + class RatingContributorTest(TransactionTestCase): def setUp(self): + self.admin_credential = { + "email": "admin@gov.id", + "password": "passwordtest" + } self.contributor_credential = { "email": "kontributor@gov.id", "password": id_generator() } + 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 = f"/profil/{self.contributor.email}/" def test_add_rating_contributor(self): - RatingContributor.objects.create(score=3, user=self.contributor) + RatingContributor.objects.create(score=3, contributor=self.contributor, user=self.anonymous) self.assertEqual(1, RatingContributor.objects.count()) def test_add_rating_contributor_should_failed_when_negative(self): with self.assertRaises(ValidationError): - RatingContributor.objects.create(score=-1, user=self.contributor) + RatingContributor.objects.create(score=-1, contributor=self.contributor, user=self.anonymous) self.assertEqual(0, RatingContributor.objects.count()) def test_add_rating_contributor_should_failed_when_bigger_than_five(self): with self.assertRaises(ValidationError): - RatingContributor.objects.create(score=6, user=self.contributor) + RatingContributor.objects.create(score=6, contributor=self.contributor, user=self.anonymous) self.assertEqual(0, RatingContributor.objects.count()) def test_submit_form_correct_rating_contributor_should_added(self): - url = f"/profil/{self.contributor.email}/" - self.client.post(url, data={"user": self.contributor.id, "score": 5}) - self.assertEqual(1, RatingContributor.objects.filter(user=self.contributor.id).count()) - self.client.post(url, data={"user": self.contributor.id, "score": 1}) - self.assertEqual(2, RatingContributor.objects.filter(user=self.contributor.id).count()) + self.client.login(**self.anonymous_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 5}) + self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor.id).count()) + self.client.logout() + + self.client.login(**self.admin_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": 1}) + self.assertEqual(2, RatingContributor.objects.filter(contributor=self.contributor.id).count()) def test_submit_form_not_correct_rating_contributor_should__not_added(self): - url = f"/profil/{self.contributor.email}/" - self.client.post(url, data={"user": self.contributor.id, "score": 6}) - self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count()) - self.client.post(url, data={"user": self.contributor.id, "score": 0}) - self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count()) - -<<<<<<< HEAD - class EmailTest(TestCase): - def test_send_email(self): - send_gmail('bass_of_ncreep@yahoo.com', 'Here is the message') - mail.send_mail('Subject here', 'Here is the message.', - 'pmplmailer@gmail.com', ['bass_of_ncreep@yahoo.com'], - fail_silently=False) - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, 'Subject here') - -======= + self.client.login(**self.anonymous_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 6}) + self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor.id).count()) + self.client.logout() + + self.client.login(**self.admin_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": 0}) + self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor.id).count()) + def test_average_rating_score_empty(self): - url = f"/profil/{self.contributor.email}/" - response = self.client.get(url) + response = self.client.get(self.url) self.assertTemplateUsed(response=response, template_name="app/katalog_kontri.html") self.assertContains(response=response, text="Rating: 0", count=1) self.assertContains(response=response, text="oleh: 0 orang", count=1) def test_average_rating_correct(self): - url = f"/profil/{self.contributor.email}/" - self.client.post(url, data={"user": self.contributor.id, "score": 5}) - response = self.client.get(url) + self.client.login(**self.anonymous_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 5}) + response = self.client.get(self.url) self.assertContains(response=response, text="Rating: 5", count=1) self.assertContains(response=response, text="oleh: 1 orang", count=1) def test_average_rating_form_incorrect_correct(self): - url = f"/profil/{self.contributor.email}/" - self.client.post(url, data={"user": self.contributor.id, "score": 6}) - response = self.client.get(url) + self.client.login(**self.anonymous_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 6}) + response = self.client.get(self.url) self.assertContains(response=response, text="Rating: 0", count=1) self.assertContains(response=response, text="oleh: 0 orang", count=1) - self.client.post(url, data={"user": self.contributor.id, "score": -1}) - response = self.client.get(url) + self.client.logout() + + self.client.login(**self.admin_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": -1}) + response = self.client.get(self.url) self.assertContains(response=response, text="Rating: 0", count=1) self.assertContains(response=response, text="oleh: 0 orang", count=1) def test_average_with_multiple_score_correct(self): score = 5 avg = [score] - url = f"/profil/{self.contributor.email}/" - self.client.post(url, data={"user": self.contributor.id, "score": score}) - response = self.client.get(url) + self.client.login(**self.anonymous_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": score}) + response = self.client.get(self.url) self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + self.client.logout() + self.anonymous2 = get_user_model().objects.create_user( + email="anonymous2@gov.id", password="test", name="Anonymous 2" + ) score = 4 avg.append(score) - self.client.post(url, data={"user": self.contributor.id, "score": score}) - response = self.client.get(url) + self.client.login(email=self.anonymous2.email, password="test") + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous2.id, "score": score}) + response = self.client.get(self.url) self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + self.client.logout() + self.anonymous3 = get_user_model().objects.create_user( + email="anonymous3@gov.id", password="test", name="Anonymous 3" + ) score = 3 avg.append(score) - self.client.post(url, data={"user": self.contributor.id, "score": score}) - response = self.client.get(url) + self.client.login(email=self.anonymous3.email, password="test") + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous3.id, "score": score}) + response = self.client.get(self.url) self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + self.client.logout() + self.anonymous4 = get_user_model().objects.create_user( + email="anonymous4@gov.id", password="test", name="Anonymous 4" + ) score = 2 avg.append(score) - self.client.post(url, data={"user": self.contributor.id, "score": score}) - response = self.client.get(url) + self.client.login(email=self.anonymous4.email, password="test") + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous4.id, "score": score}) + response = self.client.get(self.url) self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + self.client.logout() score = 1 avg.append(score) - self.client.post(url, data={"user": self.contributor.id, "score": score}) - response = self.client.get(url) + self.client.login(**self.admin_credential) + self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": score}) + response = self.client.get(self.url) self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1) self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1) + def test_not_authenticated_user_should_not_able_to_add_rating(self): + response_post = self.client.post(self.url, data={ + "contributor":self.contributor.id, + "user":self.anonymous.id, + "score":3 + }) + response_get = self.client.get(self.url) + self.assertEqual(response_post.status_code, 403) + self.assertContains(response_get, "Kamu harus login untuk memberi rating") + + def test_not_authenticated_user_should_not_able_to_delete_rating(self): + response_post = self.client.post(self.url, data={"delete":True}) + self.assertEqual(response_post.status_code, 403) + + def test_authenticated_user_should_not_delete_rating_if_has_not_been_rated(self): + self.client.login(**self.admin_credential) + self.client.post(self.url, data={ + "contributor":self.contributor.id, + "user":self.admin.id, + "score":3 + }) + + self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count()) + self.client.logout() + + self.client.login(**self.anonymous_credential) + self.client.post(self.url, data={"delete":True}) + + self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count()) + + def test_authenticated_user_should_delete_rating_if_has_been_rated(self): + self.client.login(**self.admin_credential) + self.client.post(self.url, data={ + "contributor":self.contributor.id, + "user":self.admin.id, + "score":3 + }) + response = self.client.get(self.url) + self.assertContains(response, 'Rating: 3') + self.assertContains(response, 'oleh: 1 orang') + self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count()) + + self.client.post(self.url, data={"delete":True}) + response = self.client.get(self.url) + self.assertContains(response, 'Rating: 0') + self.assertContains(response, 'oleh: 0 orang') + self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor).count()) + + def test_average_still_be_correct_when_rating_was_deleted(self): + scores = [2,4] + self.client.login(**self.admin_credential) + self.client.post(self.url, data={ + "contributor":self.contributor.id, + "user":self.admin.id, + "score":scores[0] + }) + self.client.logout() + + self.client.login(**self.anonymous_credential) + self.client.post(self.url, data={ + "contributor":self.contributor.id, + "user":self.anonymous.id, + "score":scores[1] + }) + response = self.client.get(self.url) + self.assertContains(response, f'Rating: {mean(scores)}') + self.assertContains(response, 'oleh: 2 orang') + + self.client.post(self.url, data={"delete":True}) + response = self.client.get(self.url) + self.assertContains(response, f'Rating: {scores[0]}') + self.assertContains(response, 'oleh: 1 orang') + + def test_authenticated_user_should_update_rating_if_has_been_rated(self): + self.client.login(**self.anonymous_credential) + self.client.post(self.url, data={ + "contributor":self.contributor.id, + "user":self.anonymous.id, + "score":5 + }) + response = self.client.get(self.url) + self.assertContains(response, 'Rating: 5') + self.assertContains(response, 'oleh: 1 orang') + + self.client.post(self.url, data={"update":True, "score":3}) + response = self.client.get(self.url) + self.assertContains(response, 'Rating: 3') + self.assertContains(response, 'oleh: 1 orang') + + def test_authenticated_user_should_not_update_rating_if_has_not_been_rated(self): + self.client.login(**self.anonymous_credential) + response = self.client.get(self.url) + self.assertContains(response, 'Rating: 0') + self.assertContains(response, 'oleh: 0 orang') + + self.client.post(self.url, data={"update":True, "score":3}) + response = self.client.get(self.url) + self.assertContains(response, 'Rating: 0') + self.assertContains(response, 'oleh: 0 orang') + class UserDownloadHistoryTest(TestCase): def setUp(self): @@ -2853,8 +3011,8 @@ class YTUrlVideoTest(TestCase): ) def test_yt_video_id_exists_in_Materi(self): - materiTemp = Materi.objects.create() - self.assertEqual(hasattr(materiTemp, 'yt_video_id'), True) + materi_temp = Materi.objects.create() + self.assertEqual(hasattr(materi_temp, 'yt_video_id'), True) def test_yt_video_id_exists_in_UploadMateri_page(self): self.client.login(**self.contributor_credential) @@ -2864,7 +3022,7 @@ class YTUrlVideoTest(TestCase): def test_upload_materi_with_valid_yt_video_id(self): self.client.login(**self.contributor_credential) - response = self.client.post( + self.client.post( "/unggah/", data={"title":"Materi 1", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", "descriptions":"Deskripsi Materi 1", 'categories':"1", "cover":self.cover, "content":self.content, @@ -2874,7 +3032,7 @@ class YTUrlVideoTest(TestCase): def test_upload_materi_with_invalid_yt_video_id(self): self.client.login(**self.contributor_credential) - response = self.client.post( + self.client.post( "/unggah/", data={"title":"Materi 2", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", "descriptions":"Deskripsi Materi 1", 'categories':"1", "cover":self.cover, "content":self.content, @@ -2891,7 +3049,7 @@ class YTUrlVideoTest(TestCase): def test_detail_materi_has_no_video_if_yt_video_id_empty(self): self.client.login(**self.contributor_credential) - response = self.client.post( + self.client.post( "/unggah/", data={"title":"Materi 2", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000", "descriptions":"Deskripsi Materi 1", 'categories':"1", "cover":self.cover, "content":self.content,} @@ -2923,17 +3081,7 @@ class ChangePasswordTest(TestCase): # Logout self.client.logout() - def test_change_password_url(self): - # Login - self.client.login(email="kontributor@gov.id", password="kontributor") - # Test - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - # Logout - self.client.logout() - - def test_change_password_access(self): - # Kontributor + def test_change_password_url_access(self): # Login self.client.login(email="kontributor@gov.id", password="kontributor") # Test @@ -3244,7 +3392,7 @@ class MateriRecommendationTest(TestCase): Like.objects.create(materi=materi2) Like.objects.create(materi=materi2) - materi3 = Materi.objects.create( + Materi.objects.create( title="Materi 3", author="Nandhika Prayoga", uploader=self.contributor, @@ -3297,5 +3445,3 @@ class MateriRecommendationTest(TestCase): response = Client().get("/?recommendation=1") list = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", response.content.decode())] self.assertEqual(list, [1, 2]) - ->>>>>>> 59a5a847cfe9cd71b272834c2ba68cbc7c331a7c diff --git a/app/views.py b/app/views.py index ad2cb9b0681e1a325889fb486f99122d7f303cf1..306e84073b13dea436e90faa2952bafcb618f81c 100644 --- a/app/views.py +++ b/app/views.py @@ -97,19 +97,51 @@ class KatalogPerKontributorView(TemplateView): page_number = request.GET.get("page") materi_list_by_page = paginator.get_page(page_number) context["materi_list"] = materi_list_by_page - contributor = get_object_or_404(User, email=kwargs["email"]) - context["form_rating"] = RatingContributorForm(initial={"user": contributor}) + contributor = context["contributor"] + context["form_rating"] = RatingContributorForm(initial={ + "contributor": contributor, + "user":request.user + }) context["avg_rating"] = User.objects.filter(email=kwargs["email"]) \ - .annotate(avg_rating=Avg("ratingcontributor__score"))[0] - context["count_rating"] = RatingContributor.objects.filter(user=contributor).count() + .annotate(avg_rating=Avg("contributor__score"))[0] + context["count_rating"] = RatingContributor.objects.filter(contributor=contributor).count() + + if request.user.is_authenticated: + has_rated = RatingContributor.objects.filter(user=request.user, contributor=contributor).exists() + context["has_rated"] = has_rated + return self.render_to_response(context=context) def post(self, request, *args, **kwargs): - data = RatingContributorForm(request.POST) - if data.is_valid(): - data.save() - return redirect("katalog-per-kontributor", email=kwargs["email"]) + context = self.get_context_data(**kwargs) + if not request.user.is_authenticated: + raise PermissionDenied(request) + is_delete = request.POST.get('delete', None) + is_update = request.POST.get('update', None) + if is_delete: + rating_contributor = get_object_or_404( + RatingContributor, + user=request.user, + contributor=context["contributor"] + ) + rating_contributor.delete() + elif is_update: + score = request.POST.get('score', None) + rating = RatingContributor.objects.filter( + user=request.user, + contributor=context["contributor"] + ).first() + + if rating and score: + rating.score = int(score) + rating.save() + else: + data = RatingContributorForm(request.POST) + if data.is_valid(): + data.save() + + return redirect("katalog-per-kontributor", email=kwargs["email"]) class DetailMateri(TemplateView): template_name = "app/detail_materi.html" @@ -141,8 +173,8 @@ class DetailMateri(TemplateView): has_disliked, has_liked = DetailMateriService.find_comment_like_dislike(query_set_for_comment, self.request.session) context["comment_data"] = query_set_for_comment context["review_data"] = query_set_for_review - context["has_liked"] = has_liked - context["has_disliked"] = has_disliked + context["has_liked_comment"] = has_liked + context["has_disliked_comment"] = has_disliked return self.render_to_response(context=context) @@ -605,6 +637,7 @@ class ReqMateriView(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) + context['requested_material'] = ReqMaterial.objects.all() return self.render_to_response(context) def post(self, request, *args, **kwargs): diff --git a/authentication/models.py b/authentication/models.py index 721b69c9f6d364de73e08ad1cbd4583be39e1172..84b835807a6b7e58b49389c35296466de2bc82a2 100644 --- a/authentication/models.py +++ b/authentication/models.py @@ -81,6 +81,7 @@ class User(AbstractUser): default_profile_picture = models.BooleanField(blank=True, default=False) profile_picture = models.ImageField(default="default-image.jpg") is_subscribing_to_material_comments = models.BooleanField(blank=False, default=True) + is_email_verified = models.BooleanField(default=False) objects = CustomUserManager() diff --git a/register/migrations/__init__.py b/register/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/register/models.py b/register/models.py index 71a836239075aa6e6e4ecb700e9c42c95c022d91..56cabee4d822988bebddb13863282faef88b581f 100644 --- a/register/models.py +++ b/register/models.py @@ -1,3 +1,11 @@ +from authentication.models import User from django.db import models +import uuid # Create your models here. + +class EmailVerification(models.Model): + token = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + created_on = models.DateTimeField(auto_now_add=True) + expire_on = models.DateTimeField(null=True) + user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) diff --git a/register/services.py b/register/services.py index 603855b114790a85861dc06a4200f2c777256cd9..a668e0426d20c444831f43d430fffe01deddcdc2 100644 --- a/register/services.py +++ b/register/services.py @@ -1,6 +1,12 @@ +import datetime from django.contrib.auth.hashers import make_password from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError +from .models import EmailVerification +from django.utils import timezone +from django.conf import settings +from django.core.mail import send_mail +from django.urls import reverse class RegistrationService: @@ -69,3 +75,27 @@ class RegistrationService: create_result["form"] = form return create_result + + + @staticmethod + def create_email_verification(request, user): + verify = EmailVerification() + verify.created_on = timezone.now() + verify.expire_on = verify.created_on + datetime.timedelta(hours=24) + verify.user = user + verify.save() + + path = reverse('register:verify-email', args=[verify.token]) + url = request.build_absolute_uri(path) + + email_content = f""" + Dear {user.name},\n\n + Mohon verifikasi email Anda dengan klik pada link berikut: {url} + """ + + send_mail( + subject = 'DIGIPUS: Verifikasi Alamat Email', + message = email_content, + from_email = getattr(settings, 'EMAIL_HOST_USER'), + recipient_list = [user.email], + fail_silently = False) diff --git a/register/templates/email_verify.html b/register/templates/email_verify.html new file mode 100644 index 0000000000000000000000000000000000000000..90909d04e636e165b11a42e1c83d78ebcd19b0ee --- /dev/null +++ b/register/templates/email_verify.html @@ -0,0 +1,69 @@ +{% load static %} + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Verifikasi Email</title> + <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" /> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/bootstrap/css/bootstrap.min.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}"> + <!--===============================================================================================--> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}"> + <!--===============================================================================================--> + <link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'css/util.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"> + <!--===============================================================================================--> + <link href="https://fonts.googleapis.com/css2?family=Montserrat:ital@1&display=swap" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet"> +</head> +<body> + + <main> + + {% if message %} + + <div> + {{ message }} + </div> + + <div> + Kembali ke <a href="/">home</a> + </div> + + {% endif %} + + </main> + <!--===============================================================================================--> + <script src="../static/vendor/jquery/jquery-3.2.1.min.js"></script> + <!--===============================================================================================--> + <script src="../static/vendor/animsition/js/animsition.min.js"></script> + <!--===============================================================================================--> + <script src="../static/vendor/bootstrap/js/popper.js"></script> + <script + src="../static/../static/../static/../static/../static/../static/vendor/bootstrap/js/bootstrap.min.js"></script> + <!--===============================================================================================--> + <script src="../static/../static/../static/../static/../static/vendor/select2/select2.min.js"></script> + <!--===============================================================================================--> + <script src="../static/../static/../static/../static/vendor/daterangepicker/moment.min.js"></script> + <script src="../static/../static/../static/vendor/daterangepicker/daterangepicker.js"></script> + <!--===============================================================================================--> + <script src="../static/../static/vendor/countdowntime/countdowntime.js"></script> + <!--===============================================================================================--> + <script src="../static/js/login.js"></script> + <script src="../static/js/navbar.js"></script> +</body> +</html> \ No newline at end of file diff --git a/register/tests.py b/register/tests.py index ef8205860cad0b4362a48bf9b5c1b7634595d772..799c0d2ea8e8610447e7397ca152b6a7f64e0b07 100644 --- a/register/tests.py +++ b/register/tests.py @@ -1,3 +1,8 @@ +import datetime +from django.core import mail + +from django.utils import timezone +from register.models import EmailVerification from django.test import Client, TestCase from django.urls import resolve @@ -883,3 +888,47 @@ class RegisterPublicTest(TestCase): self.assertEqual(User.objects.all().count(), 0) self.assertIn(b"Password must have at least 8 characters", response.content) + +class VerifyEmailTest(TestCase): + + def setUp(self): + self.credential = { + 'email':'example@test.com', + 'password':'P@ssw0rd' + } + self.user = User.objects.create_contributor(**self.credential) + self.user.save() + + self.verif_valid = EmailVerification( + expire_on=timezone.now() + datetime.timedelta(hours=24), + user = self.user) + self.verif_valid.save() + + self.verif_expire = EmailVerification( + created_on=timezone.now() + datetime.timedelta(hours=-24), + expire_on=timezone.now() + datetime.timedelta(hours=-1), + user = self.user) + self.verif_expire.save() + + self.client = Client() + + def test_email_notif_anonymous(self): + response = self.client.post('/registrasi/send-verify-email', {'email': self.user.email}) + self.assertEqual(response.status_code, 403) + + def test_email_notif_authorized(self): + login = self.client.login(**self.credential) + self.client.post('/registrasi/send-verify-email', {'email': self.user.email}) + self.assertEqual(len(mail.outbox), 1) + + def test_verify_email_before_24h(self): + response = self.client.get(f'/registrasi/verify-email/{self.verif_valid.token}') + self.assertContains(response, f'Email Anda {self.verif_valid.user.email} berhasil diverifikasi') + + def test_verify_email_after_24h(self): + response = self.client.get(f'/registrasi/verify-email/{self.verif_expire.token}') + self.assertContains(response, 'Link verifikasi telah expire') + + def test_verify_email_wrong_token(self): + response = self.client.get(f'/registrasi/verify-email/not-a-valid-token') + self.assertContains(response, 'Email gagal diverifikasi') diff --git a/register/urls.py b/register/urls.py index 0acd03ba88ac4292c91e311da81766e4f8faf4e3..53d6a6ed6a4629ab084d3bac362718086b6126f6 100644 --- a/register/urls.py +++ b/register/urls.py @@ -7,5 +7,7 @@ app_name = "register" urlpatterns = [ path("", views.index.as_view()), path("umum/", views.RegistrasiUmum.as_view()), - path("admin/", views.RegistrasiAdmin.as_view()) + path("admin/", views.RegistrasiAdmin.as_view()), + path("verify-email/<str:token>", views.verify_email, name='verify-email'), + path("send-verify-email", views.send_verify_email), ] diff --git a/register/views.py b/register/views.py index 3d8112fef3563207ce1cdb5b294e6ff26c340acc..91952d6907cb91b74dfd2a6e5e451f0c0a0f26c4 100644 --- a/register/views.py +++ b/register/views.py @@ -1,9 +1,16 @@ +from django.http import response +from authentication.models import User +from django.utils import timezone + +from django.shortcuts import render +from register.models import EmailVerification from django.contrib.auth import login +from django.db import models from django.http import HttpResponseRedirect from django.views.generic import TemplateView +from django.core.exceptions import PermissionDenied from register.forms import UserForm -# Create your views here. from register.services import RegistrationService @@ -98,3 +105,36 @@ class RegistrasiUmum(TemplateView): context = self.get_context_data(**kwargs) context["form"] = UserForm return self.render_to_response(context=context) + + +def send_verify_email(request): + if not request.user.is_authenticated: + raise PermissionDenied(request) + + if request.method == 'POST': + RegistrationService.create_email_verification(request, request.user) + return response.HttpResponse('Email verifikasi telah dikirim') + + +def verify_email(request, token): + template_name = 'email_verify.html' + + try: + verif = EmailVerification.objects.get(token=token) + + if verif.expire_on > timezone.now(): + user = User.objects.get(email=verif.user.email) + user.is_email_verified = True + user.save() + + return render(request, template_name, { + 'message': f'Email Anda {verif.user.email} berhasil diverifikasi' + }) + else: + return render(request, template_name, { + 'message': f'Link verifikasi telah expire' + }) + except: + return render(request, template_name, { + 'message': f'Email gagal diverifikasi' + }) diff --git a/sonar-project.properties b/sonar-project.properties index 8ef13a8bdcd322e265a0a36ff3b533387a69043a..754cc49ecee83369ab9e62537c2721e67a9e9f5e 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -9,13 +9,13 @@ sonar.projectKey=$SONARQUBE_PROJECT_KEY ## Path to sources sonar.sources=. -sonar.exclusions=**/migrations/**, **/__init__.py, digipus/**, **/test.py, manage.py, .vscode/**, static/**, staticfiles/**, static/admin/**, **/static/** +sonar.exclusions=**/migrations/**, **/__init__.py, digipus/**, **/tests.py, manage.py, .vscode/**, static/**, staticfiles/**, static/admin/**, **/static/** # sonar.inclusions= ## Path to tests sonar.tests=. #sonar.test.exclusions= -sonar.test.inclusions=**/test.py +sonar.test.inclusions=**/tests.py ## Source encoding sonar.sourceEncoding=UTF-8 diff --git a/staticfiles/css/button.css b/staticfiles/css/button.css index 90a8a87a089ed75ec7f3eda8a752f03fdf10b3e6..8ea2c9fe4fff979bcd96c0dedb3921ae5c782594 100755 --- a/staticfiles/css/button.css +++ b/staticfiles/css/button.css @@ -117,4 +117,9 @@ .margin-content { margin: 0; padding: 0; +} + +.constraint-frame { + max-width: 550px; + max-height: 550px; } \ No newline at end of file diff --git a/staticfiles/js/upload-preview.js b/staticfiles/js/upload-preview.js new file mode 100644 index 0000000000000000000000000000000000000000..c424f70d2de26b0c31676d1e0435928f92ef20c7 --- /dev/null +++ b/staticfiles/js/upload-preview.js @@ -0,0 +1,22 @@ +window.onload = () => { + document.querySelector('input[name="cover"]').setAttribute("onchange", "preview('frame_cover')"); + document.querySelector('input[name="content"]').setAttribute("onchange", "preview('frame_content')"); +} + +const preview = (elementClass) => { + let frame = document.getElementById(elementClass); + let frameParent = document.getElementById(`${elementClass}_parent`); + + // Embed elements need to be recreated on source change + if (frame.nodeName.toLowerCase() === 'embed') { + frameParent.removeChild(frame); + frameParent.insertAdjacentHTML( + 'beforeend', + '<embed id="frame_content" width="550px" height="550px" />' + ); + frame = document.getElementById(elementClass); + } + + frame.src = URL.createObjectURL(event.target.files[0]); + frameParent.removeAttribute('hidden'); +} \ No newline at end of file