diff --git a/server/egglocke/db.sqlite3 b/server/egglocke/db.sqlite3 index 0537fc6..f640f52 100644 Binary files a/server/egglocke/db.sqlite3 and b/server/egglocke/db.sqlite3 differ diff --git a/server/egglocke/pokepoll/admin.py b/server/egglocke/pokepoll/admin.py index 8c38f3f..1f194a0 100644 --- a/server/egglocke/pokepoll/admin.py +++ b/server/egglocke/pokepoll/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin # Register your models here. +from .models import Question + +admin.site.register(Question) \ No newline at end of file diff --git a/server/egglocke/pokepoll/models.py b/server/egglocke/pokepoll/models.py index 444840b..0f0d9a4 100644 --- a/server/egglocke/pokepoll/models.py +++ b/server/egglocke/pokepoll/models.py @@ -1,13 +1,17 @@ -from django.db import models - +import datetime # Create your models here. from django.db import models +from django.utils import timezone class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") + def was_published_recently(self): + now = timezone.now() + return now - datetime.timedelta(days=1) <= self.pub_date <= now + class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) diff --git a/server/egglocke/pokepoll/static/pokepoll/img/astarion.png b/server/egglocke/pokepoll/static/pokepoll/img/astarion.png new file mode 100644 index 0000000..01009e8 Binary files /dev/null and b/server/egglocke/pokepoll/static/pokepoll/img/astarion.png differ diff --git a/server/egglocke/pokepoll/static/pokepoll/img/strawberi.png b/server/egglocke/pokepoll/static/pokepoll/img/strawberi.png new file mode 100644 index 0000000..bbc2142 Binary files /dev/null and b/server/egglocke/pokepoll/static/pokepoll/img/strawberi.png differ diff --git a/server/egglocke/pokepoll/static/pokepoll/style.css b/server/egglocke/pokepoll/static/pokepoll/style.css new file mode 100644 index 0000000..1820f30 --- /dev/null +++ b/server/egglocke/pokepoll/static/pokepoll/style.css @@ -0,0 +1,7 @@ +li a { + color: green; +} + +body { + background: white url("img/astarion.png") no-repeat; +} \ No newline at end of file diff --git a/server/egglocke/pokepoll/templates/pokepoll/detail.html b/server/egglocke/pokepoll/templates/pokepoll/detail.html new file mode 100644 index 0000000..da81fc5 --- /dev/null +++ b/server/egglocke/pokepoll/templates/pokepoll/detail.html @@ -0,0 +1,21 @@ +

{{ question.question_text }}

+ + +

Submissime owo

+ +
+ {% csrf_token %} +
+

{{ question.question_text }}

+ {% if error_message %}

{{ error_message }}

{% endif %} + {% for choice in question.choice_set.all %} + +
+ {% endfor %} +
+ +
\ No newline at end of file diff --git a/server/egglocke/pokepoll/templates/pokepoll/home.html b/server/egglocke/pokepoll/templates/pokepoll/home.html new file mode 100644 index 0000000..465befc --- /dev/null +++ b/server/egglocke/pokepoll/templates/pokepoll/home.html @@ -0,0 +1,11 @@ +{% load static %} + +

Eviii's Egglocke Maker uwu :3

+ + + + +

X Eggs Submitted so far!

+ + +Submit an Egg! \ No newline at end of file diff --git a/server/egglocke/pokepoll/templates/pokepoll/submissions.html b/server/egglocke/pokepoll/templates/pokepoll/submissions.html new file mode 100644 index 0000000..0511c6a --- /dev/null +++ b/server/egglocke/pokepoll/templates/pokepoll/submissions.html @@ -0,0 +1,14 @@ +{% load static %} + + + + +{% if latest_question_list %} + +{% else %} +

No polls are available.

+{% endif %} \ No newline at end of file diff --git a/server/egglocke/pokepoll/tests.py b/server/egglocke/pokepoll/tests.py index 7ce503c..b2baa35 100644 --- a/server/egglocke/pokepoll/tests.py +++ b/server/egglocke/pokepoll/tests.py @@ -1,3 +1,104 @@ +# Create your tests here. +import datetime + from django.test import TestCase +from django.utils import timezone +from django.urls import reverse +from .models import Question -# Create your tests here. + +class QuestionModelTests(TestCase): + def test_was_published_recently_with_future_question(self): + """ + was_published_recently() returns False for questions whose pub_date + is in the future. + """ + time = timezone.now() + datetime.timedelta(days=30) + future_question = Question(pub_date=time) + self.assertIs(future_question.was_published_recently(), False) + + def test_was_published_recently_with_old_question(self): + """ + was_published_recently() returns False for questions whose pub_date + is older than 1 day. + """ + time = timezone.now() - datetime.timedelta(days=1, seconds=1) + old_question = Question(pub_date=time) + self.assertIs(old_question.was_published_recently(), False) + + + def test_was_published_recently_with_recent_question(self): + """ + was_published_recently() returns True for questions whose pub_date + is within the last day. + """ + time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) + recent_question = Question(pub_date=time) + self.assertIs(recent_question.was_published_recently(), True) + +def create_question(question_text, days): + """ + Create a question with the given `question_text` and published the + given number of `days` offset to now (negative for questions published + in the past, positive for questions that have yet to be published). + """ + time = timezone.now() + datetime.timedelta(days=days) + return Question.objects.create(question_text=question_text, pub_date=time) + + +class QuestionIndexViewTests(TestCase): + def test_no_questions(self): + """ + If no questions exist, an appropriate message is displayed. + """ + response = self.client.get(reverse("pokepoll: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): + """ + Questions with a pub_date in the past are displayed on the + index page. + """ + question = create_question(question_text="Past question.", days=-30) + response = self.client.get(reverse("pokepoll:index")) + self.assertQuerySetEqual( + response.context["latest_question_list"], + [question], + ) + + def test_future_question(self): + """ + Questions with a pub_date in the future aren't displayed on + the index page. + """ + create_question(question_text="Future question.", days=30) + response = self.client.get(reverse("pokepoll:index")) + self.assertContains(response, "No polls are available.") + self.assertQuerySetEqual(response.context["latest_question_list"], []) + + def test_future_question_and_past_question(self): + """ + Even if both past and future questions exist, only past questions + are displayed. + """ + question = create_question(question_text="Past question.", days=-30) + create_question(question_text="Future question.", days=30) + response = self.client.get(reverse("pokepoll:index")) + self.assertQuerySetEqual( + response.context["latest_question_list"], + [question], + ) + + def test_two_past_questions(self): + """ + The questions index page may display multiple questions. + """ + question1 = create_question(question_text="Past question 1.", days=-30) + question2 = create_question(question_text="Past question 2.", days=-5) + response = self.client.get(reverse("pokepoll:index")) + self.assertQuerySetEqual( + response.context["latest_question_list"], + [question2, question1], + ) \ No newline at end of file diff --git a/server/egglocke/pokepoll/urls.py b/server/egglocke/pokepoll/urls.py index a9d7f56..e6331ce 100644 --- a/server/egglocke/pokepoll/urls.py +++ b/server/egglocke/pokepoll/urls.py @@ -2,6 +2,11 @@ from . import views +app_name = "pokepoll" urlpatterns = [ - path("", views.index, name="index"), + path("", views.HomeView.as_view(), name="home"), + path("submissions/", views.IndexView.as_view(), name="submissions"), + path("/", views.DetailView.as_view(), name="detail"), + path("/results/", views.ResultsView.as_view(), name="results"), + path("/vote/", views.vote, name="vote"), ] \ No newline at end of file diff --git a/server/egglocke/pokepoll/views.py b/server/egglocke/pokepoll/views.py index 363c279..e1f8663 100644 --- a/server/egglocke/pokepoll/views.py +++ b/server/egglocke/pokepoll/views.py @@ -1,8 +1,59 @@ -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 +from django.template import loader +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.db.models import F +from django.views import generic +from django.utils import timezone # Create your views here. -from django.http import HttpResponse +from django.urls import reverse +from .models import Question, Choice +class HomeView(generic.TemplateView): + template_name = "pokepoll/home.html" -def index(request): - return HttpResponse("Hello, world. You're at the polls index.") \ No newline at end of file +class IndexView(generic.ListView): + template_name = "pokepoll/submissions.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 + ] + + +class DetailView(generic.DetailView): + model = Question + template_name = "pokepoll/detail.html" + + +class ResultsView(generic.DetailView): + model = Question + template_name = "pokepoll/results.html" + + +def vote(request, question_id): + question = get_object_or_404(Question, pk=question_id) + try: + selected_choice = question.choice_set.get(pk=request.POST["choice"]) + except (KeyError, Choice.DoesNotExist): + # Redisplay the question voting form. + return render( + request, + "pokepoll/detail.html", + { + "question": question, + "error_message": "You didn't select a choice.", + }, + ) + else: + selected_choice.votes = F("votes") + 1 + selected_choice.save() + # Always return an HttpResponseRedirect after successfully dealing + # with POST data. This prevents data from being posted twice if a + # user hits the Back button. + return HttpResponseRedirect(reverse("pokepoll:results", args=(question.id,))) \ No newline at end of file