diff --git a/app/forms.py b/app/forms.py
index 266720e86f912083e2c1335923114cc5c21c4bd3..7f22c130a7bd62a0d90ceb8e4fc88037f0f33eeb 100644
--- a/app/forms.py
+++ b/app/forms.py
@@ -1,18 +1,22 @@
 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", \
-    help_text="This is not required.<br>\
+
+    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"]
@@ -50,7 +53,6 @@ class SuntingProfilForm(forms.ModelForm):
         if any(self.errors):
             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'}
+        }
diff --git a/app/migrations/0029_guestbook.py b/app/migrations/0029_guestbook.py
new file mode 100644
index 0000000000000000000000000000000000000000..57fc440f8a0ba208b54902b0259221c854158c99
--- /dev/null
+++ b/app/migrations/0029_guestbook.py
@@ -0,0 +1,22 @@
+# 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)),
+            ],
+        ),
+    ]
diff --git a/app/migrations/0029_merge_20201101_0217.py b/app/migrations/0030_merge_20201101_0621.py
similarity index 66%
rename from app/migrations/0029_merge_20201101_0217.py
rename to app/migrations/0030_merge_20201101_0621.py
index 69826d1afcfbd93ecf7643f8109e292879d785b4..e4e725f5d7b212a3eec1de8bb51575762eb79218 100644
--- a/app/migrations/0029_merge_20201101_0217.py
+++ b/app/migrations/0030_merge_20201101_0621.py
@@ -1,4 +1,4 @@
-# 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 = [
diff --git a/app/models.py b/app/models.py
index 536b917fc5eee5fd234dceb3419ebaf549080d02..3386646a5856d39ed5eb4252cdb892f5c8057144 100644
--- a/app/models.py
+++ b/app/models.py
@@ -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="")
@@ -48,7 +50,7 @@ class MateriManager(models.Manager):
         if self.alive_only:
             return SoftDeletionQuerySet(self.model).filter(deleted_at=None)
         return SoftDeletionQuerySet(self.model)
-        
+
     def search(self, search_text):
         search_vector = None
         for field, weight in Materi.SEARCH_INDEX:
@@ -75,18 +77,20 @@ 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)
 
     all_objects = MateriManager(alive_only=False)
-    
+
     class Meta:
         abstract = True
-    
+
     def soft_delete(self):
         self.deleted_at = timezone.now()
         self.save()
 
+
 class Materi(SoftDeleteModel):
     cover = models.ImageField()
     content = models.FileField()
@@ -96,8 +100,9 @@ class Materi(SoftDeleteModel):
     publisher = models.CharField(max_length=30, default="Penerbit")
     release_year = models.IntegerField(default=current_year)
     pages = models.IntegerField(default=0)
-    descriptions = models.TextField(default="Deskripsi")    
-    status = models.CharField(max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0])
+    descriptions = models.TextField(default="Deskripsi")
+    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"])
 
@@ -154,7 +162,7 @@ class Materi(SoftDeleteModel):
     def like_count(self):
         count = Like.objects.filter(materi=self).count()
         return count
-    
+
     @property
     def comment_count(self):
         count = Comment.objects.filter(materi=self).count()
@@ -181,7 +189,7 @@ class Materi(SoftDeleteModel):
     @property
     def hot_score(self):
         view_score = math.log(max(self.view_count, 1), 10)
-        time_score = self.seconds_since_earliest_materi / 604800 # 1 week
+        time_score = self.seconds_since_earliest_materi / 604800  # 1 week
         return round(view_score + time_score, 7)
 
     @property
@@ -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):
@@ -207,18 +216,20 @@ class Comment(models.Model):
     def like_count(self):
         count = LikeComment.objects.filter(comment=self).count()
         return count
-    
+
     @property
     def dislike_count(self):
         count = DislikeComment.objects.filter(comment=self).count()
         return count
 
+
 class 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,27 +317,39 @@ 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:
             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 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)
diff --git a/app/static/app/css/guest_book.css b/app/static/app/css/guest_book.css
new file mode 100644
index 0000000000000000000000000000000000000000..681d4f1d307a052b538c5d576c5650f5362f0c09
--- /dev/null
+++ b/app/static/app/css/guest_book.css
@@ -0,0 +1,63 @@
+
+.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
diff --git a/app/templates/guest_book.html b/app/templates/guest_book.html
new file mode 100644
index 0000000000000000000000000000000000000000..b2d32fce5267d65a487a12d6b0bc9612cee1a30c
--- /dev/null
+++ b/app/templates/guest_book.html
@@ -0,0 +1,31 @@
+{% 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
diff --git a/app/tests.py b/app/tests.py
index 70a6ce3257865de95350ee668681e7d027fb8c2f..75c6aaa7774aad2c4b4149b933e763e2609cdd79 100644
--- a/app/tests.py
+++ b/app/tests.py
@@ -1,3 +1,11 @@
+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):
@@ -98,14 +96,14 @@ class DaftarKatalogSortingByJumlahUnduhTest(TestCase):
         self.client = Client()
 
         self.contributor_credential = {
-        "email": "kontributor@gov.id",
-        "password": "passwordtest"
-            }
+            "email": "kontributor@gov.id",
+            "password": "passwordtest"
+        }
 
         self.contributor_credential_2 = {
-                "email": "kontributor2@gov.id",
-                "password": "passwordtest"
-            }
+            "email": "kontributor2@gov.id",
+            "password": "passwordtest"
+        }
 
         self.contributor = get_user_model().objects.create_user(
             **self.contributor_credential, name="Kontributor 1", is_contributor=True)
@@ -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 = {
@@ -176,7 +175,7 @@ class DaftarKatalogSortingByTerhangatTest(TestCase):
 
     def get_displayed_materi_in_number(self):
         response = self.client.get("/?sort=terhangat")
-        lst = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", 
+        lst = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]",
                                             response.content.decode())]
         return lst
 
@@ -188,7 +187,7 @@ class DaftarKatalogSortingByTerhangatTest(TestCase):
         self.contributor = get_user_model().objects.create_user(
             name="Kontributor",
             is_contributor=True,
-            **self.contributor_credential, 
+            **self.contributor_credential,
         )
         self.client = Client()
         content = b"Test file"
@@ -272,7 +271,7 @@ class DaftarKatalogSortingByTerhangatTest(TestCase):
         self.generate_view_materi(materi2, 1)
 
         self.assertAlmostEqual(materi1.hot_score, materi2.hot_score)
-        
+
     def test_page_has_option_sort_by_hottest(self):
         response = self.client.get("/")
         self.assertIn("terhangat", response.content.decode())
@@ -329,9 +328,9 @@ class DaftarKatalogSortingByJumlahKomentarTest(TestCase):
         self.client = Client()
 
         self.contributor_credential = {
-        "email": "kontributor@gov.id",
-        "password": "passwordtest"
-            }
+            "email": "kontributor@gov.id",
+            "password": "passwordtest"
+        }
 
         self.contributor_credential_2 = {
             "email": "kontributor2@gov.id",
@@ -359,25 +358,27 @@ 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()
 
         self.contributor_credential = {
-                "email": "kontributor@gov.id",
-                "password": "passwordtest"
-            }
+            "email": "kontributor@gov.id",
+            "password": "passwordtest"
+        }
 
         self.contributor_credential_2 = {
-                "email": "kontributor2@gov.id",
-                "password": "passwordtest"
-            }
+            "email": "kontributor2@gov.id",
+            "password": "passwordtest"
+        }
 
         self.contributor = get_user_model().objects.create_user(
             **self.contributor_credential, name="Kontributor 1", is_contributor=True)
@@ -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()
@@ -465,14 +469,14 @@ class DetailMateriTest(TestCase):
             "ExampleCover921.jpg", b"Test file")
         self.content = SimpleUploadedFile("ExampleFile921.pdf", b"Test file")
 
-        self.materi1 = Materi.objects.create(title="Materi 1", author="Agas", 
-                        uploader=self.contributor, publisher="Kelas SC", 
-                        descriptions="Deskripsi Materi 1", status="APPROVE", 
-                        cover=self.cover, content=self.content)
-        self.materi2 = Materi.objects.create(title="Materi 2", author="Agad", 
-                        uploader=self.contributor, publisher="Kelas SM", 
-                        descriptions="Deskripsi Materi 2", status="APPROVE", 
-                        cover=self.cover, content=self.content)
+        self.materi1 = Materi.objects.create(title="Materi 1", author="Agas",
+                                             uploader=self.contributor, publisher="Kelas SC",
+                                             descriptions="Deskripsi Materi 1", status="APPROVE",
+                                             cover=self.cover, content=self.content)
+        self.materi2 = Materi.objects.create(title="Materi 2", author="Agad",
+                                             uploader=self.contributor, publisher="Kelas SM",
+                                             descriptions="Deskripsi Materi 2", status="APPROVE",
+                                             cover=self.cover, content=self.content)
         self.url = "/materi/" + str(self.materi1.id) + "/"
         self.download_url1 = self.url + "unduh"
         self.url2 = "/materi/" + str(self.materi2.id) + "/"
@@ -482,14 +486,15 @@ class DetailMateriTest(TestCase):
         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",
-                                                 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, \
-            user=self.materi_with_published_date.uploader)
+                                                                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,
+                                          user=self.materi_with_published_date.uploader)
 
     def test_detail_materi_url_exist(self):
         response = Client().get(self.url)
@@ -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,16 +621,19 @@ 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"]
-        
+
         payload = {"comment": comment, "session_id": session_id}
         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,16 +641,19 @@ 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"]
-        
+
         payload = {"comment": comment, "session_id": session_id}
         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)
 
     def test_comment_new_does_not_have_dislike(self):
@@ -646,10 +663,13 @@ class DetailMateriTest(TestCase):
 
         response = self.client.get(url_materi)
         session_id = response.context["session_id"]
-        self.client.post(url_materi, {"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")
-        comment_dislike_counter = DislikeComment.objects.filter(comment=comment, session_id=session_id).count()
+        comment = Comment.objects.get(
+            comment="This is new comment by Anonymous")
+        comment_dislike_counter = DislikeComment.objects.filter(
+            comment=comment, session_id=session_id).count()
         self.assertEqual(comment_dislike_counter, 0)
 
     def test_comment_new_does_not_have_like(self):
@@ -659,10 +679,13 @@ class DetailMateriTest(TestCase):
 
         response = self.client.get(url_materi)
         session_id = response.context["session_id"]
-        self.client.post(url_materi, {"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")
-        comment_like_counter = LikeComment.objects.filter(comment=comment, session_id=session_id).count()
+        comment = Comment.objects.get(
+            comment="This is new comment by Anonymous")
+        comment_like_counter = LikeComment.objects.filter(
+            comment=comment, session_id=session_id).count()
         self.assertEqual(comment_like_counter, 0)
 
     def test_comment_sends_email_to_contributor_that_subscribes(self):
@@ -674,12 +697,13 @@ class DetailMateriTest(TestCase):
             is_subscribing_to_material_comments=True
         )
 
-        material = Materi.objects.create(title="Materi-subscribed", author="Tester", 
-                                uploader=contributor_subscribed, publisher="Kelas PMPL", 
-                                descriptions="Deskripsi Materi subscribed", status="APPROVE", 
-                                cover=self.cover, content=self.content)
+        material = Materi.objects.create(title="Materi-subscribed", author="Tester",
+                                         uploader=contributor_subscribed, publisher="Kelas PMPL",
+                                         descriptions="Deskripsi Materi subscribed", status="APPROVE",
+                                         cover=self.cover, content=self.content)
         url = "/materi/" + str(material.id) + "/"
-        self.client.login(**self.contributor_credential) # comment with other user
+        # comment with other user
+        self.client.login(**self.contributor_credential)
 
         prev_outbox_count = len(mail.outbox)
 
@@ -701,12 +725,13 @@ class DetailMateriTest(TestCase):
             is_subscribing_to_material_comments=False
         )
 
-        material = Materi.objects.create(title="Materi-not-subscribed", author="Tester", 
-                                uploader=contributor_not_subscribed, publisher="Kelas PMPL", 
-                                descriptions="Deskripsi Materi non-subscribed", status="APPROVE", 
-                                cover=self.cover, content=self.content)
+        material = Materi.objects.create(title="Materi-not-subscribed", author="Tester",
+                                         uploader=contributor_not_subscribed, publisher="Kelas PMPL",
+                                         descriptions="Deskripsi Materi non-subscribed", status="APPROVE",
+                                         cover=self.cover, content=self.content)
         url = "/materi/" + str(material.id) + "/"
-        self.client.login(**self.contributor_credential) # comment with other user
+        # comment with other user
+        self.client.login(**self.contributor_credential)
 
         prev_outbox_count = len(mail.outbox)
 
@@ -731,12 +756,13 @@ class DetailMateriTest(TestCase):
             is_subscribing_to_material_comments=True
         )
 
-        material = Materi.objects.create(title="Materi-subscribed", author="Tester", 
-                                uploader=contributor_subscribed, publisher="Kelas PMPL", 
-                                descriptions="Deskripsi Materi subscribed", status="APPROVE", 
-                                cover=self.cover, content=self.content)
+        material = Materi.objects.create(title="Materi-subscribed", author="Tester",
+                                         uploader=contributor_subscribed, publisher="Kelas PMPL",
+                                         descriptions="Deskripsi Materi subscribed", status="APPROVE",
+                                         cover=self.cover, content=self.content)
         url = "/materi/" + str(material.id) + "/"
-        self.client.login(**contributor_subscribed_credentials) # comment with the same user
+        # comment with the same user
+        self.client.login(**contributor_subscribed_credentials)
 
         prev_outbox_count = len(mail.outbox)
 
@@ -771,7 +797,8 @@ class DetailMateriTest(TestCase):
     def test_detail_materi_not_contains_form_comment(self):
         response = self.client.get(self.url)
         self.assertNotContains(response, "Beri komentar...")
-        self.assertContains(response, "Login terlebih dahulu untuk berkomentar")
+        self.assertContains(
+            response, "Login terlebih dahulu untuk berkomentar")
 
     def create_and_delete_comment(self, is_admin=False, is_contributor=False):
         url = self.url
@@ -790,7 +817,8 @@ class DetailMateriTest(TestCase):
 
     def test_delete_comments_by_admin(self):
         self.create_and_delete_comment(is_admin=True)
-        count = Comment.objects.all().filter(comment="This is new comment by Anonymous").count()
+        count = Comment.objects.all().filter(
+            comment="This is new comment by Anonymous").count()
         self.assertEqual(count, 0)
 
     def test_delete_comments_by_contributor(self):
@@ -799,7 +827,8 @@ class DetailMateriTest(TestCase):
         self.assertRaises(PermissionDenied)
         self.assertEqual(response.status_code, 403)
 
-        count = Comment.objects.all().filter(comment="This is new comment by Anonymous").count()
+        count = Comment.objects.all().filter(
+            comment="This is new comment by Anonymous").count()
         self.assertEqual(count, 1)
 
     def test_delete_comments_by_anonymous(self):
@@ -808,10 +837,10 @@ class DetailMateriTest(TestCase):
         self.assertRaises(PermissionDenied)
         self.assertEqual(response.status_code, 403)
 
-        count = Comment.objects.all().filter(comment="This is new comment by Anonymous").count()
+        count = Comment.objects.all().filter(
+            comment="This is new comment by Anonymous").count()
         self.assertEqual(count, 1)
 
-    
     def test_review_models_can_create_new_object(self):
         test = Review.objects.create(
             username="saul", profile="121212", review="232323")
@@ -905,7 +934,7 @@ class DetailMateriTest(TestCase):
         self.assertContains(response, "Review (0)")
 
         self.client.post(
-            url, {"review": review })
+            url, {"review": review})
         self.client.post(
             url, {"review": review})
         response = self.client.get(url)
@@ -919,7 +948,8 @@ class DetailMateriTest(TestCase):
     def test_detail_materi_not_contains_form_review(self):
         response = self.client.get(self.url)
         self.assertNotContains(response, "Beri Review")
-        self.assertContains(response, "Login terlebih dahulu untuk Membuat review")
+        self.assertContains(
+            response, "Login terlebih dahulu untuk Membuat review")
 
     def test_tombol_citasiAPA(self):
         response = self.client.get(self.url)
@@ -927,25 +957,27 @@ class DetailMateriTest(TestCase):
 
     def test_hasil_citasi_APA_materi_has_no_published_date(self):
         response = self.client.get(self.url)
-        expected = self.materi1.author + " . (n.d) . " + self.materi1.title + " . " + self.materi1.publisher	
+        expected = self.materi1.author + \
+            " . (n.d) . " + self.materi1.title + " . " + self.materi1.publisher
         self.assertIn(expected, response.context["citationAPA"])
         self.assertIn(expected,
                       response.context["citationAPA"])
 
     def test_hasil_citasi_APA_materi_has_published_date(self):
         response = self.client.get(self.materi_with_published_date_url)
-        published_date = self.materi_with_published_date.published_date.strftime("%Y-%m-%d %H:%M")	
-        expected = (	
-            self.materi_with_published_date.author	
-            + " . ("	
-            + published_date	
-            + ") . "	
-            + self.materi_with_published_date.title	
-            + " . "	
-            + self.materi_with_published_date.publisher	
-        )	
+        published_date = self.materi_with_published_date.published_date.strftime(
+            "%Y-%m-%d %H:%M")
+        expected = (
+            self.materi_with_published_date.author
+            + " . ("
+            + published_date
+            + ") . "
+            + self.materi_with_published_date.title
+            + " . "
+            + self.materi_with_published_date.publisher
+        )
         self.assertIn(expected, response.context["citationAPA"])
-    
+
     def test_citation_IEEE_button(self):
         response = self.client.get(self.url)
         self.assertContains(response, "Citate IEEE")
@@ -957,19 +989,19 @@ class DetailMateriTest(TestCase):
         current_month = current_date.strftime("%b")
         current_year = str(current_date.year)
 
-        expected = (	
-            "Agas, "	
-            + "Materi 1. "	
-            + "Kelas SC, n.d. "	
-            + "Accessed on: "	
-            + current_month	
-            + ". "	
-            + current_day	
-            + ", "	
-            + current_year	
-            + ". [Online]. "	
-            + "Available: http://testserver"	
-            + self.url	
+        expected = (
+            "Agas, "
+            + "Materi 1. "
+            + "Kelas SC, n.d. "
+            + "Accessed on: "
+            + current_month
+            + ". "
+            + current_day
+            + ", "
+            + current_year
+            + ". [Online]. "
+            + "Available: http://testserver"
+            + self.url
         )
         self.assertIn(expected, response.context["citationIEEE"])
 
@@ -979,7 +1011,8 @@ class DetailMateriTest(TestCase):
         current_day = str(current_date.day)
         current_month = current_date.strftime("%b")
         current_year = str(current_date.year)
-        published_date = self.materi_with_published_date.published_date.strftime('%Y')
+        published_date = self.materi_with_published_date.published_date.strftime(
+            '%Y')
 
         expected = "Agas, " + \
                    "Materi 1. " + \
@@ -1049,10 +1082,11 @@ class DetailMateriTest(TestCase):
         context2 = {}
         DetailMateriService.init_materi_download_count(context1, self.materi1)
         DetailMateriService.init_materi_download_count(context2, self.materi2)
-        self.assertNotEqual(context1['materi_download_count'], context2['materi_download_count'])
+        self.assertNotEqual(
+            context1['materi_download_count'], context2['materi_download_count'])
         self.assertEqual(context1['materi_download_count'], 3)
         self.assertEqual(context2['materi_download_count'], 2)
-    
+
     def test_download_count_displayed_on_template_when_no_download(self):
         response = self.client.get(self.url)
         html = response.content.decode("utf-8")
@@ -1093,16 +1127,20 @@ class DetailMateriTest(TestCase):
         dcount_materi1 = self.materi1.unduh.all().count()
         dcount_materi2 = self.materi2.unduh.all().count()
 
-        self.check_materi_info_in_html(self.dcount_info_name, dcount_materi1, html)
-        self.check_materi_info_not_in_html(self.dcount_info_name, dcount_materi2, html)
-        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)
-    
+        self.check_materi_info_in_html(
+            self.dcount_info_name, dcount_materi1, html)
+        self.check_materi_info_not_in_html(
+            self.dcount_info_name, dcount_materi2, html)
+        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,
@@ -1112,8 +1150,8 @@ class DetailMateriTest(TestCase):
 
         response = self.client.get(self.url)
         html = response.content.decode("utf-8")
-        self.check_materi_info_in_html(self.lcount_info_name, 1, html)        
-        
+        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,
@@ -1125,17 +1163,17 @@ class DetailMateriTest(TestCase):
         }
         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)
@@ -1148,6 +1186,7 @@ class DetailMateriTest(TestCase):
         html = response.content.decode("utf-8")
         self.check_materi_info_in_html(self.lcount_info_name, 0, html)
 
+
 class PostsViewTest(TestCase):
 
     @classmethod
@@ -1175,14 +1214,14 @@ class PostsViewTest(TestCase):
                 "comments": [],
             }
 
-            for j in range (LIKES_COUNT_PER_POST[i]):
+            for j in range(LIKES_COUNT_PER_POST[i]):
                 Like.objects.create(
-                    timestamp=timezone.now(), 
-                    materi=post, 
+                    timestamp=timezone.now(),
+                    materi=post,
                     session_id=("dummysession-" + str(i) + '-' + str(j))
                 )
                 time.sleep(0.1)
-            
+
         for i, post_id in enumerate(post_comment_group_dict):
             post = post_comment_group_dict[post_id]["data"]
 
@@ -1202,7 +1241,8 @@ class PostsViewTest(TestCase):
             "email": "user@email.com",
             "password": "justpass"
         }
-        cls.user = User.objects.create_user(**cls.user_credentials, is_contributor=True)
+        cls.user = User.objects.create_user(
+            **cls.user_credentials, is_contributor=True)
         cls.data = cls.generate_posts_data(cls.user)
 
     def _request_as_user(self):
@@ -1252,12 +1292,12 @@ class PostsViewTest(TestCase):
 
         self.assertRegex(
             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[1]}">)' + \
-            rf'.*(<div id="post-{posts[0]}">)' + \
+            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[1]}">)' +
+            rf'.*(<div id="post-{posts[0]}">)' +
             rf'.*(<div id="post-{posts[0]}-comment-{comments[0][0]}">)'
         )
 
@@ -1269,9 +1309,11 @@ class PostsViewTest(TestCase):
 
             self.assertContains(
                 response,
-                '<span id="post-like-count-' + str(post.id) + '">' +  str(post.like_count) + '</span>',
+                '<span id="post-like-count-' +
+                str(post.id) + '">' + str(post.like_count) + '</span>',
             )
 
+
 class TemplateLoaderTest(TestCase):
     def test_template_loader_url_exist(self):
         url = "/test-page.html"
@@ -1324,13 +1366,13 @@ class UploadPageTest(TestCase):
         response = Client().get("/fake/")
         self.assertEqual(response.status_code, 404)
 
-    def test_upload_page_url_admin_doesnt_exist(self):        
+    def test_upload_page_url_admin_doesnt_exist(self):
         self.client.login(email="admin@gov.id",
                           password="admin")
         response = self.client.get("/unggah/")
         self.assertEqual(response.status_code, 403)
 
-    def test_upload_page_url_admin_cant_upload(self):        
+    def test_upload_page_url_admin_cant_upload(self):
         self.client.login(email="admin@gov.id",
                           password="admin")
         response = self.client.post("/unggah/")
@@ -1366,6 +1408,7 @@ class UploadPageTest(TestCase):
         # Negative tests
         self.assertNotContains(response, "anything")
 
+
 class UploadExcelPageTest(TestCase):
     def setUp(self):
         self.client = Client()
@@ -1449,7 +1492,9 @@ class UploadExcelPageTest(TestCase):
 
         file_path = os.path.join(settings.MEDIA_ROOT, 'dummy.xlsx')
 
-        writer = pd.ExcelWriter(file_path, engine='xlsxwriter') #pylint: disable=abstract-class-instantiated
+        # pylint: disable=abstract-class-instantiated
+        writer = pd.ExcelWriter(
+            file_path, engine='xlsxwriter')
         data_frame.to_excel(writer, index=0)
         writer.save()
 
@@ -1460,14 +1505,15 @@ class UploadExcelPageTest(TestCase):
                           password="kontributor")
 
         field_lengths = {
-            'author':30,
-            'publisher':30,
+            'author': 30,
+            'publisher': 30,
         }
-        file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths)
+        file_name, data_frame = self.create_dummy_excel(
+            field_lengths=field_lengths)
 
         with open(file_name, 'rb') as fp:
             response = self.client.post("/unggah_excel/", {'excel': fp})
-        
+
         messages = list(dj_messages.get_messages(response.wsgi_request))
         msg_text = messages[0].message
 
@@ -1478,33 +1524,34 @@ class UploadExcelPageTest(TestCase):
                           password="kontributor")
 
         field_lengths = {
-            'title':50,
-            'publisher':30,
+            'title': 50,
+            'publisher': 30,
         }
-        file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths)
+        file_name, data_frame = self.create_dummy_excel(
+            field_lengths=field_lengths)
 
         with open(file_name, 'rb') as fp:
             response = self.client.post("/unggah_excel/", {'excel': fp})
-        
+
         messages = list(dj_messages.get_messages(response.wsgi_request))
         msg_text = messages[0].message
 
         self.assertIn('Author', msg_text)
 
-
     def test_upload_excel_upload_file_publisher_error(self):
         self.client.login(email="kontributor@gov.id",
                           password="kontributor")
 
         field_lengths = {
-            'title':50,
-            'author':30,
+            'title': 50,
+            'author': 30,
         }
-        file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths)
+        file_name, data_frame = self.create_dummy_excel(
+            field_lengths=field_lengths)
 
         with open(file_name, 'rb') as fp:
             response = self.client.post("/unggah_excel/", {'excel': fp})
-        
+
         messages = list(dj_messages.get_messages(response.wsgi_request))
         msg_text = messages[0].message
 
@@ -1515,15 +1562,16 @@ class UploadExcelPageTest(TestCase):
                           password="kontributor")
 
         field_lengths = {
-            'title':50,
-            'author':30,
-            'publisher':30,
+            'title': 50,
+            'author': 30,
+            'publisher': 30,
         }
-        file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths)
+        file_name, data_frame = self.create_dummy_excel(
+            field_lengths=field_lengths)
 
         with open(file_name, 'rb') as fp:
             response = self.client.post("/unggah_excel/", {'excel': fp})
-        
+
         messages = list(dj_messages.get_messages(response.wsgi_request))
         msg_text = messages[0].message
 
@@ -1538,37 +1586,38 @@ class UploadExcelPageTest(TestCase):
         Category(name='Deep Learning').save()
 
         field_lengths = {
-            'title':50,
-            'author':30,
-            'publisher':30,
+            'title': 50,
+            'author': 30,
+            'publisher': 30,
         }
 
-        categories = ['Computer Science','Machine Learning','Deep Learning']
-        
-        file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths, categories=categories)
+        categories = ['Computer Science', 'Machine Learning', 'Deep Learning']
+
+        file_name, data_frame = self.create_dummy_excel(
+            field_lengths=field_lengths, categories=categories)
 
         with open(file_name, 'rb') as fp:
             self.client.post("/unggah_excel/", {'excel': fp})
-        
+
         title = data_frame['Title'][0]
         materi = Materi.objects.get(title=title)
         default_path = 'book-cover-placeholder.png'
 
         self.assertTrue(materi)
         self.assertEquals(materi.cover.name, default_path)
-        self.assertTrue(os.path.exists(os.path.join(settings.MEDIA_ROOT, default_path)))
+        self.assertTrue(os.path.exists(
+            os.path.join(settings.MEDIA_ROOT, default_path)))
 
     def test_upload_excel_download_template(self):
         self.client.login(email="kontributor@gov.id",
                           password="kontributor")
 
         response = self.client.get("/unggah_excel/?template=1")
-        
-        self.assertEquals(response['Content-Type'],'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
-        self.assertEquals(response['Content-Disposition'],'attachment; filename=template.xlsx')
-
-        
 
+        self.assertEquals(
+            response['Content-Type'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
+        self.assertEquals(response['Content-Disposition'],
+                          'attachment; filename=template.xlsx')
 
 
 class DashboardKontributorViewTest(TestCase):
@@ -1638,6 +1687,7 @@ class DashboardKontributorViewTest(TestCase):
         html = response.content.decode("utf-8")
         self.assertIn(ERROR_403_MESSAGE, html)
 
+
 class DeleteMateriTest(TestCase):
     def setUp(self):
         self.client = Client()
@@ -1663,7 +1713,8 @@ class DeleteMateriTest(TestCase):
             "content.txt", b"Test")
         self.cover = SimpleUploadedFile(
             "flower.jpg", b"Test file")
-        self.contributor = User.objects.create_contributor(**self.contributor_credential)
+        self.contributor = User.objects.create_contributor(
+            **self.contributor_credential)
         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()
@@ -1674,14 +1725,14 @@ class DeleteMateriTest(TestCase):
         self.client.login(**self.contributor_credential)
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 302)
-    
+
     def test_url_soft_delete_materi_is_success_as_admin(self):
         self.client.login(**self.admin_credential)
         response = self.client.get(self.url)
-        self.assertEqual(response.status_code, 302) 
+        self.assertEqual(response.status_code, 302)
         self.materi1.refresh_from_db()
         self.assertNotEqual(self.materi1.deleted_at, None)
-    
+
     def test_url_soft_delete_materi_is_success_as_superuser(self):
         self.client.login(**self.superuser_credential)
         response = self.client.get(self.url)
@@ -1689,6 +1740,7 @@ class DeleteMateriTest(TestCase):
         self.materi1.refresh_from_db()
         self.assertNotEqual(self.materi1.deleted_at, None)
 
+
 class ProfilViewTest(TestCase):
     @classmethod
     def setUpTestData(cls):
@@ -1697,10 +1749,13 @@ class ProfilViewTest(TestCase):
         cls.template_name = "profil.html"
         cls.view = ProfilView
 
-        cls.contributor_credentials = {"email": "contributor@gov.id", "password": "justpass"}
-        cls.contributor = User.objects.create_contributor(**cls.contributor_credentials)
+        cls.contributor_credentials = {
+            "email": "contributor@gov.id", "password": "justpass"}
+        cls.contributor = User.objects.create_contributor(
+            **cls.contributor_credentials)
 
-        cls.admin_credentials = {"email": "admin@gov.id", "password": "justpass"}
+        cls.admin_credentials = {
+            "email": "admin@gov.id", "password": "justpass"}
         cls.admin = User.objects.create_admin(**cls.admin_credentials)
 
     def test_returns_correct_profile_view(self):
@@ -1708,7 +1763,7 @@ class ProfilViewTest(TestCase):
         self.assertEqual(found.func.__name__, self.view.as_view().__name__)
 
     def _request_as_user(self, credentials):
-        self.client = Client() 
+        self.client = Client()
         self.client.login(**credentials)
         return self.client.get(self.url)
 
@@ -1739,10 +1794,13 @@ class SuntingProfilViewTest(TestCase):
         cls.template_name = "sunting.html"
         cls.view = SuntingProfilView
 
-        cls.contributor_credentials = {"email": "contributor@gov.id", "password": "justpass"}
-        cls.contributor = User.objects.create_contributor(**cls.contributor_credentials)
+        cls.contributor_credentials = {
+            "email": "contributor@gov.id", "password": "justpass"}
+        cls.contributor = User.objects.create_contributor(
+            **cls.contributor_credentials)
 
-        cls.admin_credentials = {"email": "admin@gov.id", "password": "justpass"}
+        cls.admin_credentials = {
+            "email": "admin@gov.id", "password": "justpass"}
         cls.admin = User.objects.create_admin(**cls.admin_credentials)
 
     def test_sunting_profile_view(self):
@@ -1750,7 +1808,7 @@ class SuntingProfilViewTest(TestCase):
         self.assertEqual(found.func.__name__, self.view.as_view().__name__)
 
     def _request_as_user(self, credentials):
-        self.client = Client() 
+        self.client = Client()
         self.client.login(**credentials)
         return self.client.get(self.url)
 
@@ -1785,8 +1843,10 @@ class SuntingProfilViewTest(TestCase):
         }
         form = SuntingProfilForm(data=form_data)
 
-        self.assertEqual(form.fields["twitter"].widget.attrs.get("autofocus"), "")
-        self.assertEqual(form.fields["instagram"].widget.attrs.get("autofocus"), None)
+        self.assertEqual(
+            form.fields["twitter"].widget.attrs.get("autofocus"), "")
+        self.assertEqual(
+            form.fields["instagram"].widget.attrs.get("autofocus"), None)
 
 
 class SuksesLoginKontributorTest(TestCase):
@@ -2131,7 +2191,7 @@ class RevisiMateriTest(TestCase):
         # Logout
         self.client.logout()
 
-    
+
 class GenerateDummyCommandTest(TestCase):
 
     def setUp(self):
@@ -2167,7 +2227,8 @@ class RemoveDummyCommandTest(TestCase):
 
         call_command("removedummy", stdout=stdout)
 
-        self.assertIn("Successfully remove all dummy object", 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)
@@ -2192,8 +2253,10 @@ class RatingMateriTest(TestCase):
         self.contributor = get_user_model().objects.create_user(
             **self.contributor_credential, name="Kontributor", is_contributor=True
         )
-        self.user_one = get_user_model().objects.create_user(**self.user_one_credential, name="User One")
-        self.user_two = get_user_model().objects.create_user(**self.user_two_credential, name="User Two")
+        self.user_one = get_user_model().objects.create_user(
+            **self.user_one_credential, name="User One")
+        self.user_two = get_user_model().objects.create_user(
+            **self.user_two_credential, name="User Two")
         self.cover = SimpleUploadedFile(
             "cover.jpg",
             b"Test file"
@@ -2219,17 +2282,20 @@ class RatingMateriTest(TestCase):
         self.assertEqual(rating.materi, self.materi1)
         self.assertEqual(rating.user, self.user_one)
         self.assertTrue(0 < rating.score < 6)
-        self.assertEqual(rating.__str__(), "Material:Materi 1 | User:User One | Rating:5")
+        self.assertEqual(rating.__str__(),
+                         "Material:Materi 1 | User:User One | Rating:5")
 
     def test_rating_model_should_not_be_created_with_rating_more_than_5(self):
         with self.assertRaises(ValidationError) as context:
             Rating(materi=self.materi1, user=self.user_one, score=6).save()
-        self.assertTrue('Rating score must be integer between 1-5' in str(context.exception))
+        self.assertTrue(
+            'Rating score must be integer between 1-5' in str(context.exception))
 
     def test_rating_model_should_not_be_created_with_rating_less_than_1(self):
         with self.assertRaises(ValidationError) as context:
             Rating(materi=self.materi1, user=self.user_one, score=0).save()
-        self.assertTrue('Rating score must be integer between 1-5' in str(context.exception))
+        self.assertTrue(
+            'Rating score must be integer between 1-5' in str(context.exception))
 
     def test_one_materi_should_be_able_to_be_related_to_multiple_rating(self):
         Rating(materi=self.materi1, user=self.user_one, score=1).save()
@@ -2281,7 +2347,8 @@ class RatingMateriTest(TestCase):
         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 = 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")
@@ -2306,52 +2373,65 @@ class RatingMateriTest(TestCase):
 
     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 = 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_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 = 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_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 = 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_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 = 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_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 = 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_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})
+        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_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):
@@ -2369,11 +2449,12 @@ class RatingMateriTest(TestCase):
         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()
+                                 self.filename, "rb").read()
 
     def test_get_random_filename_isCorrect(self):
         generated_name = get_random_filename(self.filename)
@@ -2396,6 +2477,7 @@ class FileManagementUtilTest(TestCase):
             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()
@@ -2447,15 +2529,16 @@ class RequestMateriTest(TestCase):
         self.client.login(**self.contributor_credential)
 
         response = self.client.post(self.url,
-                         data={
-                             'title': 'Requested Material'
-                         })
+                                    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.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):
@@ -2502,62 +2585,77 @@ class RatingContributorTest(TransactionTestCase):
         self.url = f"/profil/{self.contributor.email}/"
 
     def test_add_rating_contributor(self):
-        RatingContributor.objects.create(score=3, contributor=self.contributor, user=self.anonymous)
+        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, contributor=self.contributor, user=self.anonymous)
+            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, contributor=self.contributor, user=self.anonymous)
+            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):
         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.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())
+        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):
         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.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())
+        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):
         response = self.client.get(self.url)
-        self.assertTemplateUsed(response=response, template_name="app/katalog_kontri.html")
+        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):
         self.client.login(**self.anonymous_credential)
-        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 5})
+        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):
         self.client.login(**self.anonymous_credential)
-        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 6})
+        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.logout()
 
         self.client.login(**self.admin_credential)
-        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": -1})
+        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)
@@ -2566,10 +2664,13 @@ class RatingContributorTest(TransactionTestCase):
         score = 5
         avg = [score]
         self.client.login(**self.anonymous_credential)
-        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": score})
+        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.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(
@@ -2578,10 +2679,13 @@ class RatingContributorTest(TransactionTestCase):
         score = 4
         avg.append(score)
         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})
+        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.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(
@@ -2590,10 +2694,13 @@ class RatingContributorTest(TransactionTestCase):
         score = 3
         avg.append(score)
         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})
+        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.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(
@@ -2602,120 +2709,131 @@ class RatingContributorTest(TransactionTestCase):
         score = 2
         avg.append(score)
         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})
+        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.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.login(**self.admin_credential)
-        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": score})
+        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)
-    
+        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
+            "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")
-    
+        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})
+        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
+            "contributor": self.contributor.id,
+            "user": self.admin.id,
+            "score": 3
         })
-        
-        self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count())
+
+        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())
-    
+        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
+            "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.assertEqual(1, RatingContributor.objects.filter(
+            contributor=self.contributor).count())
 
-        self.client.post(self.url, data={"delete":True})
+        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())
-    
+        self.assertEqual(0, RatingContributor.objects.filter(
+            contributor=self.contributor).count())
+
     def test_average_still_be_correct_when_rating_was_deleted(self):
-        scores = [2,4]
+        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]
+            "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]
+            "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})
+        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
+            "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})
+        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})
+        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):
@@ -2754,26 +2872,30 @@ class UserDownloadHistoryTest(TestCase):
         self.materi1 = Materi.objects.first()
         self.download_url = f"/materi/{self.materi1.id}/unduh"
         self.history_url = "/download-history/"
-	
+
     def test_multiple_insert_download_statistic_with_user(self):
-        DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save()
+        DownloadStatistics(materi=self.materi1,
+                           downloader=self.user1_anonim).save()
         num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         self.assertEqual(num_of_downloads, 1)
-		
-        DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save()
+
+        DownloadStatistics(materi=self.materi1,
+                           downloader=self.user1_anonim).save()
         num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         self.assertEqual(num_of_downloads, 2)
-		
+
     def test_download_statistics_bound_to_specific_user(self):
-        DownloadStatistics(materi=self.materi1, downloader=self.user1_anonim).save()
+        DownloadStatistics(materi=self.materi1,
+                           downloader=self.user1_anonim).save()
         num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         self.assertEqual(num_of_downloads, 1)
-		
+
         DownloadStatistics(materi=self.materi1).save()
         num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         self.assertEqual(num_of_downloads, 1)
-		
-        DownloadStatistics(materi=self.materi1, downloader=self.user2_anonim).save()
+
+        DownloadStatistics(materi=self.materi1,
+                           downloader=self.user2_anonim).save()
         user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         user2_num_of_downloads = self.user2_anonim.riwayat_unduh.all().count()
         self.assertEqual(user1_num_of_downloads, 1)
@@ -2782,19 +2904,19 @@ class UserDownloadHistoryTest(TestCase):
     def test_registered_user_download(self):
         # Login
         self.client.login(**self.user1_credential)
-		
+
         self.client.get(self.download_url)
         num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         self.assertEqual(num_of_downloads, 1)
-		
+
         # Logout
         self.client.logout()
-		
+
     def test_unregistered_user_download(self):
         self.client.get(self.download_url)
         downloaded_materi = self.client.session['downloaded_materi']
         num_of_downloads = DownloadStatistics.objects.filter(
-                            pk__in=downloaded_materi).count()
+            pk__in=downloaded_materi).count()
         self.assertEqual(num_of_downloads, 1)
 
     def test_registered_user_multiple_download(self):
@@ -2807,53 +2929,53 @@ class UserDownloadHistoryTest(TestCase):
         self.client.get(self.download_url)
         num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         self.assertEqual(num_of_downloads, 2)
-		
+
         # Logout
         self.client.logout()
-		
+
     def test_unregistered_user_multiple_download(self):
         self.client.get(self.download_url)
         downloaded_materi = self.client.session['downloaded_materi']
         num_of_downloads = DownloadStatistics.objects.filter(
-                            pk__in=downloaded_materi).count()
+            pk__in=downloaded_materi).count()
         self.assertEqual(num_of_downloads, 1)
 
         self.client.get(self.download_url)
         downloaded_materi = self.client.session['downloaded_materi']
         num_of_downloads = DownloadStatistics.objects.filter(
-                            pk__in=downloaded_materi).count()
+            pk__in=downloaded_materi).count()
         self.assertEqual(num_of_downloads, 2)
-		
+
     def test_registered_user_doesnt_use_session_when_download(self):
         # Login
         self.client.login(**self.user1_credential)
-		
+
         self.client.get(self.download_url)
         self.assertFalse('downloaded_materi' in self.client.session)
-		
+
         # Logout
         self.client.logout()
-	
+
     def test_download_history_bound_to_specific_user(self):
         # Login Anonym 1
         self.client.login(**self.user1_credential)
         self.client.get(self.download_url)
         num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         self.assertEqual(num_of_downloads, 1)
-		
+
         # Logout Anonym 1
         self.client.logout()
-		
-		# Unregistered User download
+
+        # Unregistered User download
         self.client.get(self.download_url)
         user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
         downloaded_materi = self.client.session['downloaded_materi']
         guest_num_of_downloads = DownloadStatistics.objects.filter(
-                            pk__in=downloaded_materi).count()
+            pk__in=downloaded_materi).count()
         self.assertEqual(user1_num_of_downloads, 1)
         self.assertEqual(guest_num_of_downloads, 1)
-		
-		# Login Anonym 2
+
+        # Login Anonym 2
         self.client.login(**self.user2_credential)
         self.client.get(self.download_url)
         user1_num_of_downloads = self.user1_anonim.riwayat_unduh.all().count()
@@ -2861,10 +2983,11 @@ class UserDownloadHistoryTest(TestCase):
         self.assertEqual(user1_num_of_downloads, 1)
         self.assertEqual(guest_num_of_downloads, 1)
         self.assertEqual(user2_num_of_downloads, 1)
-		
+
         # Logout Anonym 2
         self.client.logout()
 
+
 class DownloadHistoryViewTest(TestCase):
     def setUp(self):
         self.user_credential = {
@@ -2881,42 +3004,42 @@ class DownloadHistoryViewTest(TestCase):
             **self.contributor_credential, name="Kontributor", is_contributor=True
         )
         self.client = Client()
-		
+
         content1 = b"Test file"
         content2 = b"File Test"
-		
-        self.cover1 = SimpleUploadedFile("cover1.jpg",content1)
-        self.content1 = SimpleUploadedFile("content1.txt",content1)
-		
-        self.cover2 = SimpleUploadedFile("cover2.jpg",content2)
-        self.content2 = SimpleUploadedFile("content2.txt",content2)
-		
+
+        self.cover1 = SimpleUploadedFile("cover1.jpg", content1)
+        self.content1 = SimpleUploadedFile("content1.txt", content1)
+
+        self.cover2 = SimpleUploadedFile("cover2.jpg", content2)
+        self.content2 = SimpleUploadedFile("content2.txt", content2)
+
         self.materi1 = Materi.objects.create(title="Materi 1", author="Agas", uploader=self.contributor,
-               publisher="Kelas SC", descriptions="Deskripsi Materi 1",
-               status="PENDING", cover=self.cover1, content=self.content1)
+                                             publisher="Kelas SC", descriptions="Deskripsi Materi 1",
+                                             status="PENDING", cover=self.cover1, content=self.content1)
         self.materi2 = Materi.objects.create(title="Materi 2", author="Danin", uploader=self.contributor,
-               publisher="Kelas DDP", descriptions="Deskripsi Materi 2",
-               status="PENDING", cover=self.cover2, content=self.content2)
-			   
+                                             publisher="Kelas DDP", descriptions="Deskripsi Materi 2",
+                                             status="PENDING", cover=self.cover2, content=self.content2)
+
         self.download_url1 = f"/materi/{self.materi1.id}/unduh"
         self.download_url2 = f"/materi/{self.materi2.id}/unduh"
         self.history_url = "/download-history/"
-		
+
         # Login
         self.client.login(**self.user_credential)
 
     def tearDown(self):
         # Logout
         self.client.logout()
-		
+
     def test_allow_registered_user(self):
         response = self.client.get(self.history_url)
         self.assertEqual(response.status_code, 200)
-	
+
     def test_allow_unregistered_user(self):
-	    # Forced Logout
+        # Forced Logout
         self.client.logout()
-		
+
         response = self.client.get(self.history_url)
         self.assertEqual(response.status_code, 200)
 
@@ -2928,14 +3051,14 @@ class DownloadHistoryViewTest(TestCase):
         response = self.client.get(self.history_url)
         resp_html = response.content.decode('utf8')
         self.assertIn(self.user_anonim.name, resp_html)
-		
+
     def test_registered_user_download_history_correctly_displayed(self):
         self.client.get(self.download_url1)
         self.client.get(self.download_url2)
         self.client.get(self.download_url1)
-		
+
         jkt_timezone = pytz.timezone(TIME_ZONE)
-		
+
         download_history = self.user_anonim.riwayat_unduh.all()
         response = self.client.get(self.history_url)
         resp_html = response.content.decode('utf8')
@@ -2943,19 +3066,20 @@ class DownloadHistoryViewTest(TestCase):
             downloaded_materi = riwayat.materi
             self.assertIn(downloaded_materi.title, resp_html)
             self.assertIn(downloaded_materi.author, resp_html)
-			
+
             jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone)
-            self.assertIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html)
-			
+            self.assertIn(jkt_timestamp.strftime(
+                "%d %B %Y %H:%M:%S"), resp_html)
+
     def test_unregistered_user_download_history_correctly_displayed(self):
         self.client.logout()
 
         self.client.get(self.download_url1)
         self.client.get(self.download_url2)
         self.client.get(self.download_url1)
-		
+
         jkt_timezone = pytz.timezone(TIME_ZONE)
-		
+
         response = self.client.get(self.history_url)
         resp_html = response.content.decode('utf8')
         for riwayat_id in self.client.session['downloaded_materi']:
@@ -2963,19 +3087,20 @@ class DownloadHistoryViewTest(TestCase):
             downloaded_materi = riwayat.materi
             self.assertIn(downloaded_materi.title, resp_html)
             self.assertIn(downloaded_materi.author, resp_html)
-			
+
             jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone)
-            self.assertIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html)
-			
+            self.assertIn(jkt_timestamp.strftime(
+                "%d %B %Y %H:%M:%S"), resp_html)
+
     def test_download_history_not_display_if_user_changed(self):
         self.client.get(self.download_url1)
         self.client.get(self.download_url2)
         self.client.get(self.download_url1)
 
         self.client.logout()
-		
+
         jkt_timezone = pytz.timezone(TIME_ZONE)
-		
+
         download_history = self.user_anonim.riwayat_unduh.all()
         response = self.client.get(self.history_url)
         resp_html = response.content.decode('utf8')
@@ -2983,25 +3108,26 @@ class DownloadHistoryViewTest(TestCase):
             downloaded_materi = riwayat.materi
             self.assertNotIn(downloaded_materi.title, resp_html)
             self.assertNotIn(downloaded_materi.author, resp_html)
-			
+
             jkt_timestamp = riwayat.timestamp.astimezone(jkt_timezone)
-            self.assertNotIn(jkt_timestamp.strftime("%d %B %Y %H:%M:%S"), resp_html)
-			
+            self.assertNotIn(jkt_timestamp.strftime(
+                "%d %B %Y %H:%M:%S"), resp_html)
+
     def test_unregistered_user_download_history_wont_be_saved_if_user_changes(self):
         self.client.logout()
 
         self.client.get(self.download_url1)
         self.client.get(self.download_url2)
         self.client.get(self.download_url1)
-		
+
         self.client.get(self.history_url)
 
         self.client.login(**self.user_credential)
         self.client.logout()
         self.assertFalse('downloaded_materi' in self.client.session)
-			
+
     def test_download_history_sorted_by_download_time(self):
-		# download with 1 second interval to differ download time
+        # download with 1 second interval to differ download time
         self.client.get(self.download_url1)
         sleep(1)
         self.client.get(self.download_url2)
@@ -3009,23 +3135,25 @@ class DownloadHistoryViewTest(TestCase):
         self.client.get(self.download_url1)
         sleep(1)
         self.client.get(self.download_url2)
-		
+
         response = self.client.get(self.history_url)
         resp_html = response.content.decode('utf8')
-		
-        table_html = ("<table" + resp_html.split("<table")[1]).split("</table>")[0] + "</table>"
+
+        table_html = ("<table" + resp_html.split("<table")
+                      [1]).split("</table>")[0] + "</table>"
         soup = BeautifulSoup(table_html, 'html.parser')
         histories_html = soup.find('tbody').find_all('tr')
         prev_timestamp = None
-		
+
         for riwayat_html in histories_html:
             materi_data = riwayat_html.find_all("td")
             date_format = "%d %B %Y %H:%M:%S"
-            materi_timestamp = datetime.strptime(materi_data[2].get_text(), date_format)
+            materi_timestamp = datetime.strptime(
+                materi_data[2].get_text(), date_format)
             if prev_timestamp:
                 self.assertTrue(prev_timestamp > materi_timestamp)
             prev_timestamp = materi_timestamp
-			
+
     def test_no_history_display_message(self):
         no_history_msg = "Anda belum mengunduh materi. Silahkan unduh materi yang anda butuhkan"
         response = self.client.get(self.history_url)
@@ -3034,9 +3162,9 @@ class DownloadHistoryViewTest(TestCase):
 
 
 class MateriModelTest(TestCase):
-    
+
     def setUp(self):
-        self.contributor = User.objects.create( 
+        self.contributor = User.objects.create(
             email="kontributor@gov.id",
             password="passwordtest",
             name="kontributor",
@@ -3056,12 +3184,15 @@ class MateriModelTest(TestCase):
         self.assertEqual(0, self.materi.like_count)
 
     def test_like_count_return_right_value_when_there_is_like(self):
-        Like.objects.create(timestamp=timezone.now(), materi=self.materi, session_id="dummysessionid1")
+        Like.objects.create(timestamp=timezone.now(),
+                            materi=self.materi, session_id="dummysessionid1")
         self.assertEqual(1, self.materi.like_count)
 
-        Like.objects.create(timestamp=timezone.now(), materi=self.materi, session_id="dummysessionid2")
+        Like.objects.create(timestamp=timezone.now(),
+                            materi=self.materi, session_id="dummysessionid2")
         self.assertEqual(2, self.materi.like_count)
 
+
 class MateriFavoriteTest(TestCase):
     @classmethod
     def setUpTestData(cls):
@@ -3070,7 +3201,8 @@ class MateriFavoriteTest(TestCase):
             "email": "user@email.com",
             "password": "justpass"
         }
-        cls.user = User.objects.create_user(**cls.user_credentials, is_contributor=True)
+        cls.user = User.objects.create_user(
+            **cls.user_credentials, is_contributor=True)
 
     def _request_as_user(self):
         self.client.login(**self.user_credentials)
@@ -3078,7 +3210,9 @@ class MateriFavoriteTest(TestCase):
 
     def test_url_resolves_to_favorite_view(self):
         found = resolve(self.url)
-        self.assertEqual(found.func.__name__, MateriFavorite.as_view().__name__)
+        self.assertEqual(found.func.__name__,
+                         MateriFavorite.as_view().__name__)
+
 
 class RandomizedMateriTest(TestCase):
     def setUp(self):
@@ -3123,7 +3257,8 @@ class RandomizedMateriTest(TestCase):
         response = Client().get("/?random=1")
         self.assertIn("Materi 1", response.content.decode())
         self.assertIn("Materi 2", response.content.decode())
-    
+
+
 class YearChoicesTest(TestCase):
     def test_release_year_contains_the_right_current_year(self):
         now = dt.date.today().year
@@ -3137,6 +3272,7 @@ class YearChoicesTest(TestCase):
 
         self.assertEqual((2000, 2000), choices[0])
 
+
 TEST_IMAGE = '''
 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
 WXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAABfElEQVQ4y52TvUuCURTGf5Zg
@@ -3176,20 +3312,24 @@ cpnR0WOUSiVEhLVKhbXXa7xcXqHyaoV6o0Hqd1MxUjqu7XYLMFkaNXtXYC09+R5UwbkYEcVaizFm
 P/LWGsLJydMs3VvCWkP3gzxK7OKu7Bl81/tEhKmpKVhYWNCJiQkNglDDMKdhLpf1/0AQhDo+Pq5z
 c3NKmqa6uLios7MXtFgsahRFGhUKHUS7KBQ0iiIdGhrS8+dndH5+XpMk0X8AMTVx/inpU4cAAAAl
 dEVYdGNyZWF0ZS1kYXRlADIwMTAtMTItMjZUMTQ6NDk6MjErMDk6MDAHHBB1AAAAJXRFWHRtb2Rp
-ZnktZGF0ZQAyMDEwLTEyLTI2VDE0OjQ5OjIxKzA5OjAwWK1mQQAAAABJRU5ErkJggg==
+ZnktZGF0ZQAyMDEwLTEyLTI2VDE0OjQ5OjIxKzA5OjAwWK1mQQAAAABJRU5ErkJggg ==
 '''.strip()
+
+
 class YTUrlVideoTest(TestCase):
     def setUp(self):
         self.client = Client()
-        self.contributor_credential = {"email": "kontributor@gov.id", "password": "passwordtest"}
+        self.contributor_credential = {
+            "email": "kontributor@gov.id", "password": "passwordtest"}
         self.contributor = get_user_model().objects.create_user(
             **self.contributor_credential, name="Kontributor", is_contributor=True
         )
 
         self.setUpImage()
         self.content = SimpleUploadedFile("ExampleFile221.pdf", b"Test file")
-        self.category = Category.objects.create(id="1", name="medis", description="kategori medis")
-    
+        self.category = Category.objects.create(
+            id="1", name="medis", description="kategori medis")
+
     @override_settings(MEDIA_ROOT=tempfile.gettempdir())
     def setUpImage(self):
         self.cover = InMemoryUploadedFile(
@@ -3214,23 +3354,23 @@ class YTUrlVideoTest(TestCase):
     def test_upload_materi_with_valid_yt_video_id(self):
         self.client.login(**self.contributor_credential)
         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,
-                                "yt_video_id":"jNwz4L9MGVY"}
+            "/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,
+                              "yt_video_id": "jNwz4L9MGVY"}
         )
         self.assertTrue(Materi.objects.get(title="Materi 1"))
-    
+
     def test_upload_materi_with_invalid_yt_video_id(self):
         self.client.login(**self.contributor_credential)
         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,
-                                "yt_video_id":"randomId"}
+            "/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,
+                              "yt_video_id": "randomId"}
         )
         self.assertEqual(Materi.objects.filter(title="Materi 2").count(), 0)
-    
+
     def test_detail_materi_has_video_if_yt_video_id_not_empty(self):
         self.test_upload_materi_with_valid_yt_video_id()
         pk = Materi.objects.get(title="Materi 1").pk
@@ -3241,20 +3381,23 @@ class YTUrlVideoTest(TestCase):
     def test_detail_materi_has_no_video_if_yt_video_id_empty(self):
         self.client.login(**self.contributor_credential)
         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,}
+            "/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, }
         )
         pk = Materi.objects.get(title="Materi 2").pk
         response = self.client.get("/materi/"+str(pk)+"/")
         html = response.content.decode("utf-8")
         self.assertNotIn("Video", html)
 
+
 class ChangePasswordTest(TestCase):
     def setUp(self):
         self.client = Client()
-        self.kontributor = User.objects.create_contributor(email="kontributor@gov.id", password="kontributor")
-        self.admin = User.objects.create_admin(email="admin@gov.id", password="admin")
+        self.kontributor = User.objects.create_contributor(
+            email="kontributor@gov.id", password="kontributor")
+        self.admin = User.objects.create_admin(
+            email="admin@gov.id", password="admin")
         self.url = "/change-password/"
         self.view = PasswordChangeViews
         self.template_name = "change-password.html"
@@ -3281,7 +3424,7 @@ class ChangePasswordTest(TestCase):
         # Logout
         self.client.logout()
 
-    
+
 class SeeRatedMateriByUser(TestCase):
     def setUp(self):
         self.client = Client()
@@ -3293,7 +3436,8 @@ class SeeRatedMateriByUser(TestCase):
             "email": "admin@gov.id",
             "password": id_generator()
         }
-        self.kontributor = User.objects.create_contributor(**self.contributor_credential)
+        self.kontributor = User.objects.create_contributor(
+            **self.contributor_credential)
         self.admin = User.objects.create_admin(**self.admin_credential)
         self.given_rating_url = "/given-rating/"
 
@@ -3318,13 +3462,17 @@ class SeeRatedMateriByUser(TestCase):
         self.materi2 = Materi.objects.all()[1]
         self.materi3 = Materi.objects.all()[2]
         time.sleep(5)
-        self.rating_test_1 = Rating(materi=self.materi1, user=self.kontributor, score=5)
+        self.rating_test_1 = Rating(
+            materi=self.materi1, user=self.kontributor, score=5)
         time.sleep(5)
-        self.rating_test_2 = Rating(materi=self.materi2, user=self.kontributor, score=4)
+        self.rating_test_2 = Rating(
+            materi=self.materi2, user=self.kontributor, score=4)
         time.sleep(5)
-        self.rating_test_3 = Rating(materi=self.materi3, user=self.kontributor, score=3)
+        self.rating_test_3 = Rating(
+            materi=self.materi3, user=self.kontributor, score=3)
         time.sleep(5)
-        self.rating_test_4 = Rating(materi=self.materi3, user=self.admin, score=3)
+        self.rating_test_4 = Rating(
+            materi=self.materi3, user=self.admin, score=3)
 
         self.rating_test_1.save()
         self.rating_test_2.save()
@@ -3364,12 +3512,14 @@ class SeeRatedMateriByUser(TestCase):
         self.assertNotIn(self.rating_test_4, response.context['rating_list'])
 
     def test_given_rating_page_no_rating_should_display_message(self):
-        user_credential = {"email": "user@mail.com", "password": id_generator()}
+        user_credential = {"email": "user@mail.com",
+                           "password": id_generator()}
         User.objects.create_contributor(**user_credential)
         self.client.login(**user_credential)
         response = self.client.get(self.given_rating_url)
         self.assertEqual(len(response.context['rating_list']), 0)
-        self.assertIn("Anda belum pernah memberikan rating ke materi", response.content.decode("utf-8"))
+        self.assertIn("Anda belum pernah memberikan rating ke materi",
+                      response.content.decode("utf-8"))
         self.assertNotIn(self.materi1.title, response.content.decode("utf-8"))
 
     def test_given_rating_page_order_should_give_default_parameter(self):
@@ -3385,24 +3535,28 @@ class SeeRatedMateriByUser(TestCase):
         self.assertEqual(response.context['order_by_key'], 'timestamp')
 
         # No order_by parameter, should default to asc
-        response = self.client.get(self.given_rating_url + '?order_by_key=score')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=score')
         self.assertEqual(response.context['order_by'], 'dsc')
         self.assertEqual(response.context['order_by_key'], 'score')
 
         # INVALID PARAMETERS
         # Starts with negative
-        response = self.client.get(self.given_rating_url + '?order_by_key=-score')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=-score')
         self.assertEqual(response.context['order_by'], 'dsc')
         self.assertEqual(response.context['order_by_key'], 'score')
 
         # Invalid params
-        response = self.client.get(self.given_rating_url + '?order_by_key=halohalohalo&order=haihaihaihai')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=halohalohalo&order=haihaihaihai')
         self.assertEqual(response.context['order_by'], 'dsc')
         self.assertEqual(response.context['order_by_key'], 'timestamp')
 
     def test_given_rating_page_order_ascending_should_be_correct(self):
         self.client.login(**self.contributor_credential)
-        response = self.client.get(self.given_rating_url + '?order_by_key=score&order=asc')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=score&order=asc')
 
         # From Low to High
         # order key score
@@ -3411,13 +3565,15 @@ class SeeRatedMateriByUser(TestCase):
         self.assertEqual(list(response.context['rating_list']),
                          [self.rating_test_3, self.rating_test_2, self.rating_test_1])
         # order key timestamp
-        response = self.client.get(self.given_rating_url + '?order_by_key=timestamp&order=asc')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=timestamp&order=asc')
         self.assertEqual(response.context['order_by'], 'asc')
         self.assertEqual(response.context['order_by_key'], 'timestamp')
         self.assertEqual(list(response.context['rating_list']),
                          [self.rating_test_1, self.rating_test_2, self.rating_test_3])
         # order key materi title
-        response = self.client.get(self.given_rating_url + '?order_by_key=materi__title&order=asc')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=materi__title&order=asc')
         self.assertEqual(response.context['order_by'], 'asc')
         self.assertEqual(response.context['order_by_key'], 'materi__title')
         self.assertEqual(list(response.context['rating_list']),
@@ -3427,26 +3583,30 @@ class SeeRatedMateriByUser(TestCase):
         self.client.login(**self.contributor_credential)
         # From High to Low
         # order key score
-        response = self.client.get(self.given_rating_url + '?order_by_key=score&order=dsc')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=score&order=dsc')
         self.assertEqual(response.context['order_by'], 'dsc')
         self.assertEqual(response.context['order_by_key'], 'score')
         self.assertEqual(list(response.context['rating_list']),
                          [self.rating_test_1, self.rating_test_2, self.rating_test_3])
 
         # order key timestamp
-        response = self.client.get(self.given_rating_url + '?order_by_key=timestamp&order=dsc')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=timestamp&order=dsc')
         self.assertEqual(response.context['order_by'], 'dsc')
         self.assertEqual(response.context['order_by_key'], 'timestamp')
         self.assertEqual(list(response.context['rating_list']),
                          [self.rating_test_3, self.rating_test_2, self.rating_test_1])
 
         # order key materi title
-        response = self.client.get(self.given_rating_url + '?order_by_key=materi__title&order=dsc')
+        response = self.client.get(
+            self.given_rating_url + '?order_by_key=materi__title&order=dsc')
         self.assertEqual(response.context['order_by'], 'dsc')
         self.assertEqual(response.context['order_by_key'], 'materi__title')
         self.assertEqual(list(response.context['rating_list']),
                          [self.rating_test_2, self.rating_test_1, self.rating_test_3])
 
+
 class PasswordValidatorPolicyTest(TestCase):
     def setUp(self):
         self.password_no_lowercase = "PASSW0RD!"
@@ -3456,28 +3616,34 @@ class PasswordValidatorPolicyTest(TestCase):
         self.password_length_lower_than_8 = "P4ss!"
         self.password_enforcing_policy = "Passw0rd!"
         self.validator = PasswordPolicyValidator()
-    
+
     def test_using_password_no_lowercase(self):
-        self.assertRaises(ValidationError, self.validator.validate, self.password_no_lowercase)
+        self.assertRaises(
+            ValidationError, self.validator.validate, self.password_no_lowercase)
 
     def test_using_password_no_upprcase(self):
-        self.assertRaises(ValidationError, self.validator.validate, self.password_no_uppercase)
-    
+        self.assertRaises(
+            ValidationError, self.validator.validate, self.password_no_uppercase)
+
     def test_using_password_no_digit(self):
-        self.assertRaises(ValidationError, self.validator.validate, self.password_no_digit)
+        self.assertRaises(
+            ValidationError, self.validator.validate, self.password_no_digit)
 
     def test_using_password_no_special_char(self):
-        self.assertRaises(ValidationError, self.validator.validate, self.password_no_special_char)
-    
+        self.assertRaises(ValidationError, self.validator.validate,
+                          self.password_no_special_char)
+
     def test_using_password_with_length_less_than_8(self):
-        self.assertRaises(ValidationError, self.validator.validate, self.password_length_lower_than_8)
-    
+        self.assertRaises(ValidationError, self.validator.validate,
+                          self.password_length_lower_than_8)
+
     def test_using_password_using_correct_policy(self):
-        self.assertEquals(self.validator.validate(self.password_enforcing_policy), None)
-    
+        self.assertEquals(self.validator.validate(
+            self.password_enforcing_policy), None)
+
 
 class LandingPageNavbarTest(TestCase):
-    
+
     def setUp(self):
         self.client = Client()
         self.contributor_credential = {
@@ -3492,62 +3658,95 @@ class LandingPageNavbarTest(TestCase):
             "email": "public@gov.id",
             "password": id_generator()
         }
-        self.kontributor = User.objects.create_contributor(**self.contributor_credential)
+        self.kontributor = User.objects.create_contributor(
+            **self.contributor_credential)
         self.admin = User.objects.create_admin(**self.admin_credential)
         self.public = User.objects.create_user(**self.public_credential)
 
     def test_navbar_admin(self):
         self.client.login(**self.admin_credential)
         response = self.client.get('/')
-        self.assertContains(response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>')
-        self.assertContains(response, '<a class="nav-link" href="/forum">Forum</a>')
-        self.assertContains(response, '<a class="nav-link" href="/news/all">Berita</a>')
-        self.assertContains(response, '<a class="nav-link" href="/profil">Profil</a>')
-        self.assertContains(response, '<a class="nav-link" href="/logout">Logout</a>')
-        self.assertContains(response, '<a class="nav-link" href="/administration">Administrasi</a>')
-        self.assertNotContains(response, '<a class="nav-link" href="/dashboard">Dasbor</a>')
+        self.assertContains(
+            response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/forum">Forum</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/news/all">Berita</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/profil">Profil</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/logout">Logout</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/administration">Administrasi</a>')
+        self.assertNotContains(
+            response, '<a class="nav-link" href="/dashboard">Dasbor</a>')
 
     def test_navbar_contributor(self):
         self.client.login(**self.contributor_credential)
         response = self.client.get('/')
-        self.assertContains(response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>')
-        self.assertContains(response, '<a class="nav-link" href="/forum">Forum</a>')
-        self.assertContains(response, '<a class="nav-link" href="/news/all">Berita</a>')
-        self.assertContains(response, '<a class="nav-link" href="/profil">Profil</a>')
-        self.assertContains(response, '<a class="nav-link" href="/logout">Logout</a>')
-        self.assertContains(response, '<a class="nav-link" href="/dashboard">Dasbor</a>')
-        self.assertNotContains(response, '<a class="nav-link" href="/administration">Administrasi</a>')
+        self.assertContains(
+            response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/forum">Forum</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/news/all">Berita</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/profil">Profil</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/logout">Logout</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/dashboard">Dasbor</a>')
+        self.assertNotContains(
+            response, '<a class="nav-link" href="/administration">Administrasi</a>')
 
     def test_navbar_public(self):
         self.client.login(**self.public_credential)
         response = self.client.get('/')
-        self.assertContains(response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>')
-        self.assertContains(response, '<a class="nav-link" href="/forum">Forum</a>')
-        self.assertContains(response, '<a class="nav-link" href="/news/all">Berita</a>')
-        self.assertContains(response, '<a class="nav-link" href="/profil">Profil</a>')
-        self.assertContains(response, '<a class="nav-link" href="/logout">Logout</a>')
-        self.assertNotContains(response, '<a class="nav-link" href="/dashboard">Dasbor</a>')
-        self.assertNotContains(response, '<a class="nav-link" href="/administration">Administrasi</a>')
+        self.assertContains(
+            response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/forum">Forum</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/news/all">Berita</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/profil">Profil</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/logout">Logout</a>')
+        self.assertNotContains(
+            response, '<a class="nav-link" href="/dashboard">Dasbor</a>')
+        self.assertNotContains(
+            response, '<a class="nav-link" href="/administration">Administrasi</a>')
 
     def test_navbar_anonymous(self):
         response = self.client.get('/')
-        self.assertContains(response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>')
-        self.assertContains(response, '<a class="nav-link" href="/forum">Forum</a>')
-        self.assertContains(response, '<a class="nav-link" href="/news/all">Berita</a>')
+        self.assertContains(
+            response, '<a class="sidebar-brand-text navbar-brand" href="/">Digipus</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/forum">Forum</a>')
+        self.assertContains(
+            response, '<a class="nav-link" href="/news/all">Berita</a>')
         self.assertContains(
             response,
             '<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">'
         )
         self.assertContains(response, 'Registrasi</a>')
-        self.assertContains(response, '<a class="dropdown-item" href="/registrasi/umum">Umum</a>')
-        self.assertContains(response, '<a class="dropdown-item" href="/registrasi">Kontributor</a>')
+        self.assertContains(
+            response, '<a class="dropdown-item" href="/registrasi/umum">Umum</a>')
+        self.assertContains(
+            response, '<a class="dropdown-item" href="/registrasi">Kontributor</a>')
         self.assertContains(response, 'Login</a>')
-        self.assertContains(response, '<a class="dropdown-item" href="/login">Kontributor</a>')
-        self.assertContains(response, '<a class="dropdown-item" href="/login_admin">Admin</a>')
-        self.assertNotContains(response, '<a class="nav-link" href="/profil">Profil</a>')
-        self.assertNotContains(response, '<a class="nav-link" href="/logout">Logout</a>')
-        self.assertNotContains(response, '<a class="nav-link" href="/dashboard">Dasbor</a>')
-        self.assertNotContains(response, '<a class="nav-link" href="/administration">Administrasi</a>')
+        self.assertContains(
+            response, '<a class="dropdown-item" href="/login">Kontributor</a>')
+        self.assertContains(
+            response, '<a class="dropdown-item" href="/login_admin">Admin</a>')
+        self.assertNotContains(
+            response, '<a class="nav-link" href="/profil">Profil</a>')
+        self.assertNotContains(
+            response, '<a class="nav-link" href="/logout">Logout</a>')
+        self.assertNotContains(
+            response, '<a class="nav-link" href="/dashboard">Dasbor</a>')
+        self.assertNotContains(
+            response, '<a class="nav-link" href="/administration">Administrasi</a>')
 
 
 class MateriRecommendationTest(TestCase):
@@ -3607,7 +3806,8 @@ class MateriRecommendationTest(TestCase):
         )
 
         response = Client().get("/?recommendation=1")
-        list = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", response.content.decode())]
+        list = [int(id) for id in re.findall(
+            r"Materi\s(\d+)[^\d]", response.content.decode())]
         self.assertEqual(list, [2, 1, 3])
 
     def test_set_date_as_tiebreak_if_like_counts_is_same(self):
@@ -3644,10 +3844,50 @@ class MateriRecommendationTest(TestCase):
         Like.objects.create(materi=materi1)
 
         response = Client().get("/?recommendation=1")
-        list = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", response.content.decode())]
+        list = [int(id) for id in re.findall(
+            r"Materi\s(\d+)[^\d]", response.content.decode())]
         self.assertEqual(list, [1, 2])
 
 
+class GuestBookTest(TestCase):
+    def test_form_name_input_has_placeholder_and_css_classes(self):
+        form = GuestBookForm()
+        self.assertIn('placeholder="Input your name"', form.as_p())
+        self.assertIn('class="form-control input-lg"', form.as_p())
+
+    def test_form_job_input_has_placeholder_and_css_classes(self):
+        form = GuestBookForm()
+        self.assertIn('placeholder="Input your job"', form.as_p())
+        self.assertIn('class="form-control input-lg"', form.as_p())
+
+    def test_form_gender_input_has_choices_and_css_classes(self):
+        form = GuestBookForm()
+        self.assertIn('option value="Male"', form.as_p())
+        self.assertIn('option value="Female">', form.as_p())
+        self.assertIn('class="form-control input-lg"', form.as_p())
+
+    def test_form_validation_for_blank_items(self):
+        form = GuestBookForm(data={'name': '', 'job': '', 'gender': ''})
+        self.assertFalse(form.is_valid())
+        self.assertEqual(
+            form.errors['name'],
+            ['Name is required']
+        )
+        self.assertEqual(
+            form.errors['job'],
+            ['Job is required']
+        )
+        self.assertEqual(
+            form.errors['gender'],
+            ['Gender is required']
+        )
+
+    def test_can_create_guest_book_instance(self):
+        guestBook = GuestBook(name='Selvy', job='Student', gender='Female')
+        guestBook.save()
+        self.assertEqual(GuestBook.objects.count(), 1)
+
+
 class MateriSearchVectorTest(TestCase):
     def setUp(self):
         Materi.SEARCH_INDEX = (("title", "A"), ("author", "B"))
@@ -3663,7 +3903,8 @@ class MateriSearchVectorTest(TestCase):
         self.assertGreaterEqual(len(search_vector_string.split(",")), 1)
 
     def test_search_vector_based_on_indexed_attribute(self):
-        materi = Materi(title="Buku 1", author="Pembuat 1", descriptions="Deskripsi 1")
+        materi = Materi(title="Buku 1", author="Pembuat 1",
+                        descriptions="Deskripsi 1")
         materi.save()
 
         search_vector_string = list(
@@ -3673,7 +3914,8 @@ class MateriSearchVectorTest(TestCase):
         self.assertIn("pembuat", search_vector_string)
 
     def test_search_vector_not_based_on_unindexed_attribute(self):
-        materi = Materi(title="Buku 1", author="Pembuat 1", descriptions="Deskripsi 1")
+        materi = Materi(title="Buku 1", author="Pembuat 1",
+                        descriptions="Deskripsi 1")
         materi.save()
 
         search_vector_string = list(
@@ -3686,12 +3928,14 @@ class MateriSearchVectorTest(TestCase):
         materi = Materi(title="Sebelum reconstruct")
         materi.save()
 
-        search_vector = list(Materi.objects.values_list("_search_vector", flat=True))[0]
+        search_vector = list(Materi.objects.values_list(
+            "_search_vector", flat=True))[0]
 
         materi.title = "Setelah reconstruct"
         materi.save()
 
-        search_vector = list(Materi.objects.values_list("_search_vector", flat=True))[0]
+        search_vector = list(Materi.objects.values_list(
+            "_search_vector", flat=True))[0]
 
         self.assertIn("setelah", search_vector)
 
@@ -3699,12 +3943,14 @@ class MateriSearchVectorTest(TestCase):
         materi = Materi(descriptions="sebelum reconstruct")
         materi.save()
 
-        search_vector = list(Materi.objects.values_list("_search_vector", flat=True))[0]
+        search_vector = list(Materi.objects.values_list(
+            "_search_vector", flat=True))[0]
 
         materi.descriptions = "sebelum reconstruct"
         materi.save()
 
-        search_vector = list(Materi.objects.values_list("_search_vector", flat=True))[0]
+        search_vector = list(Materi.objects.values_list(
+            "_search_vector", flat=True))[0]
 
         self.assertNotIn("setelah", search_vector)
 
@@ -3743,7 +3989,8 @@ class MateriSearchTest(TestCase):
         materi_2 = Materi(descriptions="ini lumayan cocok lumayan cocok")
         materi_2.save()
 
-        materi_1 = Materi(descriptions="ini sangat cocok sangat cocok sangat cocok")
+        materi_1 = Materi(
+            descriptions="ini sangat cocok sangat cocok sangat cocok")
         materi_1.save()
 
         materi_4 = Materi(descriptions="ini tidak")
@@ -3809,8 +4056,10 @@ class BacaNantiTest(TestCase):
         self.contributor = get_user_model().objects.create_user(
             **self.contributor_credential, name="Kontributor", is_contributor=True
         )
-        self.user_one = get_user_model().objects.create_user(**self.user_one_credential, name="User One")
-        self.user_two = get_user_model().objects.create_user(**self.user_two_credential, name="User Two")
+        self.user_one = get_user_model().objects.create_user(
+            **self.user_one_credential, name="User One")
+        self.user_two = get_user_model().objects.create_user(
+            **self.user_two_credential, name="User Two")
         self.cover = SimpleUploadedFile(
             "cover.jpg",
             b"Test file"
@@ -3846,7 +4095,7 @@ class BacaNantiTest(TestCase):
         self.assertEqual(read_later_one.user, self.user_one)
         self.assertEqual(read_later_two.materi, self.materi1)
         self.assertEqual(read_later_two.user, self.user_two)
-    
+
     def test_readlater_user_must_not_unique(self):
         ReadLater(materi=self.materi1, user=self.user_one).save()
         ReadLater(materi=self.materi2, user=self.user_one).save()
@@ -3870,7 +4119,7 @@ class BacaNantiTest(TestCase):
     def test_readlater_user_cant_null(self):
         with self.assertRaises(IntegrityError):
             ReadLater(materi=self.materi1).save()
-    
+
     def test_readlater_profile_page_url_exist(self):
         self.client.login(**self.user_one_credential)
         response = self.client.get(self.url)
@@ -3879,30 +4128,35 @@ class BacaNantiTest(TestCase):
     def test_readlater_profile_page_using_template(self):
         self.client.login(**self.user_one_credential)
         response = self.client.get(self.url)
-        self.assertTemplateUsed(response=response, template_name="baca-nanti.html")
-    
+        self.assertTemplateUsed(
+            response=response, template_name="baca-nanti.html")
+
     def test_toggle_readlater_url_exist(self):
         self.client.login(**self.user_one_credential)
-        response = self.client.post(self.toggle_url, {'materi_id': self.materi1.id})
+        response = self.client.post(
+            self.toggle_url, {'materi_id': self.materi1.id})
         self.assertEqual(response.status_code, 200)
 
     def test_checking_readlater_in_materi_create_object(self):
         self.client.login(**self.user_one_credential)
         self.client.post(self.toggle_url, {'materi_id': self.materi1.id})
-        read_later_exist = ReadLater.objects.filter(materi=self.materi1, user=self.user_one).exists()
+        read_later_exist = ReadLater.objects.filter(
+            materi=self.materi1, user=self.user_one).exists()
         self.assertEqual(read_later_exist, True)
-    
+
     def test_unchecking_readlater_in_materi_delete_object(self):
         self.client.login(**self.user_one_credential)
         self.client.post(self.toggle_url, {'materi_id': self.materi1.id})
         sleep(1)
         self.client.post(self.toggle_url, {'materi_id': self.materi1.id})
-        read_later_exist = ReadLater.objects.filter(materi=self.materi1, user=self.user_one).exists()
+        read_later_exist = ReadLater.objects.filter(
+            materi=self.materi1, user=self.user_one).exists()
         self.assertEqual(read_later_exist, False)
 
     def test_checking_readlater_in_materi_with_complete_paramater_return_success(self):
         self.client.login(**self.user_one_credential)
-        response = self.client.post(self.toggle_url, {'materi_id': self.materi1.id})
+        response = self.client.post(
+            self.toggle_url, {'materi_id': self.materi1.id})
         self.assertJSONEqual(
             str(response.content, encoding='utf-8'),
             {"success": True, "read_later_checked": True}
@@ -3912,20 +4166,22 @@ class BacaNantiTest(TestCase):
         self.client.login(**self.user_one_credential)
         self.client.post(self.toggle_url, {'materi_id': self.materi1.id})
         sleep(1)
-        response = self.client.post(self.toggle_url, {'materi_id': self.materi1.id})
+        response = self.client.post(
+            self.toggle_url, {'materi_id': self.materi1.id})
         self.assertJSONEqual(
             str(response.content, encoding='utf-8'),
             {"success": True, "read_later_checked": False}
         )
-    
+
     def test_toggle_readlater_return_if_method_snot_post(self):
         self.client.login(**self.user_one_credential)
-        response = self.client.get(self.toggle_url, {'materi_id': self.materi1.id})
+        response = self.client.get(
+            self.toggle_url, {'materi_id': self.materi1.id})
         self.assertJSONEqual(
             str(response.content, encoding='utf-8'),
             {"success": False, "msg": "Unsuported method"}
         )
-    
+
     def test_toggle_readlater_return_if_paramater_materi_id_not_found(self):
         self.client.login(**self.user_one_credential)
         response = self.client.post(self.toggle_url)
@@ -3934,19 +4190,21 @@ class BacaNantiTest(TestCase):
             {"success": False, "msg": "Missing parameter"}
         )
 
+
 class MateriStatsTest(TestCase):
 
     def setUp(self):
         self.credential = {
-            'email':"kontributor@gov.id", 
-            'password':"P@ssw0rd", 
+            'email': "kontributor@gov.id",
+            'password': "P@ssw0rd",
         }
 
         self.path = '/stats/'
         self.path_json = '/stats/?data=json'
         self.header = 'Summary Materi per Kategori'
 
-        self.contributor = User.objects.create_contributor(**self.credential, name="kontributor")
+        self.contributor = User.objects.create_contributor(
+            **self.credential, name="kontributor")
         self.client = Client()
 
         categories = []
@@ -3962,7 +4220,6 @@ class MateriStatsTest(TestCase):
                 m.categories.add(categories[i])
                 m.save()
 
-
     def test_stats_has_correct_template(self):
         self.client.login(**self.credential)
         response = self.client.get(self.path)
@@ -3975,9 +4232,9 @@ class MateriStatsTest(TestCase):
 
     def test_stats_as_anonymous(self):
         response = self.client.get(self.path)
-        self.assertEqual(response.status_code, 302) #redirect
+        self.assertEqual(response.status_code, 302)  # redirect
         response = self.client.get(self.path_json)
-        self.assertEqual(response.status_code, 302) #redirect
+        self.assertEqual(response.status_code, 302)  # redirect
 
     def test_stats_api_correct_data(self):
         self.client.login(**self.credential)
@@ -3992,9 +4249,9 @@ class UploadMateriTest(TestCase):
                                               password="kontributor", is_contributor=True)
         self.setUpImage()
         self.content = SimpleUploadedFile("ExampleFile221.pdf", b"Test file")
-        self.category = Category.objects.create(id="1", name="sains", description="kategori sains")
-        
-    
+        self.category = Category.objects.create(
+            id="1", name="sains", description="kategori sains")
+
     @override_settings(MEDIA_ROOT=tempfile.gettempdir())
     def setUpImage(self):
         self.cover = InMemoryUploadedFile(
@@ -4016,10 +4273,10 @@ class UploadMateriTest(TestCase):
         self.client.login(email="kontributor@gov.id",
                           password="kontributor")
 
-        data = {"title":"Dunia Binatang", "author":"Parzival", "publisher":"Buku Asyik", 
-                "release_year":"2015", "descriptions":"Buku dunia binatang seri 1",
-                'categories':"1", "cover":self.cover, "content":self.content}
-        
+        data = {"title": "Dunia Binatang", "author": "Parzival", "publisher": "Buku Asyik",
+                "release_year": "2015", "descriptions": "Buku dunia binatang seri 1",
+                'categories': "1", "cover": self.cover, "content": self.content}
+
         self.client.post("/unggah/", data=data)
 
         self.assertEqual(Materi.objects.count(), 1)
diff --git a/app/urls.py b/app/urls.py
index f89a0602819e3fcfc560e1a73d602edd0a439e04..fb1e0e927cfe413948fbf74502277c0c6d6859fe 100644
--- a/app/urls.py
+++ b/app/urls.py
@@ -5,8 +5,7 @@ from app import views
 from app.views import (DashboardKontributorView, ProfilView, StatisticsView,
                        SuksesLoginAdminView, SuksesLoginKontributorView, DownloadHistoryView,
                        SuntingProfilView, UploadMateriHTML, UploadMateriView, UploadMateriExcelView, PostsView,
-                       ReqMateriView, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, password_success, 
-                       SubmitVisitorView, ReadLaterView, MostContributor)
+                       ReqMateriView, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, password_success, SubmitVisitorView, GuestBookView, ReadLaterView, MostContributor)
 
 
 urlpatterns = [
@@ -18,13 +17,16 @@ urlpatterns = [
     path("review/delete/<int:pk_materi>/<int:pk_review>",
         views.delete_review, name="delete-review"),
     path("comment/like/", views.toggle_like_comment, name="comment-like-toggle"),
-    path("comment/dislike/", views.toggle_dislike_comment, name="comment-dislike-toggle"),
+    path("comment/dislike/", views.toggle_dislike_comment,
+         name="comment-dislike-toggle"),
     path("materi/<int:pk>/delete", views.delete_materi, name="detele-materi"),
     path("materi/<int:pk>/unduh", views.download_materi, name="download-materi"),
     path("materi/<int:pk>/view", views.view_materi, name="view-materi"),
     path("dashboard/", DashboardKontributorView.as_view(), name="dashboard"),
-    path("download-history/", DownloadHistoryView.as_view(), name="download-history"),
-    path("revisi/materi/<int:pk>/", views.RevisiMateriView.as_view(), name="revisi"),
+    path("download-history/", DownloadHistoryView.as_view(),
+         name="download-history"),
+    path("revisi/materi/<int:pk>/",
+         views.RevisiMateriView.as_view(), name="revisi"),
     path("unggah/", UploadMateriView.as_view(), name="unggah"),
     path("unggah_excel/", UploadMateriExcelView.as_view(), name="unggah_excel"),
     path("profil/", ProfilView.as_view(), name="profil"),
@@ -38,26 +40,33 @@ urlpatterns = [
     path("profil/<str:email>/", KatalogPerKontributorView.as_view(),
          name="katalog-per-kontributor"),
     path("materi/rate/", views.add_rating_materi, name="rate-materi"),
-    path("materi/<int:pk>/save-to-gdrive/", views.save_to_gdrive, name="save-to-gdrive"),
+    path("materi/<int:pk>/save-to-gdrive/",
+         views.save_to_gdrive, name="save-to-gdrive"),
     path("favorite/", MateriFavorite.as_view(), name="favorite"),
-    path("change-password/", PasswordChangeViews.as_view(template_name='change-password.html')),
+    path("change-password/",
+         PasswordChangeViews.as_view(template_name='change-password.html')),
     path("password_success/", views.password_success, name="password_success"),
     path("given-rating/", views.see_given_rating, name="see_given_rating"),
     path("submit-visitor/", SubmitVisitorView.as_view(), name="submit-visitor"),
     path("baca-nanti/", ReadLaterView.as_view(), name="read-later"),
     path("baca-nanti-toggle/", views.toggle_readlater, name="toggle-read-later"),
     path("stats/", StatisticsView.as_view(), name="stats"),
-    path("reset_password/", 
-        auth_views.PasswordResetView.as_view(template_name="password_reset.html"), 
-        name="reset_password"),
-    path("reset_password_sent/", 
-        auth_views.PasswordResetDoneView.as_view(template_name="password_reset_sent.html"), 
-        name="password_reset_done"),
-    path("reset/<uidb64>/<token>/", 
-        auth_views.PasswordResetConfirmView.as_view(template_name="password_reset_form.html"), 
-        name="password_reset_confirm"),
-    path("reset_password_complete/", 
-        auth_views.PasswordResetCompleteView.as_view(template_name="password_reset_done.html"), 
-        name="password_reset_complete"),
-    path("most-contributor/", MostContributor.as_view(), name="most-contributor")
+    path("reset_password/",
+         auth_views.PasswordResetView.as_view(
+             template_name="password_reset.html"),
+         name="reset_password"),
+    path("reset_password_sent/",
+         auth_views.PasswordResetDoneView.as_view(
+             template_name="password_reset_sent.html"),
+         name="password_reset_done"),
+    path("reset/<uidb64>/<token>/",
+         auth_views.PasswordResetConfirmView.as_view(
+             template_name="password_reset_form.html"),
+         name="password_reset_confirm"),
+    path("reset_password_complete/",
+         auth_views.PasswordResetCompleteView.as_view(
+             template_name="password_reset_done.html"),
+         name="password_reset_complete"),
+    path("most-contributor/", MostContributor.as_view(), name="most-contributor"),
+    path("guest-book/", GuestBookView.as_view(), name="guest-book")
 ]
diff --git a/app/views.py b/app/views.py
index 12bbc08ca1ad98d48615e56838d8bd99cca6b92e..1d831a6019cd4e655a8105cf694370d4ef23ccd8 100644
--- a/app/views.py
+++ b/app/views.py
@@ -24,7 +24,7 @@ from django.urls import reverse_lazy
 from django.views import defaults
 from django.views.generic import TemplateView
 
-from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm
+from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm, GuestBookForm
 from app.models import (
     Category,
     Comment,
@@ -34,6 +34,7 @@ from app.models import (
     ReqMaterial,
     Rating, RatingContributor,
     SubmitVisitor,
+    GuestBook,
     ReadLater,
     NotifikasiKontributor,
     AdminNotification
@@ -51,6 +52,7 @@ UNGGAH_URL = "/unggah/"
 UNGGAH_EXCEL_URL = "/unggah_excel/"
 LOGIN_URL = "/login/"
 
+
 def permission_denied(request, exception, template_name="error_403.html"):
     return defaults.permission_denied(request, exception, template_name)
 
@@ -66,11 +68,12 @@ class DaftarKatalog(TemplateView):
         context = self.get_context_data(**kwargs)
         context["kategori_list"] = Category.objects.all()
 
-        lst_materi = Materi.objects.filter(status="APPROVE").order_by("date_modified")
+        lst_materi = Materi.objects.filter(
+            status="APPROVE").order_by("date_modified")
         url = ""
 
-        lst_materi, url = DafterKatalogService.apply_options(lst_materi, request, url)
-
+        lst_materi, url = DafterKatalogService.apply_options(
+            lst_materi, request, url)
 
         context["materi_list"] = lst_materi
         paginator = Paginator(context["materi_list"], 15)
@@ -105,14 +108,16 @@ class KatalogPerKontributorView(TemplateView):
         contributor = context["contributor"]
         context["form_rating"] = RatingContributorForm(initial={
             "contributor": contributor,
-            "user":request.user
+            "user": request.user
         })
         context["avg_rating"] = User.objects.filter(email=kwargs["email"]) \
             .annotate(avg_rating=Avg("contributor__score"))[0]
-        context["count_rating"] = RatingContributor.objects.filter(contributor=contributor).count()
+        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()
+            has_rated = RatingContributor.objects.filter(
+                user=request.user, contributor=contributor).exists()
             context["has_rated"] = has_rated
 
         return self.render_to_response(context=context)
@@ -126,7 +131,7 @@ class KatalogPerKontributorView(TemplateView):
         is_update = request.POST.get('update', None)
         if is_delete:
             rating_contributor = get_object_or_404(
-                RatingContributor, 
+                RatingContributor,
                 user=request.user,
                 contributor=context["contributor"]
             )
@@ -137,7 +142,7 @@ class KatalogPerKontributorView(TemplateView):
                 user=request.user,
                 contributor=context["contributor"]
             ).first()
-            
+
             if rating and score:
                 rating.score = int(score)
                 rating.save()
@@ -145,9 +150,10 @@ class KatalogPerKontributorView(TemplateView):
             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"
 
@@ -156,17 +162,21 @@ class DetailMateri(TemplateView):
         if not self.request.session or not self.request.session.session_key:
             self.request.session.save()
         materi = get_object_or_404(Materi, pk=kwargs["pk"])
-        DetailMateriService.init_context_data(context, materi, self.request.session)
+        DetailMateriService.init_context_data(
+            context, materi, self.request.session)
         published_date = DetailMateriService.set_published_date(materi)
-        DetailMateriService.init_citation_and_materi_rating(context, materi, published_date, self.request)
+        DetailMateriService.init_citation_and_materi_rating(
+            context, materi, published_date, self.request)
         DetailMateriService.init_materi_download_count(context, materi)
 
         if self.request.user.is_authenticated:
-            materi_rating = Rating.objects.filter(materi=materi, user=self.request.user).first()
+            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
 
-            materi_read_later = ReadLater.objects.filter(materi=materi, user=self.request.user).first()
+            materi_read_later = ReadLater.objects.filter(
+                materi=materi, user=self.request.user).first()
             if materi_read_later is not None:
                 context['is_in_read_later_list'] = True
             else:
@@ -176,12 +186,14 @@ class DetailMateri(TemplateView):
 
         return context
 
-
     def get(self, request, *args, **kwargs):
         context = self.get_context_data(**kwargs)
-        query_set_for_comment = Comment.objects.filter(materi=context["materi_data"])
-        query_set_for_review = Review.objects.filter(materi=context["materi_data"])
-        has_disliked, has_liked = DetailMateriService.find_comment_like_dislike(query_set_for_comment, self.request.session)
+        query_set_for_comment = Comment.objects.filter(
+            materi=context["materi_data"])
+        query_set_for_review = Review.objects.filter(
+            materi=context["materi_data"])
+        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_comment"] = has_liked
@@ -191,45 +203,45 @@ class DetailMateri(TemplateView):
             opened_notif.delete()
         return self.render_to_response(context=context)
 
-
-
     def post(self, request, *args, **kwargs):
         comment_text = request.POST.get("comment", None)
-        review_text  = request.POST.get("review", None)
+        review_text = request.POST.get("review", None)
         report_text = request.POST.get("report", None)
-        if ((comment_text == None or comment_text == "" ) and \
-            (review_text == None or review_text == "") and \
-            (report_text == None or report_text == "")):
+        if ((comment_text == None or comment_text == "") and
+            (review_text == None or review_text == "") and
+                (report_text == None or report_text == "")):
             context = self.get_context_data(*args, **kwargs)
             context["error_message"] = "Anda belum menuliskan komentar"
             context["materi_data"] = get_object_or_404(Materi, pk=kwargs["pk"])
-            query_set_for_comment = Comment.objects.filter(materi=context["materi_data"])
+            query_set_for_comment = Comment.objects.filter(
+                materi=context["materi_data"])
             context["comment_data"] = query_set_for_comment
-            query_set_for_review = Review.objects.filter(materi=context["materi_data"])
+            query_set_for_review = Review.objects.filter(
+                materi=context["materi_data"])
             context["review_data"] = query_set_for_review
             return self.render_to_response(context=context)
 
         materi = get_object_or_404(Materi, pk=kwargs["pk"])
         user_obj = request.user if request.user.is_authenticated else None
         if user_obj:
-            if (comment_text != None ):
+            if (comment_text != None):
                 comment = Comment.objects.create(
                     comment=comment_text, username=DetailMateriService.get_user_name(request), materi=materi, user=user_obj
                 )
                 comment.save()
                 materi_uploader = materi.uploader
                 if materi_uploader.is_subscribing_to_material_comments and user_obj.email != materi_uploader.email:
-                    email_content =  f'User dengan email {user_obj.email} ' + \
+                    email_content = f'User dengan email {user_obj.email} ' + \
                         f'menambahkan komentar pada materi Anda dengan judul "{materi.title}".' + \
                         f'\nKomentar: "{comment.comment}".\n' + \
                         f'Silahkan akses halaman detail materi untuk berinteraksi lebih lanjut.'
 
                     MailService.send(
-                        subject = 'DIGIPUS: Komentar Baru pada Materi Anda',
-                        message = email_content,
-                        from_email = getattr(settings, 'EMAIL_HOST_USER'),
-                        recipient_list = [materi_uploader.email,],
-                    ) 
+                        subject='DIGIPUS: Komentar Baru pada Materi Anda',
+                        message=email_content,
+                        from_email=getattr(settings, 'EMAIL_HOST_USER'),
+                        recipient_list=[materi_uploader.email, ],
+                    )
             elif (review_text != None):
                 review = Review.objects.create(
                     review=review_text, username=DetailMateriService.get_user_name(request), materi=materi, user=user_obj
@@ -299,17 +311,16 @@ def add_rating_materi(request):
         materi_id = request.POST.get("materi_id", None)
         rating_score = request.POST.get("rating_score", None)
 
-
         is_valid_params, materi_id, \
-        rating_score, response, \
-        status_code = MateriFieldValidationHelperService.\
-            validate_materi_rating_params(materi_id,rating_score)
+            rating_score, response, \
+            status_code = MateriFieldValidationHelperService.\
+            validate_materi_rating_params(materi_id, rating_score)
 
         if not is_valid_params:
             return JsonResponse(response, status=status_code)
 
         is_valid_rating, materi, \
-        response, status_code = MateriFieldValidationHelperService.\
+            response, status_code = MateriFieldValidationHelperService.\
             validate_materi_rating(materi_id, request.user)
 
         if not is_valid_rating:
@@ -322,7 +333,6 @@ def add_rating_materi(request):
     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
@@ -349,7 +359,8 @@ def view_materi(request, pk):
         try:
             with open(file_path, "rb") as fh:
                 response = HttpResponse(fh.read(), content_type=mimetype[0])
-                DownloadViewMateriHelperService.build_view_materi_response(file_path, materi, response)
+                DownloadViewMateriHelperService.build_view_materi_response(
+                    file_path, materi, response)
                 return response
         except Exception:
             raise Http404(FILE_NOT_FOUND_MESSAGE)
@@ -365,6 +376,7 @@ def delete_materi(request, pk):
     materi.delete()
     return HttpResponseRedirect("/dashboard/")
 
+
 class UploadMateriView(TemplateView):
     template_name = UNGGAH_HTML
     context = {}
@@ -386,7 +398,8 @@ class UploadMateriView(TemplateView):
             if not UploadMateriService.validate_file_extension(konten, request, yt_url_id):
                 return HttpResponseRedirect(UNGGAH_URL)
             UploadMateriService.upload_materi(form, materi)
-            messages.success(request, "Materi berhasil diunggah, periksa riwayat unggah anda")
+            messages.success(
+                request, "Materi berhasil diunggah, periksa riwayat unggah anda")
             return HttpResponseRedirect(UNGGAH_URL)
         else:
             context = self.get_context_data(**kwargs)
@@ -394,8 +407,6 @@ class UploadMateriView(TemplateView):
             messages.error(request, "Terjadi kesalahan pada pengisian data")
             return self.render_to_response(context)
 
-
-
     def get(self, request, *args, **kwargs):
         if request.user.is_authenticated == False or not request.user.is_contributor:
             raise PermissionDenied(request)
@@ -424,11 +435,14 @@ class UploadMateriExcelView(TemplateView):
         if "template" in self.request.GET:
 
             data_frame = pd.DataFrame(
-                {"Title": [], "Author": [], "Publisher": [], "Categories": [], "Description": [],}
+                {"Title": [], "Author": [], "Publisher": [],
+                    "Categories": [], "Description": [], }
             )
 
+            # pylint: disable=abstract-class-instantiated
             with BytesIO() as b:
-                writer = pd.ExcelWriter(b, engine="xlsxwriter")  # pylint: disable=abstract-class-instantiated
+                writer = pd.ExcelWriter(
+                    b, engine="xlsxwriter")
                 data_frame.to_excel(writer, index=0)
                 writer.save()
                 response = HttpResponse(
@@ -462,9 +476,11 @@ class UploadMateriExcelView(TemplateView):
         for i in range(row):
 
             # Validate Categories
-            message = UploadMateriService.validate_excel_categories(categories, excel, i, message)
+            message = UploadMateriService.validate_excel_categories(
+                categories, excel, i, message)
 
-            message = UploadMateriService.validate_excel_field_length(excel, field_length, i, message)
+            message = UploadMateriService.validate_excel_field_length(
+                excel, field_length, i, message)
 
             if message != None:
                 break
@@ -475,15 +491,14 @@ class UploadMateriExcelView(TemplateView):
 
         # Second pass, save data
         with django.db.transaction.atomic():
-            UploadMateriService.upload_materi_excel(categories, excel, request, row)
+            UploadMateriService.upload_materi_excel(
+                categories, excel, request, row)
 
         messages.success(request, "Materi berhasil diunggah")
 
         return HttpResponseRedirect(UNGGAH_EXCEL_URL)
 
 
-
-
 class DashboardKontributorView(TemplateView):
     template_name = "dashboard.html"
 
@@ -493,7 +508,8 @@ class DashboardKontributorView(TemplateView):
         return super(DashboardKontributorView, self).dispatch(request, *args, **kwargs)
 
     def get_context_data(self, **kwargs):
-        context = super(DashboardKontributorView, self).get_context_data(**kwargs)
+        context = super(DashboardKontributorView,
+                        self).get_context_data(**kwargs)
         return context
 
     def get(self, request, *args, **kwargs):
@@ -540,13 +556,15 @@ class SuntingProfilView(TemplateView):
 
         current_user = self.request.user
 
-        form = SuntingProfilForm(request.POST, request.FILES, instance=current_user)
+        form = SuntingProfilForm(
+            request.POST, request.FILES, instance=current_user)
         if form.is_valid():
             current_user.default_profile_picture = True
 
             # Removing exifdata from profile picture on upload
             if request.FILES:
-                EditProfileService.update_profile_picture(current_user, request)
+                EditProfileService.update_profile_picture(
+                    current_user, request)
             else:
                 form.save()
             return HttpResponseRedirect("/profil/")
@@ -590,7 +608,8 @@ class SuksesLoginKontributorView(TemplateView):
         return super(SuksesLoginKontributorView, self).dispatch(request, *args, **kwargs)
 
     def get_context_data(self, **kwargs):
-        context = super(SuksesLoginKontributorView, self).get_context_data(**kwargs)
+        context = super(SuksesLoginKontributorView,
+                        self).get_context_data(**kwargs)
         return context
 
     def get(self, request, *args, **kwargs):
@@ -643,9 +662,11 @@ class PostsView(TemplateView):
         user = self.request.user
 
         posts = Materi.objects.filter(uploader=user).order_by("-date_created")
-        posts_data = {post.id: {"data": post, "comments": []} for post in posts}
+        posts_data = {post.id: {"data": post, "comments": []}
+                      for post in posts}
 
-        comments = Comment.objects.filter(materi__id__in=posts_data.keys()).order_by("-timestamp")
+        comments = Comment.objects.filter(
+            materi__id__in=posts_data.keys()).order_by("-timestamp")
 
         for comment in comments:
             posts_data[comment.materi.id]["comments"].append(comment)
@@ -680,7 +701,8 @@ class RevisiMateriView(TemplateView):
             raise PermissionDenied(request)
 
         current_materi = get_object_or_404(Materi, pk=kwargs["pk"])
-        form = UploadMateriForm(request.POST, request.FILES, instance=current_materi)
+        form = UploadMateriForm(
+            request.POST, request.FILES, instance=current_materi)
         if form.is_valid():
             RevisiMateriService.revisi_materi(form, request)
             return HttpResponseRedirect("/dashboard/")
@@ -717,7 +739,8 @@ class DownloadHistoryView(TemplateView):
         context = self.get_context_data(**kwargs)
         if request.user.is_authenticated:
             current_user = self.request.user
-            DownloadHistoryService.init_data_authenticated_user(context, current_user)
+            DownloadHistoryService.init_data_authenticated_user(
+                context, current_user)
         else:
             DownloadHistoryService.init_data_guest_user(context, request)
         return self.render_to_response(context)
@@ -734,6 +757,7 @@ def save_to_gdrive(request, pk):
 
     return HttpResponseRedirect(reverse('detail-materi', kwargs={'pk': pk}))
 
+
 class MateriFavorite(TemplateView):
 
     template_name = "user_favorite_materi.html"
@@ -748,7 +772,7 @@ class MateriFavorite(TemplateView):
         user = self.request.user
 
         materi = Materi.objects.filter(like=True)
-        likes_data = { mat.id: { "data": mat, "comments": [] } for mat in materi }
+        likes_data = {mat.id: {"data": mat, "comments": []} for mat in materi}
 
         comments = Comment.objects \
             .filter(materi__id__in=likes_data.keys()) \
@@ -762,11 +786,13 @@ class MateriFavorite(TemplateView):
 
         return self.render_to_response(context=context)
 
+
 class PasswordChangeViews(PasswordChangeView):
 
     from_class = PasswordChangeForm
     success_url = reverse_lazy('password_success')
 
+
 def password_success(request):
     return render(request, 'password_success.html', {})
 
@@ -785,15 +811,18 @@ def see_given_rating(request):
         try:
             if order_by_key[0] == '-':
                 order_by_key = order_by_key[1:]
-            rating_list = Rating.objects.filter(user=request.user).order_by(query_order + order_by_key)
+            rating_list = Rating.objects.filter(
+                user=request.user).order_by(query_order + order_by_key)
         except FieldError:
             order_by_key = 'timestamp'
-            rating_list = Rating.objects.filter(user=request.user).order_by(query_order + order_by_key)
+            rating_list = Rating.objects.filter(
+                user=request.user).order_by(query_order + order_by_key)
 
         return render(request, 'given-rating.html',
                       context={'rating_list': rating_list, 'order_by_key': order_by_key, 'order_by': order_by})
     return permission_denied(request, exception=None)
 
+
 class SubmitVisitorView(TemplateView):
     template_name = "submit_visitor.html"
 
@@ -819,6 +848,21 @@ class SubmitVisitorView(TemplateView):
         SubmitVisitor(msg=title, user_id=user_id, email=email).save()
         return JsonResponse({"success": True, "msg": "Buku tamu berhasil ditambahkan"})
 
+
+class GuestBookView(TemplateView):
+    def get(self, request, *args, **kwargs):
+        form = GuestBookForm()
+        return render(request, 'guest_book.html', {'form': form})
+
+    def post(self, request, *args, **kwargs):
+        name = request.POST.get('name')
+        job = request.POST.get('job')
+        gender = request.POST.get('gender')
+        guestBook = GuestBook(name=name, job=job, gender=gender)
+        guestBook.save()
+        return redirect("daftar_katalog")
+
+
 class ReadLaterView(TemplateView):
     template_name = 'baca-nanti.html'
 
@@ -834,19 +878,22 @@ class ReadLaterView(TemplateView):
     def get(self, request, *args, **kwargs):
         context = self.get_context_data(**kwargs)
         user = self.request.user
-        context["read_later_list"] = ReadLater.objects.filter(user=user).order_by('-timestamp')
+        context["read_later_list"] = ReadLater.objects.filter(
+            user=user).order_by('-timestamp')
         return self.render_to_response(context)
 
+
 def toggle_readlater(request):
     if request.method == "POST":
         materi_id = request.POST.get("materi_id", None)
         if materi_id is None:
             return JsonResponse({"success": False, "msg": MISSING_PARAMETER_MESSAGE})
-        
+
         return JsonResponse(ReadLaterService.toggle_read_later(materi_id, request.user))
     else:
         return JsonResponse({"success": False, "msg": UNSUPPORTED_MESSAGE})
 
+
 class StatisticsView(TemplateView):
     template_name = "statistik.html"
 
@@ -867,29 +914,29 @@ class StatisticsView(TemplateView):
                 result.append(e)
 
         chart_data = {
-                'labels': [e.name for e in result],
-                'datasets': [{
-                    'label': 'Jumlah Materi per Kategori',
-                    'data': [e.num for e in result],
-                    'backgroundColor': [
-                        'rgba(255, 99, 132, 0.2)',
-                        'rgba(54, 162, 235, 0.2)',
-                        'rgba(255, 206, 86, 0.2)',
-                        'rgba(75, 192, 192, 0.2)',
-                        'rgba(153, 102, 255, 0.2)',
-                        'rgba(255, 159, 64, 0.2)'
-                    ],
-                    'borderColor': [
-                        'rgba(255, 99, 132, 1)',
-                        'rgba(54, 162, 235, 1)',
-                        'rgba(255, 206, 86, 1)',
-                        'rgba(75, 192, 192, 1)',
-                        'rgba(153, 102, 255, 1)',
-                        'rgba(255, 159, 64, 1)'
-                    ],
-                    'borderWidth': 1
-                }]
-            }
+            'labels': [e.name for e in result],
+            'datasets': [{
+                'label': 'Jumlah Materi per Kategori',
+                'data': [e.num for e in result],
+                'backgroundColor': [
+                    'rgba(255, 99, 132, 0.2)',
+                    'rgba(54, 162, 235, 0.2)',
+                    'rgba(255, 206, 86, 0.2)',
+                    'rgba(75, 192, 192, 0.2)',
+                    'rgba(153, 102, 255, 0.2)',
+                    'rgba(255, 159, 64, 0.2)'
+                ],
+                'borderColor': [
+                    'rgba(255, 99, 132, 1)',
+                    'rgba(54, 162, 235, 1)',
+                    'rgba(255, 206, 86, 1)',
+                    'rgba(75, 192, 192, 1)',
+                    'rgba(153, 102, 255, 1)',
+                    'rgba(255, 159, 64, 1)'
+                ],
+                'borderWidth': 1
+            }]
+        }
         return chart_data
 
     def get(self, request, *args, **kwargs):
@@ -904,6 +951,7 @@ class StatisticsView(TemplateView):
 
             return self.render_to_response(context)
 
+
 class MostContributor(TemplateView):
     template_name = "most-contributor.html"
 
@@ -914,6 +962,6 @@ class MostContributor(TemplateView):
 
     def count_contributor(self):
         count_materi = Materi.objects.all.count()
-        content = {'count_materi':count_materi}
+        content = {'count_materi': count_materi}
         print(content)
         return content
diff --git a/requirements.txt b/requirements.txt
index 30fd8032d8b3be87ddcb85a8443175668af3dc5f..43d0d26f750678927c647708539ffbe7f4bd9589 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -101,4 +101,4 @@ whitenoise==5.0.1
 wrapt==1.11.2
 xlrd==1.2.0
 XlsxWriter==1.3.6
-zipp==3.1.0
+zipp==3.1.0
\ No newline at end of file