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'{escaped_url}' 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'{escaped_url}' 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') 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']: 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', '') # 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 } 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 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} ] } 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 } 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})