diff --git a/app/migrations/0014_rating.py b/app/migrations/0014_rating.py
new file mode 100644
index 0000000000000000000000000000000000000000..69ae6aa1e7dcd3345443a22672118f591ef8e680
--- /dev/null
+++ b/app/migrations/0014_rating.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.0.3 on 2020-09-29 11:30
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('app', '0013_auto_20200919_2055'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Rating',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
+                ('score', models.IntegerField()),
+                ('materi', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Materi')),
+                ('user', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'unique_together': {('materi', 'user')},
+            },
+        ),
+    ]
diff --git a/app/models.py b/app/models.py
index c0ac9363b9d45df75d4d8a2577db499bdaa18ef4..0e4a43fbb1a7961d11f88947bfd305cf17a6ce83 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,5 +1,6 @@
 import random
 
+from django.core.exceptions import ValidationError
 from django.db import models
 from django.utils import timezone
 
@@ -12,6 +13,7 @@ VERIFICATION_STATUS = [
     ("REVISION", "Perbaikan"),
 ]
 
+
 # Create your models here.
 
 
@@ -108,3 +110,22 @@ class DummyDownloadStatistics(models.Model):
 
 class DummyComment(models.Model):
     item = models.ForeignKey(Comment, on_delete=models.CASCADE)
+
+
+class Rating(models.Model):
+    materi = models.ForeignKey(Materi, models.CASCADE)
+    user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
+    timestamp = models.DateTimeField(default=timezone.now)
+    score = models.IntegerField()
+
+    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")
+
+    def __str__(self):
+        return "Material:{} | User:{} | Rating:{}".format(self.materi.title, self.user.name, self.score)
+
+    class Meta:
+        unique_together = ["materi", "user"]
diff --git a/app/tests.py b/app/tests.py
index ba1381f98d2382ce272903692a7868110bab461b..f8fff26448ef2e708bab73e61a4c2f49009d72eb 100644
--- a/app/tests.py
+++ b/app/tests.py
@@ -1,25 +1,21 @@
 import json
 
 from django.contrib.auth import get_user_model
-from django.core import serializers
-from django.core.files import File
+from django.core.exceptions import ValidationError
 from django.core.files.uploadedfile import SimpleUploadedFile
-from django.test import Client, RequestFactory, TestCase
+from django.db import IntegrityError
+from django.test import Client, TestCase
 from django.urls import resolve
 
 from administration.utils import id_generator
-from app.views import UploadMateriHTML, UploadMateriView
+from app.views import UploadMateriView
 from authentication.models import User
-
-from .models import Category, Comment, Materi, Like, ViewStatistics, DownloadStatistics
+from .models import Category, Comment, Materi, Like, Rating
 from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri,
                     ProfilKontributorView, SuksesLoginAdminView,
                     SuksesLoginKontributorView, SuntingProfilView,
                     ProfilAdminView, CommentsView, SuntingProfilAdminView, RevisiMateriView)
 
-from app.views import UploadMateriHTML, UploadMateriView
-from authentication.models import User
-
 
 class DaftarKatalogTest(TestCase):
     def test_daftar_katalog_url_exist(self):
@@ -928,4 +924,98 @@ class RevisiMateriTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 200)
         # Logout
-        self.client.logout()
\ No newline at end of file
+        self.client.logout()
+
+
+class RatingMateriTest(TestCase):
+    def setUp(self):
+        self.url = '/administration/'
+        self.contributor_credential = {
+            "email": "kontributor@gov.id",
+            "password": id_generator()
+        }
+        self.user_one_credential = {
+            "email": "user_one@user.id",
+            "password": id_generator()
+        }
+        self.user_two_credential = {
+            "email": "user_two@user.id",
+            "password": id_generator()
+        }
+        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.cover = SimpleUploadedFile(
+            "cover.jpg",
+            b"Test file"
+        )
+        self.content = SimpleUploadedFile(
+            "content.txt",
+            b"Test file"
+        )
+        Materi(title="Materi 1", author="Agas", uploader=self.contributor,
+               publisher="Kelas SC", descriptions="Deskripsi Materi 1",
+               status="PENDING", cover=self.cover, content=self.content).save()
+        Materi(title="Materi Dua", author="Author", uploader=self.contributor,
+               publisher="Publisher", descriptions="Deskripsi Materi Dua",
+               status="APPROVE", cover=self.cover, content=self.content).save()
+        self.materi1 = Materi.objects.all()[0]
+        self.materi2 = Materi.objects.all()[1]
+
+    def test_rating_model_can_be_created_with_proper_parameter(self):
+        Rating(materi=self.materi1, user=self.user_one, score=5).save()
+        rating = Rating.objects.first()
+        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")
+
+    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))
+
+    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))
+
+    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()
+        Rating(materi=self.materi1, user=self.user_two, score=2).save()
+        rating_one = Rating.objects.get(user=self.user_one)
+        rating_two = Rating.objects.get(user=self.user_two)
+        self.assertEqual(rating_one.materi, self.materi1)
+        self.assertEqual(rating_two.materi, self.materi1)
+        self.assertEqual(rating_one.user, self.user_one)
+        self.assertEqual(rating_two.user, self.user_two)
+
+    def test_one_user_should_be_able_to_be_related_to_two_rating(self):
+        Rating(materi=self.materi1, user=self.user_one, score=3).save()
+        Rating(materi=self.materi2, user=self.user_one, score=3).save()
+        rating_one = Rating.objects.filter(materi=self.materi1).first()
+        rating_two = Rating.objects.filter(materi=self.materi2).first()
+        self.assertEqual(rating_one.materi, self.materi1)
+        self.assertEqual(rating_two.materi, self.materi2)
+        self.assertEqual(rating_one.user, self.user_one)
+        self.assertEqual(rating_two.user, self.user_one)
+
+    def test_two_rating_should_not_have_same_user_and_materi(self):
+        with self.assertRaises(IntegrityError) as context:
+            Rating(materi=self.materi1, user=self.user_one, score=1).save()
+            Rating(materi=self.materi1, user=self.user_one, score=2).save()
+        self.assertTrue('already exists' in str(context.exception))
+
+    def test_materi_in_rating_should_not_be_null(self):
+        with self.assertRaises(IntegrityError):
+            Rating(user=self.user_one, score=1).save()
+
+    def test_user_in_rating_should_not_be_null(self):
+        with self.assertRaises(IntegrityError):
+            Rating(materi=self.materi1, score=1).save()
+
+    def test_score_in_rating_should_not_be_null(self):
+        with self.assertRaises(TypeError):
+            Rating(materi=self.materi1, user=self.user_one).save()