Resolve "Idee: QUIZ "kritisieren"" #288
@@ -7,14 +7,20 @@ from django.contrib.auth import update_session_auth_hash
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
from library.models import QivipQuiz, QivipQuestion
|
from library.models import QivipQuiz, QivipQuestion
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
@login_required
|
@login_required
|
||||||
def home(request):
|
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_quizzes_number=QivipQuiz.objects.filter(user_id=request.user).count()
|
||||||
my_questions_number = QivipQuestion.objects.filter(quiz_id__user_id=request.user).count()
|
my_questions_number = QivipQuestion.objects.filter(quiz_id__user_id=request.user).count()
|
||||||
context = {
|
context = {
|
||||||
|
'own_critisized_quizzes':own_critisized_quizzes,
|
||||||
'my_quizzes_number': my_quizzes_number,
|
'my_quizzes_number': my_quizzes_number,
|
||||||
'my_questions_number': my_questions_number,
|
'my_questions_number': my_questions_number,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
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
|
# Für das QivipQuiz Modell
|
||||||
class QivipQuizAdmin(admin.ModelAdmin):
|
class QivipQuizAdmin(admin.ModelAdmin):
|
||||||
@@ -9,6 +9,11 @@ class QivipQuizAdmin(admin.ModelAdmin):
|
|||||||
autocomplete_fields = ("reference_quizzes",)
|
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')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -64,7 +69,7 @@ class QuizCategoryAdmin(admin.ModelAdmin):
|
|||||||
# Registrierung der Modelle im Admin
|
# Registrierung der Modelle im Admin
|
||||||
admin.site.register(QivipQuiz, QivipQuizAdmin)
|
admin.site.register(QivipQuiz, QivipQuizAdmin)
|
||||||
admin.site.register(QivipQuestion, QivipQuestionAdmin)
|
admin.site.register(QivipQuestion, QivipQuestionAdmin)
|
||||||
#admin.site.register(QuizCategory, QuizCategoryAdmin)
|
admin.site.register(QuizCriticism, QuizCriticismAdmin)
|
||||||
admin.site.register(QivipQuizFavorite, QivipQuizFavoriteAdmin)
|
admin.site.register(QivipQuizFavorite, QivipQuizFavoriteAdmin)
|
||||||
admin.site.register(QuizImage, QuizImageAdmin)
|
admin.site.register(QuizImage, QuizImageAdmin)
|
||||||
admin.site.register(QuestionImage, QuestionImageAdmin)
|
admin.site.register(QuestionImage, QuestionImageAdmin)
|
||||||
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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -57,7 +57,6 @@ class QivipQuiz(models.Model):
|
|||||||
|
|
||||||
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||||
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz')
|
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz')
|
||||||
#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')
|
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)
|
creation_date = models.DateTimeField(auto_now_add=True)
|
||||||
update_date = models.DateTimeField(auto_now=True)
|
update_date = models.DateTimeField(auto_now=True)
|
||||||
@@ -87,6 +86,7 @@ class QivipQuizFavorite(models.Model):
|
|||||||
unique_together = ('user', 'quiz') # Sicherstellen, dass es nur ein Favorit pro User und Quiz gibt
|
unique_together = ('user', 'quiz') # Sicherstellen, dass es nur ein Favorit pro User und Quiz gibt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
class QivipQuestion(models.Model):
|
class QivipQuestion(models.Model):
|
||||||
|
|
||||||
@@ -117,6 +117,21 @@ class QivipQuestion(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.data[:50]
|
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):
|
class QuizRating(models.Model):
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ urlpatterns = [
|
|||||||
path('', views.overview_quiz, name='overview_quiz'),
|
path('', views.overview_quiz, name='overview_quiz'),
|
||||||
path('my/', views.overview_quiz_my, name='overview_quiz_my'),
|
path('my/', views.overview_quiz_my, name='overview_quiz_my'),
|
||||||
path('favorites/', views.overview_quiz_favorites, name='overview_quiz_favorites'),
|
path('favorites/', views.overview_quiz_favorites, name='overview_quiz_favorites'),
|
||||||
|
|
||||||
path('new/', views.new_quiz, name='new_quiz'),
|
path('new/', views.new_quiz, name='new_quiz'),
|
||||||
path('edit/<int:pk>/', views.edit_quiz, name='edit_quiz'),
|
path('edit/<int:pk>/', views.edit_quiz, name='edit_quiz'),
|
||||||
path('delete/<int:pk>/', views.delete_quiz, name='delete_quiz'),
|
path('delete/<int:pk>/', views.delete_quiz, name='delete_quiz'),
|
||||||
@@ -20,6 +19,7 @@ urlpatterns = [
|
|||||||
path('question/delete/<int:pk>/', views.delete_question, name='delete_question'),
|
path('question/delete/<int:pk>/', views.delete_question, name='delete_question'),
|
||||||
path('detail/copy/<int:pk>/', views.copy_quiz, name='copy_quiz'),
|
path('detail/copy/<int:pk>/', views.copy_quiz, name='copy_quiz'),
|
||||||
path('favorite_quiz/<int:pk>/', views.favorite_quiz, name='favorite_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-names-json/", views.quiz_names_json, name="quiz_names_json"),
|
path("quiz-names-json/", views.quiz_names_json, name="quiz_names_json"),
|
||||||
path(
|
path(
|
||||||
"autocomplete/quiz",
|
"autocomplete/quiz",
|
||||||
@@ -28,4 +28,5 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib import messages
|
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
|
from .forms import QuizForm, QuestionForm
|
||||||
import json
|
import json
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
@@ -71,305 +71,6 @@ class QuizAutocomplete(autocomplete.Select2QuerySetView):
|
|||||||
return f"{result.name} (ID: {result.pk}) (User: {result.user_id})"
|
return f"{result.name} (ID: {result.pk}) (User: {result.user_id})"
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
def pdf_solution(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="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))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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']))
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
def quiz_names_json(request):
|
||||||
@@ -430,7 +131,82 @@ def apply_quiz_filters(queryset, form):
|
|||||||
return queryset.filter(**filters)
|
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": "Die Frage oder die Antworten enthalten einen inhaltlichen Fehler.",
|
||||||
|
"spelling_error": "Die Frage oder die Antworten enthalten (einen oder mehrere) Rechtschreibfehler.",
|
||||||
|
"logic_error": "Die Frage macht keinen Sinn oder ist nur sehr schwer zu verstehen.",
|
||||||
|
"inappropriate_error": "Die Frage, die Antworten oder Bilder sind unangemessen.",
|
||||||
|
"not_completed_error":"Die Antworten sind unvollständig oder es wurde keine richtige Antwort ausgewählt.",
|
||||||
|
"quiz_error":"Das Quiz(formular) selbst ist unangebracht, falsch oder nicht verständlich."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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"):
|
def overview_quiz(request, connect="all"):
|
||||||
|
|
||||||
|
critisized_quizzes = get_critisized_quizzes()
|
||||||
form = QuizFilterForm(request.GET)
|
form = QuizFilterForm(request.GET)
|
||||||
|
|
||||||
user = request.user if request.user.is_authenticated else None
|
user = request.user if request.user.is_authenticated else None
|
||||||
@@ -502,6 +278,7 @@ def overview_quiz(request, connect="all"):
|
|||||||
favorite_quizzes = paginator_fav.page(1)
|
favorite_quizzes = paginator_fav.page(1)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
|
'critisized_quizzes':critisized_quizzes,
|
||||||
'form': form,
|
'form': form,
|
||||||
'my_quizzes': my_quizzes,
|
'my_quizzes': my_quizzes,
|
||||||
'all_quizzes': all_quizzes,
|
'all_quizzes': all_quizzes,
|
||||||
@@ -552,117 +329,6 @@ def overview_quiz_my(request):
|
|||||||
|
|
||||||
def overview_quiz_favorites(request):
|
def overview_quiz_favorites(request):
|
||||||
return overview_quiz(request, connect="favorite")
|
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
|
@login_required
|
||||||
def favorite_quiz(request, pk):
|
def favorite_quiz(request, pk):
|
||||||
@@ -802,8 +468,11 @@ def delete_quiz(request, pk):
|
|||||||
return redirect('library:overview_quiz')
|
return redirect('library:overview_quiz')
|
||||||
return render(request, 'library/delete_confirmation.html', {'object': quiz})
|
return render(request, 'library/delete_confirmation.html', {'object': quiz})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Quiz anzeigen
|
# Quiz anzeigen
|
||||||
def detail_quiz(request, pk):
|
def detail_quiz(request, pk):
|
||||||
|
critisize_quiz(request, pk)
|
||||||
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
||||||
show_answers = request.GET.get('show_answers', 'false').lower() == 'true'
|
show_answers = request.GET.get('show_answers', 'false').lower() == 'true'
|
||||||
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% block content %}
|
{% 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="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">
|
<div class="grid place-content-center h-full mb-4 flex-col items-center justify-between">
|
||||||
|
|
||||||
@@ -17,6 +17,24 @@
|
|||||||
<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-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>
|
<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="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">
|
||||||
|
|
||||||
|
|||||||
@@ -272,7 +272,7 @@
|
|||||||
{% if option.is_correct %}
|
{% if option.is_correct %}
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
<span class="text-gray-500 dark:text-white">Richtige Antwort:</span>
|
<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>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -303,7 +303,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -320,11 +320,78 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
<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">
|
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||||
Quiz-Infos
|
Quiz-Infos
|
||||||
</p>
|
</p>
|
||||||
@@ -348,16 +415,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
|
|
||||||
<p class="text-gray-600 dark:text-white">Quiz ID:<span class="font-bold"> {{ quiz.pk }}</span></p>
|
<p class="text-gray-600 dark:text-white">Quiz ID:<span class="font-bold"> {{ quiz.pk }}</span></p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% if quiz.reference_quizzes.all %}
|
{% if quiz.reference_quizzes.all %}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<p class="text-gray-600 dark:text-white">Verweis auf andere Quizze:
|
<p class="text-gray-600 dark:text-white">Verweis auf andere Quizze:
|
||||||
@@ -373,7 +436,7 @@
|
|||||||
</div>
|
</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">
|
||||||
<div class="flex flex-col max-w-full">
|
<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">
|
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||||
Credits
|
Credits
|
||||||
@@ -411,7 +474,7 @@
|
|||||||
</div>
|
</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">
|
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||||
Quiz-Statistik
|
Quiz-Statistik
|
||||||
</p>
|
</p>
|
||||||
@@ -435,23 +498,36 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
{% 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">Dieses 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>
|
||||||
|
|
||||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<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-red-800 dark:text-white dark:bg-transparent ">
|
<p class="text-lg font-bold text-red-800 dark:text-white dark:bg-transparent ">
|
||||||
Quiz kritisieren
|
Quiz kritisieren
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs opacity-50 mb-3">
|
<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>
|
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.
|
Dann weisen Sie den Autor gern darauf hin.
|
||||||
</p>
|
</p>
|
||||||
<select name="question_id" required
|
<select name="question_id" required
|
||||||
class="w-full rounded-lg border-gray-300 p-2 dark:bg-gray-800 dark:text-white">
|
class="w-full rounded-lg border-gray-300 p-2 dark:bg-gray-800 dark:text-white">
|
||||||
<option value="">Kritik an einer bestimmten Frage?</option>
|
<option value="">Kritik an einer bestimmten Frage oder am Quiz(formular)?</option>
|
||||||
|
|
||||||
|
<option value="whole_quiz">
|
||||||
|
Kritik am Quiz(formular) {{ quiz.name|truncatechars:60 }}
|
||||||
|
</option>
|
||||||
|
|
||||||
{% for question in questions %}
|
{% for question in questions %}
|
||||||
<option value="{{ question.id }}">
|
<option value="{{ question.id }}">
|
||||||
@@ -476,7 +552,13 @@
|
|||||||
</option>
|
</option>
|
||||||
|
|
||||||
<option value="inappropriate_error">
|
<option value="inappropriate_error">
|
||||||
Die Frage oder die Antworten sind unangemessen.
|
Die Frage, die Antworten oder Bilder sind unangemessen.
|
||||||
|
</option>
|
||||||
|
<option value="not_completed_error">
|
||||||
|
Die Antworten sind unvollständig oder es wurde keine richtige Antwort ausgewählt.
|
||||||
|
</option>
|
||||||
|
<option value="quiz_error">
|
||||||
|
Das Quiz(formular) selbst ist unangebracht, falsch oder nicht verständlich.
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@@ -486,15 +568,17 @@
|
|||||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||||
Abschicken
|
Abschicken
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
{% elif quiz.user_id != request.user %}
|
{% elif quiz.user_id != request.user %}
|
||||||
<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-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 ">
|
<p class="text-lg font-bold text-red-800 dark:text-white dark:bg-transparent ">
|
||||||
Quiz kritisieren
|
Quiz kritisieren
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs opacity-50 mb-3">
|
<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>
|
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.
|
Dann weisen Sie den Autor gern darauf hin.
|
||||||
</p>
|
Sie können nur Quizze anderer User kritisieren.
|
||||||
|
</div>
|
||||||
<a class="inline-flex items-center
|
<a class="inline-flex items-center
|
||||||
px-4 py-2 border border-transparent shadow-sm text-sm
|
px-4 py-2 border border-transparent shadow-sm text-sm
|
||||||
font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700
|
font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700
|
||||||
@@ -502,27 +586,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
</details>
|
||||||
|
|
||||||
{% for critique in quiz.criticism_quiz.all %}
|
|
||||||
|
|
||||||
|
|
||||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
|
||||||
|
|
||||||
Frage {{critique.question.data.question}}: {{ critique.criticism }}
|
|
||||||
<p class="text-xs dark:text-white dark:bg-transparent ">
|
|
||||||
von User {{ critique.user }}
|
|
||||||
</p>
|
|
||||||
<p class="text-xs font-bold text-red-800 dark:text-white dark:bg-transparent ">
|
|
||||||
{{ critique.creation_date }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
</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="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">
|
<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 %}
|
{% 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) -->
|
<!-- Image Section (Top) -->
|
||||||
<div class="h-48 w-full relative flex-shrink-0">
|
<div class="h-48 w-full relative flex-shrink-0">
|
||||||
{% if quiz.image %}
|
{% if quiz.image %}
|
||||||
@@ -111,6 +111,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content Section (Bottom) -->
|
<!-- Content Section (Bottom) -->
|
||||||
|
|
||||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3 dark:bg-[#2a2f3a] dark:text-white" >
|
<div class="flex-grow bg-white p-4 flex flex-col gap-3 dark:bg-[#2a2f3a] dark:text-white" >
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
<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="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">
|
<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 %}
|
{% 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) -->
|
<!-- Image Section (Top) -->
|
||||||
<div class="h-48 w-full relative flex-shrink-0 ">
|
<div class="h-48 w-full relative flex-shrink-0 ">
|
||||||
{% if quiz.image %}
|
{% if quiz.image %}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
<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">
|
<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 %}
|
{% 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) -->
|
<!-- Image Section (Top) -->
|
||||||
<div class="h-48 w-full relative flex-shrink-0">
|
<div class="h-48 w-full relative flex-shrink-0">
|
||||||
{% if quiz.image %}
|
{% if quiz.image %}
|
||||||
|
|||||||
0
temp-498.rdb
Normal file
0
temp-498.rdb
Normal file
Reference in New Issue
Block a user