Compare commits
14 Commits
160-musik-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d56c9d3419 | ||
|
|
543ae87a14 | ||
|
|
7e22e818ef | ||
|
|
81dce0e2bd | ||
|
|
45dc8bd18d | ||
|
|
0d21d0b94b | ||
|
|
022a59cc7b | ||
|
|
0b23f6bf9d | ||
|
|
fb225dc6f3 | ||
|
|
1fb0958daf | ||
|
|
cf5b428031 | ||
|
|
b596655e43 | ||
|
|
334d2aa8dd | ||
|
|
9c06c81cb1 |
45
THIRD_PARTY_LICENSES_PYTHON.md
Normal file
45
THIRD_PARTY_LICENSES_PYTHON.md
Normal file
@@ -0,0 +1,45 @@
|
||||
| Name | Version | License |
|
||||
|---------------------------|--------------|---------------------------------------------------------|
|
||||
| Automat | 25.4.16 | MIT License |
|
||||
| Django | 5.1.13 | BSD License |
|
||||
| Twisted | 25.5.0 | MIT License |
|
||||
| asgiref | 3.10.0 | BSD License |
|
||||
| attrs | 25.4.0 | UNKNOWN |
|
||||
| autobahn | 25.10.1 | UNKNOWN |
|
||||
| certifi | 2025.10.5 | Mozilla Public License 2.0 (MPL 2.0) |
|
||||
| cffi | 2.0.0 | UNKNOWN |
|
||||
| channels | 4.2.2 | BSD License |
|
||||
| channels_redis | 4.3.0 | BSD |
|
||||
| chardet | 5.2.0 | GNU Lesser General Public License v2 or later (LGPLv2+) |
|
||||
| charset-normalizer | 3.4.4 | MIT |
|
||||
| colorama | 0.4.6 | BSD License |
|
||||
| constantly | 23.10.4 | MIT License |
|
||||
| cryptography | 46.0.3 | UNKNOWN |
|
||||
| daphne | 4.1.2 | BSD License |
|
||||
| django-autocomplete-light | 3.12.1 | MIT License |
|
||||
| django-cleanup | 9.0.0 | MIT License |
|
||||
| geographiclib | 2.1 | MIT |
|
||||
| geopy | 2.4.1 | MIT License |
|
||||
| hyperlink | 21.0.0 | MIT License |
|
||||
| idna | 3.11 | UNKNOWN |
|
||||
| incremental | 24.7.2 | MIT License |
|
||||
| msgpack | 1.1.2 | UNKNOWN |
|
||||
| pillow | 11.1.0 | CMU License (MIT-CMU) |
|
||||
| pyOpenSSL | 25.3.0 | Apache Software License |
|
||||
| pyasn1 | 0.6.1 | BSD License |
|
||||
| pyasn1_modules | 0.4.2 | BSD License |
|
||||
| pycparser | 2.23 | BSD License |
|
||||
| pypng | 0.20220715.0 | MIT License |
|
||||
| python-dotenv | 1.1.1 | BSD License |
|
||||
| qrcode | 7.4.2 | BSD License |
|
||||
| redis | 6.4.0 | MIT License |
|
||||
| reportlab | 4.2.5 | BSD License |
|
||||
| requests | 2.32.5 | Apache Software License |
|
||||
| service-identity | 24.2.0 | MIT License |
|
||||
| sqlparse | 0.5.3 | BSD License |
|
||||
| txaio | 25.9.2 | UNKNOWN |
|
||||
| typing_extensions | 4.15.0 | UNKNOWN |
|
||||
| tzdata | 2025.2 | Apache Software License |
|
||||
| urllib3 | 2.5.0 | UNKNOWN |
|
||||
| whitenoise | 6.6.0 | MIT License |
|
||||
| zope.interface | 8.0.1 | Zope Public License |
|
||||
@@ -1,44 +0,0 @@
|
||||
| Name | Version | License |
|
||||
|--------------------|--------------|---------------------------------------------------------|
|
||||
| Automat | 25.4.16 | MIT License |
|
||||
| Django | 5.1.13 | BSD License |
|
||||
| Twisted | 25.5.0 | MIT License |
|
||||
| asgiref | 3.10.0 | BSD License |
|
||||
| attrs | 25.4.0 | UNKNOWN |
|
||||
| autobahn | 25.10.1 | UNKNOWN |
|
||||
| certifi | 2025.10.5 | Mozilla Public License 2.0 (MPL 2.0) |
|
||||
| cffi | 2.0.0 | UNKNOWN |
|
||||
| channels | 4.2.2 | BSD License |
|
||||
| channels_redis | 4.3.0 | BSD |
|
||||
| chardet | 5.2.0 | GNU Lesser General Public License v2 or later (LGPLv2+) |
|
||||
| charset-normalizer | 3.4.4 | MIT |
|
||||
| colorama | 0.4.6 | BSD License |
|
||||
| constantly | 23.10.4 | MIT License |
|
||||
| cryptography | 46.0.3 | UNKNOWN |
|
||||
| daphne | 4.1.2 | BSD License |
|
||||
| django-cleanup | 9.0.0 | MIT License |
|
||||
| geographiclib | 2.1 | MIT |
|
||||
| geopy | 2.4.1 | MIT License |
|
||||
| hyperlink | 21.0.0 | MIT License |
|
||||
| idna | 3.11 | UNKNOWN |
|
||||
| incremental | 24.7.2 | MIT License |
|
||||
| msgpack | 1.1.2 | UNKNOWN |
|
||||
| pillow | 11.1.0 | CMU License (MIT-CMU) |
|
||||
| pyOpenSSL | 25.3.0 | Apache Software License |
|
||||
| pyasn1 | 0.6.1 | BSD License |
|
||||
| pyasn1_modules | 0.4.2 | BSD License |
|
||||
| pycparser | 2.23 | BSD License |
|
||||
| pypng | 0.20220715.0 | MIT License |
|
||||
| python-dotenv | 1.1.1 | BSD License |
|
||||
| qrcode | 7.4.2 | BSD License |
|
||||
| redis | 6.4.0 | MIT License |
|
||||
| reportlab | 4.2.5 | BSD License |
|
||||
| requests | 2.32.5 | Apache Software License |
|
||||
| service-identity | 24.2.0 | MIT License |
|
||||
| sqlparse | 0.5.3 | BSD License |
|
||||
| txaio | 25.9.2 | UNKNOWN |
|
||||
| typing_extensions | 4.15.0 | UNKNOWN |
|
||||
| tzdata | 2025.2 | Apache Software License |
|
||||
| urllib3 | 2.5.0 | UNKNOWN |
|
||||
| whitenoise | 6.6.0 | MIT License |
|
||||
| zope.interface | 8.0.1 | Zope Public License |
|
||||
@@ -7,14 +7,20 @@ from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
from library.models import QivipQuiz, QivipQuestion
|
||||
|
||||
from django.db.models import Count
|
||||
|
||||
# Create your views here.
|
||||
@login_required
|
||||
def home(request):
|
||||
own_critisized_quizzes = QivipQuiz.objects.annotate(
|
||||
criticism_count=Count('criticism_quiz')
|
||||
).filter(criticism_count__gt=0).filter(user_id=request.user)
|
||||
|
||||
|
||||
my_quizzes_number=QivipQuiz.objects.filter(user_id=request.user).count()
|
||||
my_questions_number = QivipQuestion.objects.filter(quiz_id__user_id=request.user).count()
|
||||
context = {
|
||||
'own_critisized_quizzes':own_critisized_quizzes,
|
||||
'my_quizzes_number': my_quizzes_number,
|
||||
'my_questions_number': my_questions_number,
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ INSTALLED_APPS = [
|
||||
'channels',
|
||||
'django_cleanup',
|
||||
'django.contrib.humanize',
|
||||
'dal',
|
||||
'dal_select2',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from django.contrib import admin
|
||||
from .models import QivipFaq
|
||||
from .models import QivipFaq, QivipDescription
|
||||
# Register your models here.
|
||||
class QivipFaqAdmin(admin.ModelAdmin):
|
||||
list_display = ('question','answer',)
|
||||
|
||||
class QivipDescriptionAdmin(admin.ModelAdmin):
|
||||
list_display = ('title','description',)
|
||||
|
||||
# Registrierung der Modelle im Admin
|
||||
admin.site.register(QivipFaq, QivipFaqAdmin)
|
||||
admin.site.register(QivipFaq, QivipFaqAdmin)
|
||||
admin.site.register(QivipDescription, QivipDescriptionAdmin)
|
||||
21
django/homepage/migrations/0004_qivipinfos.py
Normal file
21
django/homepage/migrations/0004_qivipinfos.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 17:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homepage', '0003_alter_qivipfaq_answer_alter_qivipfaq_question'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QivipInfos',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.TextField(max_length=200)),
|
||||
('description', models.TextField(max_length=1000)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 17:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homepage', '0004_qivipinfos'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='QivipInfos',
|
||||
new_name='QivipInfo',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 17:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homepage', '0005_rename_qivipinfos_qivipinfo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QivipDescription',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.TextField(help_text='Funktion von qivip', max_length=200)),
|
||||
('description', models.TextField(help_text='Beschreibung derFunktion von qivip', max_length=1000)),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='QivipInfo',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 17:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homepage', '0006_qivipdescription_delete_qivipinfo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qivipdescription',
|
||||
name='description',
|
||||
field=models.TextField(help_text='Beschreibung der Funktion von qivip', max_length=1000),
|
||||
),
|
||||
]
|
||||
@@ -3,4 +3,8 @@ from django.db import models
|
||||
# Create your models here.
|
||||
class QivipFaq(models.Model):
|
||||
question = models.TextField(max_length=200)
|
||||
answer = models.TextField(max_length=1000)
|
||||
answer = models.TextField(max_length=1000)
|
||||
|
||||
class QivipDescription(models.Model): # Was kann man mit qivip machen ...
|
||||
title = models.TextField(max_length=200, help_text="Funktion von qivip")
|
||||
description= models.TextField(max_length=1000, help_text="Beschreibung der Funktion von qivip")
|
||||
@@ -1,9 +1,10 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from .models import QivipFaq
|
||||
from .models import QivipFaq, QivipDescription
|
||||
|
||||
def home(request):
|
||||
faqs=QivipFaq.objects.all()
|
||||
context={"faqs": faqs}
|
||||
descriptions=QivipDescription.objects.all()
|
||||
context={"faqs": faqs, "descriptions": descriptions}
|
||||
return render(request, 'homepage/home.html', context)
|
||||
|
||||
def impress(request):
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
from django.contrib import admin
|
||||
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizCategory, QuizImage, QuizRating
|
||||
from .models import QivipQuiz, QivipQuestion,QivipQuizFavorite, QuestionImage, QuizCategory, QuizImage, QuizRating, QuizCriticism
|
||||
|
||||
# Für das QivipQuiz Modell
|
||||
class QivipQuizAdmin(admin.ModelAdmin):
|
||||
list_display = ('name','published_at', 'user_id', 'status', 'category', 'creation_date', 'update_date') # Welche Felder sollen in der Übersicht angezeigt werden
|
||||
search_fields = ('name', 'user_id__username', 'category__name') # Suchfelder
|
||||
list_filter = ('status', 'category') # Filteroptionen
|
||||
autocomplete_fields = ("reference_quizzes",)
|
||||
|
||||
|
||||
class QuizCriticismAdmin(admin.ModelAdmin):
|
||||
list_display = ('quiz','question_criticism', 'user','criticism') # Welche Felder sollen in der Übersicht angezeigt werden
|
||||
search_fields = ('quiz','question_criticism', 'user','criticism') # Suchfelder
|
||||
list_filter=('quiz','question_criticism', 'user','criticism')
|
||||
|
||||
|
||||
|
||||
|
||||
# Für das QivipQuestion Modell
|
||||
class QivipQuestionAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'quiz_id', 'data', 'creation_date', 'update_date', 'credits')
|
||||
@@ -58,7 +69,7 @@ class QuizCategoryAdmin(admin.ModelAdmin):
|
||||
# Registrierung der Modelle im Admin
|
||||
admin.site.register(QivipQuiz, QivipQuizAdmin)
|
||||
admin.site.register(QivipQuestion, QivipQuestionAdmin)
|
||||
#admin.site.register(QuizCategory, QuizCategoryAdmin)
|
||||
admin.site.register(QuizCriticism, QuizCriticismAdmin)
|
||||
admin.site.register(QivipQuizFavorite, QivipQuizFavoriteAdmin)
|
||||
admin.site.register(QuizImage, QuizImageAdmin)
|
||||
admin.site.register(QuestionImage, QuestionImageAdmin)
|
||||
@@ -1,17 +1,31 @@
|
||||
from django import forms
|
||||
from .models import QivipQuiz, QivipQuestion, QuizCategory
|
||||
from dal import autocomplete
|
||||
|
||||
class QuizForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = QivipQuiz
|
||||
fields = ['name', 'description', 'status', 'category','difficulty','credits']
|
||||
fields = ['name', 'description', 'status', 'category','difficulty','reference_quizzes','credits']
|
||||
widgets = {
|
||||
'reference_quizzes': autocomplete.ModelSelect2Multiple(
|
||||
url='library:quiz-autocomplete',
|
||||
forward=['pk']
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(QuizForm, self).__init__(*args, **kwargs)
|
||||
for field in self.fields.values():
|
||||
field.widget.attrs.update({
|
||||
'class': 'dark:bg-[#2a2f3a]! dark:text-white!'
|
||||
|
||||
})
|
||||
self.fields["reference_quizzes"].queryset = QivipQuiz.objects.filter(
|
||||
status="öffentlich").exclude(pk=self.instance.pk)
|
||||
self.fields["reference_quizzes"].label_from_instance = lambda obj: f"{obj.name} (ID: {obj.pk}) (User: {obj.user_id})"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
18
django/library/migrations/0060_qivipquiz_similar_quizzes.py
Normal file
18
django/library/migrations/0060_qivipquiz_similar_quizzes.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 18:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0059_remove_qivipquiz_single_player_or_multiple'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='qivipquiz',
|
||||
name='similar_quizzes',
|
||||
field=models.ManyToManyField(blank=True, related_name='similar_to', to='library.qivipquiz'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 18:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0060_qivipquiz_similar_quizzes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qivipquiz',
|
||||
name='similar_quizzes',
|
||||
field=models.ManyToManyField(blank=True, to='library.qivipquiz'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 19:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0061_alter_qivipquiz_similar_quizzes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qivipquiz',
|
||||
name='similar_quizzes',
|
||||
field=models.ManyToManyField(blank=True, to='library.qivipquiz', verbose_name='Verweis auf andere Quizze'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-03 11:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0062_alter_qivipquiz_similar_quizzes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='qivipquiz',
|
||||
old_name='similar_quizzes',
|
||||
new_name='reference_quizzes',
|
||||
),
|
||||
]
|
||||
29
django/library/migrations/0064_quizcriticism.py
Normal file
29
django/library/migrations/0064_quizcriticism.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-30 13:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0063_rename_similar_quizzes_qivipquiz_reference_quizzes'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QuizCriticism',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('criticism', models.CharField(max_length=256, verbose_name='Kritik')),
|
||||
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='criticism_question', to='library.qivipquestion')),
|
||||
('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='criticism_quiz', to='library.qivipquiz')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('quiz', 'user', 'question')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-30 13:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0064_quizcriticism'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcriticism',
|
||||
name='creation_date',
|
||||
field=models.DateTimeField(auto_now_add=True, default='2025-12-30 12:00:00'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-30 13:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0065_quizcriticism_creation_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='quizcriticism',
|
||||
name='creation_date',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-30 14:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0066_remove_quizcriticism_creation_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcriticism',
|
||||
name='creation_date',
|
||||
field=models.DateTimeField(auto_now_add=True, default='2025-12-30 12:00:00'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-31 11:56
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0067_quizcriticism_creation_date'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='quizcriticism',
|
||||
old_name='question',
|
||||
new_name='question_criticism',
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='quizcriticism',
|
||||
unique_together={('quiz', 'user', 'question_criticism', 'criticism')},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-31 12:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0068_rename_question_quizcriticism_question_criticism_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='quizcriticism',
|
||||
name='question_criticism',
|
||||
field=models.CharField(max_length=1024),
|
||||
),
|
||||
]
|
||||
19
django/library/migrations/0070_quizcriticism_question.py
Normal file
19
django/library/migrations/0070_quizcriticism_question.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-31 14:03
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0069_alter_quizcriticism_question_criticism'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcriticism',
|
||||
name='question',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='library.qivipquestion'),
|
||||
),
|
||||
]
|
||||
18
django/library/migrations/0071_quizcriticism_id_question.py
Normal file
18
django/library/migrations/0071_quizcriticism_id_question.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-31 14:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0070_quizcriticism_question'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcriticism',
|
||||
name='id_question',
|
||||
field=models.IntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.1.7 on 2026-01-17 11:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0071_quizcriticism_id_question'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='qivipquestion',
|
||||
options={'ordering': ['order']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='qivipquestion',
|
||||
name='order',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -43,6 +43,12 @@ class QivipQuiz(models.Model):
|
||||
on_delete=models.PROTECT,
|
||||
related_name="quizzes"
|
||||
)
|
||||
reference_quizzes = models.ManyToManyField(
|
||||
'self',
|
||||
blank=True,
|
||||
symmetrical=False,
|
||||
verbose_name="Verweis auf andere Quizze"
|
||||
)
|
||||
|
||||
|
||||
quiz_played_Lernmodus = models.IntegerField(null=True, blank=True, default=0)
|
||||
@@ -51,7 +57,6 @@ class QivipQuiz(models.Model):
|
||||
|
||||
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz')
|
||||
#creator = models.ForeignKey(User, on_delete=models.SET_NULL,blank=True, null=True, related_name='creator_quiz')
|
||||
base_quiz = models.ForeignKey('self', on_delete=models.SET_NULL,blank=True, null=True, related_name='child_quizzes')
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
update_date = models.DateTimeField(auto_now=True)
|
||||
@@ -81,6 +86,7 @@ class QivipQuizFavorite(models.Model):
|
||||
unique_together = ('user', 'quiz') # Sicherstellen, dass es nur ein Favorit pro User und Quiz gibt
|
||||
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class QivipQuestion(models.Model):
|
||||
|
||||
@@ -107,10 +113,29 @@ class QivipQuestion(models.Model):
|
||||
related_name="questions"
|
||||
)
|
||||
credits=models.TextField(blank=True, editable=True)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order'] # Fragen nach Reihenfolge sortieren
|
||||
|
||||
def __str__(self):
|
||||
return self.data[:50]
|
||||
|
||||
class QuizCriticism(models.Model):
|
||||
quiz= models.ForeignKey(QivipQuiz, on_delete=models.CASCADE, related_name='criticism_quiz')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
question_criticism=models.CharField(max_length=1024)
|
||||
criticism=models.CharField(max_length=256, verbose_name="Kritik")
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
question = models.ForeignKey(QivipQuestion, on_delete=models.CASCADE,null=True, blank=True)
|
||||
id_question=models.IntegerField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('quiz', 'user', 'question_criticism','criticism')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.question_criticism}"
|
||||
|
||||
|
||||
|
||||
class QuizRating(models.Model):
|
||||
|
||||
@@ -3,13 +3,13 @@ from django.urls import path
|
||||
from django.conf import settings
|
||||
from . import views
|
||||
from django.conf.urls.static import static
|
||||
from .views import QuizAutocomplete
|
||||
app_name = 'library'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.overview_quiz, name='overview_quiz'),
|
||||
path('my/', views.overview_quiz_my, name='overview_quiz_my'),
|
||||
path('favorites/', views.overview_quiz_favorites, name='overview_quiz_favorites'),
|
||||
|
||||
path('new/', views.new_quiz, name='new_quiz'),
|
||||
path('edit/<int:pk>/', views.edit_quiz, name='edit_quiz'),
|
||||
path('delete/<int:pk>/', views.delete_quiz, name='delete_quiz'),
|
||||
@@ -19,8 +19,16 @@ urlpatterns = [
|
||||
path('question/delete/<int:pk>/', views.delete_question, name='delete_question'),
|
||||
path('detail/copy/<int:pk>/', views.copy_quiz, name='copy_quiz'),
|
||||
path('favorite_quiz/<int:pk>/', views.favorite_quiz, name='favorite_quiz'),
|
||||
path('critique/delete/<int:critique_id>/<int:quiz_id>', views.delete_critique, name='delete_critique'),
|
||||
path('quiz/reorder/', views.reorder_questions, name='reorder_questions'),
|
||||
|
||||
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(
|
||||
"autocomplete/quiz",
|
||||
QuizAutocomplete.as_view(),
|
||||
name="quiz-autocomplete"
|
||||
),
|
||||
|
||||
|
||||
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from django.contrib import messages
|
||||
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizImage
|
||||
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizCriticism, QuizImage
|
||||
from .forms import QuizForm, QuestionForm
|
||||
import json
|
||||
from django.core.paginator import Paginator
|
||||
@@ -40,305 +40,37 @@ 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)
|
||||
from .models import QivipQuiz
|
||||
from django.db.models import Value, CharField
|
||||
from django.db.models.functions import Concat
|
||||
# views.py
|
||||
from dal import autocomplete
|
||||
from .models import QivipQuiz
|
||||
|
||||
class QuizAutocomplete(autocomplete.Select2QuerySetView):
|
||||
|
||||
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
||||
def get_queryset(self):
|
||||
qs = QivipQuiz.objects.filter(status="öffentlich")
|
||||
|
||||
pk = self.forwarded.get('pk', None)
|
||||
if pk:
|
||||
qs = qs.exclude(pk=pk)
|
||||
|
||||
for question in questions:
|
||||
if question.data:
|
||||
question.data = json.loads(question.data)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = f'attachment; filename="Lösungen - {quiz.name}.pdf"'
|
||||
|
||||
doc = SimpleDocTemplate(response, pagesize=A4,
|
||||
rightMargin=40, leftMargin=40,
|
||||
topMargin=60, bottomMargin=60)
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Stil für Quiz-Titel
|
||||
title_style = ParagraphStyle(
|
||||
'TitleStyle',
|
||||
parent=styles['Title'],
|
||||
alignment=1, # zentriert
|
||||
fontSize=14,
|
||||
spaceAfter=2,
|
||||
)
|
||||
|
||||
# Stil für Fragen-Box
|
||||
question_box_style = ParagraphStyle(
|
||||
'QuestionBox',
|
||||
fontSize=10,
|
||||
leading=16,
|
||||
fontName='Helvetica-Bold',
|
||||
|
||||
|
||||
spaceAfter=8,
|
||||
leftIndent=6,
|
||||
rightIndent=6,
|
||||
|
||||
alignment=0
|
||||
)
|
||||
|
||||
# Stil für Antwort (richtig)
|
||||
answer_correct_style = ParagraphStyle(
|
||||
'AnswerCorrect',
|
||||
fontSize=11,
|
||||
leading=14,
|
||||
textColor=colors.green,
|
||||
leftIndent=20,
|
||||
spaceAfter=4,
|
||||
)
|
||||
|
||||
# Stil für Antwort (falsch)
|
||||
answer_wrong_style = ParagraphStyle(
|
||||
'AnswerWrong',
|
||||
fontSize=11,
|
||||
leading=14,
|
||||
textColor=colors.red,
|
||||
leftIndent=20,
|
||||
spaceAfter=4,
|
||||
)
|
||||
|
||||
# Stil für normalen Antwort-Text (ohne Antwortanzeige)
|
||||
answer_neutral_style = ParagraphStyle(
|
||||
'AnswerNeutral',
|
||||
fontSize=11,
|
||||
leading=14,
|
||||
textColor=colors.black,
|
||||
leftIndent=20,
|
||||
spaceAfter=4,
|
||||
)
|
||||
date_style = ParagraphStyle(
|
||||
name='RightAligned',
|
||||
parent=styles['Normal'],
|
||||
alignment=2,
|
||||
)
|
||||
|
||||
story = []
|
||||
|
||||
story.append(Paragraph(f"Quiz: {quiz.name}", title_style))
|
||||
story.append(Paragraph(f"- Lösungen -", title_style))
|
||||
story.append(Spacer(1, 20))
|
||||
|
||||
story.append(Paragraph(datetime.today().strftime("%d.%m.%Y"), date_style))
|
||||
story.append(Spacer(1, 20))
|
||||
|
||||
if quiz.description:
|
||||
story.append(Paragraph(quiz.description, styles['Normal']))
|
||||
story.append(Spacer(1, 20))
|
||||
if self.q:
|
||||
qs = qs.annotate(
|
||||
search_text=Concat(
|
||||
'name',
|
||||
Value(' (ID: '), 'pk', Value(') (User: '), 'user_id', Value(')'),
|
||||
output_field=CharField()
|
||||
)
|
||||
).filter(search_text__icontains=self.q)
|
||||
return qs
|
||||
|
||||
|
||||
|
||||
for i, question in enumerate(questions, start=1):
|
||||
data = question.data
|
||||
frage_text = data.get('question', 'Frage nicht gefunden.')
|
||||
|
||||
story.append(Paragraph(f"Frage {i}: {frage_text}", question_box_style))
|
||||
story.append(Spacer(1, 6))
|
||||
|
||||
rl_image = None
|
||||
if hasattr(question, 'image') and question.image and question.image.image:
|
||||
image_url = request.build_absolute_uri(question.image.image.url)
|
||||
img_response = requests.get(image_url)
|
||||
if img_response.status_code == 200:
|
||||
image_data = BytesIO(img_response.content)
|
||||
pil_image = PILImage.open(image_data)
|
||||
pil_image = ImageOps.exif_transpose(pil_image)
|
||||
orig_width, orig_height = pil_image.size
|
||||
max_width = 80
|
||||
max_height = 80
|
||||
ratio = min(max_width / orig_width, max_height / orig_height)
|
||||
new_width = orig_width * ratio
|
||||
new_height = orig_height * ratio
|
||||
output = BytesIO()
|
||||
pil_image.save(output, format='JPEG')
|
||||
output.seek(0)
|
||||
|
||||
rl_image = Image(output, width=new_width, height=new_height)
|
||||
|
||||
options = data.get('options', [])
|
||||
answer_paragraphs = []
|
||||
for idx, option in enumerate(options, start=1):
|
||||
opt_text = option.get('value', '')
|
||||
if option.get('is_correct', False):
|
||||
text = f"✔ {opt_text}"
|
||||
style = answer_correct_style
|
||||
else:
|
||||
text = f"✘ {opt_text}"
|
||||
style = answer_wrong_style
|
||||
answer_paragraphs.append(Paragraph(text, style))
|
||||
|
||||
if rl_image:
|
||||
# Tabelle mit Bild links und Antworten rechts
|
||||
from reportlab.platypus import Table, TableStyle
|
||||
from reportlab.lib.units import cm
|
||||
data_table = [[rl_image, answer_paragraphs]]
|
||||
table = Table(data_table, colWidths=[6*cm, 10*cm])
|
||||
table.setStyle(TableStyle([
|
||||
('VALIGN', (0,0), (-1,-1), 'TOP'),
|
||||
('LEFTPADDING', (0,0), (-1,-1), 0),
|
||||
('RIGHTPADDING', (0,0), (-1,-1), 10),
|
||||
]))
|
||||
story.append(table)
|
||||
else:
|
||||
# Kein Bild, Antworten einfach nacheinander
|
||||
for p in answer_paragraphs:
|
||||
story.append(p)
|
||||
|
||||
story.append(Spacer(1, 12))
|
||||
story.append(HRFlowable(width="100%", thickness=1, color=colors.grey))
|
||||
story.append(Spacer(1, 12))
|
||||
current_url = request.build_absolute_uri(f"/library/detail/{quiz.pk}/")
|
||||
escaped_url = escape(current_url) # Sicherheitshalber escapen
|
||||
|
||||
link_html = f'<a href="{escaped_url}" color="blue">{escaped_url}</a>'
|
||||
story.append(Paragraph(link_html, styles['Normal']))
|
||||
def get_result_label(self, result):
|
||||
# z.B. Name + ID + User
|
||||
return f"{result.name} (ID: {result.pk}) (User: {result.user_id})"
|
||||
|
||||
|
||||
doc.build(story)
|
||||
return response
|
||||
def pdf_task(request, pk):
|
||||
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
||||
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
||||
|
||||
for question in questions:
|
||||
if question.data:
|
||||
question.data = json.loads(question.data)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = f'attachment; filename="Aufgabenblatt - {quiz.name}.pdf"'
|
||||
|
||||
doc = SimpleDocTemplate(response, pagesize=A4,
|
||||
rightMargin=40, leftMargin=40,
|
||||
topMargin=60, bottomMargin=60)
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
title_style = ParagraphStyle(
|
||||
'TitleStyle',
|
||||
parent=styles['Title'],
|
||||
alignment=1,
|
||||
fontSize=14,
|
||||
spaceAfter=2,
|
||||
fontName='Helvetica-Bold',
|
||||
)
|
||||
|
||||
question_box_style = ParagraphStyle(
|
||||
'QuestionBox',
|
||||
fontSize=10,
|
||||
leading=16,
|
||||
fontName='Helvetica-Bold',
|
||||
spaceAfter=8,
|
||||
leftIndent=6,
|
||||
rightIndent=6,
|
||||
alignment=0
|
||||
)
|
||||
|
||||
|
||||
|
||||
answer_neutral_style = ParagraphStyle(
|
||||
'AnswerNeutral',
|
||||
fontSize=11,
|
||||
leading=14,
|
||||
textColor=colors.black,
|
||||
leftIndent=20,
|
||||
spaceAfter=4,
|
||||
)
|
||||
|
||||
date_style = ParagraphStyle(
|
||||
name='RightAligned',
|
||||
parent=styles['Normal'],
|
||||
alignment=2,
|
||||
)
|
||||
|
||||
name_style = ParagraphStyle(
|
||||
name='RightAligned',
|
||||
parent=styles['Normal'],
|
||||
alignment=0,
|
||||
)
|
||||
story = []
|
||||
|
||||
# Titel & Datum
|
||||
story.append(Paragraph(f"Aufgabenblatt: {quiz.name}", title_style))
|
||||
story.append(Spacer(1, 20))
|
||||
story.append(Paragraph(datetime.today().strftime("%d.%m.%Y"), date_style))
|
||||
story.append(Spacer(1, 20))
|
||||
story.append(Paragraph("Name: ", name_style))
|
||||
story.append(Spacer(1, 20))
|
||||
|
||||
if quiz.description:
|
||||
story.append(Paragraph(quiz.description, styles['Normal']))
|
||||
story.append(Spacer(1, 20))
|
||||
|
||||
for i, question in enumerate(questions, start=1):
|
||||
data = question.data
|
||||
frage_text = data.get('question', 'Frage nicht gefunden.')
|
||||
story.append(Paragraph(f"Frage {i}: {frage_text}", question_box_style))
|
||||
story.append(Spacer(1, 6))
|
||||
|
||||
rl_image = None
|
||||
if hasattr(question, 'image') and question.image and question.image.image:
|
||||
image_url = request.build_absolute_uri(question.image.image.url)
|
||||
img_response = requests.get(image_url)
|
||||
if img_response.status_code == 200:
|
||||
image_data = BytesIO(img_response.content)
|
||||
pil_image = PILImage.open(image_data)
|
||||
pil_image = ImageOps.exif_transpose(pil_image)
|
||||
orig_width, orig_height = pil_image.size
|
||||
max_width = 80 # gleich wie bei Lösungen
|
||||
max_height = 80
|
||||
ratio = min(max_width / orig_width, max_height / orig_height)
|
||||
new_width = orig_width * ratio
|
||||
new_height = orig_height * ratio
|
||||
output = BytesIO()
|
||||
pil_image.save(output, format='JPEG')
|
||||
output.seek(0)
|
||||
rl_image = Image(output, width=new_width, height=new_height)
|
||||
|
||||
options = data.get('options', [])
|
||||
|
||||
answer_paragraphs = []
|
||||
for idx, option in enumerate(options, start=1):
|
||||
|
||||
opt_text = option.get('value', '')
|
||||
if data.get("type") !="input":
|
||||
opt_text = option.get('value', '')
|
||||
answer_paragraphs.append(Paragraph(f"{idx}. {opt_text}", answer_neutral_style))
|
||||
else:
|
||||
story.append(Paragraph("Deine Antwort: ", answer_neutral_style))
|
||||
story.append(Spacer(1, 12))
|
||||
|
||||
if rl_image:
|
||||
from reportlab.platypus import Table, TableStyle
|
||||
from reportlab.lib.units import cm
|
||||
data_table = [[rl_image, answer_paragraphs]]
|
||||
table = Table(data_table, colWidths=[6*cm, 10*cm])
|
||||
table.setStyle(TableStyle([
|
||||
('VALIGN', (0,0), (-1,-1), 'TOP'),
|
||||
('LEFTPADDING', (0,0), (-1,-1), 0),
|
||||
('RIGHTPADDING', (0,0), (-1,-1), 10),
|
||||
]))
|
||||
story.append(table)
|
||||
else:
|
||||
for p in answer_paragraphs:
|
||||
story.append(p)
|
||||
|
||||
story.append(Spacer(1, 12))
|
||||
story.append(HRFlowable(width="100%", thickness=1, color=colors.grey))
|
||||
story.append(Spacer(1, 12))
|
||||
current_url = request.build_absolute_uri(f"/library/detail/{quiz.pk}/")
|
||||
escaped_url = escape(current_url) # Sicherheitshalber escapen
|
||||
|
||||
link_html = f'<a href="{escaped_url}" color="blue">{escaped_url}</a>'
|
||||
story.append(Paragraph(link_html, styles['Normal']))
|
||||
doc.build(story)
|
||||
return response
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
def quiz_names_json(request):
|
||||
@@ -362,6 +94,17 @@ def quiz_names_json(request):
|
||||
return JsonResponse(names, safe=False)
|
||||
|
||||
|
||||
|
||||
def reorder_questions(request):
|
||||
if request.method == "POST":
|
||||
data = json.loads(request.body)
|
||||
for item in data['order']:
|
||||
QivipQuestion.objects.filter(id=item['id']).update(order=item['position'])
|
||||
return JsonResponse({'status': 'ok'})
|
||||
return JsonResponse({'status': 'error'}, status=400)
|
||||
|
||||
|
||||
|
||||
def apply_quiz_filters(queryset, form):
|
||||
|
||||
if not form.is_valid():
|
||||
@@ -399,7 +142,84 @@ def apply_quiz_filters(queryset, form):
|
||||
return queryset.filter(**filters)
|
||||
|
||||
|
||||
|
||||
def critisize_quiz(request, pk):
|
||||
|
||||
quiz= get_object_or_404(QivipQuiz, pk=pk)
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
|
||||
question_id = request.POST.get("question_id") or "whole_quiz"
|
||||
|
||||
if question_id!="whole_quiz":
|
||||
question = quiz.questions.get(id=question_id)
|
||||
data = json.loads(question.data)
|
||||
question_text = data["question"]
|
||||
|
||||
else:
|
||||
question_text="Quiz(formular) kritisiert"
|
||||
question = None
|
||||
question_id = None
|
||||
|
||||
|
||||
critic_type = request.POST.get("critic")
|
||||
CRITICISM_MAP = {
|
||||
"content_error": "inhaltlicher Fehler",
|
||||
"spelling_error": "Rechtschreibfehler oder Grammatikfehler",
|
||||
"logic_error": "umständliche oder unsinnige Formulierung",
|
||||
"inappropriate_error": "unangemessen",
|
||||
"not_completed_error": "unvollständig",
|
||||
"source_error": "Quellen unvollständig",
|
||||
"quiz_error": "Quiz als \"öffentliches Quiz\" ungeeignet"}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
exists = QuizCriticism.objects.filter(
|
||||
id_question=question_id,
|
||||
question=question,
|
||||
quiz=quiz,
|
||||
user=request.user,
|
||||
question_criticism=question_text,
|
||||
criticism=CRITICISM_MAP[critic_type],
|
||||
).exists()
|
||||
|
||||
if exists:
|
||||
messages.error(request, "Diese Kritik wurde bereits abgegeben.") # nur Kritik abgeben, wenn noch nicht erstellt
|
||||
else:
|
||||
QuizCriticism.objects.create(
|
||||
question=question,
|
||||
id_question=question_id,
|
||||
quiz=quiz,
|
||||
user=request.user,
|
||||
question_criticism=question_text,
|
||||
criticism=CRITICISM_MAP[critic_type],
|
||||
)
|
||||
|
||||
|
||||
|
||||
def delete_critique(request,critique_id,quiz_id):
|
||||
|
||||
critique = get_object_or_404(QuizCriticism, id=critique_id)
|
||||
critique.delete()
|
||||
return redirect('library:detail_quiz', pk=quiz_id)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_critisized_quizzes():
|
||||
critisized_quizzes = QivipQuiz.objects.annotate(
|
||||
criticism_count=Count('criticism_quiz')
|
||||
).filter(criticism_count__gt=0)
|
||||
return critisized_quizzes
|
||||
|
||||
|
||||
def overview_quiz(request, connect="all"):
|
||||
|
||||
critisized_quizzes = get_critisized_quizzes()
|
||||
form = QuizFilterForm(request.GET)
|
||||
|
||||
user = request.user if request.user.is_authenticated else None
|
||||
@@ -471,6 +291,7 @@ def overview_quiz(request, connect="all"):
|
||||
favorite_quizzes = paginator_fav.page(1)
|
||||
|
||||
context = {
|
||||
'critisized_quizzes':critisized_quizzes,
|
||||
'form': form,
|
||||
'my_quizzes': my_quizzes,
|
||||
'all_quizzes': all_quizzes,
|
||||
@@ -521,117 +342,6 @@ def overview_quiz_my(request):
|
||||
|
||||
def overview_quiz_favorites(request):
|
||||
return overview_quiz(request, connect="favorite")
|
||||
"""
|
||||
def overview_quiz(request):
|
||||
# Filter
|
||||
form = QuizFilterForm(request.GET)
|
||||
try:
|
||||
favorite_quizzes_data = QivipQuizFavorite.objects.filter(user=request.user, favorite=True).order_by("-favorite_date")
|
||||
favorite_quiz_ids = favorite_quizzes_data.values_list('quiz', flat=True)
|
||||
favorite_quizzes = QivipQuiz.objects.filter(id__in=favorite_quiz_ids)
|
||||
except:
|
||||
favorite_quizzes_data = QivipQuizFavorite.objects.none()
|
||||
favorite_quizzes = QivipQuizFavorite.objects.none()
|
||||
favorite_quiz_ids = favorite_quizzes.none()
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
|
||||
# Initialize querysets
|
||||
if request.user.is_authenticated:
|
||||
filter_my_quizzes = QivipQuiz.objects.filter(user_id=request.user).annotate(max_amount_questions=Count('questions'))
|
||||
favorite_quizzes = favorite_quizzes.filter(id__in=favorite_quiz_ids).annotate(max_amount_questions=Count('questions'))
|
||||
filter_all_quizzes = QivipQuiz.objects.exclude(user_id=request.user)
|
||||
else:
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
filter_my_quizzes = QivipQuiz.objects.none()
|
||||
filter_all_quizzes = QivipQuiz.objects.all()
|
||||
|
||||
# Apply common filters to all quizzes
|
||||
filter_all_quizzes = filter_all_quizzes.annotate(max_amount_questions=Count('questions'))
|
||||
filter_all_quizzes = filter_all_quizzes.filter(max_amount_questions__gte=1, status='öffentlich')
|
||||
|
||||
if form.is_valid():
|
||||
|
||||
search = form.cleaned_data.get('search')
|
||||
username= form.cleaned_data.get('user')
|
||||
max_amount_questions= form.cleaned_data.get('max_amount_questions')
|
||||
min_amount_questions= form.cleaned_data.get('min_amount_questions')
|
||||
|
||||
filters = {}
|
||||
if username:
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
filters['user_id'] = user.id
|
||||
except User.DoesNotExist:
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
filter_my_quizzes = QivipQuiz.objects.none()
|
||||
filter_all_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
if search:
|
||||
filters['name__icontains'] = search
|
||||
if min_amount_questions:
|
||||
filters['max_amount_questions__gte'] = min_amount_questions
|
||||
if max_amount_questions:
|
||||
filters['max_amount_questions__lte'] = max_amount_questions
|
||||
|
||||
try:
|
||||
filter_all_quizzes = filter_all_quizzes.filter(**filters)
|
||||
except:
|
||||
filter_all_quizzes=QivipQuiz.objects.none()
|
||||
try:
|
||||
filter_my_quizzes = filter_my_quizzes.filter(**filters)
|
||||
except:
|
||||
filter_my_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes.filter(**filters)
|
||||
except:
|
||||
favorite_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
|
||||
|
||||
# Pagination for my quizzes
|
||||
my_quizzes_paginator = Paginator(filter_my_quizzes.order_by('-creation_date'), 8) # 8 quizzes per page
|
||||
try:
|
||||
my_quizzes = my_quizzes_paginator.page(request.GET.get('my_page', 1))
|
||||
except:
|
||||
my_quizzes = my_quizzes_paginator.page(1)
|
||||
|
||||
# Pagination for all quizzes
|
||||
all_quizzes_paginator = Paginator(filter_all_quizzes.order_by('-creation_date'), 8) # 8 quizzes per page
|
||||
try:
|
||||
all_quizzes = all_quizzes_paginator.page(request.GET.get('all_page', 1))
|
||||
except:
|
||||
all_quizzes = all_quizzes_paginator.page(1)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes.filter(
|
||||
favorites__user=request.user, # Filtere nach dem User
|
||||
favorites__favorite=True # Filtere nach favorisierten Quizzes
|
||||
).order_by('-favorites__favorite_date')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# Pagination for favorite_quizzes
|
||||
favorite_quizzes_paginator = Paginator(favorite_quizzes, 8) # 9 quizzes per page
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes_paginator.page(request.GET.get('favorite_page', 1))
|
||||
except:
|
||||
favorite_quizzes = favorite_quizzes_paginator.page(1)
|
||||
|
||||
context = {
|
||||
'show_search': True,
|
||||
'favorite_quiz_ids': favorite_quiz_ids,
|
||||
'favorite_quizzes':favorite_quizzes,
|
||||
'my_quizzes': my_quizzes,
|
||||
'all_quizzes': all_quizzes,
|
||||
'form': form,
|
||||
}
|
||||
|
||||
return render(request, 'library/overview_quiz.html', context)
|
||||
"""
|
||||
|
||||
@login_required
|
||||
def favorite_quiz(request, pk):
|
||||
@@ -771,8 +481,11 @@ def delete_quiz(request, pk):
|
||||
return redirect('library:overview_quiz')
|
||||
return render(request, 'library/delete_confirmation.html', {'object': quiz})
|
||||
|
||||
|
||||
|
||||
# Quiz anzeigen
|
||||
def detail_quiz(request, pk):
|
||||
critisize_quiz(request, pk)
|
||||
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
||||
show_answers = request.GET.get('show_answers', 'false').lower() == 'true'
|
||||
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
.select2-container {
|
||||
@apply font-black!;
|
||||
}
|
||||
|
||||
|
||||
|
||||
qp-container {
|
||||
@apply max-w-xl w-full mx-auto;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="flex justify-center items-center mt-12">
|
||||
<div class="flex justify-center items-center mt-12 mx-auto px-10">
|
||||
<div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent! dark:text-white! ">
|
||||
<div class="grid place-content-center h-full mb-4 flex-col items-center justify-between">
|
||||
|
||||
@@ -17,7 +17,25 @@
|
||||
<button class="text-center text-sm w-full mt-4 font-light text-blue-600"><a href="{% url 'accounts:password_change' %}">Passwort ändern</a></button>
|
||||
<button class="text-center text-sm w-full mt-4 font-light text-red-600"><a href="{% url 'accounts:delete_account' %}">Account löschen</a></button>
|
||||
|
||||
|
||||
<!-- Kritik eigener Quizze -->
|
||||
{% if own_critisized_quizzes %}
|
||||
<div class="border-2 border-red-600 p-6 rounded-xl shadow-inner bg-red-50 flex flex-col gap-4 items-start mt-8 dark:bg-transparent">
|
||||
<div class="flex items-center gap-2 text-xl font-semibold text-red-800 dark:text-red-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="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
|
||||
</svg>
|
||||
Kritik
|
||||
</div>
|
||||
<p class="text-xs opacity-50 dark:text-white">
|
||||
Diese Quizze wurden von anderen Nutzern kritisiert, da sie als fehlerhaft oder nicht angemessen
|
||||
wahrgenommen wurden. Bitte überprüfen und verbessern Sie Ihre Quizze so schnell wie möglich.</p>
|
||||
{% for own_critisized_quiz in own_critisized_quizzes %}
|
||||
<a href="{% url 'library:detail_quiz' own_critisized_quiz.id %}"> <span class="font-bold text-blue-600 hover:underline">{{ own_critisized_quiz }}</span> </a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="border-2 border-blue-600 p-6 rounded-xl shadow-inner bg-blue-50 flex flex-col gap-4 items-start mt-8 dark:bg-transparent">
|
||||
|
||||
<div class="flex items-center gap-2 text-xl font-semibold text-blue-800 dark:text-blue-600">
|
||||
|
||||
@@ -22,6 +22,35 @@
|
||||
|
||||
</style>
|
||||
|
||||
{% if descriptions %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">Was kann qivip?</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mb-16">
|
||||
|
||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||
Mit qivip kann man:
|
||||
</p>
|
||||
|
||||
<div class="space-y-2 text-gray-700 dark:text-white ">
|
||||
{% for description in descriptions %}
|
||||
<p>
|
||||
<li class="ml-4"><span class="font-bold">{{ description.title }}</span> {{ description.description }}</li>
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% if faqs %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
|
||||
@@ -1,61 +1,83 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% load static %}
|
||||
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
||||
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="flex flex-wrap space-y-2 items-center justify-between mb-8 dark:text-white">
|
||||
<div class="container mx-auto">
|
||||
|
||||
<h1 class="text-2xl mt-4 font-bold dark:text-white trucate">
|
||||
{{ quiz.name }}
|
||||
</h1>
|
||||
|
||||
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex flex-wrap space-y-2 items-center justify-between mb-6 dark:text-white">
|
||||
<h1 class="text-2xl font-bold mr-4 dark:text-white ">{{ quiz.name }}</h1>
|
||||
|
||||
<div class=" flex flex-wrap space-x-2 ">
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}" class=" text-white px-2 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 dark:text-white">
|
||||
<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-2 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 dark:text-white">
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="{% url 'library:copy_quiz' quiz.id %}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75" />
|
||||
</svg>
|
||||
Quiz duplizieren
|
||||
</a>
|
||||
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Spielen
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="flex items-center justify-center lg:justify-start gap-4 mt-4 flex-wrap">
|
||||
|
||||
<a href="{% url 'library:copy_quiz' quiz.id %}"
|
||||
class="inline-flex bg-green-100 text-green-800 items-center py-2 px-2 gap-2 rounded-md text-sm lg:text-md hover:border-blue-600 border-2">
|
||||
duplizieren
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
|
||||
</svg>
|
||||
|
||||
</a>
|
||||
|
||||
{% if quiz.questions.count %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="inline-flex bg-blue-100 text-blue-800 items-center py-2 px-2 gap-2 rounded-md text-sm lg:text-md hover:border-blue-600 border-2">
|
||||
spielen
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 0 1 0 1.972l-11.54 6.347a1.125 1.125 0 0 1-1.667-.986V5.653Z" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if quiz.user_id == request.user %}
|
||||
<!-- Edit -->
|
||||
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="inline-flex bg-orange-100 text-orange-800 items-center py-2 px-2 gap-2 rounded-md text-sm lg:text-md hover:border-blue-600 border-2">
|
||||
bearbeiten
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<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>
|
||||
|
||||
<!-- Delete -->
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="inline-flex bg-red-100 text-red-800 items-center py-2 px-2 gap-2 rounded-md text-sm lg:text-md hover:border-blue-600 border-2">
|
||||
löschen
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{% if quiz.description %}
|
||||
<p class="text-gray-600 mb-4 dark:text-gray-400">{{ quiz.description }}</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<summary class="list-none">
|
||||
<div class="flex items-center gap-3 mt-12">
|
||||
<p class="text-lg font-bold text-blue-600 dark:text-white">Fragen des Quizzes</p>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</summary>
|
||||
|
||||
<details open>
|
||||
<div class="flex justify-end mb-4">
|
||||
{% if detail == True %}
|
||||
|
||||
@@ -85,60 +107,76 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md border-blue-600 border-2 rounded-xl shadow-md dark:!bg-transparent dark:text-white">
|
||||
<div class="border-b border-gray-200 p-4">
|
||||
<div class="flex justify-between items-center flex-wrap">
|
||||
<h2 class="text-xl font-semibold">Fragen
|
||||
{% for question in questions %}{% if forloop.last %}({{ forloop.counter }}) {% endif %} {% endfor %}
|
||||
</h2>
|
||||
<div class="flex space-x-2 flex-wrap">
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:new_question' %}?type=input&quiz_id={{ quiz.id }}"
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<a href="{% url 'library:new_question' %}?type=guess&quiz_id={{ quiz.id }}"
|
||||
class=" mt-1 bg-pink-100 text-pink-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Schätzen
|
||||
</a>
|
||||
<div class="bg-white rounded-xl shadow-md border-2 border-blue-600 dark:!bg-transparent dark:text-white">
|
||||
<div class="border-b border-gray-200 p-4">
|
||||
<div class="flex justify-between items-center flex-wrap">
|
||||
<!-- Titel -->
|
||||
|
||||
<a href="{% url 'library:new_question' %}?type=map&quiz_id={{ quiz.id }}"
|
||||
class=" mt-1 bg-orange-100 text-orange-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Karte
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="text-xl font-semibold">
|
||||
Fragen
|
||||
{% for question in questions %}{% if forloop.last %}({{ forloop.counter }}) {% endif %}{% endfor %}
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
<!-- Neue Frage Dropdown -->
|
||||
{% if quiz.user_id == request.user %}
|
||||
<div class="relative inline-block text-left mt-2 sm:mt-0">
|
||||
<button type="button" class="inline-flex justify-center w-full rounded-md border border-blue-600 shadow-sm px-4 py-2 bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" id="menu-button" aria-expanded="true" aria-haspopup="true">
|
||||
Neue Frage
|
||||
<!-- Pfeil nach unten -->
|
||||
<svg class="-mr-1 ml-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.584l3.71-4.354a.75.75 0 011.08 1.04l-4.25 5a.75.75 0 01-1.08 0l-4.25-5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 focus:outline-none hidden group" id="dropdown-menu">
|
||||
<div class="py-1 flex flex-col" role="menu" aria-orientation="vertical" aria-labelledby="menu-button">
|
||||
<a href="{% url 'library:new_question' %}?type=input&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Eingabe</a>
|
||||
<a href="{% url 'library:new_question' %}?type=multiple_choice&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Multiple Choice</a>
|
||||
<a href="{% url 'library:new_question' %}?type=true_false&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Wahr/Falsch</a>
|
||||
<a href="{% url 'library:new_question' %}?type=order&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Reihenfolge</a>
|
||||
<a href="{% url 'library:new_question' %}?type=guess&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Schätzen</a>
|
||||
<a href="{% url 'library:new_question' %}?type=map&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Karte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dropdown Toggle Script -->
|
||||
<script>
|
||||
const btn = document.getElementById('menu-button');
|
||||
const menu = document.getElementById('dropdown-menu');
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
menu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
window.addEventListener('click', (e) => {
|
||||
if (!btn.contains(e.target) && !menu.contains(e.target)) {
|
||||
menu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
{% if questions %}
|
||||
|
||||
<div class="divide-y divide-gray-200">
|
||||
<div class="divide-y divide-gray-200" id="questions-list">
|
||||
{% for question in questions %}
|
||||
|
||||
|
||||
|
||||
<div class="w-full rounded-xl p-4 hover:bg-gray-50 dark:hover:bg-[#2a2f3a] dark:text-white">
|
||||
<p>{{ forloop.counter }}.</p>
|
||||
<div data-question-id="{{ question.id }}" class="question-item w-full rounded-xl p-4 hover:bg-gray-50 dark:hover:bg-[#2a2f3a] dark:text-white">
|
||||
|
||||
{% if quiz.user_id == request.user %} <a class="block flex h-full w-full" href="{% url 'library:edit_question' question.id %}" > {% endif %}
|
||||
<div class="flex justify-between items-start">
|
||||
|
||||
<p class="question-counter pr-4 ">{{ forloop.counter }}.</p>
|
||||
<div class="flex-grow">
|
||||
<p class="font-medium">{{ question.data.question }}</p>
|
||||
<div class="mt-1">
|
||||
@@ -272,7 +310,7 @@
|
||||
{% if option.is_correct %}
|
||||
<p class="text-sm">
|
||||
<span class="text-gray-500 dark:text-white">Richtige Antwort:</span>
|
||||
<span class="ml-1 font-medium {% if option.value == "Wahr" %} text-green-700 {% else %}text-red-700{% endif %}">{{ option.value }}</span>
|
||||
<span class="ml-1 font-medium {% if option.value == 'Wahr' %} text-green-700 {% else %}text-red-700{% endif %}">{{ option.value }}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -303,7 +341,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -318,66 +356,135 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div class="flex justify-end">
|
||||
|
||||
<!-- Kritik zum Quiz -->
|
||||
<div class="max-w-full">
|
||||
{% if quiz.criticism_quiz.all %}
|
||||
|
||||
|
||||
<summary class="list-none">
|
||||
<div class="flex items-center gap-3 mt-12">
|
||||
<p class="text-lg font-bold text-red-800 dark:text-white"> Kritik zum Quiz</p>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-red-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</summary>
|
||||
<details>
|
||||
|
||||
{% for critique in quiz.criticism_quiz.all %}
|
||||
|
||||
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-[#2a2f3a] shadow-sm">
|
||||
|
||||
{% if critique.id_question != None %}
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_question' critique.id_question %}" class="text-blue-600 font-bold hover:underline">{{critique.question_criticism}}</a>:
|
||||
{% else %}
|
||||
<span class="font-bold">{{critique.question_criticism}}</span>:
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<span class="dark:text-white dark:bg-transparent">
|
||||
{{ critique.criticism }}
|
||||
</span>
|
||||
<p class="text-sm italic dark:text-white dark:bg-transparent ">
|
||||
Kritik von User {{ critique.user }}
|
||||
</p>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<p class="text-xs italic dark:text-white dark:bg-transparent ">
|
||||
{{ critique.creation_date }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if quiz.user_id == request.user %}
|
||||
<form method="POST" class="space-y-6" enctype="multipart/form-data" action="{% url 'library:delete_critique' critique.id quiz.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="flex border-t-2 border-gray-200 mt-2">
|
||||
<button type="submit" class=" mt-2
|
||||
px-4 py-2 border border-transparent shadow-sm text-sm
|
||||
font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 cursor-pointer
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Fehler von mir behoben!
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</details>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<summary class="list-none">
|
||||
<div class="flex items-center gap-3 mt-12">
|
||||
<p class="text-lg font-bold text-blue-600 dark:text-white">Informationen über das Quiz</p>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
|
||||
</summary>
|
||||
<details>
|
||||
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||
Quiz-Infos
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<div class="flex">
|
||||
{% if quiz.description %}
|
||||
<p class="text-gray-600 dark:text-white">Beschreibung: {{ quiz.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
{% if quiz.base_quiz %}
|
||||
<p class="text-gray-600 mt-2 dark:text-white">Bearbeitung:<span class="text-blue-600 font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.user_id }}">{{ quiz.user_id }}</a></span></p>
|
||||
<p class="text-gray-600 dark:text-white">Bearbeitung:<span class="font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.user_id }}">{{ quiz.user_id }}</a></span></p>
|
||||
{% else %}
|
||||
<p class="text-gray-600 mt-2 dark:text-white">Autor:<span class="text-blue-600 font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.user_id }}">{{ quiz.user_id }}</a></span></p>
|
||||
<p class="text-gray-600 dark:text-white">Autor:<span class="font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.user_id }}">{{ quiz.user_id }}</a></span></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<div class="flex">
|
||||
{% if quiz.base_quiz %}
|
||||
<p class="text-gray-600 dark:text-white ">Kopie von: <a href="{% url 'library:detail_quiz' quiz.base_quiz.id %}"><span class="text-blue-600 font-bold">{{ quiz.base_quiz}}</span></a></p>
|
||||
<p class="text-gray-600 dark:text-white ">Kopie von: <a href="{% url 'library:detail_quiz' quiz.base_quiz.id %}"><span class="font-bold">{{ quiz.base_quiz}}</span></a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<div class="flex">
|
||||
{% if quiz.base_quiz %}
|
||||
<p class="text-gray-600 dark:text-white">Autor:<span class="text-blue-600 font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.base_quiz.user_id }}">{{ quiz.base_quiz.user_id }}</a></span></p>
|
||||
<p class="text-gray-600 dark:text-white">Autor:<span class="font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.base_quiz.user_id }}">{{ quiz.base_quiz.user_id }}</a></span></p>
|
||||
{% 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">
|
||||
|
||||
<div class="flex">
|
||||
|
||||
<p class="text-gray-600 dark:text-white">Quiz ID:<span class="font-bold"> {{ quiz.pk }}</span></p>
|
||||
|
||||
|
||||
<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"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
als Aufgabenblatt herunterladen (PDF)
|
||||
</a>
|
||||
|
||||
|
||||
<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"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Lösungen herunterladen (PDF)
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
-->
|
||||
|
||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
|
||||
|
||||
{% if quiz.reference_quizzes.all %}
|
||||
<div class="flex">
|
||||
<p class="text-gray-600 dark:text-white">Verweis auf andere Quizze:
|
||||
<span class="font-bold">
|
||||
{% for reference in quiz.reference_quizzes.all %}
|
||||
<a href="{% url 'library:detail_quiz' reference.id %}">{{ reference.name}}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% empty %}
|
||||
|
||||
{% endfor %}
|
||||
</span></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<div class="flex flex-col max-w-full">
|
||||
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||
Credits
|
||||
@@ -415,7 +522,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||
Quiz-Statistik
|
||||
</p>
|
||||
@@ -439,8 +546,164 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
{% if quiz.user_id != request.user %}
|
||||
|
||||
<summary class="list-none">
|
||||
<div class="flex items-center gap-3 mt-12">
|
||||
<p class="text-lg font-bold text-red-800 dark:text-white">Das Quiz kritisieren</p>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-red-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</summary>
|
||||
<details>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-red-800 dark:text-white dark:bg-transparent ">
|
||||
Quiz kritisieren
|
||||
</p>
|
||||
<p class="text-xs opacity-50 mb-3 dark:text-white dark:bg-transparent">
|
||||
Sie haben einen Fehler in dem Quiz entdeckt oder halten eine Frage für nicht angemessen? <br>
|
||||
Dann weisen Sie den Autor gern darauf hin.
|
||||
</p>
|
||||
<select name="question_id" required
|
||||
class="w-full rounded-lg border-gray-300 p-2 dark:bg-gray-800 dark:text-white">
|
||||
<option value="">Kritik an einer bestimmten Frage oder am Quiz insgesamt?</option>
|
||||
|
||||
<option value="whole_quiz">
|
||||
allgemeine Kritik am Quiz {{ quiz.name|truncatechars:60 }}
|
||||
</option>
|
||||
|
||||
{% for question in questions %}
|
||||
<option value="{{ question.id }}">
|
||||
Frage {{ forloop.counter }}: {{ question.data.question|truncatechars:60 }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="critic" required
|
||||
class="w-full rounded-lg border-gray-300 p-2 dark:bg-gray-800 dark:text-white mb-4">
|
||||
<option value="">Was ist Ihre Kritik?</option>
|
||||
|
||||
<option value="content_error">
|
||||
inhaltlicher Fehler
|
||||
</option>
|
||||
|
||||
<option value="spelling_error">
|
||||
Rechtschreibfehler oder Grammatikfehler
|
||||
</option>
|
||||
|
||||
<option value="logic_error">
|
||||
umständliche oder unsinnige Formulierung
|
||||
</option>
|
||||
|
||||
<option value="inappropriate_error">
|
||||
unangemessen
|
||||
</option>
|
||||
<option value="not_completed_error">
|
||||
unvollständig
|
||||
</option>
|
||||
<option value="source_error">
|
||||
Quellen unvollständig
|
||||
</option>
|
||||
<option value="quiz_error">
|
||||
Quiz als "öffentliches Quiz" ungeeignet
|
||||
</option>
|
||||
|
||||
</select>
|
||||
|
||||
<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-red-600 hover:bg-red-700
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||
Abschicken
|
||||
</button>
|
||||
{% elif not user.is_authenticated %}
|
||||
|
||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-red-800 dark:text-white dark:bg-transparent ">
|
||||
Quiz kritisieren
|
||||
</p>
|
||||
<div class="text-xs opacity-50 mb-3 dark:text-white dark:bg-transparent">
|
||||
Sie haben einen Fehler in dem Quiz entdeckt oder halten eine Frage für nicht angemessen? <br>
|
||||
Dann weisen Sie den Autor gern darauf hin.
|
||||
Sie können nur Quizze anderer User kritisieren.
|
||||
</div>
|
||||
<a 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" href="{% url 'accounts:login' %}">Anmelden</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
{% if quiz.user_id == request.user %}
|
||||
<style>.sortable-drag {
|
||||
position: fixed !important;
|
||||
z-index: 99999 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
Sortable.create(document.getElementById('questions-list'), {
|
||||
animation: 300,
|
||||
swapThreshold: 0.5,
|
||||
|
||||
delay: 100,
|
||||
delayOnTouchOnly: true,
|
||||
touchStartThreshold: 6,
|
||||
|
||||
forceFallback: true,
|
||||
fallbackTolerance: 6,
|
||||
fallbackOnBody: true,
|
||||
|
||||
|
||||
|
||||
onStart: () => {
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(75);
|
||||
}
|
||||
},
|
||||
|
||||
onEnd: () => {
|
||||
|
||||
const items = Array.from(document.querySelectorAll('#questions-list .question-item'));
|
||||
|
||||
// Counter im Browser updaten
|
||||
items.forEach((el, i) => {
|
||||
const counterEl = el.querySelector('.question-counter');
|
||||
if (counterEl) counterEl.textContent = `${i+1}.`;
|
||||
});
|
||||
// Reihenfolge sammeln
|
||||
const order = Array.from(document.querySelectorAll('#questions-list .question-item'))
|
||||
.map((el, i) => ({id: el.dataset.questionId, position: i+1}));
|
||||
|
||||
|
||||
// an Django senden
|
||||
fetch("{% url 'library:reorder_questions' %}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
body: JSON.stringify({order})
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,43 +1,97 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'base.html' %} {% block content %}
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
{{ form.media }}
|
||||
<style>
|
||||
|
||||
|
||||
{% block content %}
|
||||
.select2-container {
|
||||
width: 100% !important;
|
||||
min-width: 0; /* verhindert Überlaufen bei kleinen Screens */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Das Auswahlfeld */
|
||||
.select2-selection {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Angezeigter Text im Auswahlfeld */
|
||||
.select2-selection__rendered {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.select2-selection__choice {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
<div class="container mx-auto px-4 py-4">
|
||||
<div class="max-w-lg field-group mx-auto 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" enctype="multipart/form-data" class="dark-form space-y-4">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
{% if form.instance.image %}
|
||||
<img src="{{ form.instance.image.image.url }}" style="max-width: 100px;" alt="Bild der Frage">
|
||||
<label for="reset_image">
|
||||
<input type="checkbox" name="reset_image" id="reset_image">
|
||||
Bild zurücksetzen
|
||||
</label>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="quiz_image">Bild:</label>
|
||||
<input class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent" type="file" name="quiz_image" id="quiz_image">
|
||||
</div>
|
||||
|
||||
<div class="text-center text-gray-600 text-sm dislay-flex align-bottom">
|
||||
<label class=''>Mit der Erstellung von diesem Quiz stimmen Sie der <a class="text-blue-600" href="/privacy">Datenschutzerklärung </a> zu.</label>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center space-x-3">
|
||||
<a href="{% if object.quiz_id %}{% url 'library:detail_quiz' object.quiz_id.id %}{% else %}{% url 'library:overview_quiz' %}{% endif %}"
|
||||
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">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</style>
|
||||
|
||||
<input type="hidden" name="pk" value="{{ form.instance.pk }}" />
|
||||
|
||||
<div class="container mx-auto px-4 py-4">
|
||||
<div
|
||||
class="max-w-lg field-group mx-auto 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"
|
||||
enctype="multipart/form-data"
|
||||
class="dark-form space-y-4"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }} {% if form.instance.image %}
|
||||
<img
|
||||
src="{{ form.instance.image.image.url }}"
|
||||
style="max-width: 100px"
|
||||
alt="Bild der Frage"
|
||||
/>
|
||||
<label for="reset_image">
|
||||
<input type="checkbox" name="reset_image" id="reset_image" />
|
||||
Bild zurücksetzen
|
||||
</label>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="quiz_image">Bild:</label>
|
||||
<input
|
||||
class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent"
|
||||
type="file"
|
||||
name="quiz_image"
|
||||
id="quiz_image"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-gray-600 text-sm dislay-flex align-bottom">
|
||||
<label class=""
|
||||
>Mit der Erstellung von diesem Quiz stimmen Sie der
|
||||
<a class="text-blue-600" href="/privacy">Datenschutzerklärung </a>
|
||||
zu.</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center space-x-3">
|
||||
<a
|
||||
href="{% if object.quiz_id %}{% url 'library:detail_quiz' object.quiz_id.id %}{% else %}{% url 'library:overview_quiz' %}{% endif %}"
|
||||
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"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,672 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
<!-- Die gesamte Seite wird eigentlich nicht mehr genutzt. WICHTIG: ggf. kann Code aber hiervon noch genutzt werden. -->
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
|
||||
|
||||
.custom-outline {
|
||||
-webkit-text-stroke: 0.2px rgb(0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<button id="filterButton" class="p-2 hover:bg-gray-100 rounded-lg transition-colors duration-200 flex items-center gap-2 text-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||||
</svg>
|
||||
<span>Filter</span>
|
||||
</button>
|
||||
<div class="flex gap-2">
|
||||
<a href="{% url 'library:overview_quiz' %}" class="inline-flex items-center px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
Zurücksetzen
|
||||
</a>
|
||||
<a href="{% url 'library:new_quiz' %}" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Neues Quiz
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filterPanel" class="container mx-auto px-4">
|
||||
<div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
|
||||
<form action="{% url 'library:overview_quiz' %}" method="get" class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<input type="hidden" name="filter" value="1">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">minimale Anzahl an Fragen</label>
|
||||
<input type="number" name="min_amount_questions" class="border-2 border-gray-300 rounded-lg p-2 w-full" min="1" id="id_min_amount_questions" value="{{ form.min_amount_questions.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">maximale Anzahl an Fragen</label>
|
||||
<input type="number" name="max_amount_questions" class="border-2 border-gray-300 rounded-lg p-2 w-full" min="1" id="id_max_amount_questions" value="{{ form.max_amount_questions.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">veröffentlicht von User</label>
|
||||
<input type="text" name="user" class="border-2 border-gray-300 rounded-lg p-2 w-full" id="id_user" value="{{ form.user.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="md:col-span-3 flex justify-end">
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"></path>
|
||||
</svg>
|
||||
Filtern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="my-quizzes" class="text-2xl font-bold text-gray-900">Meine Quizze</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in my_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
||||
<!-- Hintergrundbild -->
|
||||
<div class="absolute inset-0 bg-cover bg-center"
|
||||
style="background-image: url({{ quiz.image.image.url }});">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% else %}
|
||||
<div class="h-full bg-gradient-to-r from-blue-500 to-blue-600 flex items-center justify-center font-black text-white text-4xl px-4 overflow-auto break-words hyphens-auto ">{{ quiz.name }}</div>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.id not in favorite_quiz_ids %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
<!-- Difficulty -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
<div class="flex items-center gap-1">
|
||||
{% if quiz.rating_count > 0 %}
|
||||
{% for i in '12345'|make_list %}
|
||||
{% if forloop.counter <= quiz.average_rating|floatformat:0|add:0 %}
|
||||
<span class="text-yellow-500 text-base">★</span>
|
||||
{% else %}
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authors -->
|
||||
{% if quiz.authors.all %}
|
||||
<div class="flex items-center gap-2 text-sm mt-2">
|
||||
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-600">
|
||||
{% for author in quiz.authors.all %}
|
||||
{{ author.username }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-between items-center gap-2 mt-3 border-t pt-3">
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Spielen
|
||||
</a>
|
||||
{% else %}
|
||||
Fürs Spielen ist min. 1 Frage nötig!
|
||||
{% endif %}
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</a>
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" 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>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Pagination für Meine Quizze -->
|
||||
{% if my_quizzes.paginator.num_pages > 1 %}
|
||||
<div class="flex justify-center space-x-2 mt-6 mb-8">
|
||||
{% if my_quizzes.has_previous %}
|
||||
<a href="?my_page={{ my_quizzes.previous_page_number }}{% if request.GET.all_page %}&all_page={{ request.GET.all_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ my_quizzes.number }} von {{ my_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if my_quizzes.has_next %}
|
||||
<a href="?my_page={{ my_quizzes.next_page_number }}{% if request.GET.all_page %}&all_page={{ request.GET.all_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="my-quizzes" class="text-2xl font-bold text-gray-900">Meine Favoriten</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in favorite_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
||||
<!-- Hintergrundbild -->
|
||||
<div class="absolute inset-0 bg-cover bg-center"
|
||||
style="background-image: url({{ quiz.image.image.url }});">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% else %}
|
||||
<div class="h-full bg-gradient-to-r from-blue-500 to-blue-600 flex items-center justify-center font-black text-white text-4xl px-4 overflow-auto break-words hyphens-auto ">{{ quiz.name }}</div>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.favorite == False %}
|
||||
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
<!-- Difficulty -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
<div class="flex items-center gap-1">
|
||||
{% if quiz.rating_count > 0 %}
|
||||
{% for i in '12345'|make_list %}
|
||||
{% if forloop.counter <= quiz.average_rating|floatformat:0|add:0 %}
|
||||
<span class="text-yellow-500 text-base">★</span>
|
||||
{% else %}
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authors -->
|
||||
{% if quiz.authors.all %}
|
||||
<div class="flex items-center gap-2 text-sm mt-2">
|
||||
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-600">
|
||||
{% for author in quiz.authors.all %}
|
||||
{{ author.username }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-between items-center gap-2 mt-3 border-t pt-3">
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Spielen
|
||||
</a>
|
||||
{% else %}
|
||||
Fürs Spielen ist min. 1 Frage nötig!
|
||||
{% endif %}
|
||||
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</a>
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" 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>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Pagination für Meine Quizze -->
|
||||
{% if favorite_quizzes.paginator.num_pages > 1 %}
|
||||
<div class="flex justify-center space-x-2 mt-6 mb-8">
|
||||
{% if favorite_quizzes.has_previous %}
|
||||
<a href="?my_page={{ favorite.previous_page_number }}{% if request.GET.all_page %}&all_page={{ request.GET.all_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ favorite_quizzes.number }} von {{ favorite_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if favorite_quizzes.has_next %}
|
||||
<a href="?favorite_page={{ favorite_quizzes.next_page_number }}{% if request.GET.all_page %}&all_page={{ request.GET.all_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Alle -->
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="all-quizzes" class="text-2xl font-bold text-gray-900">Alle</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if all_quizzes %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in all_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
||||
<!-- Hintergrundbild -->
|
||||
<div class="absolute inset-0 bg-cover bg-center"
|
||||
style="background-image: url({{ quiz.image.image.url }});">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% else %}
|
||||
<div class="h-full bg-gradient-to-r from-blue-500 to-blue-600 flex items-center justify-center font-black text-white text-4xl px-4 overflow-auto break-words hyphens-auto ">{{ quiz.name }}</div>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.id not in favorite_quiz_ids %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
<!-- Difficulty -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
<div class="flex items-center gap-1">
|
||||
{% if quiz.rating_count > 0 %}
|
||||
{% for i in '12345'|make_list %}
|
||||
{% if forloop.counter <= quiz.average_rating|floatformat:0|add:0 %}
|
||||
<span class="text-yellow-500 text-base">★</span>
|
||||
{% else %}
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authors -->
|
||||
{% if quiz.authors.all %}
|
||||
<div class="flex items-center gap-2 text-sm mt-2">
|
||||
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-600">
|
||||
{% for author in quiz.authors.all %}
|
||||
{{ author.username }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-between items-center gap-2 mt-3 border-t pt-3">
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Spielen
|
||||
</a>
|
||||
{% else %}
|
||||
Fürs Spielen ist min. 1 Frage nötig!
|
||||
{% endif %}
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination für andere Quiz -->
|
||||
{% if all_quizzes.paginator.num_pages > 1 %}
|
||||
<div class="flex justify-center space-x-2 mt-6 mb-8">
|
||||
{% if all_quizzes.has_previous %}
|
||||
<a href="?all_page={{ all_quizzes.previous_page_number }}{% if request.GET.my_page %}&my_page={{ request.GET.my_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ all_quizzes.number }} von {{ all_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if all_quizzes.has_next %}
|
||||
<a href="?all_page={{ all_quizzes.next_page_number }}{% if request.GET.my_page %}&my_page={{ request.GET.my_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-4">
|
||||
<p class="text-gray-600 text-center">Keine öffentlichen Quiz gefunden.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('filter') === '1') {
|
||||
document.getElementById('filterPanel').classList.add('show');
|
||||
}
|
||||
|
||||
document.getElementById('filterButton').addEventListener('click', function() {
|
||||
document.getElementById('filterPanel').classList.toggle('show');
|
||||
|
||||
// URL beim Öffnen aktualisieren
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (document.getElementById('filterPanel').classList.contains('show')) {
|
||||
params.set('filter', '1');
|
||||
} else {
|
||||
params.delete('filter');
|
||||
}
|
||||
history.replaceState(null, '', `${location.pathname}?${params}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (window.location.pathname.includes("/library/")) {
|
||||
window.addEventListener("beforeunload", function () {
|
||||
localStorage.setItem("scroll-position", window.scrollY);
|
||||
});
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
const scrollPos = localStorage.getItem("scroll-position");
|
||||
if (scrollPos !== null) {
|
||||
window.scrollTo(0, parseInt(scrollPos));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock content %}
|
||||
@@ -60,7 +60,7 @@
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in all_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden flex flex-col">
|
||||
<article class="{% if quiz in critisized_quizzes %} border-4 border-red-600 {% endif %} relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
@@ -111,6 +111,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3 dark:bg-[#2a2f3a] dark:text-white" >
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in favorite_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col ">
|
||||
<article class="{% if quiz in critisized_quizzes %} border-4 border-red-600 {% endif %} relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col ">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0 ">
|
||||
{% if quiz.image %}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in my_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<article class="{% if quiz in critisized_quizzes %} border-4 border-red-600 {% endif %} relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
|
||||
@@ -14,3 +14,4 @@ pip-licenses ~= 5.0.0
|
||||
requests
|
||||
requests
|
||||
geopy
|
||||
django-autocomplete-light
|
||||
|
||||
0
temp-498.rdb
Normal file
0
temp-498.rdb
Normal file
Reference in New Issue
Block a user