critizise Quiz
This commit is contained in:
@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from django.contrib import messages
|
||||
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizImage
|
||||
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizCriticism, QuizImage
|
||||
from .forms import QuizForm, QuestionForm
|
||||
import json
|
||||
from django.core.paginator import Paginator
|
||||
@@ -70,306 +70,7 @@ class QuizAutocomplete(autocomplete.Select2QuerySetView):
|
||||
# z.B. Name + ID + User
|
||||
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):
|
||||
@@ -430,7 +131,82 @@ def apply_quiz_filters(queryset, form):
|
||||
return queryset.filter(**filters)
|
||||
|
||||
|
||||
|
||||
def critisize_quiz(request, pk):
|
||||
|
||||
quiz= get_object_or_404(QivipQuiz, pk=pk)
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
|
||||
question_id = request.POST.get("question_id") or "whole_quiz"
|
||||
|
||||
if question_id!="whole_quiz":
|
||||
question = quiz.questions.get(id=question_id)
|
||||
data = json.loads(question.data)
|
||||
question_text = data["question"]
|
||||
|
||||
else:
|
||||
question_text="Quiz(formular) kritisiert"
|
||||
question = None
|
||||
question_id = None
|
||||
|
||||
|
||||
critic_type = request.POST.get("critic")
|
||||
|
||||
CRITICISM_MAP = {
|
||||
"content_error": "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"):
|
||||
|
||||
critisized_quizzes = get_critisized_quizzes()
|
||||
form = QuizFilterForm(request.GET)
|
||||
|
||||
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)
|
||||
|
||||
context = {
|
||||
'critisized_quizzes':critisized_quizzes,
|
||||
'form': form,
|
||||
'my_quizzes': my_quizzes,
|
||||
'all_quizzes': all_quizzes,
|
||||
@@ -552,117 +329,6 @@ def overview_quiz_my(request):
|
||||
|
||||
def overview_quiz_favorites(request):
|
||||
return overview_quiz(request, connect="favorite")
|
||||
"""
|
||||
def overview_quiz(request):
|
||||
# Filter
|
||||
form = QuizFilterForm(request.GET)
|
||||
try:
|
||||
favorite_quizzes_data = QivipQuizFavorite.objects.filter(user=request.user, favorite=True).order_by("-favorite_date")
|
||||
favorite_quiz_ids = favorite_quizzes_data.values_list('quiz', flat=True)
|
||||
favorite_quizzes = QivipQuiz.objects.filter(id__in=favorite_quiz_ids)
|
||||
except:
|
||||
favorite_quizzes_data = QivipQuizFavorite.objects.none()
|
||||
favorite_quizzes = QivipQuizFavorite.objects.none()
|
||||
favorite_quiz_ids = favorite_quizzes.none()
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
|
||||
# Initialize querysets
|
||||
if request.user.is_authenticated:
|
||||
filter_my_quizzes = QivipQuiz.objects.filter(user_id=request.user).annotate(max_amount_questions=Count('questions'))
|
||||
favorite_quizzes = favorite_quizzes.filter(id__in=favorite_quiz_ids).annotate(max_amount_questions=Count('questions'))
|
||||
filter_all_quizzes = QivipQuiz.objects.exclude(user_id=request.user)
|
||||
else:
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
filter_my_quizzes = QivipQuiz.objects.none()
|
||||
filter_all_quizzes = QivipQuiz.objects.all()
|
||||
|
||||
# Apply common filters to all quizzes
|
||||
filter_all_quizzes = filter_all_quizzes.annotate(max_amount_questions=Count('questions'))
|
||||
filter_all_quizzes = filter_all_quizzes.filter(max_amount_questions__gte=1, status='öffentlich')
|
||||
|
||||
if form.is_valid():
|
||||
|
||||
search = form.cleaned_data.get('search')
|
||||
username= form.cleaned_data.get('user')
|
||||
max_amount_questions= form.cleaned_data.get('max_amount_questions')
|
||||
min_amount_questions= form.cleaned_data.get('min_amount_questions')
|
||||
|
||||
filters = {}
|
||||
if username:
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
filters['user_id'] = user.id
|
||||
except User.DoesNotExist:
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
filter_my_quizzes = QivipQuiz.objects.none()
|
||||
filter_all_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
if search:
|
||||
filters['name__icontains'] = search
|
||||
if min_amount_questions:
|
||||
filters['max_amount_questions__gte'] = min_amount_questions
|
||||
if max_amount_questions:
|
||||
filters['max_amount_questions__lte'] = max_amount_questions
|
||||
|
||||
try:
|
||||
filter_all_quizzes = filter_all_quizzes.filter(**filters)
|
||||
except:
|
||||
filter_all_quizzes=QivipQuiz.objects.none()
|
||||
try:
|
||||
filter_my_quizzes = filter_my_quizzes.filter(**filters)
|
||||
except:
|
||||
filter_my_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes.filter(**filters)
|
||||
except:
|
||||
favorite_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
|
||||
|
||||
# Pagination for my quizzes
|
||||
my_quizzes_paginator = Paginator(filter_my_quizzes.order_by('-creation_date'), 8) # 8 quizzes per page
|
||||
try:
|
||||
my_quizzes = my_quizzes_paginator.page(request.GET.get('my_page', 1))
|
||||
except:
|
||||
my_quizzes = my_quizzes_paginator.page(1)
|
||||
|
||||
# Pagination for all quizzes
|
||||
all_quizzes_paginator = Paginator(filter_all_quizzes.order_by('-creation_date'), 8) # 8 quizzes per page
|
||||
try:
|
||||
all_quizzes = all_quizzes_paginator.page(request.GET.get('all_page', 1))
|
||||
except:
|
||||
all_quizzes = all_quizzes_paginator.page(1)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes.filter(
|
||||
favorites__user=request.user, # Filtere nach dem User
|
||||
favorites__favorite=True # Filtere nach favorisierten Quizzes
|
||||
).order_by('-favorites__favorite_date')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# Pagination for favorite_quizzes
|
||||
favorite_quizzes_paginator = Paginator(favorite_quizzes, 8) # 9 quizzes per page
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes_paginator.page(request.GET.get('favorite_page', 1))
|
||||
except:
|
||||
favorite_quizzes = favorite_quizzes_paginator.page(1)
|
||||
|
||||
context = {
|
||||
'show_search': True,
|
||||
'favorite_quiz_ids': favorite_quiz_ids,
|
||||
'favorite_quizzes':favorite_quizzes,
|
||||
'my_quizzes': my_quizzes,
|
||||
'all_quizzes': all_quizzes,
|
||||
'form': form,
|
||||
}
|
||||
|
||||
return render(request, 'library/overview_quiz.html', context)
|
||||
"""
|
||||
|
||||
@login_required
|
||||
def favorite_quiz(request, pk):
|
||||
@@ -802,8 +468,11 @@ def delete_quiz(request, pk):
|
||||
return redirect('library:overview_quiz')
|
||||
return render(request, 'library/delete_confirmation.html', {'object': quiz})
|
||||
|
||||
|
||||
|
||||
# Quiz anzeigen
|
||||
def detail_quiz(request, pk):
|
||||
critisize_quiz(request, pk)
|
||||
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
||||
show_answers = request.GET.get('show_answers', 'false').lower() == 'true'
|
||||
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
||||
|
||||
Reference in New Issue
Block a user