mirror of
https://github.com/Sosokker/ku-polls.git
synced 2025-12-18 13:04:05 +01:00
Add trend score and modify view / test to apply trending
This commit is contained in:
parent
f6d0239ec6
commit
a9eb3b6497
18
polls/migrations/0015_question_trend_score.py
Normal file
18
polls/migrations/0015_question_trend_score.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.4 on 2023-09-15 07:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('polls', '0014_remove_question_down_vote_count_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='trend_score',
|
||||
field=models.FloatField(default=0.0),
|
||||
),
|
||||
]
|
||||
@ -46,6 +46,7 @@ class Question(models.Model):
|
||||
end_date = models.DateTimeField("date ended", null=True)
|
||||
short_description = models.CharField(max_length=200, default="Cool kids have polls")
|
||||
long_description = models.TextField(max_length=2000, default="No description provide for this poll.")
|
||||
trend_score = models.FloatField(default=0.0, null=False, blank=False)
|
||||
tags = models.ManyToManyField(Tag, blank=True)
|
||||
|
||||
def was_published_recently(self):
|
||||
@ -157,6 +158,9 @@ class Question(models.Model):
|
||||
|
||||
# ! Most of the code from https://stackoverflow.com/a/70869267
|
||||
def upvote(self, user):
|
||||
"""create new SentimentVote object that represent upvote (vote_types=True)
|
||||
return True if user change the vote or vote for the first time else return False
|
||||
"""
|
||||
try:
|
||||
self.sentimentvote_set.create(user=user, question=self, vote_types=True)
|
||||
self.save()
|
||||
@ -170,6 +174,9 @@ class Question(models.Model):
|
||||
return True
|
||||
|
||||
def downvote(self, user):
|
||||
"""create new SentimentVote object that represent downvote (vote_types=False)
|
||||
return True if user change the vote or vote for the first time else return False
|
||||
"""
|
||||
try:
|
||||
self.sentimentvote_set.create(user=user, question=self, vote_types=False)
|
||||
self.save()
|
||||
@ -190,6 +197,38 @@ class Question(models.Model):
|
||||
def down_vote_count(self):
|
||||
return self.sentimentvote_set.filter(question=self, vote_types=False).count()
|
||||
|
||||
def trending_score(self, up=None, down=None):
|
||||
"""Return trend score base on the criteria below"""
|
||||
published_date_duration = timezone.now() - self.pub_date
|
||||
score = 0
|
||||
|
||||
if (published_date_duration.seconds < 259200): # Second unit
|
||||
score += 100
|
||||
elif (published_date_duration.seconds < 604800):
|
||||
score += 75
|
||||
elif (published_date_duration.seconds < 2592000):
|
||||
score += 50
|
||||
else:
|
||||
score += 25
|
||||
|
||||
if (up == None) and (down == None):
|
||||
score += ((self.up_vote_count/5) - (self.down_vote_count/5)) * 100
|
||||
else:
|
||||
score += ((up/5) - (down/5)) * 100
|
||||
|
||||
return score
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Modify save method of Question object"""
|
||||
# to-be-added instance # * https://github.com/django/django/blob/866122690dbe233c054d06f6afbc2f3cc6aea2f2/django/db/models/base.py#L447
|
||||
if self._state.adding:
|
||||
try:
|
||||
self.trend_score = self.trending_score()
|
||||
except ValueError:
|
||||
self.trend_score = self.trending_score(up=0, down=0)
|
||||
super(Question, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class Choice(models.Model):
|
||||
"""
|
||||
@ -228,9 +267,25 @@ class Vote(models.Model):
|
||||
|
||||
# ! Most of the code from https://stackoverflow.com/a/70869267
|
||||
class SentimentVote(models.Model):
|
||||
"""
|
||||
Represents a sentiment vote for a poll question.
|
||||
|
||||
Attributes:
|
||||
user (User): The user who cast the sentiment vote.
|
||||
question (Question): The poll question for which the sentiment vote is cast.
|
||||
vote_types (bool): Indicates whether the sentiment vote is an upvote (True) or a downvote (False).
|
||||
|
||||
Note:
|
||||
- When 'vote_types' is True, it represents an upvote or 'Like'.
|
||||
- When 'vote_types' is False, it represents a downvote or 'Dislike'.
|
||||
"""
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
question = models.ForeignKey(Question, on_delete=models.CASCADE)
|
||||
vote_types = models.BooleanField()
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
unique_together (list of str): Ensures that a user can only cast one sentiment vote (upvote or downvote)
|
||||
for a specific question.
|
||||
"""
|
||||
unique_together = ['user', 'question']
|
||||
@ -83,7 +83,7 @@
|
||||
<h2 class="text-2xl font-bold bg-gradient-to-r from-red-600 via-orange-600 to-yellow-600 bg-clip-text text-transparent lg:inline">Top Polls Today</h2>
|
||||
<hr class="h-px my-4 bg-gray-200 border-0 dark:bg-gray-400" />
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for question in latest_question_list %}
|
||||
{% for question in latest_question_list.trend_poll %}
|
||||
<div class="relative">
|
||||
<!-- INFO -->
|
||||
<div class="rounded-lg bg-white p-4 shadow-md border-solid border-2 border-yellow-500 relative z-10 transform translate-y-0 hover:translate-y-1 transition-transform">
|
||||
@ -129,7 +129,7 @@
|
||||
<h2 class="mb-4 text-2xl font-bold">All Polls</h2>
|
||||
<hr class="h-px my-4 bg-gray-200 border-0 dark:bg-gray-400" />
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{% for question in latest_question_list %}
|
||||
{% for question in latest_question_list.all_poll %}
|
||||
<div class="relative">
|
||||
<!-- INFO -->
|
||||
<div class="rounded-lg bg-white p-4 shadow-md border-solid border-2 border-neutral-500 relative z-10 transform translate-y-0 hover:translate-y-1 transition-transform">
|
||||
|
||||
@ -12,7 +12,7 @@ class QuestionIndexViewTests(TestCase):
|
||||
"""
|
||||
response = self.client.get(reverse("polls:index"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertQuerySetEqual(response.context["latest_question_list"], [])
|
||||
self.assertQuerySetEqual(response.context["latest_question_list"]["all_poll"], [])
|
||||
|
||||
def test_past_question(self):
|
||||
"""
|
||||
@ -24,7 +24,7 @@ class QuestionIndexViewTests(TestCase):
|
||||
question.save()
|
||||
response = self.client.get(reverse("polls:index"))
|
||||
self.assertQuerySetEqual(
|
||||
response.context["latest_question_list"],
|
||||
response.context["latest_question_list"]["all_poll"],
|
||||
[question],
|
||||
)
|
||||
|
||||
@ -37,7 +37,7 @@ class QuestionIndexViewTests(TestCase):
|
||||
future_question.pub_date = timezone.now() + timezone.timedelta(days=30)
|
||||
future_question.save()
|
||||
response = self.client.get(reverse("polls:index"))
|
||||
self.assertQuerySetEqual(response.context["latest_question_list"], [])
|
||||
self.assertQuerySetEqual(response.context["latest_question_list"]["all_poll"], [])
|
||||
|
||||
def test_future_question_and_past_question(self):
|
||||
"""
|
||||
@ -54,7 +54,7 @@ class QuestionIndexViewTests(TestCase):
|
||||
|
||||
response = self.client.get(reverse("polls:index"))
|
||||
self.assertQuerySetEqual(
|
||||
response.context["latest_question_list"],
|
||||
response.context["latest_question_list"]["all_poll"],
|
||||
[past_question],
|
||||
)
|
||||
|
||||
@ -72,6 +72,6 @@ class QuestionIndexViewTests(TestCase):
|
||||
|
||||
response = self.client.get(reverse("polls:index"))
|
||||
self.assertQuerySetEqual(
|
||||
response.context["latest_question_list"],
|
||||
response.context["latest_question_list"]["all_poll"],
|
||||
[question2, question1],
|
||||
)
|
||||
|
||||
@ -52,7 +52,7 @@ class QuestionModelTests(TestCase):
|
||||
|
||||
response = self.client.get(reverse("polls:index"))
|
||||
self.assertQuerySetEqual(
|
||||
response.context["latest_question_list"],
|
||||
response.context["latest_question_list"]["all_poll"],
|
||||
[question],
|
||||
)
|
||||
|
||||
|
||||
42
polls/tests/test_sentiment_model.py
Normal file
42
polls/tests/test_sentiment_model.py
Normal file
@ -0,0 +1,42 @@
|
||||
from django.test import TransactionTestCase, Client
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from .base import create_question
|
||||
from ..views import up_down_vote
|
||||
|
||||
# ! https://stackoverflow.com/questions/24588520/testing-several-integrityerrors-in-the-same-django-unittest-test-case
|
||||
# * https://stackoverflow.com/questions/44450533/difference-between-testcase-and-transactiontestcase-classes-in-django-test
|
||||
class UpDownVoteViewTest(TransactionTestCase):
|
||||
@classmethod
|
||||
def setUp(cls) -> None:
|
||||
cls.user = User.objects.create_user(username="test_user", password="12345abc")
|
||||
cls.q1 = create_question(question_text="test 1")
|
||||
cls.client = Client()
|
||||
|
||||
def test_vote_up_once(self):
|
||||
self.client.login(username="test_user", password="12345abc")
|
||||
self.q1.upvote(self.user)
|
||||
self.assertFalse(self.q1.upvote(self.user))
|
||||
|
||||
def test_vote_down_once(self):
|
||||
self.client.login(username="test_user", password="12345abc")
|
||||
self.q1.downvote(self.user)
|
||||
self.assertFalse(self.q1.downvote(self.user))
|
||||
|
||||
def test_can_change_up_to_down(self):
|
||||
self.client.login(username="test_user", password="12345abc")
|
||||
self.q1.upvote(self.user)
|
||||
self.q1.downvote(self.user)
|
||||
count_up = self.q1.sentimentvote_set.filter(vote_types=True).count()
|
||||
count_down = self.q1.sentimentvote_set.filter(vote_types=False).count()
|
||||
self.assertEqual(count_up, 0)
|
||||
self.assertEqual(count_down, 1)
|
||||
|
||||
def test_can_change_up_to_down(self):
|
||||
self.client.login(username="test_user", password="12345abc")
|
||||
self.q1.downvote(self.user)
|
||||
self.q1.upvote(self.user)
|
||||
count_up = self.q1.sentimentvote_set.filter(vote_types=True).count()
|
||||
count_down = self.q1.sentimentvote_set.filter(vote_types=False).count()
|
||||
self.assertEqual(count_up, 1)
|
||||
self.assertEqual(count_down, 0)
|
||||
@ -8,6 +8,7 @@ from django.utils import timezone
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
|
||||
@ -29,10 +30,18 @@ class IndexView(generic.ListView):
|
||||
Return the last published questions that is published and haven't ended yet.
|
||||
"""
|
||||
now = timezone.now()
|
||||
return Question.objects.filter(
|
||||
all_poll_queryset = Question.objects.filter(
|
||||
Q(pub_date__lte=now) & ((Q(end_date__gte=now) | Q(end_date=None)))
|
||||
).order_by("-pub_date")
|
||||
|
||||
trend_poll_queryset = Question.objects.filter(
|
||||
Q(pub_date__lte=now) & ((Q(end_date__gte=now) | Q(end_date=None))) & Q(trend_score__gte=100)
|
||||
).order_by("trend_score")[:3]
|
||||
|
||||
queryset = {'all_poll' : all_poll_queryset,
|
||||
'trend_poll' : trend_poll_queryset,}
|
||||
return queryset
|
||||
|
||||
|
||||
class DetailView(LoginRequiredMixin, generic.DetailView):
|
||||
"""
|
||||
@ -105,6 +114,13 @@ class SignUpView(generic.CreateView):
|
||||
success_url = reverse_lazy('login')
|
||||
template_name = 'registration/signup.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
valid = super(SignUpView, self).form_valid(form)
|
||||
username, password = form.cleaned_data.get("username"), form.cleaned_data.get("password1")
|
||||
user = authenticate(username=username, password=password)
|
||||
login(self.request, user)
|
||||
return valid
|
||||
|
||||
|
||||
@login_required
|
||||
def vote(request, question_id):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user