Resolve "Quiztyp - Reihenfolge" #271
@@ -170,3 +170,12 @@ Die Konfiguration des Projekts befindet sich im `config/qivip_config.json`.
|
||||
| Variable | Bedeutung | Optionen |
|
||||
| --- | --- | --- |
|
||||
| ENABLE_RATING_SYSTEM | Soll das Bewertungssystem aktiviert werden? | true [Standard], false |
|
||||
|
||||
|
||||
## Lizenzen für node_modules
|
||||
1. Installation: npm install license-checker --save-dev
|
||||
|
||||
npx license-checker --markdown > THIRD_PARTY_LICENSES.md
|
||||
## für Python Packete:
|
||||
|
||||
pip-licenses --format=markdown > THIRD_PARTY_LICENSES_PYTHON.md
|
||||
BIN
django/THIRD_PARTY_LICENSES.md
Normal file
BIN
django/THIRD_PARTY_LICENSES.md
Normal file
Binary file not shown.
BIN
django/THIRD_PARTY_LICENSES_PYTHON.md
Normal file
BIN
django/THIRD_PARTY_LICENSES_PYTHON.md
Normal file
Binary file not shown.
@@ -20,7 +20,7 @@ urlpatterns = [
|
||||
path('detail/copy/<int:pk>/', views.copy_quiz, name='copy_quiz'),
|
||||
path('favorite_quiz/<int:pk>/', views.favorite_quiz, name='favorite_quiz'),
|
||||
path("quiz-names-json/", views.quiz_names_json, name="quiz_names_json"),
|
||||
path('quiz/<int:pk>/pdf/', views.pdf_solution, name='quiz_solution'),
|
||||
path('quiz/<int:pk>/pdf/task', views.pdf_task, name='quiz_task'),
|
||||
#path('quiz/<int:pk>/pdf/', views.pdf_solution, name='quiz_solution'),
|
||||
#path('quiz/<int:pk>/pdf/task', views.pdf_task, name='quiz_task'),
|
||||
|
||||
]
|
||||
|
||||
@@ -40,7 +40,7 @@ from PIL import Image as PILImage, ImageOps
|
||||
from django.conf import settings
|
||||
import os
|
||||
from django.utils.html import escape
|
||||
|
||||
"""
|
||||
def pdf_solution(request, pk):
|
||||
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
||||
|
||||
@@ -337,7 +337,7 @@ def pdf_task(request, pk):
|
||||
story.append(Paragraph(link_html, styles['Normal']))
|
||||
doc.build(story)
|
||||
return response
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -860,7 +860,7 @@ def new_question(request):
|
||||
messages.error(request, 'Quiz nicht gefunden oder keine Berechtigung.')
|
||||
return redirect('library:overview_quiz')
|
||||
|
||||
if question_type not in ['multiple_choice', 'true_false','input']:
|
||||
if question_type not in ['multiple_choice', 'true_false','input','order']:
|
||||
base_url = reverse('library:new_question')
|
||||
return redirect(f'{base_url}?type=multiple_choice&quiz_id={quiz_id}')
|
||||
|
||||
@@ -913,6 +913,34 @@ def new_question(request):
|
||||
'options': options
|
||||
}
|
||||
|
||||
elif question_type == 'order' :
|
||||
options = []
|
||||
i = 1
|
||||
# Limit to maximum 6 options
|
||||
while request.POST.get(f'option_{i}') and i <= 6:
|
||||
is_correct = request.POST.get(f'correct_{i}') == '1'
|
||||
options.append({
|
||||
'order': i,
|
||||
'value': request.POST.get(f'option_{i}'),
|
||||
|
||||
})
|
||||
i += 1
|
||||
|
||||
|
||||
# Validate minimum 2 options
|
||||
if len(options) < 2:
|
||||
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': question_text,
|
||||
'options': options
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
question = QivipQuestion()
|
||||
question.data = json.dumps(json_data)
|
||||
question.quiz_id = quiz
|
||||
@@ -967,6 +995,17 @@ def new_question(request):
|
||||
}
|
||||
standard_time_per_question = 30
|
||||
|
||||
elif question_type == 'order':
|
||||
question_data = {
|
||||
'type': question_type,
|
||||
'question': '',
|
||||
'options': [
|
||||
{'value': ''},
|
||||
{'value': ''}
|
||||
]
|
||||
}
|
||||
standard_time_per_question = 30
|
||||
|
||||
template_name = f'library/question/question_{question_type}.html'
|
||||
context = {
|
||||
'question': question_data,
|
||||
@@ -1092,6 +1131,32 @@ def edit_question(request, pk):
|
||||
'options': options
|
||||
}
|
||||
|
||||
elif question_type == 'order':
|
||||
options = []
|
||||
i = 1
|
||||
# Limit to maximum 6 options
|
||||
while request.POST.get(f'option_{i}') and i <= 6:
|
||||
options.append({
|
||||
'order': i,
|
||||
'value': request.POST.get(f'option_{i}')
|
||||
})
|
||||
i += 1
|
||||
|
||||
# Validate minimum 2 options
|
||||
if len(options) < 2:
|
||||
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
# Validate maximum 6 options
|
||||
if len(options) > 6:
|
||||
messages.error(request, 'Maximal 6 Optionen sind erlaubt.')
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': question_text,
|
||||
'options': options
|
||||
}
|
||||
question.data = json.dumps(json_data)
|
||||
question.time_per_question = request.POST.get('time_per_question', '30')
|
||||
try:
|
||||
|
||||
573
django/package-lock.json
generated
573
django/package-lock.json
generated
@@ -10,7 +10,11 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@tailwindcss/cli": "^4.1.5",
|
||||
"sortablejs": "^1.15.6",
|
||||
"tailwindcss": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"license-checker": "^25.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
@@ -537,6 +541,55 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/array-find-index": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
|
||||
"integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
@@ -548,6 +601,60 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/debuglog": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
|
||||
"integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
@@ -559,6 +666,16 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
@@ -571,6 +688,15 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -582,11 +708,106 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -622,6 +843,33 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/license-checker": {
|
||||
"version": "25.0.1",
|
||||
"resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz",
|
||||
"integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^2.4.1",
|
||||
"debug": "^3.1.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nopt": "^4.0.1",
|
||||
"read-installed": "~4.0.3",
|
||||
"semver": "^5.5.0",
|
||||
"spdx-correct": "^3.0.0",
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-satisfies": "^4.0.0",
|
||||
"treeify": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"license-checker": "bin/license-checker"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
||||
@@ -859,6 +1107,39 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
@@ -867,11 +1148,101 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hosted-git-info": "^2.1.4",
|
||||
"resolve": "^1.10.0",
|
||||
"semver": "2 || 3 || 4 || 5",
|
||||
"validate-npm-package-license": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||
"integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/osenv": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
|
||||
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"os-homedir": "^1.0.0",
|
||||
"os-tmpdir": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -888,6 +1259,177 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/read-installed": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz",
|
||||
"integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debuglog": "^1.0.1",
|
||||
"read-package-json": "^2.0.0",
|
||||
"readdir-scoped-modules": "^1.0.0",
|
||||
"semver": "2 || 3 || 4 || 5",
|
||||
"slide": "~1.1.3",
|
||||
"util-extend": "^1.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/read-package-json": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz",
|
||||
"integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==",
|
||||
"deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.1",
|
||||
"json-parse-even-better-errors": "^2.3.0",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"npm-normalize-package-bin": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdir-scoped-modules": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
|
||||
"integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
|
||||
"deprecated": "This functionality has been moved to @npmcli/fs",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debuglog": "^1.0.1",
|
||||
"dezalgo": "^1.0.0",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"once": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/slide": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
|
||||
"integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
|
||||
"integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A=="
|
||||
},
|
||||
"node_modules/spdx-compare": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz",
|
||||
"integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-find-index": "^1.0.2",
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-ranges": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-correct": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
|
||||
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-exceptions": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
|
||||
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"spdx-exceptions": "^2.1.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-license-ids": {
|
||||
"version": "3.0.21",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
|
||||
"integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdx-ranges": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz",
|
||||
"integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdx-satisfies": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz",
|
||||
"integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"spdx-compare": "^1.0.0",
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-ranges": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz",
|
||||
@@ -911,6 +1453,37 @@
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/treeify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
|
||||
"integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/util-extend": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
|
||||
"integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"spdx-correct": "^3.0.0",
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@tailwindcss/cli": "^4.1.5",
|
||||
"sortablejs": "^1.15.6",
|
||||
"tailwindcss": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"license-checker": "^25.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,6 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'answer_stats',
|
||||
'stats': stats,
|
||||
'answer': answer,
|
||||
}))
|
||||
|
||||
elif message_type == 'update_participants':
|
||||
@@ -175,7 +174,7 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
return False
|
||||
|
||||
@database_sync_to_async
|
||||
def save_answer(self, participant_id, answer_index, time_remaining):
|
||||
def save_answer(self, participant_id, answer, time_remaining):
|
||||
try:
|
||||
participant = QuizGameParticipant.objects.get(
|
||||
quiz_game__join_code=self.join_code,
|
||||
@@ -184,58 +183,66 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
quiz_game = participant.quiz_game
|
||||
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
|
||||
|
||||
# Check if answer is correct
|
||||
is_correct = False
|
||||
question_data = json.loads(current_question.data)
|
||||
print(answer_index)
|
||||
try:
|
||||
if answer_index >= 0 : # -1 means timeout
|
||||
is_correct = question_data['options'][answer_index]['is_correct']
|
||||
print(is_correct)
|
||||
except:
|
||||
user_input = str(answer_index).strip().lower().replace(" ", "")
|
||||
question_type = question_data.get("type")
|
||||
|
||||
is_correct = False
|
||||
answer_index = None
|
||||
answers_order = None
|
||||
answer_text = None
|
||||
|
||||
if question_type in ["multiple_choice", "true_false"]:
|
||||
# answer ist hier eine Integer-Antwort (Index)
|
||||
answer_index = int(answer)
|
||||
if answer_index >= 0:
|
||||
is_correct = question_data['options'][answer_index].get('is_correct', False)
|
||||
|
||||
elif question_type == "input":
|
||||
user_input = str(answer).strip().lower().replace(" ", "")
|
||||
correct_value = str(question_data['options'][0].get('value', '')).strip().lower().replace(" ", "")
|
||||
print("Richtig",correct_value)
|
||||
print(user_input)
|
||||
if user_input == correct_value:
|
||||
is_correct=True
|
||||
answer_index=int(0)
|
||||
print(is_correct)
|
||||
else:
|
||||
answer_index=int(1)
|
||||
is_correct = (user_input == correct_value)
|
||||
answer_text = user_input
|
||||
answer_index = 0 if is_correct else 1
|
||||
|
||||
elif question_type == "order":
|
||||
# answer ist hier eine Liste von Strings mit der Reihenfolge
|
||||
answers_order = answer
|
||||
correct_sequence = sorted(question_data["options"], key=lambda x: x["order"])
|
||||
correct_values = [opt["value"] for opt in correct_sequence]
|
||||
is_correct = (answers_order == correct_values)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Calculate score based on correctness and time
|
||||
# Score berechnen
|
||||
score = 0
|
||||
if is_correct:
|
||||
base_score = 1000
|
||||
time_factor = time_remaining / int(current_question.time_per_question)/int(1000)
|
||||
time_factor = time_remaining / int(current_question.time_per_question) / 1000
|
||||
score = int(base_score * (0.5 + 0.5 * time_factor))
|
||||
|
||||
# Update or create answer in QuizAnswer table
|
||||
# Antwort speichern oder aktualisieren
|
||||
answer_obj, created = QuizAnswer.objects.get_or_create(
|
||||
participant=participant,
|
||||
question_index=quiz_game.current_question_index,
|
||||
defaults={
|
||||
'answer_index': answer_index,
|
||||
'answers_order': answers_order,
|
||||
'is_correct': is_correct,
|
||||
'score': score,
|
||||
'time_remaining': time_remaining
|
||||
}
|
||||
)
|
||||
|
||||
if not created:
|
||||
answer_obj.answer_index = answer_index
|
||||
answer_obj.answers_order = answers_order
|
||||
answer_obj.is_correct = is_correct
|
||||
answer_obj.score = score
|
||||
answer_obj.time_remaining = time_remaining
|
||||
answer_obj.save()
|
||||
|
||||
# Teilnehmer aktualisieren
|
||||
participant.last_score = score
|
||||
participant.score += score
|
||||
participant.last_answer=answer_index
|
||||
participant.last_answer_correct = is_correct
|
||||
participant.save()
|
||||
|
||||
@@ -243,25 +250,46 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
except (QuizGameParticipant.DoesNotExist, IndexError):
|
||||
return False
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def get_answer_stats(self):
|
||||
try:
|
||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||
|
||||
# Count answers for each option in the current question
|
||||
stats = {}
|
||||
answers = QuizAnswer.objects.filter(
|
||||
participant__quiz_game=quiz_game,
|
||||
question_index=quiz_game.current_question_index
|
||||
)
|
||||
|
||||
for answer in answers:
|
||||
if answer.answer_index >= 0: # Ignore timeouts (-1)
|
||||
stats[answer.answer_index] = stats.get(answer.answer_index, 0) + 1
|
||||
|
||||
|
||||
|
||||
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
|
||||
|
||||
question_data = json.loads(current_question.data)
|
||||
question_type = question_data.get("type")
|
||||
stats = {}
|
||||
|
||||
for single_answer in answers:
|
||||
|
||||
|
||||
if question_type == "order":
|
||||
correct_answer=[str(opt["value"]) for opt in sorted(question_data["options"], key=lambda x: x["order"])]
|
||||
|
||||
if single_answer.answers_order== correct_answer:
|
||||
key = 0 # richtig
|
||||
else:
|
||||
key = 1 # falsch
|
||||
stats[key] = stats.get(key, 0) + 1
|
||||
else:
|
||||
key = str(single_answer.answer_index if single_answer.answer_index is not None else "unanswered")
|
||||
stats[key] = stats.get(key, 0) + 1
|
||||
return stats
|
||||
except QuizGame.DoesNotExist:
|
||||
return {}
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def save_rating(self, participant_id, rating):
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.1.7 on 2025-07-18 13:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0012_quizgameparticipant_last_score'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizanswer',
|
||||
name='answer_text',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quizanswer',
|
||||
name='answers_order',
|
||||
field=models.JSONField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='quizanswer',
|
||||
name='answer_index',
|
||||
field=models.IntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -63,7 +63,9 @@ class QuizGameParticipant(models.Model):
|
||||
class QuizAnswer(models.Model):
|
||||
participant = models.ForeignKey(QuizGameParticipant, on_delete=models.CASCADE, related_name='answers')
|
||||
question_index = models.IntegerField()
|
||||
answer_index = models.IntegerField()
|
||||
answer_index = models.IntegerField(null=True, blank=True) # für MC, TF
|
||||
answer_text = models.TextField(null=True, blank=True) # für Input
|
||||
answers_order = models.JSONField(null=True, blank=True) # NEU für "order"
|
||||
is_correct = models.BooleanField()
|
||||
score = models.IntegerField()
|
||||
time_remaining = models.IntegerField()
|
||||
|
||||
@@ -340,12 +340,27 @@ def waiting_room(request, join_code):
|
||||
def selected_mode(request, join_code):
|
||||
|
||||
|
||||
|
||||
mode = request.GET.get('mode','default')
|
||||
request.session['mode'] = mode
|
||||
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||
quiz = get_object_or_404(QivipQuiz, pk=quiz_game.quiz_id.id)
|
||||
QuizGameParticipant.objects.filter(quiz_game=quiz_game).delete()
|
||||
|
||||
# Hier werden die Fragen gemischt!
|
||||
import random
|
||||
questions = quiz_game.quiz_id.questions.all()
|
||||
for question in questions:
|
||||
try:
|
||||
data = json.loads(question.data)
|
||||
if (data.get('type') == 'order' or data.get('type') == 'multiple_choice') and 'options' in data:
|
||||
|
||||
random.shuffle(data['options'])
|
||||
question.data = json.dumps(data)
|
||||
question.save(update_fields=['data'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
context = {
|
||||
'quiz': quiz,
|
||||
'quiz_game': quiz_game,
|
||||
|
||||
2
django/static/js/sortablejs/Sortable.min.js
vendored
Normal file
2
django/static/js/sortablejs/Sortable.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -97,20 +97,24 @@
|
||||
class=" mt-1 bg-green-100 text-green-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Eingabe
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if quiz.user_id == request.user %}
|
||||
|
||||
|
||||
<a href="{% url 'library:new_question' %}?type=multiple_choice&quiz_id={{ quiz.id }}"
|
||||
class=" mt-1 bg-blue-100 text-blue-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Multiple Choice
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if quiz.user_id == request.user %}
|
||||
|
||||
<a href="{% url 'library:new_question' %}?type=true_false&quiz_id={{ quiz.id }}"
|
||||
class=" mt-1 bg-purple-100 text-purple-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Wahr/Falsch
|
||||
</a>
|
||||
|
||||
<a href="{% url 'library:new_question' %}?type=order&quiz_id={{ quiz.id }}"
|
||||
class=" mt-1 bg-yellow-100 text-yellow-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Reihenfolge
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,8 +135,8 @@
|
||||
<div class="flex-grow">
|
||||
<p class="font-medium">{{ question.data.question }}</p>
|
||||
<div class="mt-1">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium {% if question.data.type == 'multiple_choice' %}bg-blue-100 text-blue-800 {% elif question.data.type == 'input' %} bg-green-100 text-green-800 {% else %}bg-purple-100 text-purple-800{% endif %}">
|
||||
{% if question.data.type == 'multiple_choice' %}Multiple Choice{% elif question.data.type == 'input' %}Eingabe{% else %}Wahr/Falsch{% endif %}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium {% if question.data.type == 'multiple_choice' %}bg-blue-100 text-blue-800 {% elif question.data.type == 'input' %} bg-green-100 text-green-800 {% elif question.data.type == 'true_false' %}bg-purple-100 text-purple-800{% else %}bg-yellow-100 text-yellow-800{% endif %}">
|
||||
{% if question.data.type == 'multiple_choice' %}Multiple Choice{% elif question.data.type == 'input' %}Eingabe{% elif question.data.type == 'true_false' %}Wahr/Falsch{% else %}Reihenfolge{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
@@ -141,7 +145,7 @@
|
||||
{% if question.data.type == 'multiple_choice' %}
|
||||
<div class="w-full gap-4 md:flex justify-between ">
|
||||
<ul class="space-y-1 ">
|
||||
{% for option in question.data.options %}
|
||||
{% for option in question.data.options|dictsort:"order" %}
|
||||
|
||||
|
||||
<li class="flex items-center text-sm ">
|
||||
@@ -164,6 +168,21 @@
|
||||
<img src="{{ question.image.image.url }}" alt="Bild der Frage" class="w-[200px] rounded-lg shadow-md border-blue-600 border-2 rounded-xl shadow-md object-contain mt-4 md:mt-0" />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif question.data.type == 'order' %}
|
||||
<div class="w-full gap-4 md:flex justify-between ">
|
||||
<ul class="space-y-1 ">
|
||||
{% for option in question.data.options|dictsort:"order" %}
|
||||
<li class="flex items-center text-sm">
|
||||
{{ forloop.counter }}.
|
||||
<span class="font-medium text-green-700 dark:text-white">{{ option.value }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if question.image.image.url %}
|
||||
<img src="{{ question.image.image.url }}" alt="Bild der Frage" class="w-[200px] rounded-lg shadow-md border-blue-600 border-2 rounded-xl shadow-md object-contain mt-4 md:mt-0" />
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
{% elif question.data.type == 'input' %}
|
||||
<div class="w-full gap-4 md:flex justify-between ">
|
||||
@@ -253,12 +272,13 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Links müssen wieder ergänzt werden!
|
||||
{% if questions %}
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-start sm:items-center my-6">
|
||||
|
||||
<!-- Aufgabenblatt herunterladen Button -->
|
||||
|
||||
<a href="{% url 'library:quiz_task' pk=quiz.pk %}?show_answers=true"
|
||||
|
||||
<a href=""
|
||||
target="_blank"
|
||||
class=" inline-flex items-center gap-2 px-2 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white shadow-md transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none"
|
||||
@@ -269,8 +289,8 @@
|
||||
als Aufgabenblatt herunterladen (PDF)
|
||||
</a>
|
||||
|
||||
<!-- Lösung herunterladen Button -->
|
||||
<a href="{% url 'library:quiz_solution' pk=quiz.pk %}?show_answers=true"
|
||||
|
||||
<a href=""
|
||||
target="_blank"
|
||||
class="inline-flex items-center gap-2 px-2 py-2 rounded-lg bg-green-600 hover:bg-green-700 text-white shadow-md transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none"
|
||||
@@ -285,6 +305,7 @@
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
-->
|
||||
<div class="flex flex-col max-w-full">
|
||||
<p class="text-gray-600 mt-2 dark:text-white ">Credits: </p>
|
||||
{% if quiz.credits %}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Antwortmöglichkeiten</label>
|
||||
<div id="options-container" class="space-y-3">
|
||||
{% if question and question.data.options %}
|
||||
{% for option in question.data.options %}
|
||||
{% for option in question.data.options|dictsort:"order" %}
|
||||
<div class="flex items-center space-x-3">
|
||||
<textarea name="option_{{ forloop.counter }}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
|
||||
189
django/templates/library/question/question_order.html
Normal file
189
django/templates/library/question/question_order.html
Normal file
@@ -0,0 +1,189 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold dark:text-white">{% if question %}Frage bearbeiten{% else %}Reihenfolge Frage{% endif %}</h1>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||
Zurück zum Quiz
|
||||
</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent dark:text-white">
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Question Text -->
|
||||
<div>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700 dark:text-white">Frage</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="question" id="question" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ question.data.question|default:'' }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% if image %}
|
||||
<img src="{{ image.image.url }}" style="max-width: 100px;" alt="Bild der Frage">
|
||||
<label for="reset_image">
|
||||
<input type="checkbox" name="reset_ques_image" id="reset_ques_image">
|
||||
Bild zurücksetzen
|
||||
</label>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="question_image">Bild:</label>
|
||||
<input value="image.url" class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent" type="file" name="question_image" id="question_image">
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">richtige Reihenfolge</label>
|
||||
<div id="options-container" class="space-y-3">
|
||||
{% if question and question.data.options %}
|
||||
{% for option in question.data.options|dictsort:"order" %}
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="number bg-blue-500 p-2 rounded-lg text-white">{{ forloop.counter }}.</span>
|
||||
<textarea name="option_{{ forloop.counter }}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ option.value }}</textarea>
|
||||
<label class="inline-flex items-center">
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for i in "1234" %}
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="number bg-blue-500 p-2 rounded-lg text-white">{{ forloop.counter }}.</span>
|
||||
<textarea name="option_{{ forloop.counter }}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<button type="button" onclick="addOption()"
|
||||
class="mt-3 inline-flex items-center px-3 py-1.5 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
<svg class="h-4 w-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
Option hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
||||
<select name="time_per_question" id="time_per_question" class="dark:text-white dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="10" {% if standard_time_per_question == "10" or time_per_question == "10" %}selected{% endif %}>10 Sekunden</option>
|
||||
<option value="20" {% if standard_time_per_question == "20" or time_per_question == "20" %}selected{% endif %}>20 Sekunden</option>
|
||||
<option value="30" {% if standard_time_per_question == "30" or time_per_question == "30" %}selected{% endif %}>30 Sekunden</option>
|
||||
<option value="45" {% if standard_time_per_question == "45" or time_per_question == "45" %}selected{% endif %}>45 Sekunden</option>
|
||||
<option value="60" {% if standard_time_per_question == "60" or time_per_question == "60" %}selected{% endif %}>1 Minute</option>
|
||||
<option value="120" {% if standard_time_per_question == "120" or time_per_question == "120" %}selected{% endif %}>2 Minuten</option>
|
||||
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700 dark:text-white">Credits</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="credits" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md "
|
||||
>{{ credits }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Abbrechen
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
{% if question %}Speichern{% else %}Erstellen{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
function addOption() {
|
||||
const container = document.getElementById('options-container');
|
||||
const optionCount = container.children.length + 1;
|
||||
|
||||
if (optionCount > 6) {
|
||||
alert('Maximal 6 Optionen sind erlaubt.');
|
||||
return;
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex items-center space-x-3';
|
||||
div.innerHTML = `
|
||||
|
||||
|
||||
|
||||
<span class="number bg-blue-500 p-2 rounded-lg text-white">${optionCount}.</span>
|
||||
<textarea name="option_${optionCount}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
`;
|
||||
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function removeOption(button) {
|
||||
|
||||
const container = document.getElementById('options-container');
|
||||
if (container.children.length > 2) {
|
||||
button.parentElement.remove();
|
||||
updateOptionNumbers();
|
||||
} else {
|
||||
alert('Mindestens zwei Optionen müssen vorhanden sein.');
|
||||
}
|
||||
}
|
||||
|
||||
function updateOptionNumbers() {
|
||||
const container = document.getElementById('options-container');
|
||||
const options = container.children;
|
||||
Array.from(options).forEach((option, index) => {
|
||||
const number = index + 1;
|
||||
const textInput = option.querySelector('textarea');
|
||||
|
||||
|
||||
|
||||
textInput.name = `option_${number}`;
|
||||
const numberSpan = option.querySelector('span.number');
|
||||
if (numberSpan) {
|
||||
numberSpan.textContent = `${number}.`;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,8 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
{% load static %}
|
||||
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
||||
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
@@ -48,7 +50,8 @@
|
||||
|
||||
|
||||
</div>
|
||||
{% if question_data.type != "input" %}
|
||||
{% if question_data.type == "multiple_choice" or question_data.type == "true_false" %}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4" id="options-container">
|
||||
{% for option in question_data.options %}
|
||||
<button
|
||||
@@ -81,8 +84,70 @@
|
||||
onclick="submitAnswer({{ forloop.counter0 }});" {% endif %}>
|
||||
<p class="text-lg">{{ option.value }}</p>
|
||||
</button>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% elif question_data.type == "order" %}
|
||||
|
||||
<div id="sortable-options" class=" mb-4 flex flex-col items-center">
|
||||
{% for option in question_data.options %}
|
||||
|
||||
<button data-index="{{ forloop.counter0 }}"
|
||||
data-value="{{ option.value }}"
|
||||
|
||||
class=" my-2 w-3/4 max-w-xl
|
||||
{% if forloop.counter0 == 0 %}
|
||||
bg-green-500
|
||||
hover:bg-green-600
|
||||
{% elif forloop.counter0 == 1 %}
|
||||
bg-red-500
|
||||
hover:bg-red-600
|
||||
{% elif forloop.counter0 == 2 %}
|
||||
bg-blue-500
|
||||
hover:bg-blue-600
|
||||
{% elif forloop.counter0 == 3 %}
|
||||
bg-purple-500
|
||||
hover:bg-purple-600
|
||||
{% elif forloop.counter0 == 4 %}
|
||||
bg-orange-500
|
||||
hover:bg-orange-600
|
||||
{% elif forloop.counter0 == 5 %}
|
||||
bg-yellow-500
|
||||
hover:bg-yellow-600
|
||||
{% else %}
|
||||
bg-gray-500
|
||||
hover:bg-gray-600
|
||||
{% endif %}
|
||||
p-4 rounded-lg text-white transition-colors duration-200 answer-option overflow-auto break-words hyphens-auto "
|
||||
data-index="{{ forloop.counter0 }}"
|
||||
|
||||
>
|
||||
|
||||
<p class="text-lg flex items-center">
|
||||
<span class="order-number inline-block w-8 h-8 rounded-full bg-white text-gray-800 font-bold text-center leading-8 mr-3">1</span>
|
||||
|
||||
{{ option.value }}
|
||||
</p>
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
{% if request.session.mode == "learn" %}
|
||||
<button
|
||||
onclick="submitOrderAnswer()"
|
||||
id="send_order" class="mt-2 text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a] ">
|
||||
abschicken
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% else %}
|
||||
{% if request.session.mode == "learn" %}
|
||||
<form action="">
|
||||
@@ -131,8 +196,12 @@
|
||||
} else if (data.type === 'game_state_update' &&
|
||||
data.action === 'show_scores' &&
|
||||
data.redirect_url) {
|
||||
window.location.href = data.redirect_url;
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
submitAnswer( -1 );
|
||||
window.location.href = data.redirect_url;
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -180,7 +249,7 @@
|
||||
'type': 'advance_to_scores',
|
||||
'host_id': hostId
|
||||
}));
|
||||
}, 2000);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Timer
|
||||
@@ -199,6 +268,7 @@
|
||||
|
||||
if (remaining === 0) {
|
||||
advanceToScores();
|
||||
|
||||
} else {
|
||||
requestAnimationFrame(updateTimer);
|
||||
}
|
||||
@@ -281,5 +351,74 @@
|
||||
}));
|
||||
}
|
||||
}
|
||||
let initialOrder = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const container = document.getElementById('sortable-options');
|
||||
if (container) {
|
||||
initialOrder = Array.from(container.children).map(div => div.dataset.index);
|
||||
|
||||
Sortable.create(container, {
|
||||
animation: 150,
|
||||
onUpdate: updateOrderNumbers // <--- Nummern aktualisieren nach Sortieren
|
||||
});
|
||||
|
||||
updateOrderNumbers(); // initial setzen
|
||||
}
|
||||
});
|
||||
|
||||
function updateOrderNumbers() {
|
||||
const container = document.getElementById('sortable-options');
|
||||
const children = container.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const label = children[i].querySelector('.order-number');
|
||||
if (label) {
|
||||
label.textContent = `${i + 1}.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function submitOrderAnswer() {
|
||||
const order_button=document.getElementById("send_order")
|
||||
order_button.disabled=true;
|
||||
order_button.classList.add('opacity-50');
|
||||
const orderedDivs = document.querySelectorAll('#sortable-options button');
|
||||
|
||||
const selectedOrder = Array.from(orderedDivs).map(div => div.dataset.value);
|
||||
|
||||
|
||||
const now = Date.now();
|
||||
let timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
||||
{% if request.session.mode == "learn" %}
|
||||
timeRemaining = questionDuration;
|
||||
{% endif %}
|
||||
|
||||
gameSocket.send(JSON.stringify({
|
||||
type: 'submit_answer',
|
||||
participant_id: '{{ host_id }}',
|
||||
answer: selectedOrder,
|
||||
time_remaining: timeRemaining
|
||||
}));
|
||||
|
||||
hasAnswered = true;
|
||||
}
|
||||
|
||||
function resetOrderSelection() {
|
||||
const container = document.getElementById('sortable-options');
|
||||
const divs = Array.from(container.children);
|
||||
const indexMap = {};
|
||||
divs.forEach(div => {
|
||||
indexMap[div.dataset.index] = div;
|
||||
});
|
||||
|
||||
// Sort children back to initial order
|
||||
container.innerHTML = '';
|
||||
initialOrder.forEach(i => {
|
||||
container.appendChild(indexMap[i]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,10 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load static %}
|
||||
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-end mb-4">
|
||||
<button onclick="leaveGame()" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded">
|
||||
@@ -22,10 +26,11 @@
|
||||
<div id="time-bar" class="bg-blue-600 h-4 rounded-full transition-all duration-100 linear" style="width: 100%;"></div>
|
||||
</div>
|
||||
|
||||
{% if question_data.type != "input" %}
|
||||
{% if question_data.type == "multiple_choice" or question_data.type == "true_false" %}
|
||||
<div class="grid grid-cols-2 gap-4" id="options-container">
|
||||
{% for option in question_data.options %}
|
||||
<button
|
||||
<button data-index="{{ forloop.counter0 }}"
|
||||
data-value="{{ option.value }}"
|
||||
class="
|
||||
{% if forloop.counter0 == 0 %}
|
||||
bg-green-500
|
||||
@@ -56,6 +61,71 @@
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% elif question_data.type == "order" %}
|
||||
|
||||
<div id="sortable-options" class=" mb-4 flex flex-col items-center">
|
||||
{% for option in question_data.options %}
|
||||
|
||||
<button data-index="{{ forloop.counter0 }}"
|
||||
data-value="{{ option.value }}"
|
||||
|
||||
class=" my-2 w-3/4 max-w-xl
|
||||
{% if forloop.counter0 == 0 %}
|
||||
bg-green-500
|
||||
hover:bg-green-600
|
||||
{% elif forloop.counter0 == 1 %}
|
||||
bg-red-500
|
||||
hover:bg-red-600
|
||||
{% elif forloop.counter0 == 2 %}
|
||||
bg-blue-500
|
||||
hover:bg-blue-600
|
||||
{% elif forloop.counter0 == 3 %}
|
||||
bg-purple-500
|
||||
hover:bg-purple-600
|
||||
{% elif forloop.counter0 == 4 %}
|
||||
bg-orange-500
|
||||
hover:bg-orange-600
|
||||
{% elif forloop.counter0 == 5 %}
|
||||
bg-yellow-500
|
||||
hover:bg-yellow-600
|
||||
{% else %}
|
||||
bg-gray-500
|
||||
hover:bg-gray-600
|
||||
{% endif %}
|
||||
p-4 rounded-lg text-white transition-colors duration-200 answer-option overflow-auto break-words hyphens-auto "
|
||||
data-index="{{ forloop.counter0 }}"
|
||||
|
||||
>
|
||||
|
||||
<p class="text-lg flex items-center">
|
||||
<span class="order-number inline-block w-8 h-8 rounded-full bg-white text-gray-800 font-bold text-center leading-8 mr-3">1</span>
|
||||
|
||||
{{ option.value }}
|
||||
</p>
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button id="send_order"
|
||||
onclick="submitOrderAnswer()"
|
||||
class="mt-2 text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a] ">
|
||||
abschicken
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% else %}
|
||||
<form action="">
|
||||
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
||||
@@ -89,7 +159,11 @@
|
||||
data.action === 'show_scores' &&
|
||||
data.redirect_url) {
|
||||
window.location.href = data.redirect_url;
|
||||
{% if question_data.type == "order" %}
|
||||
submitOrderAnswer();
|
||||
{% else %}
|
||||
submitAnswer(-1);
|
||||
{% endif %}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -159,7 +233,13 @@
|
||||
|
||||
if (remaining === 0 && !hasAnswered) {
|
||||
// Auto-submit timeout answer
|
||||
{% if question_data.type == "order" %}
|
||||
submitOrderAnswer();
|
||||
{% else %}
|
||||
submitAnswer(-1);
|
||||
{% endif %}
|
||||
|
||||
|
||||
} else if (remaining > 0) {
|
||||
requestAnimationFrame(updateTimer);
|
||||
}
|
||||
@@ -176,5 +256,74 @@
|
||||
|
||||
// Start timer when page loads
|
||||
updateTimer();
|
||||
|
||||
let initialOrder = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const container = document.getElementById('sortable-options');
|
||||
if (container) {
|
||||
initialOrder = Array.from(container.children).map(div => div.dataset.index);
|
||||
|
||||
Sortable.create(container, {
|
||||
animation: 150,
|
||||
onUpdate: updateOrderNumbers // <--- Nummern aktualisieren nach Sortieren
|
||||
});
|
||||
|
||||
updateOrderNumbers(); // initial setzen
|
||||
}
|
||||
});
|
||||
|
||||
function updateOrderNumbers() {
|
||||
const container = document.getElementById('sortable-options');
|
||||
const children = container.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const label = children[i].querySelector('.order-number');
|
||||
if (label) {
|
||||
label.textContent = `${i + 1}.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function submitOrderAnswer() {
|
||||
const order_button=document.getElementById("send_order")
|
||||
order_button.disabled=true;
|
||||
order_button.classList.add('opacity-50');
|
||||
const orderedDivs = document.querySelectorAll('#sortable-options button');
|
||||
|
||||
const selectedOrder = Array.from(orderedDivs).map(div => div.dataset.value);
|
||||
|
||||
|
||||
const now = Date.now();
|
||||
let timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
||||
{% if request.session.mode == "learn" %}
|
||||
timeRemaining = questionDuration;
|
||||
{% endif %}
|
||||
|
||||
gameSocket.send(JSON.stringify({
|
||||
type: 'submit_answer',
|
||||
participant_id: participantId,
|
||||
answer: selectedOrder,
|
||||
time_remaining: timeRemaining
|
||||
}));
|
||||
|
||||
hasAnswered = true;
|
||||
}
|
||||
|
||||
function resetOrderSelection() {
|
||||
const container = document.getElementById('sortable-options');
|
||||
const divs = Array.from(container.children);
|
||||
const indexMap = {};
|
||||
divs.forEach(div => {
|
||||
indexMap[div.dataset.index] = div;
|
||||
});
|
||||
|
||||
// Sort children back to initial order
|
||||
container.innerHTML = '';
|
||||
initialOrder.forEach(i => {
|
||||
container.appendChild(indexMap[i]);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -6,9 +6,12 @@
|
||||
<h1 class="text-3xl font-bold mb-6">Ergebnisse</h1>
|
||||
|
||||
<div class="mb-6">
|
||||
|
||||
<h2 class="text-xl font-bold mb-4">Aktuelle Frage</h2>
|
||||
<p class="text-lg mb-2">{{ question_data.question }}</p>
|
||||
{% if question_data.type != "order" %}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
{% for option in question_data.options %}
|
||||
|
||||
<div id="option-box-{{ forloop.counter0 }}" class=" dark:bg-transparent relative p-4 rounded-lg overflow-auto break-words hyphens-auto {% if option.is_correct %} border-2 border-green-500{% else %}border-2 border-red-500{% endif %}">
|
||||
@@ -22,6 +25,28 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="option-box-0" class="relative p-4 rounded-lg border-2 border-green-500 overflow-auto">
|
||||
<div id="option-fill-0" class="absolute left-0 top-0 h-full bg-green-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-lg">Richtige Reihenfolge</p>
|
||||
<ul class="">
|
||||
{% for option in question_data.options|dictsort:"order" %}
|
||||
<li class="text-lg"> {{ forloop.counter }}. <span class="">{{ option.value }}</span> </li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="text-gray-600" id="option-count-0">0 Antworten</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="option-box-1" class="relative p-4 rounded-lg border-2 border-red-500 overflow-auto mt-2">
|
||||
<div id="option-fill-1" class="absolute left-0 top-0 h-full bg-red-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-lg">Falsche Reihenfolge</p>
|
||||
<p class="text-gray-600" id="option-count-1">0 Antworten</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
|
||||
@@ -10,3 +10,4 @@ channels_redis
|
||||
python-dotenv # Production server safety
|
||||
qrcode~=7.4.2
|
||||
reportlab~=4.2.0
|
||||
pip-licenses ~= 5.0.0
|
||||
Reference in New Issue
Block a user