Files
qivip/django/library/views.py
2025-06-27 15:37:45 +02:00

818 lines
28 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
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('-creation_date'), 8)
paginator_fav = Paginator(favorite_quizzes.order_by('-creation_date'), 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
new_quiz.published_at = timezone.now()
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})