Add basic quiz functionality

This commit is contained in:
2024-11-23 22:25:24 +01:00
parent 6bf0a56b88
commit 27b8c40c1c
40 changed files with 2471 additions and 53 deletions

0
tvdt/quiz/__init__.py Normal file
View File

43
tvdt/quiz/admin.py Normal file
View File

@@ -0,0 +1,43 @@
from django.contrib import admin
from .models import Question, Answer, Candidate, Quiz, Season, GivenAnswer
class CandidatesAdmin(admin.StackedInline):
model = Candidate
extra = 1
@admin.register(Season)
class SeasonAdmin(admin.ModelAdmin):
inlines = [CandidatesAdmin]
class QuestionInline(admin.TabularInline):
model = Question
extra = 0
@admin.register(Quiz)
class QuizAdmin(admin.ModelAdmin):
inlines = [QuestionInline]
class AnswerInline(admin.TabularInline):
model = Answer
extra = 0
@admin.register(Question)
class QuestionAdmin(admin.ModelAdmin):
inlines = [AnswerInline]
@admin.register(Candidate)
class CandidateAdmin(admin.ModelAdmin):
pass
@admin.register(GivenAnswer)
class GivenAnswerAdmin(admin.ModelAdmin):
pass

8
tvdt/quiz/apps.py Normal file
View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class QuizConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "quiz"
verbose_name = _("quiz")

41
tvdt/quiz/converters.py Normal file
View File

@@ -0,0 +1,41 @@
import base64
import binascii
from .models import Season, Candidate
class SeasonCodeConverter:
regex = r"[A-Za-z\d]{5}"
def to_python(self, value: str) -> Season:
try:
return Season.objects.get(season_code=value.upper())
except Season.DoesNotExist:
raise ValueError
def to_url(self, value: Season) -> str:
return value.season_code
class CandidateConverter:
regex = r"[A-Za-z\d]{5}\/[\w\-=]+"
def to_python(self, value: str) -> Candidate:
season_code, base64_name = value.split("/")
try:
name = base64.urlsafe_b64decode(base64_name).decode()
except binascii.Error:
raise ValueError
try:
season = Season.objects.get(season_code=season_code)
candidate = Candidate.objects.get(name=name, season=season)
return candidate
except [Season.DoesNotExist, Candidate.DoesNotExist]:
raise ValueError
def to_url(self, candidate: Candidate) -> str:
base64_candidate = base64.urlsafe_b64encode(candidate.name.encode()).decode()
return f"{candidate.season.season_code}/{base64_candidate}"

File diff suppressed because it is too large Load Diff

8
tvdt/quiz/helpers.py Normal file
View File

@@ -0,0 +1,8 @@
import random
import string
def generate_season_code(length: int = 5) -> str:
return "".join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(length)
)

View File

@@ -0,0 +1,122 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-20 00:23+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: quiz/models/answer.py:10
msgid "text"
msgstr "tekst"
#: quiz/models/answer.py:15 quiz/models/given_answer.py:21
#: quiz/models/question.py:9 quiz/models/question.py:21
msgid "question"
msgstr "vraag"
#: quiz/models/answer.py:17
msgid "is right answer"
msgstr "is goede antwoord"
#: quiz/models/answer.py:19 quiz/models/candidate.py:25
msgid "candidates"
msgstr "kandidaten"
#: quiz/models/answer.py:23 quiz/models/given_answer.py:24
msgid "answer"
msgstr "antwoord"
#: quiz/models/answer.py:24
msgid "answers"
msgstr "antwoorden"
#: quiz/models/candidate.py:15 quiz/models/quiz.py:9 quiz/models/season.py:12
msgid "name"
msgstr "naam"
#: quiz/models/candidate.py:24 quiz/models/correction.py:14
#: quiz/models/given_answer.py:15 quiz/models/quiz_time.py:11
msgid "candidate"
msgstr "kandidaat"
#: quiz/models/correction.py:20 quiz/models/question.py:11
#: quiz/models/quiz.py:26 quiz/models/quiz_time.py:13
msgid "quiz"
msgstr "test"
#: quiz/models/correction.py:25
msgid "correction"
msgstr "joker"
#: quiz/models/correction.py:26
msgid "corrections"
msgstr "jokers"
#: quiz/models/given_answer.py:30
msgid "given answer"
msgstr "gegeven antwoord"
#: quiz/models/given_answer.py:31
msgid "given answers"
msgstr "gegeven antwoorden"
#: quiz/models/question.py:13
msgid "number"
msgstr "nummer"
#: quiz/models/question.py:14
msgid "enabled"
msgstr "ingeschakeld"
#: quiz/models/question.py:22
msgid "questions"
msgstr "vraag"
#: quiz/models/quiz.py:14 quiz/models/season.py:38
msgid "season"
msgstr "seizoen"
#: quiz/models/quiz.py:27
msgid "quizzes"
msgstr "tests"
#: quiz/models/quiz_time.py:14
msgid "seconds"
msgstr "seconden"
#: quiz/models/quiz_time.py:17
msgid "quiz time"
msgstr "testtijd"
#: quiz/models/quiz_time.py:18
msgid "quiz times"
msgstr "testtijden"
#: quiz/models/season.py:19
msgid "active quiz"
msgstr "actieve test"
#: quiz/models/season.py:23
msgid "season code"
msgstr "seizoencode"
#: quiz/models/season.py:26
msgid "preregister candidates"
msgstr "kandidaten voorregistreren"
#: quiz/models/season.py:39
msgid "seasons"
msgstr "seizoenen"

View File

@@ -0,0 +1,311 @@
# Generated by Django 5.1.2 on 2024-10-19 21:54
import django.db.models.deletion
import quiz.helpers
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Candidate",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=16, verbose_name="naam")),
],
options={
"verbose_name": "candidate",
"verbose_name_plural": "candidates",
},
),
migrations.CreateModel(
name="Question",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("question", models.CharField(max_length=256, verbose_name="question")),
("number", models.PositiveSmallIntegerField(verbose_name="number")),
("enabled", models.BooleanField(default=True, verbose_name="enabled")),
],
options={
"verbose_name": "question",
"verbose_name_plural": "questions",
},
),
migrations.CreateModel(
name="Quiz",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=64, verbose_name="naam")),
],
options={
"verbose_name": "quiz",
"verbose_name_plural": "quizzes",
},
),
migrations.CreateModel(
name="Answer",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("text", models.CharField(max_length=64, verbose_name="text")),
(
"is_right_answer",
models.BooleanField(verbose_name="is right answer"),
),
(
"candidates",
models.ManyToManyField(
to="quiz.candidate", verbose_name="candidates"
),
),
(
"question",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="answers",
to="quiz.question",
verbose_name="question",
),
),
],
options={
"verbose_name": "answer",
"verbose_name_plural": "answers",
},
),
migrations.AddField(
model_name="question",
name="quiz",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="questions",
to="quiz.quiz",
verbose_name="quiz",
),
),
migrations.CreateModel(
name="QuizTime",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("seconds", models.PositiveIntegerField(verbose_name="seconds")),
(
"candidate",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="quiz.candidate",
verbose_name="candidate",
),
),
(
"quiz",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="quiz.quiz",
verbose_name="quiz",
),
),
],
options={
"verbose_name": "quiz time",
"verbose_name_plural": "quiz times",
},
),
migrations.CreateModel(
name="Season",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=64, verbose_name="naam")),
(
"season_code",
models.CharField(
default=quiz.helpers.generate_season_code,
max_length=5,
verbose_name="season code",
),
),
(
"active_quiz",
models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="quiz.quiz",
verbose_name="active quiz",
),
),
],
options={
"verbose_name": "season",
"verbose_name_plural": "seasons",
},
),
migrations.AddField(
model_name="quiz",
name="season",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="quizzes",
to="quiz.season",
verbose_name="season",
),
),
migrations.AddField(
model_name="candidate",
name="season",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="candidates",
to="quiz.season",
verbose_name="season",
),
),
migrations.CreateModel(
name="GivenAnswer",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"answer",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="quiz.answer",
verbose_name="answer",
),
),
(
"candidate",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="answers",
to="quiz.candidate",
verbose_name="candidate",
),
),
(
"question",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="given_answers",
to="quiz.question",
verbose_name="question",
),
),
],
options={
"verbose_name": "given answer",
"verbose_name_plural": "given answers",
"unique_together": {("candidate", "question")},
},
),
migrations.AlterUniqueTogether(
name="question",
unique_together={("quiz", "number")},
),
migrations.CreateModel(
name="Correction",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"candidate",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="corrections_used",
to="quiz.candidate",
verbose_name="candidate",
),
),
(
"quiz",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="corrections_used",
to="quiz.quiz",
verbose_name="quiz",
),
),
],
options={
"verbose_name": "correction",
"verbose_name_plural": "corrections",
"unique_together": {("candidate", "quiz")},
},
),
migrations.AddIndex(
model_name="candidate",
index=models.Index(
fields=["season", "name"], name="quiz_candid_season__d83118_idx"
),
),
migrations.AlterUniqueTogether(
name="candidate",
unique_together={("season", "name")},
),
]

View File

@@ -0,0 +1,56 @@
# Generated by Django 5.1.2 on 2024-10-19 22:17
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("quiz", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="season",
name="preregister_candidates",
field=models.BooleanField(
default=True, verbose_name="preregister candidates"
),
),
migrations.AlterField(
model_name="answer",
name="candidates",
field=models.ManyToManyField(
blank=True, to="quiz.candidate", verbose_name="candidates"
),
),
migrations.AlterField(
model_name="candidate",
name="name",
field=models.CharField(max_length=16, verbose_name="name"),
),
migrations.AlterField(
model_name="quiz",
name="name",
field=models.CharField(max_length=64, verbose_name="name"),
),
migrations.AlterField(
model_name="season",
name="active_quiz",
field=models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="quiz.quiz",
verbose_name="active quiz",
),
),
migrations.AlterField(
model_name="season",
name="name",
field=models.CharField(max_length=64, verbose_name="name"),
),
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.1.2 on 2024-11-03 19:21
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("quiz", "0002_season_preregister_candidates_and_more"),
]
operations = [
migrations.AddField(
model_name="givenanswer",
name="created",
field=models.DateTimeField(auto_now_add=True, default=None),
preserve_default=False,
),
migrations.AlterField(
model_name="givenanswer",
name="question",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="given_answers",
to="quiz.question",
verbose_name="question",
),
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.1.3 on 2024-11-23 16:15
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("quiz", "0003_givenanswer_created_alter_givenanswer_question"),
]
operations = [
migrations.AlterUniqueTogether(
name="question",
unique_together=set(),
),
migrations.AlterOrderWithRespectTo(
name="question",
order_with_respect_to="quiz",
),
migrations.AlterOrderWithRespectTo(
name="answer",
order_with_respect_to="question",
),
migrations.RemoveField(
model_name="question",
name="number",
),
]

View File

View File

@@ -0,0 +1,8 @@
from .answer import Answer
from .candidate import Candidate
from .correction import Correction
from .given_answer import GivenAnswer
from .question import Question
from .quiz import Quiz
from .quiz_time import QuizTime
from .season import Season

View File

@@ -0,0 +1,23 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta
class Answer(models.Model):
text = models.CharField(max_length=64, verbose_name=_("text"))
question = models.ForeignKey(
"Question",
on_delete=models.CASCADE,
related_name="answers",
verbose_name=_("question"),
)
is_right_answer = models.BooleanField(verbose_name=_("is right answer"))
candidates = models.ManyToManyField(
"Candidate", verbose_name=_("candidates"), blank=True
)
class Meta(TypedModelMeta):
verbose_name = _("answer")
verbose_name_plural = _("answers")
order_with_respect_to = "question"

View File

@@ -0,0 +1,48 @@
from typing import Self
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta
from .given_answer import GivenAnswer
from .question import NoActiveTestForSeason, QuizAlreadyFinished, Question
class Candidate(models.Model):
season = models.ForeignKey(
"Season",
on_delete=models.CASCADE,
related_name="candidates",
verbose_name="season",
)
name = models.CharField(max_length=16, verbose_name=_("name"))
def get_next_question(self, candidate: Self) -> "Question":
quiz = candidate.season.active_quiz
if quiz is None:
raise NoActiveTestForSeason()
question = (
Question.objects.filter(quiz=quiz)
.exclude(
id__in=GivenAnswer.objects.filter(
candidate=candidate, question__quiz=quiz
).values_list("question_id", flat=True)
)
.first()
)
if question is None:
raise QuizAlreadyFinished()
return question
def __str__(self) -> str:
return f"{self.name} ({self.season})"
class Meta(TypedModelMeta):
unique_together = ["season", "name"]
indexes = [models.Index(fields=["season", "name"])]
verbose_name = _("candidate")
verbose_name_plural = _("candidates")

View File

@@ -0,0 +1,26 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta
from .candidate import Candidate
from .quiz import Quiz
class Correction(models.Model):
candidate = models.ForeignKey(
"Candidate",
on_delete=models.CASCADE,
related_name="corrections_used",
verbose_name=_("candidate"),
)
quiz = models.ForeignKey(
"Quiz",
on_delete=models.CASCADE,
related_name="corrections_used",
verbose_name=_("quiz"),
)
class Meta(TypedModelMeta):
unique_together = ("candidate", "quiz")
verbose_name = _("correction")
verbose_name_plural = _("corrections")

View File

@@ -0,0 +1,29 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta
class GivenAnswer(models.Model):
candidate = models.ForeignKey(
"Candidate",
on_delete=models.CASCADE,
related_name="answers",
verbose_name=_("candidate"),
)
question = models.ForeignKey(
"Question",
on_delete=models.CASCADE,
null=True,
related_name="given_answers",
verbose_name=_("question"),
)
answer = models.ForeignKey(
"Answer", on_delete=models.CASCADE, verbose_name=_("answer")
)
created = models.DateTimeField(auto_now_add=True)
class Meta(TypedModelMeta):
unique_together = ["candidate", "question"]
verbose_name = _("given answer")
verbose_name_plural = _("given answers")

View File

@@ -0,0 +1,31 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta
class NoActiveTestForSeason(Exception):
pass
class QuizAlreadyFinished(Exception):
pass
class Question(models.Model):
question = models.CharField(max_length=256, verbose_name=_("question"))
quiz = models.ForeignKey(
"Quiz",
on_delete=models.CASCADE,
related_name="questions",
verbose_name=_("quiz"),
)
enabled = models.BooleanField(default=True, verbose_name=_("enabled"))
def __str__(self) -> str:
return f"{self._order + 1}. {self.question} ({self.quiz}) ({self.answers.count()} answers, {self.answers.filter(is_right_answer=True).count()} correct)"
class Meta(TypedModelMeta):
verbose_name = _("question")
verbose_name_plural = _("questions")
order_with_respect_to = "quiz"

25
tvdt/quiz/models/quiz.py Normal file
View File

@@ -0,0 +1,25 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta
class Quiz(models.Model):
name = models.CharField(max_length=64, verbose_name=_("name"))
season = models.ForeignKey(
"Season",
on_delete=models.CASCADE,
related_name="quizzes",
verbose_name=_("season"),
)
def is_valid_quiz(self) -> bool:
pass
# Check > 0 active questions
# Check every question 1 right answer
def __str__(self) -> str:
return f"{self.season.name} - {self.name}"
class Meta(TypedModelMeta):
verbose_name = _("quiz")
verbose_name_plural = _("quizzes")

View File

@@ -0,0 +1,15 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta
class QuizTime(models.Model):
candidate = models.ForeignKey(
"Candidate", on_delete=models.CASCADE, verbose_name=_("candidate")
)
quiz = models.ForeignKey("Quiz", on_delete=models.CASCADE, verbose_name=_("quiz"))
seconds = models.PositiveIntegerField(verbose_name=_("seconds"))
class Meta(TypedModelMeta):
verbose_name = _("quiz time")
verbose_name_plural = _("quiz times")

View File

@@ -0,0 +1,39 @@
import random
import string
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta
from ..helpers import generate_season_code
class Season(models.Model):
name = models.CharField(max_length=64, verbose_name=_("name"))
active_quiz = models.ForeignKey(
"Quiz",
on_delete=models.SET_NULL,
null=True,
blank=True,
default=None,
verbose_name=_("active quiz"),
related_name="+",
)
season_code = models.CharField(
max_length=5, default=generate_season_code, verbose_name=_("season code")
)
preregister_candidates = models.BooleanField(
default=True, verbose_name=_("preregister candidates")
)
def renew_season_code(self) -> str:
self.season_code = generate_season_code()
self.save()
return self.season_code
def __str__(self) -> str:
return self.name
class Meta(TypedModelMeta):
verbose_name = _("season")
verbose_name_plural = _("seasons")

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View File

@@ -0,0 +1,33 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{# <script src="https://cdn.tailwindcss.com"></script>#}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<title>{% block title %}{% endblock %}</title>
<style>
html, body {
height: 100%;
background-image: url("{% static "quiz/background.png" %}");
background-position: center center;
background-repeat: no-repeat;
background-color: black;
display: grid;
align-items: center;
justify-self: center;
color: white;
}
</style>
</head>
<body>
<div class="container">
{% block body %}{% endblock %}
</div>
{% block script %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,9 @@
{% extends "quiz/base.html" %}
{% load crispy_forms_tags %}
{% block body %}
<p>{{ season.name }} ({{ season.season_code }})</p>
{% crispy form %}
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends 'quiz/base.html' %}
{% load i18n %}
{% block body %}
<h2>{{ question.question }}</h2>
<form method="post">
{% csrf_token %}
{% for answer in question.answers.all %}
<div><button class="btn btn-outline-success" type="submit" name="answer" value="{{ answer.id }}">{{ answer.text }}</button></div>
{% empty %}
{% translate "Weirdly enough this question has no answers..." %}
{% endfor %}
</form>
{% endblock %}

View File

@@ -0,0 +1,7 @@
{% extends 'quiz/base.html' %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block title %}{% translate "Tijd voor de test" %}{% endblock %}
{% block body %}
{% crispy form %}
{% endblock %}

3
tvdt/quiz/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

14
tvdt/quiz/urls.py Normal file
View File

@@ -0,0 +1,14 @@
from django.urls import path, register_converter
from .converters import CandidateConverter, SeasonCodeConverter
from .views import SelectSeasonView
from .views.questionview import QuestionView
from .views.enternameview import EnterNameView
register_converter(SeasonCodeConverter, "season")
register_converter(CandidateConverter, "candidate")
urlpatterns = [
path("", SelectSeasonView.as_view(), name="home"),
path("<season:season>/", EnterNameView.as_view(), name="quiz"),
path("<candidate:candidate>/", QuestionView.as_view(), name="question"),
]

View File

@@ -0,0 +1 @@
from .selectseasonview import SelectSeasonView

View File

@@ -0,0 +1,51 @@
from crispy_forms.helper import FormHelper
from django import forms
from django.http import Http404
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views import View
from ..models import Season, Candidate
class EnterNameForm(forms.Form):
name = forms.CharField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
class EnterNameView(View):
template_name = "quiz/enter_name.html"
forms_class = EnterNameForm
def get(self, request, season: Season, *args, **kwargs):
if season.active_quiz == None:
raise Http404("No quiz active")
return render(
request,
self.template_name,
{"form": self.forms_class(), "season": season},
)
def post(self, request, season: Season, *args, **kwargs):
name = request.POST.get("name")
if season.preregister_candidates:
try:
candidate = Candidate.objects.get(season=season, name=name)
except Candidate.DoesNotExist:
raise Http404("Candidate not found")
else:
candidate, created = Candidate.objects.get_or_create(
season=season, name=name
)
return redirect(
reverse(
"question",
kwargs={"candidate": candidate},
)
)

View File

@@ -0,0 +1,44 @@
from django.contrib import messages
from django.core.exceptions import BadRequest
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import render
from django.utils.translation import gettext as _
from django.views import View
from ..models import Candidate, Answer, GivenAnswer
from ..models.question import NoActiveTestForSeason
class QuestionView(View):
template_name = "quiz/question.html"
def get(
self, request: HttpRequest, candidate: Candidate, *args, **kwargs
) -> HttpResponse:
try:
question = candidate.get_next_question(candidate)
except NoActiveTestForSeason:
messages.error(request, _("No active Quiz for season"))
raise Http404("No active Quiz for seaon")
return render(
request,
"quiz/question.html",
{"candidate": candidate, "question": question},
)
def post(self, request: HttpRequest, candidate: Candidate, *args, **kwargs):
answer_id = request.POST.get("answer")
if answer_id == None:
raise BadRequest
try:
answer = Answer.objects.get(id=answer_id)
except Answer.DoesNotExist:
raise BadRequest
GivenAnswer.objects.create(
candidate=candidate, question=answer.question, answer=answer
)
return self.get(request, candidate, args, kwargs)

View File

@@ -0,0 +1,29 @@
from crispy_forms.helper import FormHelper
from django.http import Http404
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic.edit import FormView
from django import forms
from ..models import Season
class SelectSeasonForm(forms.Form):
code = forms.CharField(max_length=5)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
class SelectSeasonView(FormView):
form_class = SelectSeasonForm
template_name = "quiz/select_season.html"
def form_valid(self, form):
try:
season = Season.objects.get(season_code=form.cleaned_data["code"].upper())
except Season.DoesNotExist:
raise Http404("Season does not exist")
return redirect(reverse("quiz", kwargs={"season": season}))