This commit is contained in:
ben8
2025-07-13 13:12:45 +02:00
parent 4ac5dd794b
commit 736db1c480
4 changed files with 363 additions and 4 deletions

View File

@@ -17,6 +17,329 @@ 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)