Compare commits

..

28 Commits

Author SHA1 Message Date
4fb13e9136 Footer immer Unten 2025-04-05 12:43:49 +02:00
Juhn-Vitus Saß
f3591bb9da Merge branch '48-quiz-fragen-templates-erstellen' into 'master'
Resolve "Quiz Fragen Templates erstellen"

Closes #48

See merge request enrichment-2024/qivip!15
2025-04-05 09:08:01 +00:00
nik8
16fcaf6346 Lupe-Symbol ersetzt 2025-03-28 21:55:25 +01:00
nik8
8a6d337392 Symbole vereinheitlicht 2025-03-22 15:16:20 +01:00
ben2
5f4fd4345e Merge branch '60-refactoring-views-py-try-accept-loschen' into 'master'
Resolve "Refactoring: Views.py try accept löschen"

Closes #60

See merge request enrichment-2024/qivip!23
2025-03-22 14:01:23 +00:00
Laurenz Wolff
7e93165c58 Überflüssiger Code entfernt 2025-03-22 14:59:34 +01:00
nik8
419160d144 Merge branch '59-uniocodes-austauschen' into 'master'
Resolve "Uniocodes Austauschen"

Closes #59

See merge request enrichment-2024/qivip!22
2025-03-22 13:58:25 +00:00
nik8
0c6580e732 Ersetzen der Symbole 2025-03-22 14:58:01 +01:00
nik8
8f4b5d581d Merge branch '54-filter-fur-library' into 'master'
Resolve "Filter für library"

Closes #54

See merge request enrichment-2024/qivip!21
2025-03-22 11:59:25 +00:00
nik8
809d857e94 Swiper_package 2025-03-22 12:58:59 +01:00
Frank Poetzsch-Heffter
98142c91f3 Pfad für tailwind-Dateien angepasst 2025-03-22 11:36:25 +00:00
nik8
93b2675b9e Zwischenstand 2025-03-22 12:21:39 +01:00
nik8
157daae740 struktur_library 2025-03-20 21:34:17 +01:00
nik8
f6459965e0 Suchfunktion_update 2025-03-18 17:22:42 +01:00
nik8
a3331543f2 Filter_library 2025-03-17 18:28:12 +01:00
nik8
9d71f2bbf3 Merge branch '50-in-der-handy-ansicht-ist-qivip-zu-dicht-an-dem-text-darunter-home-seite' into 'master'
Resolve "In der Handy Ansicht ist "qivip" zu dicht an dem Text darunter (Home Seite)"

Closes #50

See merge request enrichment-2024/qivip!20
2025-03-17 12:28:07 +00:00
nik8
8cceecf1bd Abstand_qivip_slogan 2025-03-17 13:27:18 +01:00
nik8
c330c4bc70 Merge branch '49-beim-speichern-eines-neuen-quiz-richtig-weiterfuhrung-nicht-das-man-zweilmal-auf-speichern' into 'master'
Resolve "Beim Speichern eines neuen Quiz richtig weiterführung, nicht das man zweilmal auf speichern drücken muss..."

Closes #49

See merge request enrichment-2024/qivip!19
2025-03-17 12:14:18 +00:00
nik8
26bf2a2c55 unnötige_Zeile_entfernt 2025-03-17 13:13:47 +01:00
nik8
86958c6d16 Weiterleitung_nach_Quiz_erstellen_gefixt 2025-03-17 13:12:04 +01:00
nik8
83a3d1584a Merge branch '47-weiterleitung-nach-registrierung-fixen' into 'master'
Resolve "Weiterleitung nach Registrierung fixen"

Closes #47

See merge request enrichment-2024/qivip!18
2025-03-17 11:41:31 +00:00
nik8
a07c66ad1a Weiterleitung_Registrierung_gefixt_autom_Login_nach_Regestrierung 2025-03-17 12:39:20 +01:00
nik8
956e69b56f Moderieren_link_zu_library 2025-03-16 18:17:16 +01:00
juhnsa
db71ea2a5b Farben aendern 2025-03-16 17:54:31 +01:00
nik8
526e486fd3 Text in Box 2025-03-16 17:53:48 +01:00
juhnsa
b149cbcfee Components verfuegbar machen 2025-03-16 17:49:49 +01:00
nik8
12c5cec7a1 Merge branch '51-im-impressum-einen-kasten-machen-schoner' into 'master'
Resolve "Im Impressum einen Kasten machen (schöner)"

Closes #51

See merge request enrichment-2024/qivip!17
2025-03-16 16:41:59 +00:00
juhnsa
d2375e136a Templates fuer Fragen 2025-03-16 17:08:38 +01:00
31 changed files with 624 additions and 90 deletions

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ db.sqlite3
tailwindcss.exe tailwindcss.exe
t-style.css t-style.css
dump.rdb dump.rdb
media
node_modules

View File

@@ -100,10 +100,10 @@ npx @tailwindcss/cli -i <INPUT-DATEI> -o <OUTPUT-DATEI> --watch --minify
```sh ```sh
# unter Linux mit Tailwind Binärdatei: # unter Linux mit Tailwind Binärdatei:
tailwindcss -i core/static/homepage/t-input.css -o core/static/homepage/t-style.css --watch --minify tailwindcss -i static/css/t-input.css -o static/css/t-style.css --watch --minify
# unter Windows mit NPM: # unter Windows mit NPM (in bash mit '/' statt '\'):
npx @tailwindcss/cli -i .\core\static\homepage\t-input.css -o .\core\static\homepage\t-style.css --watch --minify npx @tailwindcss/cli -i .\static\css\t-input.css -o .\static\css\t-style.css --watch --minify
``` ```
## Issue - Merge Request - Merge ## Issue - Merge Request - Merge

View File

@@ -1,5 +1,6 @@
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, authenticate
from .forms import RegisterForm from .forms import RegisterForm
# Create your views here. # Create your views here.
@@ -11,8 +12,9 @@ def register(response):
if response.method == "POST": if response.method == "POST":
form = RegisterForm(response.POST) form = RegisterForm(response.POST)
if form.is_valid(): if form.is_valid():
form.save() user=form.save()
return redirect("home") login(response, user) #automatischer Login nach Registrierung
return redirect("accounts:home")
else: else:
form = RegisterForm() form = RegisterForm()

View File

@@ -129,3 +129,7 @@ STATICFILES_DIRS = [BASE_DIR / 'static']
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = '/account/login/' LOGIN_URL = '/account/login/'
import os
MEDIA_URL = '/media/' # URL, um auf Medien-Dateien zuzugreifen
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # Speicherort für hochgeladene Dateien

View File

@@ -23,4 +23,5 @@ urlpatterns = [
path('', include('homepage.urls')), path('', include('homepage.urls')),
path('play/', include('play.urls')), path('play/', include('play.urls')),
path('library/', include('library.urls')), path('library/', include('library.urls')),
path('components/', include('components.urls'))
] ]

View File

@@ -9,7 +9,7 @@ class QivipQuizAdmin(admin.ModelAdmin):
# Für das QivipQuestion Modell # Für das QivipQuestion Modell
class QivipQuestionAdmin(admin.ModelAdmin): class QivipQuestionAdmin(admin.ModelAdmin):
list_display = ('quiz_id', 'data', 'creation_date', 'update_date') list_display = ('id', 'quiz_id', 'data', 'creation_date', 'update_date')
search_fields = ('data',) search_fields = ('data',)
list_filter = ('quiz_id',) list_filter = ('quiz_id',)

View File

@@ -4,9 +4,28 @@ from .models import QivipQuiz, QivipQuestion
class QuizForm(forms.ModelForm): class QuizForm(forms.ModelForm):
class Meta: class Meta:
model = QivipQuiz model = QivipQuiz
fields = ['name', 'description', 'status', 'category', 'tags',"difficulty"] fields = ['name', 'description','image', 'status', 'category', 'tags',"difficulty"]
class QuestionForm(forms.ModelForm): class QuestionForm(forms.ModelForm):
class Meta: class Meta:
model = QivipQuestion model = QivipQuestion
fields = ['quiz_id', 'data'] fields = ['quiz_id', 'data']
from django import forms
class QuizFilterForm(forms.Form):
search = forms.CharField(required=False, label="Suche", widget=forms.TextInput(attrs={
'class': 'flex flex-grow rounded-lg p-2' ,'placeholder': 'Suche ...',
}))
min_amout_questions = forms.IntegerField(required=False, min_value=1, label="Minimale Anzahl an Fragen", widget=forms.NumberInput(attrs={
'class': 'border-2 border-gray-300 rounded-lg p-2 w-full'
}))
max_amout_questions = forms.IntegerField(required=False, min_value=1, label="Maximale Anzahl an Fragen", widget=forms.NumberInput(attrs={
'class': 'border-2 border-gray-300 rounded-lg p-2 w-full'
}))
user = forms.CharField(required=False, label="von User", widget=forms.TextInput(attrs={
'class': 'border-2 border-gray-300 rounded-lg p-2 w-full'
}))

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-21 17:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0022_alter_qivipquiz_description_alter_qivipquiz_name'),
]
operations = [
migrations.AddField(
model_name='qivipquiz',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='quiz_images/'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-22 10:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0023_qivipquiz_image'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='image',
field=models.ImageField(blank=True, max_length=256, null=True, upload_to='quiz_images/'),
),
]

View File

@@ -24,7 +24,7 @@ class QivipQuiz(models.Model):
if value.strip() == "In dem Quiz geht es um ...": if value.strip() == "In dem Quiz geht es um ...":
raise ValidationError("Bitte gib eine aussagekräftige Beschreibung ein.") raise ValidationError("Bitte gib eine aussagekräftige Beschreibung ein.")
image = models.ImageField(max_length=256, upload_to='quiz_images/', blank=True, null=True) # Bild speichern in media/quiz_images/
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz') user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz')
creation_date = models.DateTimeField(auto_now_add=True) creation_date = models.DateTimeField(auto_now_add=True)

View File

@@ -1,6 +1,8 @@
from django.urls import path from django.urls import path
from . import views
from django.conf import settings
from . import views
from django.conf.urls.static import static
app_name = 'library' app_name = 'library'
urlpatterns = [ urlpatterns = [
@@ -12,4 +14,6 @@ urlpatterns = [
path('question/new/', views.new_question, name='new_question'), path('question/new/', views.new_question, name='new_question'),
path('question/edit/<int:pk>/', views.edit_question, name='edit_question'), path('question/edit/<int:pk>/', views.edit_question, name='edit_question'),
path('question/delete/<int:pk>/', views.delete_question, name='delete_question'), path('question/delete/<int:pk>/', views.delete_question, name='delete_question'),
] ]# Nur für die Entwicklungsumgebung
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -7,29 +7,94 @@ from .models import QivipQuiz, QivipQuestion
from .forms import QuizForm, QuestionForm from .forms import QuizForm, QuestionForm
import json import json
from django.core.paginator import Paginator from django.core.paginator import Paginator
from .models import QivipQuiz
from .forms import QuizFilterForm
from django.db.models import Count
from django.contrib.auth.models import User
# Übersicht aller Quizze # Übersicht aller Quizze
def overview_quiz(request): def overview_quiz(request):
try:
quizzes = QivipQuiz.objects.filter(user_id=request.user)
all_quizzes = QivipQuiz.objects.filter(status='öffentlich').exclude(user_id=request.user).filter(question__isnull=False).distinct()
return render(request, 'library/overview_quiz.html', {'quizzes': quizzes, 'all_quizzes': all_quizzes})
#Filter
form = QuizFilterForm(request.GET)
try:
filter_my_quizzes = QivipQuiz.objects.filter(user_id=request.user).annotate(max_amout_questions=Count('question'))
except: except:
all_quizzes = QivipQuiz.objects.filter(status='öffentlich').filter(question__isnull=False).distinct() filter_my_quizzes = QivipQuiz.objects.none()
return render(request, 'library/overview_quiz.html', {'quizzes': None, 'all_quizzes': all_quizzes})
filter_other_quizzes = QivipQuiz.objects.annotate(max_amout_questions=Count('question'))
if form.is_valid():
search = form.cleaned_data.get('search')
username= form.cleaned_data.get('user')
max_amout_questions= form.cleaned_data.get('max_amout_questions')
min_amout_questions= form.cleaned_data.get('min_amout_questions')
if search: # Suche nach Namen
filter_other_quizzes = filter_other_quizzes.filter(name__icontains=search)
filter_my_quizzes = filter_my_quizzes.filter(name__icontains=search)
if min_amout_questions:
filter_other_quizzes = filter_other_quizzes.filter(max_amout_questions__gte=min_amout_questions)
try:
filter_my_quizzes =filter_my_quizzes.filter(max_amout_questions__gte=min_amout_questions)
except:
filter_my_quizzes = QivipQuiz.objects.none()
if max_amout_questions:
filter_other_quizzes = filter_other_quizzes.filter(max_amout_questions__lte=max_amout_questions)
try:
filter_my_quizzes =filter_my_quizzes.filter(max_amout_questions__lte=max_amout_questions)
except:
filter_my_quizzes = QivipQuiz.objects.none()
if username:
try:
user = User.objects.get(username=username) # Benutzer anhand des Namens holen
filter_other_quizzes = filter_other_quizzes.filter(user_id=user.id)
filter_my_quizzes = filter_my_quizzes.filter(user_id=user.id) # Nach der user_id filtern
except User.DoesNotExist:
filter_other_quizzes = QivipQuiz.objects.none() # Falls User nicht existiert, leere Query zurückgeben
filter_my_quizzes = QivipQuiz.objects.none()
try:
filter_other_quizzes=filter_other_quizzes.exclude(user_id=request.user)
except:
filter_other_quizzes=filter_other_quizzes
# Anzahl der Quiz pro Seite für Swiper
print("kein Filter ,form_valid")
context = {
'show_search': True,
'filter_my_quizzes': filter_my_quizzes,
'filter_other_quizzes': filter_other_quizzes,
'form': form,
}
return render(request, 'library/overview_quiz.html', context)
# Neues Quiz erstellen # Neues Quiz erstellen
@login_required @login_required
def new_quiz(request): def new_quiz(request):
if request.method == 'POST': if request.method == 'POST':
form = QuizForm(request.POST) form = QuizForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
quiz = form.save(commit=False) quiz = form.save(commit=False)
quiz.user_id = request.user quiz.user_id = request.user
quiz.save() quiz.save()
form.save_m2m() # Speichert die Many-to-Many Beziehungen (Tags) form.save_m2m() # Speichert die Many-to-Many Beziehungen (Tags)
return redirect('library:edit_quiz', pk=quiz.pk) return redirect('library:detail_quiz', pk=quiz.pk)
else: else:
form = QuizForm() form = QuizForm()
return render(request, 'library/form.html', {'form': form}) return render(request, 'library/form.html', {'form': form})
@@ -39,10 +104,10 @@ def new_quiz(request):
def edit_quiz(request, pk): def edit_quiz(request, pk):
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user) quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
if request.method == 'POST': if request.method == 'POST':
form = QuizForm(request.POST, instance=quiz) form = QuizForm(request.POST, instance=quiz, files=request.FILES)
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect('library:overview_quiz') return redirect('library:detail_quiz', pk=quiz.pk)
else: else:
form = QuizForm(instance=quiz) form = QuizForm(instance=quiz)
return render(request, 'library/form.html', {'form': form}) return render(request, 'library/form.html', {'form': form})

35
django/package-lock.json generated Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "django",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "django",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"swiper": "^11.2.6"
}
},
"node_modules/swiper": {
"version": "11.2.6",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.6.tgz",
"integrity": "sha512-8aXpYKtjy3DjcbzZfz+/OX/GhcU5h+looA6PbAzHMZT6ESSycSp9nAjPCenczgJyslV+rUGse64LMGpWE3PX9Q==",
"funding": [
{
"type": "patreon",
"url": "https://www.patreon.com/swiperjs"
},
{
"type": "open_collective",
"url": "http://opencollective.com/swiper"
}
],
"license": "MIT",
"engines": {
"node": ">= 4.7.0"
}
}
}
}

15
django/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "django",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"swiper": "^11.2.6"
}
}

View File

@@ -4,7 +4,9 @@ from . import views
app_name = 'play' app_name = 'play'
urlpatterns = [ urlpatterns = [
path('lobby/<str:join_code>', views.lobby, name='lobby'), path('game/<str:join_code>', views.lobby, name='lobby'),
path('lobby/<str:join_code>/participate', views.create_participant, name='create_participant'), path('game/<str:join_code>/participate', views.create_participant, name='create_participant'),
path('join', views.join_game, name='join_game'), path('join', views.join_game, name='join_game'),
path('game/<str:join_code>/<int:question_id>', views.question, name='question'),
path('game/<str:join_code>/<int:question_id>/participant', views.question_participant, name='question_participant'),
] ]

View File

@@ -1,9 +1,11 @@
from django.shortcuts import render from django.shortcuts import render
from django.shortcuts import render, redirect, get_object_or_404, reverse from django.shortcuts import render, redirect, get_object_or_404, reverse
from play.models import QuizGame, QuizGameParticipant from play.models import QuizGame, QuizGameParticipant
from library.models import QivipQuiz from library.models import QivipQuiz, QivipQuestion
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
import json
# Create your views here. # Create your views here.
def lobby(request, join_code): def lobby(request, join_code):
if not "participant_id" in request.COOKIES: if not "participant_id" in request.COOKIES:
@@ -58,3 +60,27 @@ def join_game(request):
# TODO: Mit message eine Fehlermeldung weitergeben (und im entsprechenden Template Designen) # TODO: Mit message eine Fehlermeldung weitergeben (und im entsprechenden Template Designen)
pass pass
return render(request, 'play/join_game.html') return render(request, 'play/join_game.html')
def question(request, join_code, question_id):
question = get_object_or_404(QivipQuestion, pk=question_id)
if question.data:
question.data = json.loads(question.data)
context = {
'question': question,
}
return render(request, 'play/game/question_host.html', {'question': question, 'debug': "debug"})
def question_participant(request, join_code, question_id):
question = get_object_or_404(QivipQuestion, pk=question_id)
if question.data:
question.data = json.loads(question.data)
context = {
'question': question,
}
return render(request, 'play/game/question_participant.html', {'question': question, 'debug': "debug"})

View File

@@ -51,3 +51,60 @@ a.qp-a-button-small {
@apply p-3 rounded-full bg-indigo-500 hover:bg-indigo-600 text-white focus:scale-105 @apply p-3 rounded-full bg-indigo-500 hover:bg-indigo-600 text-white focus:scale-105
transition duration-200 font-black shadow-md hover:shadow-lg; transition duration-200 font-black shadow-md hover:shadow-lg;
} }
div.qp-answer-container {
@apply grid grid-cols-2 h-screen;
}
div.qp-answer-container div {
@apply grid place-content-center rounded-xl m-2 place-self-stretch text-white transition duration-300;
@apply nth-1:bg-green-500 hover:nth-1:bg-green-600;
@apply nth-2:bg-red-500 hover:nth-2:bg-red-600;
@apply nth-3:bg-blue-500 hover:nth-3:bg-blue-600;
@apply nth-4:bg-yellow-500 hover:nth-4:bg-yellow-600;
@apply nth-5:bg-purple-500 hover:nth-5:bg-purple-600;
@apply nth-6:bg-black hover:nth-6:bg-slate-800;
}
div.qp-answer-container div p {
@apply font-black text-xl sm:text-2xl md:text-4xl;
}
div.qp-answer-container-host {
@apply grid grid-cols-2 absolute bottom-0 w-screen xl:px-20 xl:pb-10;
}
div.qp-answer-container-host div {
@apply grid place-content-center rounded-xl m-1 place-self-stretch text-white transition duration-300;
@apply nth-1:bg-green-500 hover:nth-1:bg-green-600;
@apply nth-2:bg-red-500 hover:nth-2:bg-red-600;
@apply nth-3:bg-blue-500 hover:nth-3:bg-blue-600;
@apply nth-4:bg-yellow-500 hover:nth-4:bg-yellow-600;
@apply nth-5:bg-purple-500 hover:nth-5:bg-purple-600;
@apply nth-6:bg-black hover:nth-6:bg-slate-800;
}
div.qp-answer-container-host div p {
@apply font-black text-xl p-4;
}
div.qp-question-container {
@apply text-center text-3xl px-4 py-9 h-screen;
}
div.qp-question-container img {
@apply max-w-[50vw] max-h-[40vh] mx-auto;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -10,8 +10,13 @@
</head> </head>
<body> <body>
{% include 'partials/_nav.html' %} {% include 'partials/_nav.html' %}
<div class="flex flex-col h-screen overflow-x-hidden">
{% block content%}{% endblock %} {% block content%}{% endblock %}
<div class="fixed bottom-0 w-full">
{% include 'partials/_footer.html' %} {% include 'partials/_footer.html' %}
</div>
</div>
{% block extra_js %}{% endblock %} {% block extra_js %}{% endblock %}
</body> </body>
</html> </html>

View File

@@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% block title %}Antwort{% endblock %} {% block title %}Antwort{% endblock %}
{% block content %} {% block content %}
<link rel="stylesheet" href="{% static 'homepage/t-style.css' %}"> <link rel="stylesheet" href="{% static 'css/t-style.css' %}">
<!-- TODO Bedingung festlegen: Quiztyp unterscheiden % if <Bedingung> % --> <!-- TODO Bedingung festlegen: Quiztyp unterscheiden % if <Bedingung> % -->
{% if user.is_authenticated %} {% if user.is_authenticated %}

View File

@@ -1,5 +1,5 @@
{% load static %} {% load static %}
<link rel="stylesheet" href="{% static 'homepage/t-style.css' %}"> <link rel="stylesheet" href="{% static 'css/t-style.css' %}">
<!doctype html> <!doctype html>
<html class="h-full"> <html class="h-full">
<head> <head>

View File

@@ -4,11 +4,11 @@
<div class="grid place-content-center md:h-screen h-150 w-full -z-50 top-0 p-4"> <div class="grid place-content-center md:h-screen h-150 w-full -z-50 top-0 p-4">
<div class="text-center"> <div class="text-center">
<h2 class="font-bold md:text-8xl text-6xl md:mb-8 text-blue-600">qivip</h2> <h2 class="font-bold md:text-8xl text-6xl md:mb-8 text-blue-600">qivip</h2>
<h3 class="italic font-extralight sm:text-2xl text-md">Interaktives Lernen neu definiert.</h3> <h3 class="mt-4 italic font-extralight sm:text-2xl text-md">Interaktives Lernen neu definiert.</h3>
</div> </div>
<div class="grid sm:grid-cols-3 place-items-stretch text-center mt-8 gap-4 p-4 border-3 bg-blue-100 border-blue-100 rounded-md"> <div class="grid sm:grid-cols-3 place-items-stretch text-center mt-8 gap-4 p-4 border-3 bg-blue-100 border-blue-100 rounded-md">
<a class="qp-a-button bg-green-500 text-white" href="{% url 'play:join_game' %}">Teilnehmen</a> <a class="qp-a-button bg-green-500 text-white" href="{% url 'play:join_game' %}">Teilnehmen</a>
<a class="qp-a-button bg-indigo-500 text-white" href="#">Moderieren</a> <a class="qp-a-button bg-indigo-500 text-white" href="{% url 'library:overview_quiz' %}">Moderieren</a>
<a class="qp-a-button bg-purple-500 text-white" href="{% url 'library:new_quiz' %}">Erstellen</a> <a class="qp-a-button bg-purple-500 text-white" href="{% url 'library:new_quiz' %}">Erstellen</a>
</div> </div>
</div> </div>

View File

@@ -5,7 +5,7 @@
<div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md"> <div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md">
<div class="grid place-content-center h-120"> <div class="grid place-content-center h-120">
<h1 class="text-center m-4 text-3xl break-words lg:text-6xl">Datenschutz-<br>erklärung</b></h1> <h1 class="text-center m-4 text-3xl lg:text-6xl">Datenschutz-</br>erklärung</h1>
Hier muss die Datenschutzerklärung stehen! <br> Hier muss die Datenschutzerklärung stehen! <br>
<br> <br>

View File

@@ -5,8 +5,17 @@
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold">{{ quiz.name }}</h1> <h1 class="text-2xl font-bold">{{ quiz.name }}</h1>
<div class="flex space-x-2"> <div class="flex space-x-2">
<a href="{% url 'library:edit_quiz' quiz.id %}" class=" text-white px-4 py-2 rounded-md text-sm lg:text-lg hover:scale-110 transition-colors">&#x270F</a> <a href="{% url 'library:edit_quiz' quiz.id %}" class=" text-white px-4 py-2 rounded-md text-sm lg:text-lg hover:scale-110 transition-colors">
<a href="{% url 'library:delete_quiz' quiz.id %}" class=" text-white px-4 py-2 rounded-md text-sm lg:text-lg hover:scale-110 transition-colors">&#x1F5D1</a> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7 text-gray-600">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</a>
<a href="{% url 'library:delete_quiz' quiz.id %}" class=" text-white px-4 py-2 rounded-md text-sm lg:text-lg hover:scale-110 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7 text-gray-600">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</a>
</div> </div>
</div> </div>
@@ -89,7 +98,10 @@
<a href=" {% url 'library:delete_question' question.id %}" <a href=" {% url 'library:delete_question' question.id %}"
class=" text-red-700 px-4 py-1 rounded hover:scale-110 "> class=" text-red-700 px-4 py-1 rounded hover:scale-110 ">
&#x1F5D1 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7 text-gray-600">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</a> </a>
</div> </div>

View File

@@ -2,7 +2,7 @@
{% block content %} {% block content %}
<div class="input-group border-blue-600 border-2 rounded-xl shadow-md mt-12"> <div class="input-group border-blue-600 border-2 rounded-xl shadow-md mt-12">
<form method="post"> <form method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<button type="submit">Speichern</button> <button type="submit">Speichern</button>

View File

@@ -1,61 +1,209 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
{% load static %}
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="{% static 'swiper/swiper-bundle.min.css' %}">
<style>
.swiper {
width: 100%;
height: 100%;
}
.swiper-slide {
text-align: center;
font-size: 18px;
background: #fff;
display: flex;
justify-content: center;
align-items: center;
}
</style>
</head>
<div class="flex justify-end mr-2"> <div class="flex justify-end mr-2">
<a href="{% url 'library:new_quiz' %}" class="mt-2 p-2 shadow-md border-blue-600 border-2 rounded-md text-black mb-12">Neues Quiz erstellen</a> <a href="{% url 'library:new_quiz' %}" class="mt-2 p-2 shadow-md border-blue-600 border-2 rounded-md text-black ">Neues Quiz erstellen</a>
</div>
<div class="flex justify-end mr-2">
<a href="{% url 'library:overview_quiz' %}" class="mt-2 p-2 shadow-md border-blue-600 border-2 rounded-md text-black ">Suche und Filter zurücksetzen</a>
</div> </div>
{% if quizzes %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 px-4 place-items-center">
<h1 class="font-bold mb-4 px-4 bg-blue-100">meine eigenen Quiz</h1></div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 px-4 place-items-center">
{% for quiz in quizzes %}
<div class="bg-white w-full max-w-md rounded-lg p-4 shadow-md border-blue-600 border-2 rounded-xl h-80 ">
<h2 class="text-lg font-bold break-words truncate"><a href="{% url 'library:edit_quiz' quiz.id %}">{{ quiz.name }}</a></h2>
<p class="text-sm text-gray-600 pt-11 h-54"><span class="break-words line-clamp-2">{{ quiz.description }} </span><br> Status: <span class="font-bold">{{ quiz.status }}</span>
<br> Schwierigkeit:<span class="font-bold"> {{ quiz.difficulty }}</span>
<br> Anzahl der Fragen:<span class="font-bold"> {{ quiz.question.count }}</span> </p>
<div class="w-screen flex justify-center items-center">
<div class="w-full m-4 border-blue-600 border-2 rounded-xl shadow-md p-6 lg:w-1/4">
<form method="get" class="flex flex-col space-y-4">
<h1 class="text-xl font-bold">Filter</h1>
Minimale Anzahl an Fragen {{ form.min_amout_questions }}
Maximale Anzahl an Fragen {{ form.max_amout_questions }}
von User {{ form.user }}
<!-- Rendert die Form-Felder -->
<button type="submit" class="qp-a-button-small border-2 border-blue-600 text-black">Filtern</button>
</form>
</div>
</div>
<!-- Swiper Container für eigene Quiz -->
{% if filter_my_quizzes %}
<div class="text-center m-8 bg-blue-600 grid mx-8 h-8 place-items-center rounded-xl shadow-md ">
<h1 id="my-quizzes" class="flex px-4 font-bold text-white">Eigene Quiz</h1>
</div>
<div> <div class="swiper mySwiper">
<div class="flex justify-between items-center gap-2"> <div class="swiper-wrapper">
<a href="#" class="qp-a-button-small border-2 border-blue-600 text-black">Spiel starten</a> {% for quiz in filter_my_quizzes %}
<div class="swiper-slide">
<div class="relative h-80 bg-white bg-cover text-white w-full max-w-md rounded-lg p-4 shadow-md border-blue-600 border-2 rounded-xl flex flex-col justify-between"
{% if quiz.image %} style="background-image: url('http://127.0.0.1:8000/library{{ quiz.image.url }}');" {% endif %}>
<div class="absolute inset-0 bg-gray-900/60 bg-opacity-50 rounded-lg"></div>
<h2 class=" font-bold break-words truncate text-white font-bold text-white drop-shadow-lg custom-outline"> <a href="{% url 'library:detail_quiz' quiz.id %}">{{ quiz.name }}</a></h2>
<p class="text-white font-bold text-white drop-shadow-lg custom-outline">
<span class="break-words line-clamp-2 ">{{ quiz.description }}</span><br>
Schwierigkeit: <span class="font-bold">{{ quiz.difficulty }}</span><br>
Anzahl der Fragen: <span class="font-bold ">{{ quiz.question.count }}</span><br>
Status: <span class="font-bold ">{{ quiz.status }}</span>
</p>
<!-- Buttons bleiben am unteren Rand -->
<div class="flex justify-between items-center gap-2 ">
<a href="#" class="qp-a-button-small border-2 border-blue-600 text-white font-bold text-white drop-shadow-lg custom-outline">Spiel starten</a>
<div class="flex gap-2"> <div class="flex gap-2">
<a href="{% url 'library:detail_quiz' quiz.id %}" class="qp-a-button-small border-2 border-blue-600 text-white">&#x270F;</a> <a href="{% url 'library:detail_quiz' quiz.id %}" class="qp-a-button-small border-2 border-blue-600 text-white text-white font-bold text-white drop-shadow-lg custom-outline">
<a href="{% url 'library:delete_quiz' quiz.id %}" class="qp-a-button-small border-2 border-blue-600 text-white">&#x1F5D1;</a> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7">
</div> <path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</div> </svg>
</a>
<a href="{% url 'library:delete_quiz' quiz.id %}" class="qp-a-button-small border-2 border-blue-600 text-white text-white font-bold text-white drop-shadow-lg custom-outline">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</a>
</div> </div>
</div> </div>
</div>
</div>
{% endfor %} {% endfor %}
</div>
{% if filter_my_quizzes|length > 1 %}
<div class="swiper-button-next"></div>
<div class="swiper-button-prev"></div>
{% endif %} {% endif %}
</div> </div>
{% endif %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 px-4 place-items-center"> <!-- Swiper Container für Quiz von anderen Nutzern -->
<h1 class="font-bold px-4 bg-blue-100 mt-8 mb-4">Quiz von anderen Nutzern</h1></div> {% if filter_other_quizzes %}
<div class="text-center m-8 bg-blue-600 grid mx-8 h-8 place-items-center rounded-xl shadow-md">
<h1 id="other-quizzes" class="flex px-4 font-bold text-white">Quiz von anderen Nutzern</h1>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 px-4 place-items-center"> <div class="swiper mySwiper">
{% for quiz in all_quizzes %} <div class="swiper-wrapper">
<div class="h-80 bg-white w-full max-w-md rounded-lg p-4 shadow-md border-blue-600 border-2 rounded-xl ">
<h2 class="text-lg font-bold break-words truncate">{{ quiz.name }}</h2> {% for quiz in filter_other_quizzes %}
<p class="text-sm text-gray-600 pt-11 h-54"><span class="break-words line-clamp-2">{{ quiz.description }} </span> <br> Schwierigkeit:<span class="font-bold "> <div class="swiper-slide">
{{ quiz.difficulty }}</span><br> Anzahl der Fragen:<span class="font-bold"> {{ quiz.question.count }}</span> <div class="relative h-80 bg-white bg-cover text-white w-full max-w-md rounded-lg p-4 shadow-md border-blue-600 border-2 rounded-xl flex flex-col justify-between"
<br>Erstmalig erstellt am:<span class="font-bold"> {{ quiz.creation_date }}</span> {% if quiz.image %} style="background-image: url('http://127.0.0.1:8000/library{{ quiz.image.url }}');" {% endif %}>
<div class="absolute inset-0 bg-gray-900/60 bg-opacity-50 rounded-lg"></div>
<h2 class=" font-bold break-words truncate text-white font-bold text-white drop-shadow-lg custom-outline"> <a href="{% url 'library:detail_quiz' quiz.id %}">{{ quiz.name }}</a></h2>
<p class="text-white font-bold text-white drop-shadow-lg custom-outline">
<span class="break-words line-clamp-2 ">{{ quiz.description }}</span><br>
Schwierigkeit: <span class="font-bold">{{ quiz.difficulty }}</span><br>
Anzahl der Fragen: <span class="font-bold ">{{ quiz.question.count }}</span><br>
Erstellt am: <span class="font-bold ">{{ quiz.creation_date }}</span>
</p> </p>
<div>
<div class="flex justify-between items-center gap-2"> <!-- Buttons bleiben am unteren Rand -->
<a href="#" class="qp-a-button-small border-2 border-blue-600 text-black">Spiel starten</a> <div class="flex justify-between items-center gap-2 ">
<a href="#" class="qp-a-button-small border-2 border-blue-600 text-white font-bold text-white drop-shadow-lg custom-outline">Spiel starten</a>
<a href="{% url 'library:detail_quiz' quiz.id %}" class="qp-a-button-small border-2 border-blue-600 text-white text-white font-bold text-white drop-shadow-lg custom-outline">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</a>
<div class="flex gap-2"> <div class="flex gap-2">
<a href="{% url 'library:overview_quiz' %}?user={{ quiz.user_id }}" class="text-gray-600 text-sm">
<div class="qp-a-button-small text-gray-600 text-sm "> Quiz von {{ quiz.user_id }}</div> <button type="submit" class=" text-sm py-1 text-white font-bold text-white drop-shadow-lg custom-outline hover:text-gray-300"> Quiz von {{ quiz.user_id }}</button>
</a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
{% if filter_other_quizzes|length > 1 %}
<div class="swiper-button-next"></div>
<div class="swiper-button-prev"></div>
{% endif %}
</div>
{% endif %}
{% if not filter_other_quizzes and not filter_my_quizzes %}
<div class="grid text-center font-bold items-center">Es wurde kein Quiz passendes gefunden!</div>
{% endif %}
<!-- Swiper.js -->
<script src="{% static 'swiper/swiper-bundle.min.js' %}"></script>
<script>
var swiper = new Swiper('.swiper', {
slidesPerView: getSlidesPerView(),
direction: getDirection(),
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
on: {
resize: function () {
swiper.changeDirection(getDirection());
swiper.params.slidesPerView = getSlidesPerView();
swiper.update();
},
},
});
function getDirection() {
return window.innerWidth <= 0? 'vertical' : 'horizontal';
}
function getSlidesPerView() {
if (window.innerWidth <= 840) {
return 1; // Smartphones
} else if (window.innerWidth <= 1024) {
return 2; // Tablets
} else {
return 3; // Desktops
}
}
</script>
<style>
.custom-outline {
-webkit-text-stroke: 0.2px rgb(0, 0, 0); /* Schwarze Umrandung */
}
</style>
{% endblock %} {% endblock %}

View File

@@ -1,10 +1,29 @@
<nav class="flex justify-between bg-blue-600 h-12 px-4 sm:px-6 lg:px-8 rounded-lg m-2 shadow-md"> <nav class="flex justify-between items-center bg-blue-600 h-12 px-4 sm:px-6 lg:px-8 rounded-lg m-2 shadow-md overflow-x-auto whitespace-nowrap">
<div class="flex items-center"> <div class="flex items-center flex-shrink-0">
<h2 class="text-xl font-bold text-white hover:scale-110 transition duration-200"><a href="{% url 'homepage:home' %}">qivip</a></h2> <h2 class="text-xl font-bold text-white hover:scale-110 transition duration-200">
<a href="{% url 'homepage:home' %}">qivip</a>
</h2>
</div> </div>
<div class="flex items-center">
{% if show_search %}
<form method="get" class="mr-2 ml-2 flex items-center w-full max-w-sm bg-white rounded-full px-4 py-1 shadow-md">
<input type="text" name="search" placeholder="Suche ..." value="{{ request.GET.search }}"
class="w-full px-3 py-1 text-black bg-transparent focus:outline-none">
<button type="submit" class=" py-1 text-blue-600">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>
</button>
</form>
{% endif %}
<div class="flex items-center space-x-4">
<ul class="flex space-x-4 qp-nav-list"> <ul class="flex space-x-4 qp-nav-list">
{% if show_search %}
<li class="hidden md:flex"><a href="{% url 'library:overview_quiz' %}">Bibliothek</a></li>
{% else%}
<li><a href="{% url 'library:overview_quiz' %}">Bibliothek</a></li> <li><a href="{% url 'library:overview_quiz' %}">Bibliothek</a></li>
{% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li><a href="{% url 'accounts:home' %}">Konto</a></li> <li><a href="{% url 'accounts:home' %}">Konto</a></li>
{% else %} {% else %}

View File

@@ -0,0 +1,35 @@
{% load static %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/t-style.css' %}">
<title>qivip</title>
</head>
<body>
<div>
<div class="qp-question-container">
<p class="text-gray-700 font-bold text-xl uppercase">Frage X</p>
<img src="integral.png">
<h1>Frage: {{ question.data.question }}</h1>
<div>
<div class="p-4">
<p class="text-blue-500 text-xl">Verbleibende Zeit:</p>
<p id="remaining_time" class="text-6xl">36</p>
</div>
<div class="p-4">
<p class="text-blue-500 text-xl">7/14 Antworten</p>
</div>
</div>
</div>
<div class="qp-answer-container-host" id="qp-answer-container-host">
{% for option in question.data.options %}
<div>
<p>{{ option.value }}</p>
</div>
{% endfor %}
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
{% load static %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/t-style.css' %}">
<title>qivip</title>
</head>
<body>
<div class="qp-answer-container" id="qp-answer-container">
{% for option in question.data.options %}
<div>
<p>{{ option.value }}</p>
</div>
{% endfor %}
</div>
</body>
</html>

View File

@@ -1,3 +1,4 @@
django~=5.1.4 django~=5.1.4
channels~=4.2.0 channels~=4.2.0
daphne~=4.1.2 daphne~=4.1.2
pillow~=11.1.0