diff --git a/app/models.py b/app/models.py
index fb932f7546c54b0f22af0e02c9e1212f123b51f6..b4d3fda4b9f0563394814cb97af4647a76cfdafd 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,5 +1,6 @@
import random
import datetime
+import math
from django.contrib.postgres import search
from django.core.exceptions import ValidationError
@@ -135,6 +136,25 @@ class Materi(SoftDeleteModel):
count = Review.objects.filter(materi=self).count()
return count
+ @staticmethod
+ def earliest_materi_timestamp():
+ return Materi.objects.earliest('date_created').date_created.timestamp()
+
+ @property
+ def seconds_since_earliest_materi(self):
+ return self.date_created.timestamp() - Materi.earliest_materi_timestamp()
+
+ @property
+ def view_count(self):
+ count = ViewStatistics.objects.filter(materi=self).count()
+ return count
+
+ @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
+ return round(view_score + time_score, 7)
+
@property
def is_like(self):
like = False
diff --git a/app/services.py b/app/services.py
index 4ae99b55b1349e03cdcf79d9ef244944592b64b9..6b298bf76b65929bf2a458ee31869639f6023a8a 100644
--- a/app/services.py
+++ b/app/services.py
@@ -63,6 +63,9 @@ class DafterKatalogService:
lst_materi = lst_materi.order_by('date_created')
elif (get_sort == "terpopuler"):
lst_materi = lst_materi.annotate(count=Count('like__id')).order_by('-count')
+ elif (get_sort == "terhangat"):
+ lst_materi = sorted(lst_materi,
+ key=lambda t: (t.hot_score, t.date_created), reverse=True)
elif (get_sort == "jumlah_unduh"):
lst_materi = lst_materi.annotate(count=Count('unduh__id')).order_by('-count')
elif (get_sort == "jumlah_tampilan"):
diff --git a/app/templates/app/katalog_materi.html b/app/templates/app/katalog_materi.html
index 62736dfe989318cc88f0b122fb4ca01e737cc96a..9309d5d8bbe9223685f91ffb7169f08256718193 100644
--- a/app/templates/app/katalog_materi.html
+++ b/app/templates/app/katalog_materi.html
@@ -134,6 +134,9 @@
terpopuler
+
+ terhangat
+
judul
diff --git a/app/tests.py b/app/tests.py
index d65e62e06fe7eda4938aa653c4da9a976b4b3100..79d25ec49eda35ade3c1db8746815e3017c0b7ce 100644
--- a/app/tests.py
+++ b/app/tests.py
@@ -6,6 +6,8 @@ import random
import re
import tempfile
import time
+import itertools
+from django.test import override_settings
from datetime import datetime
from io import StringIO
from time import sleep
@@ -27,6 +29,7 @@ from django.core.files import File
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.management import call_command
from django.db.utils import IntegrityError
+from pytz import timezone, UTC
from django.test import (Client, RequestFactory, TestCase, TransactionTestCase,
override_settings)
from django.urls import resolve, reverse
@@ -207,6 +210,163 @@ class DaftarKatalogSortingByJumlahTampilanTest(TestCase):
response = self.client.get("/?sort=jumlah_tampilan")
self.assertRegex(str(response.content), rf'.*Materi 2.*Materi 1.*')
+
+class DaftarKatalogSortingByTerhangatTest(TestCase):
+ @classmethod
+ def generate_view_materi(cls, materi, view_count):
+ for _ in itertools.repeat(None, view_count):
+ ViewStatistics.objects.create(materi=materi)
+
+ 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]",
+ response.content.decode())]
+ return lst
+
+ def setUp(self):
+ self.contributor_credential = {
+ "email": "kontributor@gov.id",
+ "password": id_generator()
+ }
+ self.contributor = get_user_model().objects.create_user(
+ name="Kontributor",
+ is_contributor=True,
+ **self.contributor_credential,
+ )
+ self.client = Client()
+ content = b"Test file"
+ self.cover = SimpleUploadedFile(
+ "cover.jpg",
+ content
+ )
+ self.content = SimpleUploadedFile(
+ "content.txt",
+ content
+ )
+
+ self.materi_data = {
+ "author": "Reyhan",
+ "uploader": self.contributor,
+ "publisher": "Publisher",
+ "descriptions": "Deskripsi Materi",
+ "status": "APPROVE",
+ "cover": self.cover,
+ "content": self.content,
+ }
+
+ def test_1_week_difference_give_1_hot_score_difference(self):
+ materi1 = Materi.objects.create(
+ title='Materi 1',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi2 = Materi.objects.create(
+ title='Materi 2',
+ date_created=datetime(2020, 10, 8, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi3 = Materi.objects.create(
+ title='Materi 3',
+ date_created=datetime(2020, 10, 15, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ self.generate_view_materi(materi1, 1)
+ self.generate_view_materi(materi2, 1)
+ self.generate_view_materi(materi3, 1)
+
+ self.assertAlmostEqual(materi3.hot_score - materi2.hot_score, 1)
+ self.assertAlmostEqual(materi2.hot_score - materi1.hot_score, 1)
+
+ def test_10_exponential_view_count_difference_give_1_hot_score_difference(self):
+ materi1 = Materi.objects.create(
+ title='Materi 1',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi2 = Materi.objects.create(
+ title='Materi 2',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi3 = Materi.objects.create(
+ title='Materi 3',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ self.generate_view_materi(materi1, 1)
+ self.generate_view_materi(materi2, 10)
+ self.generate_view_materi(materi3, 100)
+
+ self.assertAlmostEqual(materi3.hot_score - materi2.hot_score, 1)
+ self.assertAlmostEqual(materi2.hot_score - materi1.hot_score, 1)
+
+ def test_0_and_1_views_has_the_same_hot_score(self):
+ materi1 = Materi.objects.create(
+ title='Materi 1',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi2 = Materi.objects.create(
+ title='Materi 2',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ self.generate_view_materi(materi1, 0)
+ 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())
+
+ def test_page_display_sort_by_hottest(self):
+ materi1 = Materi.objects.create(
+ title='Materi 1',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi2 = Materi.objects.create(
+ title='Materi 2',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi3 = Materi.objects.create(
+ title='Materi 3',
+ date_created=datetime(2020, 10, 8, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi4 = Materi.objects.create(
+ title='Materi 4',
+ date_created=datetime(2020, 10, 9, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ self.generate_view_materi(materi1, 11)
+ self.generate_view_materi(materi2, 10)
+ self.generate_view_materi(materi3, 1)
+ self.generate_view_materi(materi4, 1)
+
+ lst = self.get_displayed_materi_in_number()
+ self.assertEqual(lst, [4, 1, 3, 2])
+
+ def test_prefer_newest_materi_if_hot_score_is_same(self):
+ materi1 = Materi.objects.create(
+ title='Materi 1',
+ date_created=datetime(2020, 10, 1, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ materi2 = Materi.objects.create(
+ title='Materi 2',
+ date_created=datetime(2020, 10, 8, 7, 0, 0, tzinfo=UTC),
+ **self.materi_data
+ )
+ self.generate_view_materi(materi1, 10)
+ self.generate_view_materi(materi2, 1)
+
+ lst = self.get_displayed_materi_in_number()
+ self.assertEqual(lst, [2, 1])
+
+
class DaftarKatalogSortingByJumlahKomentarTest(TestCase):
def setUp(self):
self.client = Client()
@@ -3465,7 +3625,7 @@ class MateriRecommendationTest(TestCase):
response = Client().get("/?recommendation=1")
list = [int(id) for id in re.findall(r"Materi\s(\d+)[^\d]", response.content.decode())]
self.assertEqual(list, [1, 2])
-
+
class BacaNantiTest(TestCase):
def setUp(self):
self.contributor_credential = {
diff --git a/digipus/__pycache__/settings.cpython-36.pyc b/digipus/__pycache__/settings.cpython-36.pyc
index 43d2d1c4169e7113ebfa287197b9840fd637a652..2e8d5505327c65aad19bcc06e556bd3f737fca54 100644
Binary files a/digipus/__pycache__/settings.cpython-36.pyc and b/digipus/__pycache__/settings.cpython-36.pyc differ