diff --git a/app/forms.py b/app/forms.py
index d6f9d1c94dcefee16837c8a7fa9a98ac2b641c22..5b8b3438c71bbe7efcb9dbee7746662563efb42b 100644
--- a/app/forms.py
+++ b/app/forms.py
@@ -59,7 +59,7 @@ class SuntingProfilForm(forms.ModelForm):
 class RatingContributorForm(forms.ModelForm):
     class Meta:
         model = RatingContributor
-        fields = ['score', 'user']
+        fields = ['score', 'user', 'contributor']
         SCORE_CHOICE = (
             ('', 'Select score'),
             ('1', '1'),  # First one is the value of select option and second is the displayed value in option
@@ -69,6 +69,7 @@ class RatingContributorForm(forms.ModelForm):
             ('5', '5'),
         )
         widgets = {
-            'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control'},),
-            'user': forms.HiddenInput()
+            'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control', 'id':'form-rating'},),
+            'user': forms.HiddenInput(),
+            'contributor': forms.HiddenInput()
         }
diff --git a/app/migrations/0027_auto_20201030_1648.py b/app/migrations/0027_auto_20201030_1648.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c3a8b0a4d27b16db4d83a5dbd283d5556565f13
--- /dev/null
+++ b/app/migrations/0027_auto_20201030_1648.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.1 on 2020-10-30 09:48
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('app', '0026_submitvisitor'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='ratingcontributor',
+            name='contributor',
+            field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, related_name='contributor', to='authentication.user'),
+            preserve_default=False,
+        ),
+        migrations.AlterField(
+            model_name='ratingcontributor',
+            name='user',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AlterUniqueTogether(
+            name='ratingcontributor',
+            unique_together={('contributor', 'user')},
+        ),
+    ]
diff --git a/app/models.py b/app/models.py
index f50c904311702c26984f43b30fde58c3ee278f57..e531ad121c188ab50d73c93dfbedd3eafb464389 100644
--- a/app/models.py
+++ b/app/models.py
@@ -253,13 +253,17 @@ class Rating(models.Model):
 class RatingContributor(models.Model):
     timestamp = models.DateTimeField(auto_now=True)
     score = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
-    user = models.ForeignKey(User, on_delete=models.CASCADE)
+    contributor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='contributor')
+    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user')
 
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
         if 1 <= self.score <= 5:
             super().save(force_insert, force_update, using, update_fields)
         else:
             raise ValidationError("Rating score must be integer between 1-5")
+    
+    class Meta:
+        unique_together = ["contributor", "user"]
 
 
 class LaporanMateri(models.Model):
diff --git a/app/templates/app/katalog_kontri.html b/app/templates/app/katalog_kontri.html
index 3dc7915a72348cc02da5cf53aff3dc4143963b70..38d7e95fe9db70860a996bdacc5c1cec0ece7e44 100644
--- a/app/templates/app/katalog_kontri.html
+++ b/app/templates/app/katalog_kontri.html
@@ -1,41 +1,33 @@
 {% extends "base.html" %}
 {% load static %}
 {% load humanize %}
-{% block title %}Digipus - {% endblock %}
+{% block title %}Digipus - {{ contributor.name }}'s' Profile{% endblock %}
 {% block header %}
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
 
-<!DOCTYPE html>
-<html lang="en">
+<link href="../../static/app/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
+<link href="../../static/app/css/heroic-features.css" rel="stylesheet">
 
-<head>
-    <title>Digipus Home</title>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="stylesheet" type="text/css" href="{% static 'app/css/katalog_materi.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'app/css/katalog_kontri.css' %}">
 
-    <link href="../../static/app/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
-    <link href="../../static/app/css/heroic-features.css" rel="stylesheet">
+<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'css/util.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}">
 
-    <link rel="stylesheet" type="text/css" href="{% static 'app/css/katalog_materi.css' %}">
-    <link rel="stylesheet" type="text/css" href="{% static 'app/css/katalog_kontri.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}">
+<link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}">
 
-    <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
-    <link rel="stylesheet" type="text/css" href="{% static 'css/util.css' %}">
-    <link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}">
+<link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" />
 
-    <link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}">
-    <link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}">
-    <link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}">
-    <link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}">
-    <link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}">
-    <link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}">
+{% endblock header %}
 
-    <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" />
-
-
-    {% endblock header %}
-    {% block content %}
-
-</head>
+{% block content %}
 
 <body style="background-color: #f8f8f8;">
     <div class="container main">
@@ -87,10 +79,28 @@
                         </table>
                     </div>
                 </div>
-            <h4 id="rating">Rating: {% if  avg_rating.avg_rating %}{{ avg_rating.avg_rating|floatformat:"2"|intcomma }}{% else %}0{% endif %}</h4>
-            <h6>oleh: {{ count_rating }} orang</h6>
-            <div class="row"><form method="post">{{ form_rating }} {% csrf_token %}
-                <button type="submit" class="form-control" style="margin-top: 1rem">Submit</button></form> </div>
+                <div class="row mx-5 mt-3">
+                    <h4 id="rating">Rating: {% if  avg_rating.avg_rating %}{{ avg_rating.avg_rating|floatformat:"2"|intcomma }}{% else %}0{% endif %}
+                    </h4>
+                </div>
+                <div class="row mx-5 mt-2">
+                    <h6>oleh: {{ count_rating }} orang</h6>
+                </div>
+                <div class="row mx-5 mt-3">
+                    {% if not user.is_authenticated %}
+                    <h4><a href="{% url 'login' %}">Kamu harus login untuk memberi rating</a></h4>
+                    {% else %}
+                    <form method="post" class="form-inline">{{ form_rating }} {% csrf_token %}
+                        {% if has_rated %}
+                        <button class="form-control mx-3" id="updateButton">Update</button>
+                        <button class="btn btn-danger" id="deleteButton">Delete Rating</button>
+                        {% else %}
+                        <button type="submit" class="form-control mx-3">Submit</button>
+                        {% endif %}
+                    </form>
+                    {% endif %}
+
+                </div>
             </div>
         </header>
 
@@ -134,7 +144,73 @@
         </div>
     </div>
 </body>
-
-</html>
-
+{% endblock %}
+{% block extra_scripts %}
+<script type="text/javascript">
+    // using jQuery
+    function getCookie(name) {
+        var cookieValue = null;
+        if (document.cookie && document.cookie != '') {
+            var cookies = document.cookie.split(';');
+            for (var i = 0; i < cookies.length; i++) {
+                var cookie = jQuery.trim(cookies[i]);
+                if (cookie.substring(0, name.length + 1) == (name + '=')) {
+                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                    break;
+                }
+            }
+        }
+        return cookieValue;
+    }
+    var csrftoken = getCookie('csrftoken');
+
+    function csrfSafeMethod(method) {
+        // these HTTP methods do not require CSRF protection
+        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+    }
+
+    function reload () {
+        window.location = ''
+    };
+
+    $('#deleteButton').click(function () {
+        console.log("anjing")
+        $.ajaxSetup({
+            beforeSend: function (xhr, settings) {
+                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
+                }
+            }
+        });
+        $.ajax({
+            type: 'POST',
+            url: "{% url 'katalog-per-kontributor' contributor.email%}",
+            data: {
+                'delete': true
+            },
+            success: reload,
+            dataType: 'html'
+        });
+    });
+
+    $('#updateButton').click(function () {
+        $.ajaxSetup({
+            beforeSend: function (xhr, settings) {
+                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
+                }
+            }
+        });
+        $.ajax({
+            type: 'POST',
+            url: "{% url 'katalog-per-kontributor' contributor.email%}",
+            data: {
+                'update': true,
+                'score': $('#form-rating').val()
+            },
+            success: reload,
+            dataType: 'html'
+        });
+    });
+</script>
 {% endblock %}
\ No newline at end of file
diff --git a/app/tests.py b/app/tests.py
index c205163662ad9273b8c20495541f3646fbcbb7a6..9f10c73f27f528bd814fb834b1084391f60514d9 100644
--- a/app/tests.py
+++ b/app/tests.py
@@ -428,7 +428,7 @@ class DetailMateriTest(TestCase):
         session_id = response.context["session_id"]
 
         payload = {"comment": comment, "session_id": session_id}
-        ajax_response = self.client.post("/comment/dislike/", payload)
+        self.client.post("/comment/dislike/", payload)
         num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment).count()
         self.assertEqual(num_of_comment_dislikes, 0)
 
@@ -443,7 +443,7 @@ class DetailMateriTest(TestCase):
         session_id = response.context["session_id"]
 
         payload = {"comment": comment, "session_id": session_id}
-        ajax_response = self.client.post("/comment/like/", payload)
+        self.client.post("/comment/like/", payload)
         num_of_comment_likes = LikeComment.objects.filter(comment=comment).count()
         self.assertEqual(num_of_comment_likes, 0)
 
@@ -458,9 +458,9 @@ class DetailMateriTest(TestCase):
         session_id = response.context["session_id"]
         
         payload = {"comment": comment, "session_id": session_id}
-        ajax_response = self.client.post("/comment/dislike/", payload)
+        self.client.post("/comment/dislike/", payload)
         
-        ajax_response = self.client.post("/comment/dislike/", payload)
+        self.client.post("/comment/dislike/", payload)
         num_of_comment_dislikes = DislikeComment.objects.filter(comment=comment, session_id=session_id).count()
         self.assertEqual(num_of_comment_dislikes, 0)
 
@@ -475,9 +475,9 @@ class DetailMateriTest(TestCase):
         session_id = response.context["session_id"]
         
         payload = {"comment": comment, "session_id": session_id}
-        ajax_response = self.client.post("/comment/like/", payload)
+        self.client.post("/comment/like/", payload)
         
-        ajax_response = self.client.post("/comment/like/", payload)
+        self.client.post("/comment/like/", payload)
         num_of_comment_likes = LikeComment.objects.filter(comment=comment, session_id=session_id).count()
         self.assertEqual(num_of_comment_likes, 0)
 
@@ -619,7 +619,7 @@ class DetailMateriTest(TestCase):
         url = self.url
         self.client.login(**self.admin_credential)
         self.client.post(url, {"comment": "This is new comment by Anonymous"})
-        deleteURL = "/delete/" + str(self.materi1.id) + "/" + str(
+        delete_url = "/delete/" + str(self.materi1.id) + "/" + str(
             Comment.objects.get(comment="This is new comment by Anonymous").id)
         if is_admin:
             self.client.login(**self.admin_credential)
@@ -627,7 +627,7 @@ class DetailMateriTest(TestCase):
             self.client.login(**self.contributor_credential)
         if not is_admin and not is_contributor:
             self.client.login(**self.anonymous_credential)
-        response = self.client.get(deleteURL)
+        response = self.client.get(delete_url)
         return response
 
     def test_delete_comments_by_admin(self):
@@ -1352,7 +1352,7 @@ class UploadExcelPageTest(TestCase):
         file_name, data_frame = self.create_dummy_excel(field_lengths=field_lengths, categories=categories)
 
         with open(file_name, 'rb') as fp:
-            response = self.client.post("/unggah_excel/", {'excel': fp})
+            self.client.post("/unggah_excel/", {'excel': fp})
         
         title = data_frame['Title'][0]
         materi = Materi.objects.get(title=title)
@@ -1618,18 +1618,7 @@ class SuksesLoginKontributorTest(TestCase):
         # Logout
         self.client.logout()
 
-    def test_sukses_login_kontributor_url(self):
-        # Login
-        self.client.login(email="kontributor@gov.id",
-                          password="kontributor")
-        # Test
-        response = self.client.get(self.url)
-        self.assertEqual(response.status_code, 200)
-        # Logout
-        self.client.logout()
-
-    def test_sukses_login_kontributor_access(self):
-        # Kontributor
+    def test_sukses_login_kontributor_url_access(self):
         # Login
         self.client.login(email="kontributor@gov.id",
                           password="kontributor")
@@ -1665,18 +1654,7 @@ class SuksesLoginAdminTest(TestCase):
         # Logout
         self.client.logout()
 
-    def test_sukses_login_admin_url(self):
-        # Login
-        self.client.login(email="admin@gov.id",
-                          password="admin")
-        # Test
-        response = self.client.get(self.url)
-        self.assertEqual(response.status_code, 200)
-        # Logout
-        self.client.logout()
-
-    def test_sukses_login_admin_access(self):
-        # Kontributor
+    def test_sukses_login_admin_url_access(self):
         # Login
         self.client.login(email="admin@gov.id",
                           password="admin")
@@ -1731,7 +1709,7 @@ class LikeMateriTest(TestCase):
             'materi_id': materi_id,
             'session_id': session_id
         }
-        ajax_response = Client().post(self.url_like, payload)
+        Client().post(self.url_like, payload)
         num_of_likes = Like.objects.filter(materi=self.materi1).count()
         self.assertEqual(num_of_likes, 1)
 
@@ -1748,7 +1726,7 @@ class LikeMateriTest(TestCase):
             'materi_id': materi_id,
             'session_id': session_id
         }
-        ajax_response = Client().post(self.url_like, payload)
+        Client().post(self.url_like, payload)
         num_of_likes = Like.objects.filter(materi=self.materi1).count()
         self.assertEqual(num_of_likes, 1)
 
@@ -1760,7 +1738,7 @@ class LikeMateriTest(TestCase):
             'materi_id': materi_id,
             'session_id': session_id
         }
-        ajax_response = Client().post(self.url_like, payload)
+        Client().post(self.url_like, payload)
         num_of_likes = Like.objects.filter(materi=self.materi1).count()
         self.assertEqual(num_of_likes, 0)
 
@@ -1777,7 +1755,7 @@ class LikeMateriTest(TestCase):
             'materi_id': materi_id,
             'session_id': session_id
         }
-        ajax_response = Client().post(self.url_like, payload)
+        Client().post(self.url_like, payload)
         num_of_likes = Like.objects.filter(materi=self.materi1).count()
         self.assertEqual(num_of_likes, 1)
 
@@ -1789,7 +1767,7 @@ class LikeMateriTest(TestCase):
             'materi_id': materi_id,
             'session_id': session_id
         }
-        ajax_response = Client().post(self.url_like, payload)
+        Client().post(self.url_like, payload)
         num_of_likes = Like.objects.filter(materi=self.materi1).count()
         self.assertEqual(num_of_likes, 2)
 
@@ -1850,17 +1828,17 @@ class ViewMateriStatissticsTest(TestCase):
 
     # Test single view
     def test_count_one_materi_view(self):
-        response = self.client.get(self.url)
+        self.client.get(self.url)
         num_of_views = self.materi1.baca.all().count()
         self.assertEqual(num_of_views, 1)
 
     # Test more than one view
     def test_count_more_than_one_materi_view(self):
-        response = self.client.get(self.url)
+        self.client.get(self.url)
         num_of_views = self.materi1.baca.all().count()
         self.assertEqual(num_of_views, 1)
 
-        response = Client().get(self.url)
+        Client().get(self.url)
         num_of_views = self.materi1.baca.all().count()
         self.assertEqual(num_of_views, 2)
 
@@ -1892,17 +1870,17 @@ class DownloadMateriStatissticsTest(TestCase):
 
     # Test single download
     def test_count_one_materi_download(self):
-        response = self.client.get(self.url)
+        self.client.get(self.url)
         num_of_downloads = self.materi1.unduh.all().count()
         self.assertEqual(num_of_downloads, 1)
 
     # Test more than one download
     def test_count_more_than_one_materi_download(self):
-        response = self.client.get(self.url)
+        self.client.get(self.url)
         num_of_downloads = self.materi1.unduh.all().count()
         self.assertEqual(num_of_downloads, 1)
 
-        response = Client().get(self.url)
+        Client().get(self.url)
         num_of_downloads = self.materi1.unduh.all().count()
         self.assertEqual(num_of_downloads, 2)
 
@@ -1948,7 +1926,7 @@ class RevisiMateriTest(TestCase):
         # Logout
         self.client.logout()
 
-    def test_revisi_materi_url(self):
+    def test_revisi_materi_url_access(self):
         # Login
         self.client.login(**self.contributor_credential)
         # Test
@@ -1957,15 +1935,6 @@ class RevisiMateriTest(TestCase):
         # Logout
         self.client.logout()
 
-    def test_revisi_materi_access(self):
-        # Kontributor
-        # Login
-        self.client.login(**self.contributor_credential)
-        # Test
-        response = self.client.get(self.url)
-        self.assertEqual(response.status_code, 200)
-        # Logout
-        self.client.logout()
     
 class GenerateDummyCommandTest(TestCase):
 
@@ -2204,7 +2173,7 @@ class RatingMateriTest(TestCase):
         response = self.client.get(self.url_materi)
         self.assertEqual(1, response.context.get('materi_rating_score'))
 
-class fileManagementUtilTest(TestCase):
+class FileManagementUtilTest(TestCase):
     def setUp(self):
         self.filename = "image_with_exif_data.gif"
         self.file_content = open(settings.BASE_DIR + "/app/test_files/" +
@@ -2273,8 +2242,8 @@ class RequestMateriTest(TestCase):
         self.assertEqual(response['location'], '/login/')
 
     def test_saving_and_retrieving_material_requests(self):
-        first_material_request = ReqMaterial(title="Material 1").save()
-        second_material_request = ReqMaterial(title="Material 2").save()
+        ReqMaterial(title="Material 1").save()
+        ReqMaterial(title="Material 2").save()
         saved_material_request = ReqMaterial.objects.all()
         self.assertEqual(saved_material_request.count(), 2)
 
@@ -2313,104 +2282,244 @@ class RequestMateriTest(TestCase):
 
 class RatingContributorTest(TransactionTestCase):
     def setUp(self):
+        self.admin_credential = {
+            "email": "admin@gov.id",
+            "password": "passwordtest"
+        }
         self.contributor_credential = {
             "email": "kontributor@gov.id",
             "password": id_generator()
         }
+        self.anonymous_credential = {
+            "email": "anonymous@gov.id",
+            "password": "passwordtest"
+        }
+        self.admin = get_user_model().objects.create_user(
+            **self.admin_credential, name="Admin", is_admin=True)
         self.contributor = get_user_model().objects.create_user(
             **self.contributor_credential, name="Kontributor", is_contributor=True
         )
+        self.anonymous = get_user_model().objects.create_user(
+            **self.anonymous_credential, name="Anonymous"
+        )
+
+        self.url = f"/profil/{self.contributor.email}/"
 
     def test_add_rating_contributor(self):
-        RatingContributor.objects.create(score=3, user=self.contributor)
+        RatingContributor.objects.create(score=3, contributor=self.contributor, user=self.anonymous)
         self.assertEqual(1, RatingContributor.objects.count())
 
     def test_add_rating_contributor_should_failed_when_negative(self):
         with self.assertRaises(ValidationError):
-            RatingContributor.objects.create(score=-1, user=self.contributor)
+            RatingContributor.objects.create(score=-1, contributor=self.contributor, user=self.anonymous)
         self.assertEqual(0, RatingContributor.objects.count())
 
     def test_add_rating_contributor_should_failed_when_bigger_than_five(self):
         with self.assertRaises(ValidationError):
-            RatingContributor.objects.create(score=6, user=self.contributor)
+            RatingContributor.objects.create(score=6, contributor=self.contributor, user=self.anonymous)
         self.assertEqual(0, RatingContributor.objects.count())
 
     def test_submit_form_correct_rating_contributor_should_added(self):
-        url = f"/profil/{self.contributor.email}/"
-        self.client.post(url, data={"user": self.contributor.id, "score": 5})
-        self.assertEqual(1, RatingContributor.objects.filter(user=self.contributor.id).count())
-        self.client.post(url, data={"user": self.contributor.id, "score": 1})
-        self.assertEqual(2, RatingContributor.objects.filter(user=self.contributor.id).count())
+        self.client.login(**self.anonymous_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 5})
+        self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor.id).count())
+        self.client.logout()
+
+        self.client.login(**self.admin_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id,  "score": 1})
+        self.assertEqual(2, RatingContributor.objects.filter(contributor=self.contributor.id).count())
 
     def test_submit_form_not_correct_rating_contributor_should__not_added(self):
-        url = f"/profil/{self.contributor.email}/"
-        self.client.post(url, data={"user": self.contributor.id, "score": 6})
-        self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count())
-        self.client.post(url, data={"user": self.contributor.id, "score": 0})
-        self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count())
+        self.client.login(**self.anonymous_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 6})
+        self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor.id).count())
+        self.client.logout()
+
+        self.client.login(**self.admin_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": 0})
+        self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor.id).count())
 
     def test_average_rating_score_empty(self):
-        url = f"/profil/{self.contributor.email}/"
-        response = self.client.get(url)
+        response = self.client.get(self.url)
         self.assertTemplateUsed(response=response, template_name="app/katalog_kontri.html")
         self.assertContains(response=response, text="Rating: 0", count=1)
         self.assertContains(response=response, text="oleh: 0 orang", count=1)
 
     def test_average_rating_correct(self):
-        url = f"/profil/{self.contributor.email}/"
-        self.client.post(url, data={"user": self.contributor.id, "score": 5})
-        response = self.client.get(url)
+        self.client.login(**self.anonymous_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 5})
+        response = self.client.get(self.url)
         self.assertContains(response=response, text="Rating: 5", count=1)
         self.assertContains(response=response, text="oleh: 1 orang", count=1)
 
     def test_average_rating_form_incorrect_correct(self):
-        url = f"/profil/{self.contributor.email}/"
-        self.client.post(url, data={"user": self.contributor.id, "score": 6})
-        response = self.client.get(url)
+        self.client.login(**self.anonymous_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": 6})
+        response = self.client.get(self.url)
         self.assertContains(response=response, text="Rating: 0", count=1)
         self.assertContains(response=response, text="oleh: 0 orang", count=1)
-        self.client.post(url, data={"user": self.contributor.id, "score": -1})
-        response = self.client.get(url)
+        self.client.logout()
+
+        self.client.login(**self.admin_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": -1})
+        response = self.client.get(self.url)
         self.assertContains(response=response, text="Rating: 0", count=1)
         self.assertContains(response=response, text="oleh: 0 orang", count=1)
 
     def test_average_with_multiple_score_correct(self):
         score = 5
         avg = [score]
-        url = f"/profil/{self.contributor.email}/"
-        self.client.post(url, data={"user": self.contributor.id, "score": score})
-        response = self.client.get(url)
+        self.client.login(**self.anonymous_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous.id, "score": score})
+        response = self.client.get(self.url)
         self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1)
         self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1)
+        self.client.logout()
 
+        self.anonymous2 = get_user_model().objects.create_user(
+            email="anonymous2@gov.id", password="test", name="Anonymous 2"
+        )
         score = 4
         avg.append(score)
-        self.client.post(url, data={"user": self.contributor.id, "score": score})
-        response = self.client.get(url)
+        self.client.login(email=self.anonymous2.email, password="test")
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous2.id, "score": score})
+        response = self.client.get(self.url)
         self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1)
         self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1)
+        self.client.logout()
 
+        self.anonymous3 = get_user_model().objects.create_user(
+            email="anonymous3@gov.id", password="test", name="Anonymous 3"
+        )
         score = 3
         avg.append(score)
-        self.client.post(url, data={"user": self.contributor.id, "score": score})
-        response = self.client.get(url)
+        self.client.login(email=self.anonymous3.email, password="test")
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous3.id, "score": score})
+        response = self.client.get(self.url)
         self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1)
         self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1)
+        self.client.logout()
 
+        self.anonymous4 = get_user_model().objects.create_user(
+            email="anonymous4@gov.id", password="test", name="Anonymous 4"
+        )
         score = 2
         avg.append(score)
-        self.client.post(url, data={"user": self.contributor.id, "score": score})
-        response = self.client.get(url)
+        self.client.login(email=self.anonymous4.email, password="test")
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.anonymous4.id, "score": score})
+        response = self.client.get(self.url)
         self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1)
         self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1)
+        self.client.logout()
 
         score = 1
         avg.append(score)
-        self.client.post(url, data={"user": self.contributor.id, "score": score})
-        response = self.client.get(url)
+        self.client.login(**self.admin_credential)
+        self.client.post(self.url, data={"contributor": self.contributor.id, "user": self.admin.id, "score": score})
+        response = self.client.get(self.url)
         self.assertContains(response=response, text=f"Rating: {mean(avg)}", count=1)
         self.assertContains(response=response, text=f"oleh: {len(avg)} orang", count=1)
+    
+    def test_not_authenticated_user_should_not_able_to_add_rating(self):
+        response_post = self.client.post(self.url, data={
+            "contributor":self.contributor.id,
+            "user":self.anonymous.id,
+            "score":3
+        })
+        response_get = self.client.get(self.url)
+        self.assertEqual(response_post.status_code, 403)
+        self.assertContains(response_get, "Kamu harus login untuk memberi rating")
+    
+    def test_not_authenticated_user_should_not_able_to_delete_rating(self):
+        response_post = self.client.post(self.url, data={"delete":True})
+        self.assertEqual(response_post.status_code, 403)
+    
+    def test_authenticated_user_should_not_delete_rating_if_has_not_been_rated(self):
+        self.client.login(**self.admin_credential)
+        self.client.post(self.url, data={
+            "contributor":self.contributor.id,
+            "user":self.admin.id,
+            "score":3
+        })
+        
+        self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count())
+        self.client.logout()
 
+        self.client.login(**self.anonymous_credential)
+        self.client.post(self.url, data={"delete":True})
+        
+        self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count())
+    
+    def test_authenticated_user_should_delete_rating_if_has_been_rated(self):
+        self.client.login(**self.admin_credential)
+        self.client.post(self.url, data={
+            "contributor":self.contributor.id,
+            "user":self.admin.id,
+            "score":3
+        })
+        response = self.client.get(self.url)
+        self.assertContains(response, 'Rating: 3')
+        self.assertContains(response, 'oleh: 1 orang')
+        self.assertEqual(1, RatingContributor.objects.filter(contributor=self.contributor).count())
+
+        self.client.post(self.url, data={"delete":True})
+        response = self.client.get(self.url)
+        self.assertContains(response, 'Rating: 0')
+        self.assertContains(response, 'oleh: 0 orang')
+        self.assertEqual(0, RatingContributor.objects.filter(contributor=self.contributor).count())
+    
+    def test_average_still_be_correct_when_rating_was_deleted(self):
+        scores = [2,4]
+        self.client.login(**self.admin_credential)
+        self.client.post(self.url, data={
+            "contributor":self.contributor.id,
+            "user":self.admin.id,
+            "score":scores[0]
+        })
+        self.client.logout()
+
+        self.client.login(**self.anonymous_credential)
+        self.client.post(self.url, data={
+            "contributor":self.contributor.id,
+            "user":self.anonymous.id,
+            "score":scores[1]
+        })
+        response = self.client.get(self.url)
+        self.assertContains(response, f'Rating: {mean(scores)}')
+        self.assertContains(response, 'oleh: 2 orang')
+
+        self.client.post(self.url, data={"delete":True})
+        response = self.client.get(self.url)
+        self.assertContains(response, f'Rating: {scores[0]}')
+        self.assertContains(response, 'oleh: 1 orang')
+    
+    def test_authenticated_user_should_update_rating_if_has_been_rated(self):
+        self.client.login(**self.anonymous_credential)
+        self.client.post(self.url, data={
+            "contributor":self.contributor.id,
+            "user":self.anonymous.id,
+            "score":5
+        })
+        response = self.client.get(self.url)
+        self.assertContains(response, 'Rating: 5')
+        self.assertContains(response, 'oleh: 1 orang')
+
+        self.client.post(self.url, data={"update":True, "score":3})
+        response = self.client.get(self.url)
+        self.assertContains(response, 'Rating: 3')
+        self.assertContains(response, 'oleh: 1 orang')
+    
+    def test_authenticated_user_should_not_update_rating_if_has_not_been_rated(self):
+        self.client.login(**self.anonymous_credential)
+        response = self.client.get(self.url)
+        self.assertContains(response, 'Rating: 0')
+        self.assertContains(response, 'oleh: 0 orang')
+
+        self.client.post(self.url, data={"update":True, "score":3})
+        response = self.client.get(self.url)
+        self.assertContains(response, 'Rating: 0')
+        self.assertContains(response, 'oleh: 0 orang')
+    
 
 class UserDownloadHistoryTest(TestCase):
     def setUp(self):
@@ -2899,8 +3008,8 @@ class YTUrlVideoTest(TestCase):
         )
 
     def test_yt_video_id_exists_in_Materi(self):
-        materiTemp = Materi.objects.create()
-        self.assertEqual(hasattr(materiTemp, 'yt_video_id'), True)
+        materi_temp = Materi.objects.create()
+        self.assertEqual(hasattr(materi_temp, 'yt_video_id'), True)
 
     def test_yt_video_id_exists_in_UploadMateri_page(self):
         self.client.login(**self.contributor_credential)
@@ -2910,7 +3019,7 @@ class YTUrlVideoTest(TestCase):
 
     def test_upload_materi_with_valid_yt_video_id(self):
         self.client.login(**self.contributor_credential)
-        response = self.client.post(
+        self.client.post(
             "/unggah/", data={"title":"Materi 1", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000",
                                 "descriptions":"Deskripsi Materi 1", 'categories':"1",
                                 "cover":self.cover, "content":self.content,
@@ -2920,7 +3029,7 @@ class YTUrlVideoTest(TestCase):
     
     def test_upload_materi_with_invalid_yt_video_id(self):
         self.client.login(**self.contributor_credential)
-        response = self.client.post(
+        self.client.post(
             "/unggah/", data={"title":"Materi 2", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000",
                                 "descriptions":"Deskripsi Materi 1", 'categories':"1",
                                 "cover":self.cover, "content":self.content,
@@ -2937,7 +3046,7 @@ class YTUrlVideoTest(TestCase):
 
     def test_detail_materi_has_no_video_if_yt_video_id_empty(self):
         self.client.login(**self.contributor_credential)
-        response = self.client.post(
+        self.client.post(
             "/unggah/", data={"title":"Materi 2", "author":"Agas", "publisher":"Kelas SC", "release_year":"2000",
                                 "descriptions":"Deskripsi Materi 1", 'categories':"1",
                                 "cover":self.cover, "content":self.content,}
@@ -2969,17 +3078,7 @@ class ChangePasswordTest(TestCase):
         # Logout
         self.client.logout()
 
-    def test_change_password_url(self):
-        # Login
-        self.client.login(email="kontributor@gov.id", password="kontributor")
-        # Test
-        response = self.client.get(self.url)
-        self.assertEqual(response.status_code, 200)
-        # Logout
-        self.client.logout()
-
-    def test_change_password_access(self):
-        # Kontributor
+    def test_change_password_url_access(self):
         # Login
         self.client.login(email="kontributor@gov.id", password="kontributor")
         # Test
@@ -3290,7 +3389,7 @@ class MateriRecommendationTest(TestCase):
         Like.objects.create(materi=materi2)
         Like.objects.create(materi=materi2)
 
-        materi3 = Materi.objects.create(
+        Materi.objects.create(
             title="Materi 3",
             author="Nandhika Prayoga",
             uploader=self.contributor,
diff --git a/app/views.py b/app/views.py
index 763dd85c0e5e4ccb23377bc7336bb54adda1b1c5..a0062ed070c2065cc77c3e37757c749b70f470d1 100644
--- a/app/views.py
+++ b/app/views.py
@@ -90,19 +90,51 @@ class KatalogPerKontributorView(TemplateView):
         page_number = request.GET.get("page")
         materi_list_by_page = paginator.get_page(page_number)
         context["materi_list"] = materi_list_by_page
-        contributor = get_object_or_404(User, email=kwargs["email"])
-        context["form_rating"] = RatingContributorForm(initial={"user": contributor})
+        contributor = context["contributor"]
+        context["form_rating"] = RatingContributorForm(initial={
+            "contributor": contributor,
+            "user":request.user
+        })
         context["avg_rating"] = User.objects.filter(email=kwargs["email"]) \
-            .annotate(avg_rating=Avg("ratingcontributor__score"))[0]
-        context["count_rating"] = RatingContributor.objects.filter(user=contributor).count()
+            .annotate(avg_rating=Avg("contributor__score"))[0]
+        context["count_rating"] = RatingContributor.objects.filter(contributor=contributor).count()
+
+        if request.user.is_authenticated:
+            has_rated = RatingContributor.objects.filter(user=request.user, contributor=contributor).exists()
+            context["has_rated"] = has_rated
+
         return self.render_to_response(context=context)
 
     def post(self, request, *args, **kwargs):
-        data = RatingContributorForm(request.POST)
-        if data.is_valid():
-            data.save()
-        return redirect("katalog-per-kontributor", email=kwargs["email"])
+        context = self.get_context_data(**kwargs)
+        if not request.user.is_authenticated:
+            raise PermissionDenied(request)
 
+        is_delete = request.POST.get('delete', None)
+        is_update = request.POST.get('update', None)
+        if is_delete:
+            rating_contributor = get_object_or_404(
+                RatingContributor, 
+                user=request.user,
+                contributor=context["contributor"]
+            )
+            rating_contributor.delete()
+        elif is_update:
+            score = request.POST.get('score', None)
+            rating = RatingContributor.objects.filter(
+                user=request.user,
+                contributor=context["contributor"]
+            ).first()
+            
+            if rating and score:
+                rating.score = int(score)
+                rating.save()
+        else:
+            data = RatingContributorForm(request.POST)
+            if data.is_valid():
+                data.save()
+                
+        return redirect("katalog-per-kontributor", email=kwargs["email"])
 
 class DetailMateri(TemplateView):
     template_name = "app/detail_materi.html"