This commit is contained in:
2024-12-11 23:22:09 +01:00
parent 4b86b33872
commit 9b7944c14d
53 changed files with 2054 additions and 249 deletions

View File

6
tvdt/backoffice/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class BackofficeConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "backoffice"

View File

@@ -0,0 +1,14 @@
from quiz.models import Quiz
class QuizConverter:
regex = r"\d+"
def to_python(self, value: str) -> Quiz:
try:
return Quiz.objects.get(id=value)
except Quiz.DoesNotExist:
raise ValueError
def to_url(self, value: Quiz | int) -> str:
return str(value.id) if isinstance(value, Quiz) else value

View File

@@ -0,0 +1,36 @@
{% load i18n %}
{% load static %}
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<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>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<title>
{% block title %}
{% translate "Tijd voor de test" %}
{% endblock title %}
</title>
</head>
<body>
{% block nav %}
{% include "backoffice/nav.html" %}
{% endblock nav %}
<main>
<div class="container">
{% include "messages.html" %}
{% block body %}
{% endblock body %}
</div>
</main>
</body>
{% block script %}
{% endblock script %}
</html>

View File

@@ -0,0 +1,45 @@
{% extends "backoffice/base.html" %}
{% load i18n %}
{% block body %}
<h2>{% translate "Your Seasons" %}</h2>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">{% translate "Name" %}</th>
<th scope="col">{% translate "Active Quiz" %}</th>
<th scope="col">{% translate "Season Code" %}</th>
<th scope="col">{% translate "Preregister?" %}</th>
<th scope="col">{% translate "Manage" %}</th>
</tr>
</thead>
<tbody>
{% for season in seasons %}
<tr class="align-middle">
<td>{{ season.name }}</td>
<td>
{% if season.active_quiz %}
{{ season.active_quiz.name }}
{% else %}
{% translate "No active quiz" %}
{% endif %}
</td>
<td>
<a {% if season.active_quiz %}href="{% url "enter_name" season %}"{% else %}class="disabled" {% endif %}>{{ season.season_code }}</a>
</td>
<td>
<input class="form-check-input"
type="checkbox"
disabled
{% if season.preregister_candidates %}checked{% endif %}
aria-label="Preregister Enabled">
</td>
<td>
<a href="{% url "backoffice:season" season %}">{% translate "Manage" %}</a>
</td>
</tr>
{% empty %}
EMPTY
{% endfor %}
</tbody>
</table>
{% endblock body %}

View File

@@ -0,0 +1,51 @@
{% load i18n %}
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Tijd voor de test</a>
<button class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
{% url "backoffice:index" as expected %}
<a class="nav-link{% if expected == request.path %} active{% endif %}"
href="{{ expected }}">Seizoenen</a>
</li>
</ul>
<ul class="navbar-nav ml-auto mb-e mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="{% url 'admin:index' %}">Django Admin</a>
</li>
<li class="nav-item py-2 py-lg-1 col-12 col-lg-auto">
<div class="vr d-none d-lg-flex h-100 mx-lg-2 text-white"></div>
<hr class="d-lg-none my-2 text-white-50">
</li>
<li>
<form class="d-flex" action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<select name="language" class="form-select me-2">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"
{% if language.code == LANGUAGE_CODE %}selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<button class="btn btn-outline-secondary" type="submit">Go</button>
</form>
</li>
</ul>
</div>
</div>
</nav>

View File

@@ -0,0 +1,92 @@
{% extends "backoffice/base.html" %}
{% load i18n %}
{% block body %}
<p>
<h2>{% translate "Quiz" %}: {{ quiz.season.name }} - {{ quiz.name }}</h2>
</p>
<div id="questions">
<p>
<h4>{% translate "Questions" %}</h4>
</p>
<div class="accordion">
{% for question in quiz.questions.all %}
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#question-{{ forloop.counter0 }}"
aria-controls="question-{{ forloop.counter0 }}">
{% with question_error=question.errors %}
{% if question_error %}
<span data-bs-toggle="tooltip"
title="{{ question_error }}"
class="badge text-bg-danger rounded-pill me-2">!</span>
{% endif %}
{% endwith %}
{{ forloop.counter }}. {{ question.question }}
</button>
</h2>
<div id="question-{{ forloop.counter0 }}"
class="accordion-collapse collapse">
<div class="accordion-body">
{% for answer in question.answers.all %}
<li {% if answer.is_right_answer %}class="text-decoration-underline"{% endif %}>{{ answer.text }}</li>
{% empty %}
{% translate "There are no answers for this question" %}
{% endfor %}
</div>
</div>
</div>
{% empty %}
EMPTY
{% endfor %}
</div>
</div>
<div class="scores">
<p>
<h4>{% translate "Score" %}</h4>
</p>
<div class="btn-toolbar" role="toolbar">
<div class="btn-group btn-group-lg me-2">
<a class="btn btn-primary">{% translate "Start Elimination" %}</a>
</div>
<div class="btn-group btn-group-lg">
<a class="btn btn-secondary">{% translate "Prepare Custom Elimination" %}</a>
<a class="btn btn-secondary">{% translate "Load Prepared Elimination" %}</a>
</div>
</div>
<p>{% translate "Number of dropouts:" %} {{ quiz.dropouts }}</p>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">{% translate "Candidate" %}</th>
<th scope="col">{% translate "Correct Answers" %}</th>
<th scope="col">{% translate "Corrections" %}</th>
<th scope="col">{% translate "Score" %}</th>
<th scope="col">{% translate "Time" %}</th>
</tr>
</thead>
<tbody>
{% with result=quiz.get_score %}
{% for candidate in result %}
<tr class="table-{% if forloop.revcounter > quiz.dropouts %}success{% else %}danger{% endif %}">
<td>{{ candidate.name }}</td>
<td>{{ candidate.correct }}</td>
<td>{{ candidate.corrections }}</td>
<td>{{ candidate.score }}</td>
<td>{{ candidate.time }}</td>
</tr>
{% empty %}
{% endfor %}
</tbody>
</table>
{% endwith %}
</div>
{% endblock body %}
{% block script %}
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
</script>
{% endblock script %}

View File

@@ -0,0 +1,25 @@
{% extends "backoffice/base.html" %}
{% load i18n %}
{% block body %}
<p>
<h2>{% translate "Season" %}: {{ season.name }}</h2>
</p>
<div class="row">
<div class="col-md-6 col-12">
<h4>{% translate "Quizzes" %}</h4>
<div class="list-group">
{% for quiz in season.quizzes.all %}
<a class="list-group-item list-group-item-action{% if season.active_quiz == quiz %} active{% endif %}"
href="{% url "backoffice:quiz" quiz %}">{{ quiz.name }}</a>
{% empty %}
{% endfor %}
</div>
</div>
<div class="col-md-6 col-12">
<h4>{% translate "Candidates" %}</h4>
<ul>
{% for candidate in season.candidates.all %}<li>{{ candidate.name }}</li>{% endfor %}
</ul>
</div>
</div>
{% endblock body %}

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

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

21
tvdt/backoffice/urls.py Normal file
View File

@@ -0,0 +1,21 @@
from django.contrib.auth.decorators import login_required
from django.urls import path, register_converter
from tvdt.converters import SeasonCodeConverter
from .converters import QuizConverter
from .views import BackofficeIndexView, QuizView, SeasonView
register_converter(SeasonCodeConverter, "season")
register_converter(QuizConverter, "quiz")
app_name = "backoffice"
urlpatterns = [
path("", login_required(BackofficeIndexView.as_view()), name="index"),
path(
"<season:season>/",
login_required(SeasonView.as_view()),
name="season",
),
path("<quiz:quiz>/", login_required(QuizView.as_view()), name="quiz"),
]

View File

@@ -0,0 +1,3 @@
from .home import BackofficeIndexView
from .quiz import QuizView
from .season import SeasonView

View File

@@ -0,0 +1,10 @@
from django.http import HttpRequest
from django.views.generic import TemplateView
class BackofficeIndexView(TemplateView):
template_name = "backoffice/index.html"
def get(self, request: HttpRequest, *args, **kwargs):
seasons = request.user.seasons.all()
return self.render_to_response({"seasons": seasons})

View File

@@ -0,0 +1,13 @@
from django.http import HttpRequest
from django.views import View
from django.views.generic.base import TemplateResponseMixin
from quiz.models import Quiz
class QuizView(View, TemplateResponseMixin):
template_name = "backoffice/quiz.html"
def get(self, request: HttpRequest, quiz: Quiz, *args, **kwargs):
return self.render_to_response({"quiz": quiz})

View File

@@ -0,0 +1,12 @@
from django.http import HttpRequest
from django.views import View
from django.views.generic.base import TemplateResponseMixin
from quiz.models import Season
class SeasonView(View, TemplateResponseMixin):
template_name = "backoffice/season.html"
def get(self, request: HttpRequest, season: Season, *args, **kwargs):
return self.render_to_response({"season": season})