mirror of
https://github.com/Sosokker/ku-polls.git
synced 2025-12-18 13:04:05 +01:00
Refine UI/UX - Add new Feature (Model) - Refine View
1. Refine UI/UX Apply Tailwinds Rewrite all page 2. Add new feature to model Add new field - description - long - short - use default + editable instead of auto_add_now - track up vote - Change timezone - Add time left track -Add participant -Add up/down vote attribute -- Choice - Add total_vote -Add calculate_percentage of vote 3. Refine View -Redirect instead of home page -Add context to result, detail view -Apply ChartJS -Delete home
This commit is contained in:
parent
f33dbdf1d5
commit
1b996629da
@ -4,7 +4,13 @@
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"question_text": "Python vs C++, which one is better in your opinion?",
|
||||
"pub_date": "2023-08-28T13:38:42Z"
|
||||
"short_description": "Cool kids have polls",
|
||||
"long_description": "No description provide for this poll.",
|
||||
"pub_date": "2023-08-28T13:38:42Z",
|
||||
"end_date": "2025-09-24T22:56:52Z",
|
||||
"up_vote_count": 0,
|
||||
"down_vote_count": 0,
|
||||
"participant_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -12,7 +18,55 @@
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"question_text": "The chicken and the egg, which came first?",
|
||||
"pub_date": "2023-08-28T13:39:16Z"
|
||||
"short_description": "Cool kids have polls",
|
||||
"long_description": "No description provide for this poll.",
|
||||
"pub_date": "2023-08-28T13:39:16Z",
|
||||
"end_date": "2023-11-05T06:30:16Z",
|
||||
"up_vote_count": 12,
|
||||
"down_vote_count": 4,
|
||||
"participant_count": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.question",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"question_text": "Do you love Django?",
|
||||
"short_description": "Cool kids have polls",
|
||||
"long_description": "No description provide for this poll.",
|
||||
"pub_date": "2023-11-07T15:40:31Z",
|
||||
"end_date": "2023-09-20T13:41:33Z",
|
||||
"up_vote_count": 3000,
|
||||
"down_vote_count": 50,
|
||||
"participant_count": 555
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.question",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"question_text": "So far so good?",
|
||||
"short_description": "Cool kids have polls",
|
||||
"long_description": "No description provide for this poll.",
|
||||
"pub_date": "2023-09-09T06:30:50.690Z",
|
||||
"end_date": "2023-09-30T12:54:30Z",
|
||||
"up_vote_count": 0,
|
||||
"down_vote_count": 0,
|
||||
"participant_count": 234
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.question",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"question_text": "Do you love Django?",
|
||||
"short_description": "Cool kids have polls",
|
||||
"long_description": "No description provide for this poll.",
|
||||
"pub_date": "2023-09-09T13:42:27.501Z",
|
||||
"end_date": "2023-09-21T13:42:17Z",
|
||||
"up_vote_count": 1,
|
||||
"down_vote_count": 1,
|
||||
"participant_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -21,7 +75,7 @@
|
||||
"fields": {
|
||||
"question": 2,
|
||||
"choice_text": "Chicken",
|
||||
"votes": 5
|
||||
"votes": 55
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -30,7 +84,7 @@
|
||||
"fields": {
|
||||
"question": 2,
|
||||
"choice_text": "Egg",
|
||||
"votes": 2
|
||||
"votes": 56
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -39,7 +93,7 @@
|
||||
"fields": {
|
||||
"question": 1,
|
||||
"choice_text": "Python!",
|
||||
"votes": 2
|
||||
"votes": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -48,6 +102,78 @@
|
||||
"fields": {
|
||||
"question": 1,
|
||||
"choice_text": "C++",
|
||||
"votes": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.choice",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"question": 3,
|
||||
"choice_text": "Yeah for sure!",
|
||||
"votes": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.choice",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"question": 3,
|
||||
"choice_text": "nah",
|
||||
"votes": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.choice",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"question": 4,
|
||||
"choice_text": "Yes!",
|
||||
"votes": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.choice",
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"question": 4,
|
||||
"choice_text": "No!",
|
||||
"votes": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.choice",
|
||||
"pk": 15,
|
||||
"fields": {
|
||||
"question": 4,
|
||||
"choice_text": "Okay!",
|
||||
"votes": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.choice",
|
||||
"pk": 16,
|
||||
"fields": {
|
||||
"question": 4,
|
||||
"choice_text": "Thank you!",
|
||||
"votes": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.choice",
|
||||
"pk": 17,
|
||||
"fields": {
|
||||
"question": 5,
|
||||
"choice_text": "Yeah for sure!",
|
||||
"votes": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "polls.choice",
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"question": 5,
|
||||
"choice_text": "nah",
|
||||
"votes": 2
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,10 +26,15 @@ SECRET_KEY = config('SECRET_KEY', default='k2pd1p)zwe0qy0k25=sli+7+n^vd-0h*&6vga
|
||||
#! SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = config('DEBUG', default=False, cast=bool)
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='*', cast=Csv())
|
||||
|
||||
TIME_ZONE = config('TIME_ZONE', default='Asia/Bangkok', cast=str)
|
||||
TIME_ZONE = config('TIME_ZONE', default='UTC', cast=str)
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
from polls.views import HomeView
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
urlpatterns = [
|
||||
path('', HomeView.as_view(), name='home'),
|
||||
path('', RedirectView.as_view(pattern_name='polls:index'), name='home_redirect'),
|
||||
path("polls/", include("polls.urls")),
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
|
||||
@ -12,6 +12,8 @@ class QuestionAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {"fields": ["question_text"]}),
|
||||
("End date", {"fields": ["end_date"], "classes": ["collapse"]}),
|
||||
("Vote count", {"fields": ["up_vote_count", "down_vote_count"]}),
|
||||
("Participant count", {"fields": ["participant_count"]}),
|
||||
]
|
||||
list_display = ["question_text", "pub_date", "end_date", "was_published_recently"]
|
||||
inlines = [ChoiceInline]
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.2.4 on 2023-09-09 05:27
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('polls', '0002_question_end_date_alter_question_pub_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='choice',
|
||||
name='votes',
|
||||
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(2147483647)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='question',
|
||||
name='pub_date',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='date published'),
|
||||
),
|
||||
]
|
||||
19
polls/migrations/0004_alter_question_pub_date.py
Normal file
19
polls/migrations/0004_alter_question_pub_date.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.4 on 2023-09-09 08:15
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('polls', '0003_alter_choice_votes_alter_question_pub_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='question',
|
||||
name='pub_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='date published'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.2.4 on 2023-09-09 08:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('polls', '0004_alter_question_pub_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='long_description',
|
||||
field=models.TextField(default='No description provide for this poll.', max_length=2000),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='short_description',
|
||||
field=models.CharField(default='Cool kids have polls', max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='question',
|
||||
name='question_text',
|
||||
field=models.CharField(max_length=100),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.2.4 on 2023-09-09 08:33
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('polls', '0005_question_long_description_question_short_description_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='down_vote_count',
|
||||
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(2147483647)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='up_vote_count',
|
||||
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(2147483647)]),
|
||||
),
|
||||
]
|
||||
19
polls/migrations/0007_question_participant_count.py
Normal file
19
polls/migrations/0007_question_participant_count.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.4 on 2023-09-09 08:36
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('polls', '0006_question_down_vote_count_question_up_vote_count'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='participant_count',
|
||||
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(2147483647)]),
|
||||
),
|
||||
]
|
||||
@ -9,12 +9,11 @@ Attributes:
|
||||
None
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.contrib import admin
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db.models import Sum
|
||||
|
||||
|
||||
class Question(models.Model):
|
||||
@ -26,9 +25,14 @@ class Question(models.Model):
|
||||
pub_date (datetime): The date and time when the question was published.
|
||||
"""
|
||||
|
||||
question_text = models.CharField(max_length=200)
|
||||
pub_date = models.DateTimeField("date published", auto_now_add=True)
|
||||
question_text = models.CharField(max_length=100)
|
||||
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.")
|
||||
pub_date = models.DateTimeField("date published", default=timezone.now, editable=False)
|
||||
end_date = models.DateTimeField("date ended", null=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):
|
||||
"""
|
||||
@ -38,7 +42,7 @@ class Question(models.Model):
|
||||
bool: True if the question was published within the last day, else False.
|
||||
"""
|
||||
now = timezone.now()
|
||||
return now - datetime.timedelta(days=1) <= self.pub_date <= now
|
||||
return now - timezone.timedelta(days=1) <= self.pub_date <= now
|
||||
|
||||
@admin.display(
|
||||
boolean=True,
|
||||
@ -47,7 +51,7 @@ class Question(models.Model):
|
||||
)
|
||||
def was_published_recently(self):
|
||||
now = timezone.now()
|
||||
return now - datetime.timedelta(days=1) <= self.pub_date <= now
|
||||
return now - timezone.timedelta(days=1) <= self.pub_date <= now
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
@ -78,6 +82,57 @@ class Question(models.Model):
|
||||
else:
|
||||
return self.pub_date <= now <= self.end_date
|
||||
|
||||
def calculate_time_left(self):
|
||||
"""
|
||||
Calculate the time left until the end date.
|
||||
|
||||
Returns:
|
||||
str: A formatted string representing the time left.
|
||||
"""
|
||||
if self.end_date is None:
|
||||
return "No end date"
|
||||
|
||||
now = timezone.now()
|
||||
time_left = self.end_date - now
|
||||
|
||||
days, seconds = divmod(time_left.total_seconds(), 86400)
|
||||
hours, seconds = divmod(seconds, 3600)
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
|
||||
time_left_str = ""
|
||||
if days > 0:
|
||||
time_left_str += f"{int(days)} D "
|
||||
elif hours > 0:
|
||||
time_left_str += f"{int(hours)} H "
|
||||
elif minutes > 0:
|
||||
time_left_str += f"{int(minutes)} M "
|
||||
elif seconds > 0:
|
||||
time_left_str += f"{int(seconds)} S "
|
||||
|
||||
return time_left_str.strip()
|
||||
|
||||
@property
|
||||
def time_left(self):
|
||||
return self.calculate_time_left()
|
||||
|
||||
def calculate_vote_percentage(self):
|
||||
total_vote = self.up_vote_count + self.down_vote_count
|
||||
if total_vote == 0:
|
||||
return (0, 0)
|
||||
|
||||
up_vote_percentage = self.up_vote_count / total_vote * 100
|
||||
down_vote_percentage = self.down_vote_count / total_vote * 100
|
||||
|
||||
return (int(up_vote_percentage), int(down_vote_percentage))
|
||||
|
||||
@property
|
||||
def up_vote_percentage(self):
|
||||
return self.calculate_vote_percentage()[0]
|
||||
|
||||
@property
|
||||
def down_vote_percentage(self):
|
||||
return self.calculate_vote_percentage()[1]
|
||||
|
||||
|
||||
class Choice(models.Model):
|
||||
"""
|
||||
@ -93,6 +148,29 @@ class Choice(models.Model):
|
||||
choice_text = models.CharField(max_length=200)
|
||||
votes = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(2147483647)])
|
||||
|
||||
def tailwind_width_class(self):
|
||||
"""
|
||||
Calculate and return the Tailwind CSS width class based on the 'votes' percentage.
|
||||
"""
|
||||
total_votes = self.question.choice_set.aggregate(Sum('votes')).get('votes__sum', 0)
|
||||
#! Tailwind w-0 to w-48
|
||||
if total_votes == 0:
|
||||
return 'w-0'
|
||||
|
||||
ratio = self.votes / total_votes
|
||||
|
||||
scaled_value = ratio * 48
|
||||
|
||||
return f'w-{int(round(scaled_value))}'
|
||||
|
||||
def calculate_percentage(self):
|
||||
total_votes_for_question = self.question.choice_set.aggregate(Sum('votes'))['votes__sum'] or 0
|
||||
|
||||
if total_votes_for_question == 0:
|
||||
return 0
|
||||
else:
|
||||
return round((self.votes / total_votes_for_question) * 100, 2)
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns a string representation of the choice.
|
||||
|
||||
@ -1,191 +1,9 @@
|
||||
/*! NAVBAR */
|
||||
|
||||
header {
|
||||
background-color: #1C1C1C;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-left h1 a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
||||
.nav-right ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-right li {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.nav-right a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
padding: 10px 20px;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-right a:hover {
|
||||
background-color: #fff;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
/*! HOME AND POLL CARD */
|
||||
|
||||
.hero-section {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
text-align: center;
|
||||
color: #1c1c1c;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.polls-section {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
text-align: center;
|
||||
color: #1c1c1c;
|
||||
}
|
||||
|
||||
.poll-cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.poll-card {
|
||||
background-color: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.poll-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
/*! DETAILED */
|
||||
|
||||
.poll-details {
|
||||
padding: 30px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.poll-form {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.poll-question {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.choice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.choice input[type="radio"] {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.choice-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.vote-button {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.vote-button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
/*! RESULT */
|
||||
|
||||
.poll-results {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.poll-question {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.choice-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 20px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.choice-item {
|
||||
font-size: 18px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.vote-again {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
text-decoration: none;
|
||||
color: #007bff;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.vote-again:hover {
|
||||
color: #0056b3;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Open Sans Extrabold'), local('OpenSans-Extrabold'),
|
||||
url('https://fonts.googleapis.com/css2?family=Kanit&family=Open+Sans&family=Roboto:ital@1&display=swap') format('truetype');
|
||||
}
|
||||
@ -1,25 +1,17 @@
|
||||
{% load static %}
|
||||
<html>
|
||||
<head>
|
||||
<title>MySite Polls</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400" rel="stylesheet">
|
||||
<link rel='stylesheet' href='{% static 'polls/base.css' %}'>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<header>
|
||||
<div class='nav-left'>
|
||||
<h1><a href={% url 'home' %}>MySite Polls | </a></h1>
|
||||
</div>
|
||||
<div class='nav-right'>
|
||||
<ul>
|
||||
<li><a href="{% url 'home' %}">Home</a></li>
|
||||
<li><a href="{% url 'polls:index' %}">View Polls</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/htmx.org@1.7.0/dist/htmx.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<title>Your Poll Website</title>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</body>
|
||||
</html>
|
||||
@ -2,23 +2,109 @@
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<section class="poll-details">
|
||||
<form action="{% url 'polls:vote' question.id %}" method="post" class="poll-form">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend class="poll-question">{{ question.question_text }}</legend>
|
||||
{% if error_message %}
|
||||
<p class="error-message"><strong>{{ error_message }}</strong></p>
|
||||
{% endif %}
|
||||
{% for choice in question.choice_set.all %}
|
||||
<div class="choice">
|
||||
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
|
||||
<label for="choice{{ forloop.counter }}" class="choice-text">{{ choice.choice_text }}</label>
|
||||
<!-- Vote Page Content -->
|
||||
<div class="container mx-auto p-4">
|
||||
<h1 class="text-3xl font-semibold mb-2">{{ question_text }}</h1>
|
||||
<hr class="h-px my-4 bg-gray-200 border-0 dark:bg-gray-700">
|
||||
<p class="text-gray-600 mb-4 hover:bg-amber-100 text-black px-2 py-1 rounded-md mr-2">{{ long_description }}</p>
|
||||
|
||||
<!-- Tags and Stats Section (tag) to be add -->
|
||||
<div class="flex flex-wrap items-center text-gray-600 mb-4">
|
||||
<div class="flex items-center bg-orange-100 text-black px-2 py-1 rounded-md mr-2">
|
||||
<span class="mr-2">👤 {{ participant_count }} Participants</span>
|
||||
<span class="mr-2">👍 {{ question.up_vote_percentage }}% Upvoted</span>
|
||||
<span>👎 {{ question.down_vote_percentage }}% Downvoted</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modern Choice Selection -->
|
||||
<div class="bg-white p-4 rounded-lg shadow-md mb-4 ">
|
||||
<h2 class="text-xl font-semibold mb-4">Which one would you prefer:</h2>
|
||||
|
||||
<div class="flex flex-col space-y-4">
|
||||
<form action="{% url 'polls:vote' question.id %}" method="post" class="poll-form" id="poll-form">
|
||||
{% csrf_token %}
|
||||
<div class="bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<div id="selected-choice" class="mt-4 text-lg font-bold text-green-500">Please Select a Choice😊</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<button type="submit" class="vote-button">Vote</button>
|
||||
</form>
|
||||
</section>
|
||||
{% if error_message %}
|
||||
<div class="bg-red p-4 rounded-lg shadow-md mb-4 ">
|
||||
<p class="error-message text-red-500"><strong>{{ error_message }}</strong></p>
|
||||
</div
|
||||
{% endif %}
|
||||
<div class="bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<!-- Buttons as choices (hidden) -->
|
||||
{% for choice in question.choice_set.all %}
|
||||
<label>
|
||||
<input type="radio" name="choice" value="{{ choice.id }}" class="hidden" />
|
||||
<button
|
||||
type="button"
|
||||
class="choice-button bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg shadow-md transition-colors duration-300 w-full py-5 font-bold text-lg truncate"
|
||||
onclick="toggleChoice(this, '{{ choice.id }}')"
|
||||
>
|
||||
{{ choice.choice_text }}
|
||||
</button>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<div class="flex flex-row-reverse">
|
||||
<a href="{% url 'polls:index' %}" class="bg-orange-400 text-white px-4 py-2 rounded-lg hover:bg-orange-500 transition-colors duration-300">
|
||||
Back to Polls
|
||||
</a>
|
||||
<button type="submit" class="bg-orange-400 text-white px-4 py-2 rounded-lg hover:bg-orange-600 transition-colors duration-300 hidden" id="vote-button">
|
||||
Go Back
|
||||
</button>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 mx-5 rounded-lg hover:bg-blue-600 transition-colors duration-300">Vote</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const toggleChoice = (button, choiceId) => {
|
||||
const choiceInput = document.querySelector(`input[name="choice"][value="${choiceId}"]`);
|
||||
|
||||
if (choiceInput) {
|
||||
// already selected -> unselect it
|
||||
if (choiceInput.checked) {
|
||||
choiceInput.checked = false;
|
||||
button.classList.remove('bg-green-500', 'border-solid', 'border-2', 'border-green-500', 'hover:bg-green-600');
|
||||
button.classList.add('bg-blue-500', 'hover:bg-blue-600');
|
||||
// Clear display
|
||||
document.getElementById('selected-choice').textContent = 'Please Select a Choice😊';
|
||||
} else {
|
||||
// Unselect all choices
|
||||
document.querySelectorAll('input[name="choice"]').forEach((choice) => {
|
||||
choice.checked = false;
|
||||
});
|
||||
|
||||
// Select the clicked choice
|
||||
choiceInput.checked = true;
|
||||
|
||||
// Reset the style of all choice buttons
|
||||
document.querySelectorAll('.choice-button').forEach((btn) => {
|
||||
btn.classList.remove('bg-green-500', 'border-solid', 'border-2', 'border-green-500', 'hover:bg-green-600');
|
||||
btn.classList.add('bg-blue-500', 'hover:bg-blue-600');
|
||||
});
|
||||
|
||||
button.classList.remove('bg-blue-500', 'hover:bg-blue-600');
|
||||
button.classList.add('bg-green-500', 'border-solid', 'border-2', 'border-green-500', 'hover:bg-green-600');
|
||||
|
||||
const choiceText = button.textContent.trim();
|
||||
document.getElementById('selected-choice').textContent = `You selected: ${choiceText}`;
|
||||
}
|
||||
|
||||
// Enable the "Vote" button -> if select
|
||||
const voteButton = document.getElementById('vote-button');
|
||||
voteButton.disabled = !document.querySelector('input[name="choice"]:checked');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
{% extends 'polls/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<section class="hero-section">
|
||||
<div class="hero-content">
|
||||
<h1>Welcome to KU Polls</h1>
|
||||
<p>Explore and participate in our weird poll questions.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="polls-section">
|
||||
<h2>Recent Polls</h2>
|
||||
<p class="total-polls">Total number of polls: {{ total_open_polls }}</p>
|
||||
<div class="poll-cards">
|
||||
{% if latest_question_list %}
|
||||
{% for question in latest_question_list %}
|
||||
<div class="poll-card">
|
||||
<h3><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></h3>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>No polls are available.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
@ -1,19 +1,97 @@
|
||||
{% extends 'polls/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<section class="polls-section">
|
||||
<h2>Recent Polls</h2>
|
||||
<div class="poll-cards">
|
||||
{% if latest_question_list %}
|
||||
{% for question in latest_question_list %}
|
||||
<div class="poll-card">
|
||||
<h3><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></h3>
|
||||
<p class="publication-date">Published on: {{ question.pub_date|date:"F j, Y" }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>No polls are available.</p>
|
||||
{% endif %}
|
||||
<main>
|
||||
<!-- Navbar -->
|
||||
<nav class="bg-blue-500 p-4">
|
||||
<div class="container mx-auto flex items-center justify-between">
|
||||
<div class="text-2xl font-semibold text-white">KU POLL</div>
|
||||
|
||||
<!-- Button -->
|
||||
<div>
|
||||
{% comment %} <button class="mr-4 rounded-md bg-green-500 px-4 py-2 text-white">New Poll</button> {% endcomment %}
|
||||
<button class="text-white">Sign In</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mx-auto p-4">
|
||||
<header class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-2xl font-semibold">Explore Polls</h1>
|
||||
<button class="rounded-md bg-green-500 px-4 py-2 text-white">New Poll</button>
|
||||
</header>
|
||||
|
||||
<!-- Filter Section -->
|
||||
<div class="mb-4">
|
||||
<input type="text" placeholder="Search for polls..." class="w-full rounded-md border border-gray-300 px-4 py-2" />
|
||||
</div>
|
||||
|
||||
<!-- Trends Polls Section -->
|
||||
<section class="mb-6">
|
||||
<h2 class="mb-4 text-2xl font-semibold">Trending</h2>
|
||||
<div class="bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
|
||||
{% for question in latest_question_list %}
|
||||
<a class="rounded-lg bg-white p-4 shadow-md">
|
||||
<h2 class="mb-2 text-xl font-semibold">{{ question.question_text }}</h2>
|
||||
<hr class="h-px my-2 bg-gray-200 border-0 dark:bg-gray-400">
|
||||
<p class="mb-2 text-gray-600">{{ question.short_description }}</p>
|
||||
<div class="mb-2 flex items-center text-gray-600">
|
||||
<span class="mr-2">👍</span>
|
||||
<span>{{ question.up_vote_percentage }}% Upvoted</span>
|
||||
|
||||
<span class="ml-4 mr-2">👎</span>
|
||||
<span>{{ question.down_vote_percentage }}% Downvoted</span>
|
||||
</div>
|
||||
<!-- Tag / Time -->
|
||||
<div class="flex items-center text-gray-600">
|
||||
<span class="mr-2 rounded-md bg-green-500 px-2 py-1 text-white">🕒 {{ question.time_left }}</span>
|
||||
<span class="mr-2 rounded-md bg-orange-100 px-2 py-1 text-black"> {{ question.participant_count }} Participants 👤</span>
|
||||
</div>
|
||||
<div class="flex items-center text-gray-600 py-4">
|
||||
<button onclick="window.location.href='{% url 'polls:detail' question.id %}'" class="mr-2 rounded-md bg-white px-2 py-1 text-black border-solid border-2 border-black hover:bg-gray-500 transform translate-y-0 hover:translate-y-1 transition-transform">VOTE</button>
|
||||
<button onclick="window.location.href='{% url 'polls:results' question.id %}'" class="mr-2 rounded-md bg-white px-2 py-1 text-black border-solid border-2 border-black hover:bg-gray-500 transform translate-y-0 hover:translate-y-1 transition-transform">VIEW</button>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Poll Cards Section -->
|
||||
<section>
|
||||
<h2 class="mb-4 text-2xl font-semibold">Polls</h2>
|
||||
<div class="bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<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 %}
|
||||
<a href="{% url 'polls:detail' question.id %}" class="rounded-lg bg-white p-4 shadow-md">
|
||||
<h2 class="mb-2 text-xl font-semibold">{{ question.question_text }}</h2>
|
||||
<p class="mb-2 text-gray-600">{{ question.short_description }}</p>
|
||||
<div class="mb-2 flex items-center text-gray-600">
|
||||
|
||||
<span class="mr-2">👍</span>
|
||||
<span>{{ question.up_vote_percentage }}% Upvoted</span>
|
||||
|
||||
<span class="ml-4 mr-2">👎</span>
|
||||
<span>{{ question.down_vote_percentage }}% Downvoted</span>
|
||||
</div>
|
||||
<!-- Tag Time -->
|
||||
<div class="flex items-center text-gray-600">
|
||||
<span class="mr-2 rounded-md bg-green-500 px-2 py-1 text-white">🕒 {{ question.time_left }}</span>
|
||||
<span class="mr-2 rounded-md bg-orange-100 px-2 py-1 text-black"> {{ question.participant_count }} Participants 👤</span>
|
||||
</div>
|
||||
<div class="flex items-center text-gray-600 py-4">
|
||||
<button onclick="window.location.href='{% url 'polls:detail' question.id %}'" class="mr-2 rounded-md bg-white px-2 py-1 text-black border-solid border-2 border-black hover:bg-gray-500 transform translate-y-0 hover:translate-y-1 transition-transform">VOTE</button>
|
||||
<button onclick="window.location.href='{% url 'polls:results' question.id %}'" class="mr-2 rounded-md bg-white px-2 py-1 text-black border-solid border-2 border-black hover:bg-gray-500 transform translate-y-0 hover:translate-y-1 transition-transform">VIEW</button>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
|
||||
@ -2,16 +2,116 @@
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<section class="poll-results">
|
||||
<div class="poll-header">
|
||||
<h1 class="poll-question">{{ question.question_text }}</h1>
|
||||
</div>
|
||||
<ul class="choice-list">
|
||||
<!-- Result Page Content -->
|
||||
<div class="container mx-auto p-4">
|
||||
<h1 class="text-3xl font-semibold mb-4">{{ question.question_text }}</h1>
|
||||
<hr class="h-px my-4 bg-gray-200 border-0 dark:bg-gray-700">
|
||||
|
||||
<!-- Result Summary -->
|
||||
<div class="bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<h2 class="text-xl font-semibold mb-2">Result Summary:</h2>
|
||||
|
||||
{% for choice in question.choice_set.all %}
|
||||
<li class="choice-item">{{ choice.choice_text }} — {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span>{{ choice.choice_text }}</span>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2">👍 {{ choice.votes }}</span>
|
||||
<div class="percentage-bar">
|
||||
<div class="bar bg-blue-500 h-2" style="width: {{ choice.calculate_percentage }}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{% url 'polls:detail' question.id %}" class="vote-again">Vote again?</a>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Result Page Content -->
|
||||
<div class="container mx-auto grid grid-cols-3 gap-4">
|
||||
<!-- Statistics -->
|
||||
<div class="col-span-1 bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<h2 class="text-xl font-semibold mb-2">🕵️ Statistics</h2>
|
||||
<span class="mr-2 rounded-md bg-orange-100 px-2 py-1 text-black"> {{ question.participant_count }} Participants 👤</span>
|
||||
<span class="mr-2 rounded-md bg-orange-100 px-2 py-1 text-black">👍 {{ question.up_vote_percentage }}% </span>
|
||||
<span class="mr-2 rounded-md bg-orange-100 px-2 py-1 text-black" >👎 {{ question.down_vote_percentage }}% </span>
|
||||
</div>
|
||||
|
||||
<!-- Pie Chart -->
|
||||
<div class="col-span-1 bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<h2 class="text-xl font-semibold mb-2">👋 Vote Percentage</h2>
|
||||
<div class="w-full h-48 bg-gray-100 rounded-lg">
|
||||
<canvas id="percentageChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bar Chart -->
|
||||
<div class="col-span-1 bg-white p-4 rounded-lg shadow-md mb-4">
|
||||
<h2 class="text-xl font-semibold mb-2">👏 Vote Count</h2>
|
||||
<div class="w-full h-48 bg-gray-100 rounded-lg">
|
||||
<canvas id="voteCountChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to Polls Button -->
|
||||
<a href="{% url 'polls:index' %}" class="bg-orange-400 text-white px-4 py-2 rounded-lg hover:bg-orange-500 transition-colors duration-300">
|
||||
Back to Polls
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
|
||||
<script>
|
||||
var percentageCtx = document.getElementById('percentageChart').getContext('2d');
|
||||
var percentageChart = new Chart(percentageCtx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: [{% for choice in question.choice_set.all %}"{{ choice.choice_text }}",{% endfor %}],
|
||||
datasets: [{
|
||||
label: 'Percentage',
|
||||
data: [{% for choice in question.choice_set.all %}{{ choice.calculate_percentage }},{% endfor %}],
|
||||
backgroundColor: [
|
||||
'rgba(75, 192, 192, 0.2)',
|
||||
'rgba(255, 99, 132, 0.2)',
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(255, 99, 132, 1)',
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
}
|
||||
});
|
||||
var voteCountCtx = document.getElementById('voteCountChart').getContext('2d');
|
||||
var voteCountChart = new Chart(voteCountCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: [{% for choice in question.choice_set.all %}"{{ choice.choice_text }}",{% endfor %}],
|
||||
datasets: [{
|
||||
label: 'Vote Count',
|
||||
data: [{% for choice in question.choice_set.all %}{{ choice.votes }},{% endfor %}],
|
||||
backgroundColor: [
|
||||
'rgba(75, 192, 192, 0.2)',
|
||||
'rgba(255, 99, 132, 0.2)',
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(255, 99, 132, 1)',
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock content %}
|
||||
@ -124,7 +124,6 @@ class QuestionIndexViewTests(TestCase):
|
||||
"""
|
||||
response = self.client.get(reverse("polls:index"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "No polls are available.")
|
||||
self.assertQuerySetEqual(response.context["latest_question_list"], [])
|
||||
|
||||
def test_past_question(self):
|
||||
@ -150,7 +149,6 @@ class QuestionIndexViewTests(TestCase):
|
||||
future_question.pub_date = timezone.now() + timezone.timedelta(days=30)
|
||||
future_question.save()
|
||||
response = self.client.get(reverse("polls:index"))
|
||||
self.assertContains(response, "No polls are available.")
|
||||
self.assertQuerySetEqual(response.context["latest_question_list"], [])
|
||||
|
||||
def test_future_question_and_past_question(self):
|
||||
|
||||
@ -8,42 +8,15 @@ from django.views.generic import TemplateView
|
||||
from .models import Choice, Question
|
||||
|
||||
|
||||
class HomeView(TemplateView):
|
||||
"""
|
||||
Provide a view for Home page(first page).
|
||||
"""
|
||||
|
||||
template_name = 'polls/home.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
all_questions = Question.objects.all()
|
||||
#* Check if the question is published and can be voted. Then, sort by pub_date
|
||||
published_questions = [q for q in all_questions if q.is_published() and q.can_vote()]
|
||||
latest_published_questions = sorted(published_questions, key=lambda q: q.pub_date, reverse=True)[:5]
|
||||
|
||||
context['latest_question_list'] = latest_published_questions
|
||||
context['total_open_polls'] = sum(1 for q in published_questions if q.end_date is None)
|
||||
context['total_polls'] = all_questions.count()
|
||||
return context
|
||||
|
||||
|
||||
class IndexView(generic.ListView):
|
||||
"""
|
||||
Provide a view for Index page that list all polls.
|
||||
"""
|
||||
"""View for index.html."""
|
||||
|
||||
template_name = "polls/index.html"
|
||||
context_object_name = "latest_question_list"
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Return the last five published questions (not including those set to be
|
||||
published in the future).
|
||||
"""
|
||||
return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[
|
||||
:5
|
||||
]
|
||||
"""Return the last five published questions."""
|
||||
return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[:5]
|
||||
|
||||
|
||||
class DetailView(generic.DetailView):
|
||||
@ -61,15 +34,35 @@ class DetailView(generic.DetailView):
|
||||
"""
|
||||
return Question.objects.filter(pub_date__lte=timezone.now())
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
question = self.object
|
||||
|
||||
context['question_text'] = question.question_text
|
||||
context['short_description'] = question.short_description
|
||||
context['long_description'] = question.long_description
|
||||
context['pub_date'] = question.pub_date
|
||||
context['end_date'] = question.end_date
|
||||
context['up_vote_count'] = question.up_vote_count
|
||||
context['down_vote_count'] = question.down_vote_count
|
||||
context['participant_count'] = question.participant_count
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class ResultsView(generic.DetailView):
|
||||
"""
|
||||
Provide a view for result page that show up when user submit on of the choices.
|
||||
"""
|
||||
|
||||
model = Question
|
||||
template_name = "polls/results.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['question'] = self.object
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
return render(self.request, self.template_name, context)
|
||||
|
||||
|
||||
def vote(request, question_id):
|
||||
"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user