1296 lines
44 KiB
Python
1296 lines
44 KiB
Python
from django.utils import timezone
|
|
import uuid
|
|
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
|
|
from django.shortcuts import render, redirect, get_object_or_404
|
|
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 .forms import QuizForm, QuestionForm
|
|
import json
|
|
from django.core.paginator import Paginator
|
|
from .models import QivipQuiz
|
|
from .forms import QuizFilterForm
|
|
from django.db.models import Count
|
|
from django.contrib.auth.models import User
|
|
from urllib.parse import urlparse, urlunparse
|
|
# Übersicht aller Quizze
|
|
|
|
from reportlab.lib.pagesizes import A4
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
|
from reportlab.lib import colors
|
|
from django.http import HttpResponse
|
|
import json
|
|
from datetime import datetime
|
|
from reportlab.platypus import Image
|
|
|
|
from reportlab.lib import colors
|
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
from reportlab.lib.pagesizes import A4
|
|
from django.http import HttpResponse
|
|
from django.shortcuts import get_object_or_404
|
|
import json
|
|
from io import BytesIO
|
|
import requests
|
|
from django.core.files.storage import default_storage
|
|
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)
|
|
|
|
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):
|
|
form = QuizFilterForm(request.GET)
|
|
if form.is_valid():
|
|
search = form.cleaned_data.get('search')
|
|
|
|
|
|
names = QivipQuiz.objects.all().annotate(max_amount_questions=Count('questions')).filter(max_amount_questions__gte=1, status='öffentlich', name__icontains=search)
|
|
|
|
|
|
if request.user.is_authenticated:
|
|
names = names | QivipQuiz.objects.filter(
|
|
user_id=request.user.id,
|
|
name__icontains=search
|
|
)
|
|
|
|
names=list(names.values_list('name', flat=True).distinct())
|
|
|
|
|
|
return JsonResponse(names, safe=False)
|
|
|
|
|
|
def apply_quiz_filters(queryset, form):
|
|
|
|
if not form.is_valid():
|
|
return queryset
|
|
queryset = queryset.annotate(question_count=Count('questions'))
|
|
search = form.cleaned_data.get('search')
|
|
username = form.cleaned_data.get('user')
|
|
min_questions = form.cleaned_data.get('min_amount_questions')
|
|
max_questions = form.cleaned_data.get('max_amount_questions')
|
|
category = form.cleaned_data.get('categories')
|
|
|
|
filters = {}
|
|
|
|
if search:
|
|
filters['name__icontains'] = search
|
|
|
|
if min_questions is not None:
|
|
filters['question_count__gte'] = min_questions
|
|
|
|
if max_questions is not None:
|
|
filters['question_count__lte'] = max_questions
|
|
|
|
|
|
if category is not None:
|
|
filters['category'] = category
|
|
|
|
|
|
if username:
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
filters['user_id'] = user.id
|
|
except User.DoesNotExist:
|
|
return queryset.none()
|
|
|
|
return queryset.filter(**filters)
|
|
|
|
|
|
def overview_quiz(request, connect="all"):
|
|
form = QuizFilterForm(request.GET)
|
|
|
|
user = request.user if request.user.is_authenticated else None
|
|
|
|
# Meine Quizzeze mit mind. 1 Frage
|
|
filter_my_quizzes = QivipQuiz.objects.none()
|
|
if user:
|
|
filter_my_quizzes = QivipQuiz.objects.annotate(max_amount_questions=Count('questions')).filter(
|
|
user_id=user.id,
|
|
max_amount_questions__gte=0
|
|
)
|
|
|
|
# Öffentliche Alle mit mind. 1 Frage
|
|
filter_all_quizzes = QivipQuiz.objects.annotate(max_amount_questions=Count('questions')).filter(
|
|
status='öffentlich',
|
|
max_amount_questions__gte=1
|
|
)
|
|
if user:
|
|
filter_all_quizzes = filter_all_quizzes.exclude(user_id=user.id)
|
|
|
|
# Favoriten mit mind. 1 Frage
|
|
favorite_quiz_ids = []
|
|
favorite_quizzes = QivipQuiz.objects.none()
|
|
if user:
|
|
favorite_quiz_ids = QivipQuizFavorite.objects.filter(user=user, favorite=True).values_list('quiz', flat=True)
|
|
favorite_quizzes = QivipQuiz.objects.annotate(max_amount_questions=Count('questions')).filter(
|
|
id__in=favorite_quiz_ids,
|
|
max_amount_questions__gte=1
|
|
)
|
|
|
|
# Suche anwenden, falls vorhanden
|
|
|
|
|
|
filter_my_quizzes = apply_quiz_filters(filter_my_quizzes, form)
|
|
filter_all_quizzes = apply_quiz_filters(filter_all_quizzes, form)
|
|
favorite_quizzes = apply_quiz_filters(favorite_quizzes, form)
|
|
if request.user.is_authenticated:
|
|
filter_all_quizzes = apply_quiz_filters(filter_all_quizzes, form) | apply_quiz_filters(filter_my_quizzes, form)
|
|
|
|
# Treffer zählen
|
|
total_my = filter_my_quizzes.count()
|
|
total_all = filter_all_quizzes.count()
|
|
total_favorite = favorite_quizzes.count()
|
|
|
|
total_all_found=total_my + total_all + total_favorite
|
|
|
|
# Paginierung
|
|
paginator_my = Paginator(filter_my_quizzes.order_by('-creation_date'), 8)
|
|
paginator_all = Paginator(filter_all_quizzes.order_by('-published_at'), 8)
|
|
paginator_fav = Paginator(favorite_quizzes.order_by('-published_at'), 8)
|
|
|
|
my_page = request.GET.get('my_page', 1)
|
|
all_page = request.GET.get('all_page', 1)
|
|
fav_page = request.GET.get('favorite_page', 1)
|
|
|
|
try:
|
|
my_quizzes = paginator_my.page(my_page)
|
|
except:
|
|
my_quizzes = paginator_my.page(1)
|
|
|
|
try:
|
|
all_quizzes = paginator_all.page(all_page)
|
|
except:
|
|
all_quizzes = paginator_all.page(1)
|
|
|
|
try:
|
|
favorite_quizzes = paginator_fav.page(fav_page)
|
|
except:
|
|
favorite_quizzes = paginator_fav.page(1)
|
|
|
|
context = {
|
|
'form': form,
|
|
'my_quizzes': my_quizzes,
|
|
'all_quizzes': all_quizzes,
|
|
'favorite_quizzes': favorite_quizzes,
|
|
'total_my': total_my,
|
|
'total_all': total_all,
|
|
'total_favorite': total_favorite,
|
|
'total_all_found':total_all_found,
|
|
'show_search': True,
|
|
'favorite_quiz_ids': list(favorite_quiz_ids),
|
|
'url_my': '/library/my/',
|
|
'url_all': '/library/',
|
|
'url_favorite': '/library/favorites/',
|
|
}
|
|
|
|
query_params = request.GET.copy()
|
|
|
|
querydict_my = query_params.copy()
|
|
querydict_all = query_params.copy()
|
|
querydict_fav = query_params.copy()
|
|
|
|
# Entferne die jeweiligen Seiten-Parameter, damit wir sie neu setzen können
|
|
querydict_my.pop('my_page', None)
|
|
querydict_all.pop('all_page', None)
|
|
querydict_fav.pop('favorite_page', None)
|
|
|
|
# Füge die "bereinigten" Querystrings dem context hinzu
|
|
context.update({
|
|
'querystring_my': querydict_my.urlencode(),
|
|
'querystring_all': querydict_all.urlencode(),
|
|
'querystring_favorite': querydict_fav.urlencode(),
|
|
})
|
|
|
|
if connect=="all":
|
|
context.update({"link":"library:overview_quiz"})
|
|
return render(request, 'library/overview_quiz.html', context)
|
|
elif connect=="my":
|
|
context.update({"link":"library:overview_quiz_my"})
|
|
return render(request, 'library/overview_quiz_my.html', context)
|
|
elif connect=="favorite":
|
|
context.update({"link":"library:overview_quiz_favorites"})
|
|
return render(request, 'library/overview_quiz_favorite.html', context)
|
|
|
|
|
|
def overview_quiz_my(request):
|
|
return overview_quiz(request, connect="my")
|
|
|
|
|
|
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):
|
|
quiz= get_object_or_404(QivipQuiz, pk=pk)
|
|
favorite = QivipQuizFavorite.objects.filter(user=request.user, quiz=quiz).first()
|
|
|
|
if favorite:
|
|
favorite.favorite=not favorite.favorite
|
|
|
|
|
|
|
|
|
|
if favorite.favorite==True:
|
|
favorite.favorite_date = timezone.now()
|
|
favorite.save()
|
|
else:
|
|
favorite.favorite_date = None
|
|
favorite.delete()
|
|
statement="Das Quiz '"+ favorite.quiz.name+ "' wurde erfolgreich von den Favoriten entfernt!"
|
|
messages.error(request, statement)
|
|
|
|
|
|
else:
|
|
favorite=QivipQuizFavorite.objects.create(user=request.user, quiz=quiz, favorite=True, favorite_date=timezone.now())
|
|
statement="Das Quiz '"+ favorite.quiz.name+ "' wurde erfolgreich zu den Favoriten hinzugefügt!"
|
|
messages.success(request,statement )
|
|
|
|
return redirect(request.META.get('HTTP_REFERER', 'library:overview_quiz'))
|
|
|
|
|
|
# Neues Quiz erstellen
|
|
@login_required
|
|
def new_quiz(request):
|
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
|
|
form = QuizForm(request.POST, request.FILES)
|
|
|
|
|
|
|
|
if form.is_valid():
|
|
quiz = form.save(commit=False)
|
|
status = form.cleaned_data.get('status')
|
|
|
|
if status == 'öffentlich' and not quiz.published_at:
|
|
quiz.published_at = timezone.now()
|
|
|
|
try:
|
|
quiz_image=request.FILES['quiz_image']
|
|
|
|
imagequiz=QuizImage.objects.create(image=quiz_image)
|
|
quiz.image=imagequiz
|
|
|
|
except:
|
|
pass
|
|
|
|
|
|
quiz.user_id = request.user
|
|
|
|
|
|
#quiz.creator = request.user
|
|
quiz.save()
|
|
#form.save_m2m() # Speichert die Many-to-Many Beziehungen (Tags)
|
|
return redirect('library:detail_quiz', pk=quiz.pk)
|
|
|
|
else:
|
|
form = QuizForm()
|
|
return render(request, 'library/form.html', {'form': form})
|
|
|
|
# Quiz bearbeiten
|
|
@login_required
|
|
def edit_quiz(request, pk):
|
|
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
|
|
if request.method == 'POST':
|
|
form = QuizForm(request.POST, instance=quiz, files=request.FILES)
|
|
|
|
|
|
|
|
if form.is_valid():
|
|
|
|
|
|
|
|
if 'reset_image' in request.POST:
|
|
quiz.image = None
|
|
|
|
try:
|
|
quiz_image=request.FILES['quiz_image']
|
|
imagequiz=QuizImage.objects.create(image=quiz_image)
|
|
quiz.image=imagequiz
|
|
|
|
|
|
except:
|
|
pass
|
|
|
|
quiz = form.save(commit=False)
|
|
|
|
# Beispiel: Status aus dem Formular holen
|
|
status = form.cleaned_data.get('status')
|
|
if status == 'öffentlich' and not quiz.published_at:
|
|
quiz.published_at = timezone.now()
|
|
|
|
form.save()
|
|
for img in QuizImage.objects.all():
|
|
try:
|
|
img.delete()
|
|
img.image.delete(save=False)
|
|
except:
|
|
pass
|
|
return redirect('library:detail_quiz', pk=pk)
|
|
#return modified(request, pk)
|
|
|
|
else:
|
|
form = QuizForm(instance=quiz)
|
|
return render(request, 'library/form.html', {'form': form})
|
|
|
|
# Quiz löschen
|
|
@login_required
|
|
def delete_quiz(request, pk):
|
|
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
|
|
if request.method == 'POST':
|
|
quiz.delete()
|
|
for img in QuizImage.objects.all():
|
|
try:
|
|
img.delete()
|
|
img.image.delete(save=False)
|
|
except:
|
|
pass
|
|
for img in QuestionImage.objects.all():
|
|
try:
|
|
img.delete()
|
|
img.image.delete(save=False)
|
|
except:
|
|
pass
|
|
|
|
return redirect('library:overview_quiz')
|
|
return render(request, 'library/delete_confirmation.html', {'object': quiz})
|
|
|
|
# Quiz anzeigen
|
|
def detail_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)
|
|
# Parse JSON data for each question
|
|
for question in questions:
|
|
if question.data:
|
|
question.data = json.loads(question.data)
|
|
|
|
context = {
|
|
'quiz': quiz,
|
|
'questions': questions,
|
|
'detail':show_answers,
|
|
}
|
|
if quiz.status!="privat" or quiz.user_id==request.user:
|
|
return render(request, 'library/detail_quiz.html', context)
|
|
else:
|
|
return redirect('library:overview_quiz')
|
|
|
|
|
|
|
|
@login_required
|
|
def copy_quiz(request, pk):
|
|
original_quiz = get_object_or_404(QivipQuiz, pk=pk)
|
|
|
|
|
|
|
|
# Quiz kopieren (ohne ID, damit ein neues Objekt erstellt wird)
|
|
|
|
# Quiz kopieren (ohne ID, damit ein neues Objekt erstellt wird)
|
|
|
|
new_quiz = original_quiz # Kopie erstellen (aber Achtung: Noch gleiche Referenz!)
|
|
new_quiz.pk = None # Setzt die ID auf None, damit Django es als neues Objekt erkennt
|
|
new_quiz.uuid = uuid.uuid4() # Neue UUID generieren
|
|
new_quiz.user_id = request.user # Neuer Besitzer ist der aktuelle User
|
|
new_quiz.name += " - Kopie"
|
|
new_quiz.base_quiz_id = pk
|
|
new_quiz.rating_count = 0
|
|
new_quiz.average_rating = 3.0
|
|
if original_quiz.status=="öffentlich":
|
|
new_quiz.published_at = timezone.now()
|
|
else:
|
|
new_quiz.published_at = None
|
|
|
|
new_quiz.save() # Speichern als neues Objekt
|
|
# Optional: Beschreibung anpassen
|
|
# Alle zugehörigen Fragen kopieren
|
|
questions = QivipQuestion.objects.filter(quiz_id=pk)
|
|
for question in questions:
|
|
question.uuid = uuid.uuid4()
|
|
question.pk = None # Setze pk auf None, damit es als neues Objekt gespeichert wird
|
|
question.quiz_id = original_quiz # Verknüpfe mit dem neuen Quiz
|
|
question.data = question.data # `data`-Feld wird explizit übernommen # Verknüpfe mit dem neuen Quiz
|
|
|
|
question.save()
|
|
|
|
messages.success(request, "Quiz wurde erfolgreich kopiert!")
|
|
return redirect('library:detail_quiz', pk=new_quiz.pk)
|
|
|
|
|
|
# Übersicht aller Fragen
|
|
@login_required
|
|
def question_list(request):
|
|
questions = QivipQuestion.objects.filter(quiz_id__user_id=request.user)
|
|
|
|
# Parse JSON data for each question
|
|
for question in questions:
|
|
if question.data:
|
|
question.data = json.loads(question.data)
|
|
|
|
return render(request, 'library/question_list.html', {'questions': questions})
|
|
|
|
# Neue Frage erstellen
|
|
@login_required
|
|
def new_question(request):
|
|
question_type = request.GET.get('type')
|
|
quiz_id = request.GET.get('quiz_id')
|
|
question_data = None
|
|
|
|
if not quiz_id:
|
|
messages.error(request, 'Quiz ID muss angegeben werden.')
|
|
return redirect('library:overview_quiz')
|
|
|
|
try:
|
|
quiz = QivipQuiz.objects.get(pk=quiz_id, user_id=request.user)
|
|
except QivipQuiz.DoesNotExist:
|
|
messages.error(request, 'Quiz nicht gefunden oder keine Berechtigung.')
|
|
return redirect('library:overview_quiz')
|
|
|
|
if question_type not in ['multiple_choice', 'true_false','input','order','guess','map']:
|
|
base_url = reverse('library:new_question')
|
|
return redirect(f'{base_url}?type=multiple_choice&quiz_id={quiz_id}')
|
|
|
|
if request.method == 'POST':
|
|
question_text = request.POST.get('question', 'Kein Fragentext angegeben')
|
|
|
|
# Handle different question types
|
|
if question_type == 'true_false':
|
|
correct_answer = request.POST.get('correct_answer') == 'true'
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'options': [
|
|
{'value': 'Wahr', 'is_correct': correct_answer},
|
|
{'value': 'Falsch', 'is_correct': not correct_answer}
|
|
]
|
|
}
|
|
|
|
elif question_type == 'input':
|
|
correct_answer = request.POST.get('correct_answer')
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'options': [
|
|
{'value': correct_answer, 'is_correct': True}
|
|
|
|
]
|
|
}
|
|
elif question_type == 'multiple_choice' :
|
|
options = []
|
|
i = 1
|
|
# Limit to maximum 6 options
|
|
while request.POST.get(f'option_{i}') and i <= 6:
|
|
is_correct = request.POST.get(f'correct_{i}') == '1'
|
|
options.append({
|
|
'order': i,
|
|
'value': request.POST.get(f'option_{i}'),
|
|
'is_correct': is_correct
|
|
})
|
|
i += 1
|
|
|
|
# Validate minimum 2 options
|
|
if len(options) < 2:
|
|
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
|
|
return redirect(request.get_full_path())
|
|
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'options': options
|
|
}
|
|
|
|
elif question_type == 'order' :
|
|
options = []
|
|
i = 1
|
|
# Limit to maximum 6 options
|
|
while request.POST.get(f'option_{i}') and i <= 6:
|
|
is_correct = request.POST.get(f'correct_{i}') == '1'
|
|
options.append({
|
|
'order': i,
|
|
'value': request.POST.get(f'option_{i}'),
|
|
|
|
})
|
|
i += 1
|
|
|
|
|
|
# Validate minimum 2 options
|
|
if len(options) < 2:
|
|
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
|
|
return redirect(request.get_full_path())
|
|
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'options': options
|
|
}
|
|
|
|
|
|
elif question_type == 'guess':
|
|
correct_answer = request.POST.get('correct_answer')
|
|
min_value = request.POST.get('min')
|
|
max_value = request.POST.get('max')
|
|
tolerance = request.POST.get('tolerance')
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'correct_answer': correct_answer,
|
|
'min': min_value,
|
|
'max': max_value,
|
|
'tolerance': tolerance,
|
|
'options': [
|
|
{'value': correct_answer, 'is_correct': True}
|
|
]
|
|
}
|
|
|
|
elif question_type == 'map':
|
|
target_location = request.POST.get('location')
|
|
tolerance_radius = request.POST.get('tolerance_radius')
|
|
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'target_location': target_location,
|
|
'tolerance_radius': tolerance_radius
|
|
}
|
|
question = QivipQuestion()
|
|
question.data = json.dumps(json_data)
|
|
question.quiz_id = quiz
|
|
try:
|
|
question_image=request.FILES['question_image']
|
|
imagequestion=QuestionImage.objects.create(image=question_image)
|
|
question.image=imagequestion
|
|
except:
|
|
pass
|
|
|
|
|
|
question.time_per_question = request.POST.get('time_per_question', '30')
|
|
try:
|
|
question.credits=request.POST.get('credits',"")
|
|
except:
|
|
question.credits=""
|
|
question.save()
|
|
#return modified(request, pk=quiz.pk)
|
|
return redirect('library:detail_quiz', pk=quiz.pk)
|
|
else:
|
|
# Initialize empty question data for new questions
|
|
if question_type == 'true_false':
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': '',
|
|
'options': [
|
|
{'value': 'Wahr', 'is_correct': True},
|
|
{'value': 'Falsch', 'is_correct': False}
|
|
]
|
|
}
|
|
standard_time_per_question = 30
|
|
|
|
elif question_type == 'input':
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': '',
|
|
'options': [
|
|
|
|
|
|
]
|
|
}
|
|
standard_time_per_question = 30
|
|
|
|
elif question_type == 'multiple_choice':
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': '',
|
|
'options': [
|
|
{'value': '', 'is_correct': False},
|
|
{'value': '', 'is_correct': False}
|
|
]
|
|
}
|
|
standard_time_per_question = 30
|
|
|
|
elif question_type == 'order':
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': '',
|
|
'options': [
|
|
{'value': ''},
|
|
{'value': ''}
|
|
]
|
|
}
|
|
standard_time_per_question = 30
|
|
elif question_type == 'guess':
|
|
correct_answer = request.POST.get('correct_answer')
|
|
min_value = request.POST.get('min')
|
|
max_value = request.POST.get('max')
|
|
tolerance = request.POST.get('tolerance')
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': '',
|
|
'correct_answer': correct_answer,
|
|
'min': min_value,
|
|
'max': max_value,
|
|
'tolerance': tolerance,
|
|
'options': [
|
|
{'value': correct_answer, 'is_correct': True}
|
|
]
|
|
}
|
|
standard_time_per_question = 30
|
|
|
|
elif question_type == 'map':
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': '',
|
|
'target_location': "",
|
|
'tolerance_radius': 90000
|
|
}
|
|
standard_time_per_question = 30
|
|
|
|
template_name = f'library/question/question_{question_type}.html'
|
|
context = {
|
|
'question': question_data,
|
|
'quiz_id': quiz_id,
|
|
'quiz': quiz,
|
|
'standard_time_per_question': standard_time_per_question,
|
|
}
|
|
|
|
return render(request, template_name, context)
|
|
|
|
# Frage bearbeiten
|
|
@login_required
|
|
def edit_question(request, pk):
|
|
question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user)
|
|
if 'reset_ques_image' in request.POST:
|
|
question.image = None
|
|
if question.quiz_id.user_id != request.user:
|
|
return redirect('library:question_list')
|
|
|
|
# Parse the existing JSON data
|
|
try:
|
|
question_data = json.loads(question.data) if question.data else {}
|
|
question_type = question_data.get('type', 'multiple_choice')
|
|
question_image = question.image
|
|
|
|
# For true/false questions, get the correct answer from the options
|
|
if question_type == 'true_false':
|
|
# Default to true if no options exist
|
|
correct_answer = True
|
|
if question_data.get('options'):
|
|
correct_answer = question_data['options'][0].get('is_correct', True)
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': question_data.get('question', ''),
|
|
'correct_answer': correct_answer, # Add this for template compatibility
|
|
'options': [
|
|
{'value': 'Wahr', 'is_correct': correct_answer},
|
|
{'value': 'Falsch', 'is_correct': not correct_answer}
|
|
]
|
|
}
|
|
elif question_type == 'map':
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': question_data.get('question', ''),
|
|
'target_location': question_data.get('target_location', ''),
|
|
'tolerance_radius': question_data.get('tolerance_radius', 90000)
|
|
}
|
|
else:
|
|
# For multiple choice questions
|
|
question_data.setdefault('type', question_type)
|
|
question_data.setdefault('question', '')
|
|
question_data.setdefault('options', [])
|
|
|
|
except (json.JSONDecodeError, AttributeError):
|
|
# Handle invalid JSON or None data
|
|
question_type = question_data.get('type', 'multiple_choice')
|
|
if question_type == 'true_false':
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': '',
|
|
'correct_answer': True, # Default to true for new questions
|
|
'options': [
|
|
{'value': 'Wahr', 'is_correct': True},
|
|
{'value': 'Falsch', 'is_correct': False}
|
|
]
|
|
}
|
|
else:
|
|
question_data = {
|
|
'type': question_type,
|
|
'question': '',
|
|
'options': []
|
|
}
|
|
|
|
if request.method == 'POST':
|
|
question_text = request.POST.get('question', '')
|
|
try:
|
|
question_image=request.FILES['question_image']
|
|
imagequestion=QuestionImage.objects.create(image=question_image)
|
|
question.image=imagequestion
|
|
except Exception:
|
|
pass
|
|
|
|
# Handle different question types
|
|
if question_type == 'true_false':
|
|
correct_answer = request.POST.get('correct_answer') == 'true'
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'options': [
|
|
{'value': 'Wahr', 'is_correct': correct_answer},
|
|
{'value': 'Falsch', 'is_correct': not correct_answer}
|
|
]
|
|
}
|
|
elif question_type == 'input':
|
|
correct_answer = request.POST.get('correct_answer')
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'options': [
|
|
{'value': correct_answer,'is_correct':True}
|
|
|
|
]
|
|
}
|
|
|
|
elif question_type == 'multiple_choice':
|
|
options = []
|
|
i = 1
|
|
# Limit to maximum 6 options
|
|
while request.POST.get(f'option_{i}') and i <= 6:
|
|
options.append({
|
|
'order': i,
|
|
'value': request.POST.get(f'option_{i}'),
|
|
'is_correct': request.POST.get(f'correct_{i}') == '1'
|
|
})
|
|
i += 1
|
|
|
|
# Validate minimum 2 options
|
|
if len(options) < 2:
|
|
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
|
|
return redirect(request.get_full_path())
|
|
|
|
# Validate maximum 6 options
|
|
if len(options) > 6:
|
|
messages.error(request, 'Maximal 6 Optionen sind erlaubt.')
|
|
return redirect(request.get_full_path())
|
|
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'options': options
|
|
}
|
|
|
|
elif question_type == 'order':
|
|
options = []
|
|
i = 1
|
|
# Limit to maximum 6 options
|
|
while request.POST.get(f'option_{i}') and i <= 6:
|
|
options.append({
|
|
'order': i,
|
|
'value': request.POST.get(f'option_{i}')
|
|
})
|
|
i += 1
|
|
|
|
# Validate minimum 2 options
|
|
if len(options) < 2:
|
|
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
|
|
return redirect(request.get_full_path())
|
|
|
|
# Validate maximum 6 options
|
|
if len(options) > 6:
|
|
messages.error(request, 'Maximal 6 Optionen sind erlaubt.')
|
|
return redirect(request.get_full_path())
|
|
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'options': options
|
|
}
|
|
elif question_type == 'guess':
|
|
correct_answer = request.POST.get('correct_answer')
|
|
min_value = request.POST.get('min')
|
|
max_value = request.POST.get('max')
|
|
tolerance = request.POST.get('tolerance')
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'correct_answer': correct_answer,
|
|
'min': min_value,
|
|
'max': max_value,
|
|
'tolerance': tolerance,
|
|
'options': [
|
|
{'value': correct_answer, 'is_correct': True}
|
|
]
|
|
}
|
|
|
|
elif question_type == 'map':
|
|
target_location = request.POST.get('location')
|
|
tolerance_radius = request.POST.get('tolerance_radius')
|
|
|
|
json_data = {
|
|
'type': question_type,
|
|
'question': question_text,
|
|
'target_location': target_location,
|
|
'tolerance_radius': tolerance_radius
|
|
}
|
|
question.data = json.dumps(json_data)
|
|
question.time_per_question = request.POST.get('time_per_question', '30')
|
|
try:
|
|
question.credits=request.POST.get('credits',"")
|
|
except:
|
|
question.credits=""
|
|
|
|
question.save()
|
|
for img in QuestionImage.objects.all():
|
|
try:
|
|
img.delete()
|
|
img.image.delete(save=False)
|
|
except:
|
|
pass
|
|
#return modified(request, question.quiz_id.pk)
|
|
return redirect('library:detail_quiz', pk=question.quiz_id.pk)
|
|
|
|
else:
|
|
template_name = f'library/question/question_{question_type}.html'
|
|
context = {
|
|
'credits':question.credits,
|
|
'question': {'data': question_data}, # Wrap in data structure expected by template
|
|
'quiz_id': question.quiz_id.pk,
|
|
'quiz': question.quiz_id,
|
|
'time_per_question': question.time_per_question,
|
|
'image': question_image,
|
|
}
|
|
|
|
return render(request, template_name, context)
|
|
|
|
|
|
# Frage löschen
|
|
@login_required
|
|
def delete_question(request, pk):
|
|
question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user)
|
|
|
|
# Parse JSON data for question
|
|
if question.data:
|
|
question.data = json.loads(question.data)
|
|
|
|
if request.method == 'POST':
|
|
question.delete()
|
|
for img in QuestionImage.objects.all():
|
|
try:
|
|
img.delete()
|
|
img.image.delete(save=False)
|
|
except:
|
|
pass
|
|
return redirect('library:detail_quiz', pk=question.quiz_id.pk)
|
|
return render(request, 'library/delete_confirmation.html', {'object': question})
|