diff --git a/classquiz/db/models.py b/classquiz/db/models.py
index dbc41ba..829f867 100644
--- a/classquiz/db/models.py
+++ b/classquiz/db/models.py
@@ -85,24 +85,33 @@ class RangeQuizAnswer(BaseModel):
max_correct: int
+class VotingQuizAnswer(BaseModel):
+ answer: str
+ image: str | None = None
+ color: str | None
+
+
class QuizQuestionType(str, Enum):
ABCD = "ABCD"
RANGE = "RANGE"
+ VOTING = "VOTING"
class QuizQuestion(BaseModel):
question: str
time: str # in Secs
type: None | QuizQuestionType = QuizQuestionType.ABCD
- answers: list[ABCDQuizAnswer] | RangeQuizAnswer
+ answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[VotingQuizAnswer]
image: str | None = None
@validator("answers")
def answers_not_none_if_abcd_type(cls, v, values):
- if values["type"] == QuizQuestionType.ABCD and len(v) == 0:
+ if values["type"] == QuizQuestionType.ABCD and type(v[0]) != ABCDQuizAnswer:
raise ValueError("Answers can't be none if type is ABCD")
if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswer:
raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE")
+ if values["type"] == QuizQuestionType.VOTING and type(v[0]) != VotingQuizAnswer:
+ raise ValueError("Answer must be from type VotingQuizAnswer if type is VOTING")
return v
diff --git a/classquiz/routers/live.py b/classquiz/routers/live.py
index 9196304..e140bd7 100644
--- a/classquiz/routers/live.py
+++ b/classquiz/routers/live.py
@@ -17,6 +17,7 @@ from classquiz.db.models import (
RangeQuizAnswer,
ABCDQuizAnswer,
QuizQuestionType,
+ VotingQuizAnswer,
)
from classquiz.auth import check_api_key
from classquiz.socket_server import ReturnQuestion, sio
@@ -35,7 +36,7 @@ class _ABCDQuizAnswer(ABCDQuizAnswer):
class _QuizQuestion(QuizQuestion):
- answers: list[ABCDQuizAnswer] | RangeQuizAnswer
+ answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[VotingQuizAnswer]
class _GetLivePlayGame(PlayGame):
diff --git a/classquiz/socket_server/__init__.py b/classquiz/socket_server/__init__.py
index 537f2f7..29e3bfd 100644
--- a/classquiz/socket_server/__init__.py
+++ b/classquiz/socket_server/__init__.py
@@ -11,7 +11,7 @@ import socketio
from cryptography.fernet import Fernet
from classquiz.config import redis, settings
-from classquiz.db.models import PlayGame, QuizQuestionType, GameSession, GamePlayer, QuizQuestion
+from classquiz.db.models import PlayGame, QuizQuestionType, GameSession, GamePlayer, QuizQuestion, VotingQuizAnswer
from pydantic import BaseModel, ValidationError, validator
from datetime import datetime
@@ -113,8 +113,10 @@ async def join_game(sid: str, data: dict):
{"username": data.username, "sid": sid},
room=redis_res.admin,
)
- lol = fernet.encrypt(datetime.now().isoformat().encode("utf-8")).decode("utf-8")
- await sio.emit("time_sync", lol, room=sid)
+ # +++ Time-Sync +++
+ encrypted_datetime = fernet.encrypt(datetime.now().isoformat().encode("utf-8")).decode("utf-8")
+ await sio.emit("time_sync", encrypted_datetime, room=sid)
+ # --- Time-Sync ---
sio.enter_room(sid, data.game_pin)
@@ -183,14 +185,16 @@ class RangeQuizAnswerWithoutSolution(BaseModel):
class ReturnQuestion(QuizQuestion):
- answers: list[ABCDQuizAnswerWithoutSolution] | RangeQuizAnswerWithoutSolution
+ answers: list[ABCDQuizAnswerWithoutSolution] | RangeQuizAnswerWithoutSolution | list[VotingQuizAnswer]
@validator("answers")
def answers_not_none_if_abcd_type(cls, v, values):
- if values["type"] == QuizQuestionType.ABCD and len(v) == 0:
+ if values["type"] == QuizQuestionType.ABCD and type(v[0]) != ABCDQuizAnswerWithoutSolution:
raise ValueError("Answers can't be none if type is ABCD")
if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswerWithoutSolution:
raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE")
+ if values["type"] == QuizQuestionType.VOTING and type(v[0]) != VotingQuizAnswer:
+ print("Answer must be from type VotingQuizAnswer if type is VOTING")
return v
@@ -204,13 +208,17 @@ async def set_question_number(sid, data: str):
game_data.current_question = int(float(data))
await redis.set(f"game:{session['game_pin']}", game_data.json())
await redis.set(f"game:{session['game_pin']}:current_time", datetime.now().isoformat())
+ # print(game_data.dict(include={"questions"})["questions"][int(float(data))])
+ temp_return = game_data.dict(include={"questions"})["questions"][int(float(data))]
+ if game_data.questions[int(float(data))].type == QuizQuestionType.VOTING:
+ for i in range(len(temp_return["answers"])):
+ temp_return["answers"][i] = VotingQuizAnswer(**temp_return["answers"][i])
+ print(temp_return)
await sio.emit(
"set_question_number",
{
"question_index": int(float(data)),
- "question": ReturnQuestion(
- **game_data.dict(include={"questions"})["questions"][int(float(data))]
- ).dict(),
+ "question": ReturnQuestion(**temp_return).dict(),
},
room=game_pin,
)
@@ -258,6 +266,8 @@ async def submit_answer(sid: str, data: dict):
<= game_data.questions[int(float(data.question_index))].answers.max_correct
):
answer_right = True
+ elif game_data.questions[int(float(data.question_index))].type == QuizQuestionType.VOTING:
+ answer_right = False
else:
raise NotImplementedError
latency = int(float((await sio.get_session(sid))["ping"]))
@@ -317,7 +327,6 @@ async def submit_answer(sid: str, data: dict):
async def get_final_results(sid: str, _data: dict):
session: dict = await sio.get_session(sid)
game_data = PlayGame(**json.loads(await redis.get(f"game:{session['game_pin']}")))
- results = {}
if not session["admin"]:
return
results = await generate_final_results(game_data, session["game_pin"])
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index fcaab13..2e68f12 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -77,7 +77,7 @@ devDependencies:
'@sentry/tracing': 7.11.1
'@sveltejs/adapter-auto': 1.0.0-next.80
'@sveltejs/adapter-node': 1.0.0-next.86
- '@sveltejs/kit': 1.0.0-next.504_svelte@3.49.0+vite@3.1.2
+ '@sveltejs/kit': 1.0.0-next.508_svelte@3.49.0+vite@3.1.2
'@tailwindcss/typography': 0.5.4_tailwindcss@3.1.8
'@types/canvas-confetti': 1.4.3
'@types/cookie': 0.5.1
@@ -579,10 +579,10 @@ packages:
- supports-color
dev: true
- /@sveltejs/kit/1.0.0-next.504_svelte@3.49.0+vite@3.1.2:
+ /@sveltejs/kit/1.0.0-next.508_svelte@3.49.0+vite@3.1.2:
resolution:
{
- integrity: sha512-KrhlSHT3aCVnhRgUoN6aGIjIw3nWEdNwfoZcE1x65F5D7Ju/K9D8dQwyal9v0aBAZyN9nFuXxsYGaMLE9sppfw==
+ integrity: sha512-qUnuhuL82meE0lSwrsS/FIvtDoE83Au4SxVLCim3FzoCnJc7bFZ9vYRbRUYxAjfYKRMjKVUrp9iHFObn9LmnUg==
}
engines: { node: '>=16.14' }
hasBin: true
@@ -598,13 +598,12 @@ packages:
kleur: 4.1.5
magic-string: 0.26.3
mime: 3.0.0
- node-fetch: 3.2.10
sade: 1.8.1
set-cookie-parser: 2.5.1
sirv: 2.0.2
svelte: 3.49.0
tiny-glob: 0.2.9
- undici: 5.9.1
+ undici: 5.11.0
vite: 3.1.2_sass@1.54.4
transitivePeerDependencies:
- diff-match-patch
@@ -1398,6 +1397,16 @@ packages:
}
dev: true
+ /busboy/1.6.0:
+ resolution:
+ {
+ integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
+ }
+ engines: { node: '>=10.16.0' }
+ dependencies:
+ streamsearch: 1.1.0
+ dev: true
+
/callsites/3.1.0:
resolution:
{
@@ -1717,14 +1726,6 @@ packages:
css-tree: 1.1.3
dev: true
- /data-uri-to-buffer/4.0.0:
- resolution:
- {
- integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
- }
- engines: { node: '>= 12' }
- dev: true
-
/debug/4.3.4:
resolution:
{
@@ -2503,17 +2504,6 @@ packages:
svelte: 3.49.0
dev: true
- /fetch-blob/3.2.0:
- resolution:
- {
- integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
- }
- engines: { node: ^12.20 || >= 14.13 }
- dependencies:
- node-domexception: 1.0.0
- web-streams-polyfill: 3.2.1
- dev: true
-
/file-entry-cache/6.0.1:
resolution:
{
@@ -2570,16 +2560,6 @@ packages:
}
dev: true
- /formdata-polyfill/4.0.10:
- resolution:
- {
- integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
- }
- engines: { node: '>=12.20.0' }
- dependencies:
- fetch-blob: 3.2.0
- dev: true
-
/fraction.js/4.2.0:
resolution:
{
@@ -3358,14 +3338,6 @@ packages:
}
dev: true
- /node-domexception/1.0.0:
- resolution:
- {
- integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
- }
- engines: { node: '>=10.5.0' }
- dev: true
-
/node-fetch/2.6.7:
resolution:
{
@@ -3381,18 +3353,6 @@ packages:
whatwg-url: 5.0.0
dev: true
- /node-fetch/3.2.10:
- resolution:
- {
- integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==
- }
- engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
- dependencies:
- data-uri-to-buffer: 4.0.0
- fetch-blob: 3.2.0
- formdata-polyfill: 4.0.10
- dev: true
-
/node-gyp-build/4.5.0:
resolution:
{
@@ -4540,6 +4500,14 @@ packages:
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
dev: true
+ /streamsearch/1.1.0:
+ resolution:
+ {
+ integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+ }
+ engines: { node: '>=10.0.0' }
+ dev: true
+
/string-width/4.2.3:
resolution:
{
@@ -4960,12 +4928,14 @@ packages:
}
dev: true
- /undici/5.9.1:
+ /undici/5.11.0:
resolution:
{
- integrity: sha512-6fB3a+SNnWEm4CJbgo0/CWR8RGcOCQP68SF4X0mxtYTq2VNN8T88NYrWVBAeSX+zb7bny2dx2iYhP3XHi00omg==
+ integrity: sha512-oWjWJHzFet0Ow4YZBkyiJwiK5vWqEYoH7BINzJAJOLedZ++JpAlCbUktW2GQ2DS2FpKmxD/JMtWUUWl1BtghGw==
}
engines: { node: '>=12.18' }
+ dependencies:
+ busboy: 1.6.0
dev: true
/update-browserslist-db/1.0.5_browserslist@4.21.3:
@@ -5057,14 +5027,6 @@ packages:
pbf: 3.2.1
dev: true
- /web-streams-polyfill/3.2.1:
- resolution:
- {
- integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
- }
- engines: { node: '>= 8' }
- dev: true
-
/webidl-conversions/3.0.1:
resolution:
{
diff --git a/frontend/src/lib/admin.svelte b/frontend/src/lib/admin.svelte
index 0662483..8cf5b91 100644
--- a/frontend/src/lib/admin.svelte
+++ b/frontend/src/lib/admin.svelte
@@ -114,8 +114,8 @@
{#if selected_question + 1 === quiz_data.questions.length && timer_res === '0' && question_results !== null}
{#if JSON.stringify(final_results) === JSON.stringify([null])}
+ >Get final results
+
{/if}
{:else if timer_res === '0' || selected_question === -1}
{#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1}
@@ -129,13 +129,13 @@
{/if}
{#if question_results === null && selected_question !== -1}
+ >Show results
+
{/if}
{:else if selected_question !== -1}
+ >Stop time and show solutions
+
{:else}
{#await import('svelte-range-slider-pips')}
- {$t('editor_page.right_click_to_delete')} Unknown Question Type (shouldn't happen) {answer} No correct answers, since this is a poll-question {answer}