Fakultas Ilmu Komputer UI

Commit 8e7e67a9 authored by Selvy Fitriani's avatar Selvy Fitriani
Browse files

Merge branch '1706039446/110' into 'master'

[#110] Guest Book For Non Registered User

See merge request !112
parents defcd506 60a1898e
Pipeline #60185 passed with stages
in 26 minutes and 38 seconds
from django import forms
from app.models import Materi, Category, RatingContributor
from app.models import Materi, Category, RatingContributor, GuestBook
from authentication.models import User
import datetime
def year_choices():
return[(r,r) for r in range(2000, datetime.date.today().year+1)]
return[(r, r) for r in range(2000, datetime.date.today().year+1)]
class UploadMateriForm(forms.ModelForm):
categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all(),widget=forms.CheckboxSelectMultiple(attrs={'style' : 'column-count:2'}),required=True)
release_year = forms.TypedChoiceField(coerce=int, choices=year_choices, initial=datetime.date.today().year)
yt_video_id = forms.CharField(label="Youtube Video Id", \
categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all(
), widget=forms.CheckboxSelectMultiple(attrs={'style': 'column-count:2'}), required=True)
release_year = forms.TypedChoiceField(
coerce=int, choices=year_choices, initial=datetime.date.today().year)
yt_video_id = forms.CharField(label="Youtube Video Id",
help_text="This is not required.<br>\
Please insert only Youtube link videos! Take a note for the video id!<br>\
Example : https://www.youtube.com/watch?v=DkJ-50GLi2I <br> has video id DkJ-50GLi2I", required=False)
......@@ -29,14 +33,13 @@ class UploadMateriForm(forms.ModelForm):
field.widget.attrs["class"] = "form-control"
field.widget.attrs["placeholder"] = field.initial
field.initial = ""
self.fields['categories'].widget.attrs.update({'class' : "native"})
self.fields['categories'].widget.attrs.update({'class': "native"})
class SuntingProfilForm(forms.ModelForm):
class Meta:
model = User
fields = ["email","name","instansi", "nik", "alamat", "nomor_telpon",
fields = ["email", "name", "instansi", "nik", "alamat", "nomor_telpon",
"profile_picture", "linkedin",
"facebook", "twitter", "instagram", "biography",
"is_subscribing_to_material_comments"]
......@@ -51,7 +54,6 @@ class SuntingProfilForm(forms.ModelForm):
key = list(self.errors)[0]
self.fields[key].widget.attrs["autofocus"] = ""
self.fields["email"].widget.attrs["readonly"] = True
......@@ -61,14 +63,43 @@ class RatingContributorForm(forms.ModelForm):
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
# First one is the value of select option and second is the displayed value in option
('1', '1'),
('2', '2'),
('3', '3'),
('4', '4'),
('5', '5'),
)
widgets = {
'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control', 'id':'form-rating'},),
'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control', 'id': 'form-rating'},),
'user': forms.HiddenInput(),
'contributor': forms.HiddenInput()
}
class GuestBookForm(forms.models.ModelForm):
class Meta:
model = GuestBook
fields = ['name', 'job', 'gender']
gender_choices = (
('Male', 'Male'),
('Female', 'Female')
)
widgets = {
'name': forms.fields.TextInput(attrs={
'placeholder': 'Input your name',
'class': 'form-control input-lg'
}),
'job': forms.fields.TextInput(attrs={
'placeholder': 'Input your job',
'class': 'form-control input-lg'
}),
'gender': forms.fields.Select(choices=gender_choices, attrs={
'class': 'form-control input-lg'
})
}
error_messages = {
'name': {'required': 'Name is required'},
'job': {'required': 'Job is required'},
'gender': {'required': 'Gender is required'}
}
# Generated by Django 3.1 on 2020-10-31 18:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0028_adminnotification'),
]
operations = [
migrations.CreateModel(
name='GuestBook',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField(default='')),
('job', models.TextField(default='')),
('gender', models.CharField(default='Male', max_length=6)),
],
),
]
# Generated by Django 3.1 on 2020-10-31 19:17
# Generated by Django 3.1 on 2020-10-31 23:21
from django.db import migrations
......@@ -6,8 +6,8 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('app', '0029_guestbook'),
('app', '0028_notifikasikontributor'),
('app', '0028_adminnotification'),
]
operations = [
......
......@@ -26,9 +26,11 @@ def get_random_color():
color = "%06x" % random.randint(0, 0xFFFFFF)
return color
def current_year():
return datetime.date.today().year
class Category(models.Model):
name = models.CharField(max_length=20)
description = models.TextField(blank=False, default="")
......@@ -75,6 +77,7 @@ class SoftDeletionQuerySet(models.query.QuerySet):
def delete(self):
return super(SoftDeletionQuerySet, self).update(deleted_at=timezone.now())
class SoftDeleteModel(models.Model):
deleted_at = models.DateTimeField(blank=True, null=True)
......@@ -87,6 +90,7 @@ class SoftDeleteModel(models.Model):
self.deleted_at = timezone.now()
self.save()
class Materi(SoftDeleteModel):
cover = models.ImageField()
content = models.FileField()
......@@ -97,7 +101,8 @@ class Materi(SoftDeleteModel):
release_year = models.IntegerField(default=current_year)
pages = models.IntegerField(default=0)
descriptions = models.TextField(default="Deskripsi")
status = models.CharField(max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0])
status = models.CharField(
max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0])
categories = models.ManyToManyField(Category)
date_created = models.DateTimeField(default=timezone.now)
date_modified = models.DateTimeField(auto_now=True)
......@@ -117,7 +122,8 @@ class Materi(SoftDeleteModel):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
search_index = {field: weight for (field, weight) in Materi.SEARCH_INDEX}
search_index = {field: weight for (
field, weight) in Materi.SEARCH_INDEX}
if "update_fields" in kwargs:
is_search_index_updated = bool(
set(search_index.keys()) & set(kwargs["update_fields"])
......@@ -127,9 +133,11 @@ class Materi(SoftDeleteModel):
self._search_vector = None
for field, weight in search_index.items():
if self._search_vector is None:
self._search_vector = search.SearchVector(field, weight=weight)
self._search_vector = search.SearchVector(
field, weight=weight)
else:
self._search_vector += search.SearchVector(field, weight=weight)
self._search_vector += search.SearchVector(
field, weight=weight)
self.save(update_fields=["_search_vector"])
......@@ -197,7 +205,8 @@ class Comment(models.Model):
profile = models.CharField(max_length=100, default=get_random_color)
comment = models.CharField(max_length=240, default="comments")
materi = models.ForeignKey(Materi, models.SET_NULL, null=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
user = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
......@@ -213,12 +222,14 @@ class Comment(models.Model):
count = DislikeComment.objects.filter(comment=self).count()
return count
class Review(models.Model):
username = models.CharField(max_length=100)
profile = models.CharField(max_length=100, default=get_random_color)
review = models.TextField(default="review")
materi = models.ForeignKey(Materi, models.SET_NULL, null=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
user = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
......@@ -247,6 +258,7 @@ class ReqMaterial(models.Model):
title = models.CharField(max_length=100)
timestamp = models.DateTimeField(default=timezone.now)
class SubmitVisitor(models.Model):
user_id = models.CharField(max_length=50)
email = models.CharField(max_length=50)
......@@ -255,13 +267,16 @@ class SubmitVisitor(models.Model):
class ViewStatistics(models.Model):
materi = models.ForeignKey(Materi, models.SET_NULL, null=True, related_name="baca")
materi = models.ForeignKey(
Materi, models.SET_NULL, null=True, related_name="baca")
timestamp = models.DateTimeField(default=timezone.now)
class DownloadStatistics(models.Model):
materi = models.ForeignKey(Materi, models.SET_NULL, null=True, related_name="unduh")
downloader = models.ForeignKey(User, models.SET_NULL, blank=True, null=True, related_name="riwayat_unduh")
materi = models.ForeignKey(
Materi, models.SET_NULL, null=True, related_name="unduh")
downloader = models.ForeignKey(
User, models.SET_NULL, blank=True, null=True, related_name="riwayat_unduh")
timestamp = models.DateTimeField(default=timezone.now)
......@@ -302,9 +317,12 @@ class Rating(models.Model):
class RatingContributor(models.Model):
timestamp = models.DateTimeField(auto_now=True)
score = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
contributor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='contributor')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user')
score = models.PositiveIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(5)])
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:
......@@ -317,12 +335,21 @@ class RatingContributor(models.Model):
class LaporanMateri(models.Model):
materi = models.ForeignKey(Materi, on_delete=models.CASCADE, max_length=120)
materi = models.ForeignKey(
Materi, on_delete=models.CASCADE, max_length=120)
user = models.ForeignKey(User, on_delete=models.CASCADE)
laporan = models.TextField(validators=[MinValueValidator(30), MaxValueValidator(120)], default="")
laporan = models.TextField(
validators=[MinValueValidator(30), MaxValueValidator(120)], default="")
timestamp = models.DateTimeField(default=timezone.now)
is_rejected = models.BooleanField(default=False)
class GuestBook(models.Model):
name = models.TextField(default='')
job = models.TextField(default='')
gender = models.CharField(max_length=6, default="Male")
class ReadLater(models.Model):
materi = models.ForeignKey(Materi, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
......@@ -331,13 +358,16 @@ class ReadLater(models.Model):
class Meta:
unique_together = ["materi", "user"]
class NotifikasiKontributor(models.Model):
materi = models.ForeignKey(Materi, on_delete=models.CASCADE, max_length=120)
materi = models.ForeignKey(
Materi, on_delete=models.CASCADE, max_length=120)
user = models.ForeignKey(User, on_delete=models.CASCADE)
feedback = models.CharField(max_length=20, default="")
def __str__(self):
return "Status: {}".format(self.feedback)
class AdminNotification(models.Model):
materi = models.ForeignKey(Materi, on_delete=models.CASCADE)
.form-style-6{
font: 95% Arial, Helvetica, sans-serif;
max-width: 500px;
margin: 25px auto;
padding: 16px;
background: #F7F7F7;
align-content: center;
justify-content: center;
}
.form-style-6 h1{
background: #43D1AF;
padding: 20px 0;
font-size: 140%;
font-weight: 300;
text-align: center;
color: #fff;
margin: -16px -16px 16px -16px;
}
.form-style-6 input[type="text"],
.form-style-6 select
{
-webkit-transition: all 0.30s ease-in-out;
-moz-transition: all 0.30s ease-in-out;
-ms-transition: all 0.30s ease-in-out;
-o-transition: all 0.30s ease-in-out;
outline: none;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
width: 100%;
background: #fff;
margin-bottom: 4%;
border: 1px solid #ccc;
color: #555;
font: 95% Arial, Helvetica, sans-serif;
}
.form-style-6 input[type="text"]:focus,
.form-style-6 textarea:focus,
.form-style-6 select:focus
{
box-shadow: 0 0 5px #43D1AF;
padding: 3%;
border: 1px solid #43D1AF;
}
.form-style-6 button[type="submit"]{
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
width: 100%;
padding: 3%;
background: #1da1f2;
border-bottom: 2px solid #1da1f2;
border-top-style: none;
border-right-style: none;
border-left-style: none;
color: #fff;
}
.form-style-6 input[type="submit"]:hover,
.form-style-6 input[type="button"]:hover{
background: #2EBC99;
}
\ No newline at end of file
{% extends 'base.html' %}
{% load static %}
{% block title %}Digipus Home{% endblock %}
{% block header %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Digipus Home</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, inital-scale=1">
<link rel="stylesheet" type="text/css" href="{% static 'app/css/guest_book.css' %}">
{% endblock header %}
</head>
<!-- Page Content -->
{% block content %}
<div class="form-group form-style-6" id="myForm">
<form method="POST">
<h3 style="text-align: center;">Buku Tamu Non Anggota</h3>
{% csrf_token %}
{{form}}
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
{% endblock %}
</html>
\ No newline at end of file
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException
from selenium import webdriver
from django.test import LiveServerTestCase
import requests
from statistics import mean
import mock
import pandas as pd
import base64
......@@ -37,7 +45,7 @@ from django.test import (Client, RequestFactory, TestCase, TransactionTestCase,
from django.urls import resolve, reverse
from django.utils import timezone
from app.forms import SuntingProfilForm, year_choices
from app.forms import SuntingProfilForm, year_choices, GuestBookForm
from app.utils.fileManagementUtil import (get_random_filename,
remove_image_exifdata)
from app.utils.PasswordValidator import PasswordPolicyValidator
......@@ -45,7 +53,7 @@ from app.views import UploadMateriHTML, add_rating_materi
from .models import (Category, Comment, DislikeComment, DownloadStatistics,
Like, LikeComment, Materi, Rating, RatingContributor,
ReadLater, ReqMaterial, Review, ViewStatistics, AdminNotification, NotifikasiKontributor)
ReadLater, ReqMaterial, Review, ViewStatistics, GuestBook, AdminNotification, NotifikasiKontributor)
from .services import DetailMateriService
from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri,
KatalogPerKontributorView, MateriFavorite,
......@@ -57,16 +65,6 @@ from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri,
ERROR_403_MESSAGE = "Kamu harus login untuk mengakses halaman ini"
from statistics import mean
import requests
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from webdriver_manager.chrome import ChromeDriverManager
class DaftarKatalogTest(TestCase):
def test_daftar_katalog_url_exist(self):
......@@ -133,6 +131,7 @@ class DaftarKatalogSortingByJumlahUnduhTest(TestCase):
response = self.client.get("/?sort=jumlah_unduh")
self.assertRegex(str(response.content), rf'.*Materi 2.*Materi 1.*')
class DaftarKatalogSortingByJumlahTampilanTest(TestCase):
def setUp(self):
self.contributor_credential = {
......@@ -359,12 +358,14 @@ class DaftarKatalogSortingByJumlahKomentarTest(TestCase):
url = f"/materi/{self.last_uploaded_material.id}/"
self.client.login(**self.contributor_credential_2)
self.client.post(url, {"comment": "This is new comment by Kontributor 2"})
self.client.post(
url, {"comment": "This is new comment by Kontributor 2"})
def test_sorting_by_jumlah_komentar(self):
response = self.client.get("/?sort=jumlah_komentar")
self.assertRegex(str(response.content), rf'.*Materi 2.*Materi 1.*')
class DaftarKatalogPerKontributorTest(TestCase):
def setUp(self):
self.client = Client()
......@@ -410,7 +411,8 @@ class DaftarKatalogPerKontributorTest(TestCase):
def test_katalog_per_kontributor_using_katalog_per_kontributor_func(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, KatalogPerKontributorView.as_view().__name__)
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)
......@@ -425,20 +427,22 @@ class DaftarKatalogPerKontributorTest(TestCase):
jumlah = response.context_data["materi_list"]
self.assertEqual(len(list_materi), len(jumlah))
class DetailMateriTest(TestCase):
def _get_materi_info_html(self, info_name, info_value):
info_html = '<div class="info" id="1"><dl class="col col-4">'
info_html += f'<dt class="info-name">{info_name}</dt>' + '</dl><dd>'
info_html += f'<p class="info-content">{info_value}</p>' + '</dd></div>'
info_html += f'<p class="info-content">{info_value}</p>' + \
'</dd></div>'
return info_html
def check_materi_info_in_html(self, info_name, info_value, html_content):
expected_content = self._get_materi_info_html(info_name, info_value)
self.assertIn(expected_content, re.sub(">\s*<","><", html_content))
self.assertIn(expected_content, re.sub(">\s*<", "><", html_content))
def check_materi_info_not_in_html(self, info_name, info_value, html_content):
expected_content = self._get_materi_info_html(info_name, info_value)
self.assertNotIn(expected_content, re.sub(">\s*<","><", html_content))
self.assertNotIn(expected_content, re.sub(">\s*<", "><", html_content))
def setUp(self):
self.client = Client()
......@@ -485,10 +489,11 @@ class DetailMateriTest(TestCase):
publisher="Kelas SC", descriptions="Deskripsi Materi 1",
status="APPROVE", cover=self.cover, content=self.content,
date_modified=timezone.now(), date_created=timezone.now())
self.materi_with_published_date_url = "/materi/" + str(self.materi_with_published_date.id) + "/"
VerificationReport.objects.create(report='{"feedback": "Something", "kriteria": [{"title": "Kriteria 1", "status": true},' + \
' {"title": "Kriteria 2", "status": true}, {"title": "Kriteria 3", "status": true}]}', \
timestamp="2020-10-09 06:21:33", status="Diterima", materi= self.materi_with_published_date, \
self.materi_with_published_date_url = "/materi/" + \
str(self.materi_with_published_date.id) + "/"
VerificationReport.objects.create(report='{"feedback": "Something", "kriteria": [{"title": "Kriteria 1", "status": true},' +
' {"title": "Kriteria 2", "status": true}, {"title": "Kriteria 3", "status": true}]}',
timestamp="2020-10-09 06:21:33", status="Diterima", materi=self.materi_with_published_date,
user=self.materi_with_published_date.uploader)
def test_detail_materi_url_exist(self):
......@@ -580,14 +585,17 @@ class DetailMateriTest(TestCase):
self.client.get("/logout/")
self.client.login(**self.anonymous_credential)
self.client.post(url_materi, {"comment": "This is new comment by Anonymous"})
comment = Comment.objects.get(comment="This is new comment by Anonymous").id
self.client.post(
url_materi, {"comment": "This is new comment by Anonymous"})
comment = Comment.objects.get(
comment="This is new comment by Anonymous").id
response = self.client.get(url_materi)
session_id = response.context["session_id"]
payload = {"comment": comment, "session_id": session_id}
self.client.post("/comment/dislike/", payload)
num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment).count()
num_of_comment_dislikes = DislikeComment.objects.filter(
comment=comment).count()
self.assertEqual(num_of_comment_dislikes, 0)
def test_comment_liked_by_anonymous(self):
......@@ -595,14 +603,17 @@ class DetailMateriTest(TestCase):
self.client.get("/logout/")
self.client.login(**self.anonymous_credential)
self.client.post(url_materi, {"comment": "This is new comment by Anonymous"})
comment = Comment.objects.get(comment="This is new comment by Anonymous").id
self.client.post(
url_materi, {"comment": "This is new comment by Anonymous"})
comment = Comment.objects.get(
comment="This is new comment by Anonymous").id
response = self.client.get(url_materi)
session_id = response.context["session_id"]
payload = {"comment": comment, "session_id": session_id}
self.client.post("/comment/like/", payload)
num_of_comment_likes = LikeComment.objects.filter(comment=comment).count()
num_of_comment_likes = LikeComment.objects.filter(
comment=comment).count()
self.assertEqual(num_of_comment_likes, 0)
def test_comment_undisliked_by_anonymous(self):
......@@ -610,8 +621,10 @@ class DetailMateriTest(TestCase):
self.client.get("/logout/")
self.client.login(**self.anonymous_credential)
self.client.post(url_materi, {"comment": "This is new comment by Anonymous"})
comment = Comment.objects.get(comment="This is new comment by Anonymous")
self.client.post(
url_materi, {"comment": "This is new comment by Anonymous"})
comment = Comment.objects.get(
comment="This is new comment by Anonymous")
response = self.client.get(url_materi)
session_id = response.context["session_id"]
......@@ -619,7 +632,8 @@ class DetailMateriTest(TestCase):
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()
num_of_comment_dislikes = DislikeComment.objects.filter(
comment=comment, session_id=session_id).count()
self.assertEqual(num_of_comment_dislikes, 0)
def test_comment_unliked_by_anonymous(self):
......@@ -627,8 +641,10 @@ class DetailMateriTest(TestCase):
self.client.get("/logout/")
self.client.login(**self.anonymous_credential)
self.client.post(url_materi, {"comment": "This is new comment by Anonymous"})
comment = Comment.objects.get(comment="This is new comment by Anonymous")
self.client.post(
url_materi, {"comment": "This is new comment by Anonymous"})
comment = Comment.objects.get(
comment="This is new comment by Anonymous")
response = self.client.get(url_materi)
session_id = response.context["session_id"]
......@@ -636,7 +652,8 @@ class DetailMateriTest(TestCase):
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()
num_of_comment_likes = LikeComment.objects.filter(
comment=comment, session_id=session_id).count()
self.assertEqual(num_of_comment_likes, 0)