Add up/down Vote + Update admin field

This commit is contained in:
sosokker 2023-09-15 02:22:06 +07:00
parent c3b5984e33
commit f6d0239ec6
7 changed files with 193 additions and 26 deletions

View File

@ -8,9 +8,6 @@
"end_date": "2023-09-29T20:31:49Z", "end_date": "2023-09-29T20:31:49Z",
"short_description": "Cool kids have polls", "short_description": "Cool kids have polls",
"long_description": "No description provide for this poll.", "long_description": "No description provide for this poll.",
"up_vote_count": 5,
"down_vote_count": 0,
"participant_count": 6,
"tags": [] "tags": []
} }
}, },
@ -23,9 +20,6 @@
"end_date": "2023-10-18T23:50:19Z", "end_date": "2023-10-18T23:50:19Z",
"short_description": "Cool kids have polls", "short_description": "Cool kids have polls",
"long_description": "No description provide for this poll.", "long_description": "No description provide for this poll.",
"up_vote_count": 1,
"down_vote_count": 0,
"participant_count": 0,
"tags": [] "tags": []
} }
}, },
@ -38,9 +32,6 @@
"end_date": "2023-11-28T19:50:53Z", "end_date": "2023-11-28T19:50:53Z",
"short_description": "Cool kids have polls", "short_description": "Cool kids have polls",
"long_description": "No description provide for this poll.", "long_description": "No description provide for this poll.",
"up_vote_count": 1,
"down_vote_count": 0,
"participant_count": 0,
"tags": [] "tags": []
} }
}, },
@ -53,9 +44,6 @@
"end_date": "2023-09-29T17:51:18Z", "end_date": "2023-09-29T17:51:18Z",
"short_description": "Cool kids have polls", "short_description": "Cool kids have polls",
"long_description": "No description provide for this poll.", "long_description": "No description provide for this poll.",
"up_vote_count": 10,
"down_vote_count": 0,
"participant_count": 0,
"tags": [] "tags": []
} }
}, },
@ -144,7 +132,7 @@
"model": "polls.vote", "model": "polls.vote",
"pk": 2, "pk": 2,
"fields": { "fields": {
"choice": 2, "choice": 1,
"user": 3, "user": 3,
"question": 1 "question": 1
} }
@ -153,7 +141,7 @@
"model": "polls.vote", "model": "polls.vote",
"pk": 3, "pk": 3,
"fields": { "fields": {
"choice": 1, "choice": 2,
"user": 2, "user": 2,
"question": 1 "question": 1
} }
@ -248,6 +236,24 @@
"question": 3 "question": 3
} }
}, },
{
"model": "polls.vote",
"pk": 14,
"fields": {
"choice": 7,
"user": 3,
"question": 4
}
},
{
"model": "polls.vote",
"pk": 15,
"fields": {
"choice": 2,
"user": 6,
"question": 1
}
},
{ {
"model": "polls.sentimentvote", "model": "polls.sentimentvote",
"pk": 1, "pk": 1,
@ -256,5 +262,104 @@
"question": 1, "question": 1,
"vote_types": false "vote_types": false
} }
},
{
"model": "polls.sentimentvote",
"pk": 2,
"fields": {
"user": 2,
"question": 2,
"vote_types": true
}
},
{
"model": "polls.sentimentvote",
"pk": 3,
"fields": {
"user": 2,
"question": 4,
"vote_types": true
}
},
{
"model": "polls.sentimentvote",
"pk": 4,
"fields": {
"user": 2,
"question": 3,
"vote_types": false
}
},
{
"model": "polls.sentimentvote",
"pk": 5,
"fields": {
"user": 2,
"question": 1,
"vote_types": false
}
},
{
"model": "polls.sentimentvote",
"pk": 6,
"fields": {
"user": 6,
"question": 1,
"vote_types": true
}
},
{
"model": "polls.sentimentvote",
"pk": 7,
"fields": {
"user": 6,
"question": 3,
"vote_types": true
}
},
{
"model": "polls.sentimentvote",
"pk": 8,
"fields": {
"user": 6,
"question": 4,
"vote_types": true
}
},
{
"model": "polls.sentimentvote",
"pk": 9,
"fields": {
"user": 3,
"question": 1,
"vote_types": false
}
},
{
"model": "polls.sentimentvote",
"pk": 10,
"fields": {
"user": 3,
"question": 3,
"vote_types": true
}
},
{
"model": "polls.sentimentvote",
"pk": 11,
"fields": {
"user": 3,
"question": 4,
"vote_types": false
}
},
{
"model": "polls.sentimentvote",
"pk": 12,
"fields": {
"user": 3,
"question": 2,
"vote_types": true
}
} }
] ]

View File

@ -13,11 +13,10 @@ class QuestionAdmin(admin.ModelAdmin):
(None, {"fields": ["question_text"]}), (None, {"fields": ["question_text"]}),
("Published date", {"fields": ["pub_date"], "classes": ["collapse"]}), ("Published date", {"fields": ["pub_date"], "classes": ["collapse"]}),
("End date", {"fields": ["end_date"], "classes": ["collapse"]}), ("End date", {"fields": ["end_date"], "classes": ["collapse"]}),
("Sentiment Vote count", {"fields": ["up_vote_count", "down_vote_count"]}),
] ]
list_display = ["question_text", "pub_date", "end_date", "was_published_recently", "can_vote"] list_display = ["question_text", "pub_date", "end_date", "was_published_recently", "can_vote"]
inlines = [ChoiceInline] inlines = [ChoiceInline]
list_filter = ["pub_date", ] list_filter = ["pub_date", "end_date"]
search_fields = ["question_text"] search_fields = ["question_text"]

View File

@ -0,0 +1,25 @@
# Generated by Django 4.2.4 on 2023-09-14 19:15
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('polls', '0013_alter_vote_question'),
]
operations = [
migrations.RemoveField(
model_name='question',
name='down_vote_count',
),
migrations.RemoveField(
model_name='question',
name='participant_count',
),
migrations.RemoveField(
model_name='question',
name='up_vote_count',
),
]

View File

@ -12,7 +12,6 @@ Attributes:
from django.db import models, IntegrityError from django.db import models, IntegrityError
from django.utils import timezone from django.utils import timezone
from django.contrib import admin from django.contrib import admin
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db.models import Sum from django.db.models import Sum
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -49,10 +48,6 @@ class Question(models.Model):
long_description = models.TextField(max_length=2000, default="No description provide for this poll.") long_description = models.TextField(max_length=2000, default="No description provide for this poll.")
tags = models.ManyToManyField(Tag, blank=True) tags = models.ManyToManyField(Tag, blank=True)
up_vote_count = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(2147483647)])
down_vote_count = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(2147483647)])
participant_count = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(2147483647)])
def was_published_recently(self): def was_published_recently(self):
""" """
Checks if the question was published recently or not. Checks if the question was published recently or not.
@ -164,7 +159,6 @@ class Question(models.Model):
def upvote(self, user): def upvote(self, user):
try: try:
self.sentimentvote_set.create(user=user, question=self, vote_types=True) self.sentimentvote_set.create(user=user, question=self, vote_types=True)
self.up_vote_count += 1
self.save() self.save()
except IntegrityError: except IntegrityError:
vote = self.sentimentvote_set.filter(user=user) vote = self.sentimentvote_set.filter(user=user)
@ -175,11 +169,9 @@ class Question(models.Model):
return False return False
return True return True
def downvote(self, user): def downvote(self, user):
try: try:
self.sentimentvote_set.create(user=user, question=self, vote_types=False) self.sentimentvote_set.create(user=user, question=self, vote_types=False)
self.up_vote_count += 1
self.save() self.save()
except IntegrityError: except IntegrityError:
vote = self.sentimentvote_set.filter(user=user) vote = self.sentimentvote_set.filter(user=user)
@ -190,6 +182,14 @@ class Question(models.Model):
return False return False
return True return True
@property
def up_vote_count(self):
return self.sentimentvote_set.filter(question=self, vote_types=True).count()
@property
def down_vote_count(self):
return self.sentimentvote_set.filter(question=self, vote_types=False).count()
class Choice(models.Model): class Choice(models.Model):
""" """

View File

@ -11,6 +11,16 @@
<div class="flex flex-wrap items-center space-x-2"> <div class="flex flex-wrap items-center space-x-2">
<header class="flex items-center justify-center"> <header class="flex items-center justify-center">
<div class="flex flex-wrap items-center space-x-2"> <div class="flex flex-wrap items-center space-x-2">
<form method="post" action="{% url "polls:upvote" question.id %}">
{% csrf_token %}
<button class="flex items-center whitespace-nowrap rounded-full border-black border-2 border-solid bg-neutral-200 px-5 py-2 text-sm font-bold text-black transition duration-150 ease-in-out hover:scale-[101%] hover:bg-green-500 focus:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 active:bg-orange-900"
type="submit" {% if user_voted == 'upvote' %}disabled{% endif %}>Upvote👍</button>
</form>
<form method="post" action="{% url "polls:downvote" question.id %}">
{% csrf_token %}
<button class="flex items-center whitespace-nowrap rounded-full border-black border-2 border-solid bg-neutral-200 px-5 py-2 text-sm font-bold text-black transition duration-150 ease-in-out hover:scale-[101%] hover:bg-yellow-500 focus:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 active:bg-orange-900"
type="submit" {% if user_voted == 'downvote' %}disabled{% endif %}>Downvote👎</button>
</form>
<!--End--> <!--End-->
<button <button
onclick="window.location.href='{% url 'polls:index' %}'" onclick="window.location.href='{% url 'polls:index' %}'"

View File

@ -9,4 +9,6 @@ urlpatterns = [
path("<int:pk>/results/", views.ResultsView.as_view(), name="results"), path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
path("<int:question_id>/vote/", views.vote, name="vote"), path("<int:question_id>/vote/", views.vote, name="vote"),
path("signup/", views.SignUpView.as_view(), name="signup"), path("signup/", views.SignUpView.as_view(), name="signup"),
path("upvote/<int:question_id>", views.up_down_vote, {'vote_type' : 'upvote'}, name="upvote"),
path("downvote/<int:question_id>", views.up_down_vote, {'vote_type' : 'downvote'}, name="downvote"),
] ]

View File

@ -1,4 +1,5 @@
import logging import logging
from typing import Any
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse from django.urls import reverse
@ -86,9 +87,18 @@ class ResultsView(LoginRequiredMixin, generic.DetailView):
model = Question model = Question
template_name = "polls/results.html" template_name = "polls/results.html"
def render_to_response(self, context, **response_kwargs): def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
return render(self.request, self.template_name, context) context = super().get_context_data(**kwargs)
user_voted = None
question = self.get_object()
if question.sentimentvote_set.filter(user=self.request.user, question=question, vote_types=True).exists():
user_voted = 'upvote'
elif question.sentimentvote_set.filter(user=self.request.user, question=question, vote_types=False).exists():
user_voted = 'downvote'
context['user_voted'] = user_voted
return context
class SignUpView(generic.CreateView): class SignUpView(generic.CreateView):
form_class = SignUpForm form_class = SignUpForm
@ -139,6 +149,22 @@ def vote(request, question_id):
return redirect("polls:index") return redirect("polls:index")
@login_required
def up_down_vote(request, question_id, vote_type):
ip = get_client_ip(request)
question = get_object_or_404(Question, pk=question_id)
if request.method == "POST":
if vote_type == "upvote":
if question.upvote(request.user):
messages.success(request, "You upvoted this Poll😊")
elif vote_type == "downvote":
if question.downvote(request.user):
messages.success(request, "You downvoted this Poll😭")
return redirect(reverse("polls:results", args=(question_id,)))
# https://stackoverflow.com/questions/4581789/how-do-i-get-user-ip-address-in-django # https://stackoverflow.com/questions/4581789/how-do-i-get-user-ip-address-in-django
def get_client_ip(request): def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')