Merge branch '108-bilderupload-verbessern' into 'master'

Resolve "Bilderupload verbessern!"

Closes #108

See merge request enrichment-2024/qivip!60
This commit is contained in:
ben8
2025-04-23 15:30:51 +00:00
13 changed files with 275 additions and 20 deletions

View File

@@ -1,5 +1,5 @@
from django.contrib import admin from django.contrib import admin
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuizCategory from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizCategory, QuizImage
# Für das QivipQuiz Modell # Für das QivipQuiz Modell
class QivipQuizAdmin(admin.ModelAdmin): class QivipQuizAdmin(admin.ModelAdmin):
@@ -23,8 +23,17 @@ class QuizCategoryAdmin(admin.ModelAdmin):
class QivipQuizFavoriteAdmin(admin.ModelAdmin): class QivipQuizFavoriteAdmin(admin.ModelAdmin):
list_display = ('user', 'favorite', 'quiz') # Welche Felder sollen in der Übersicht angezeigt werden list_display = ('user', 'favorite', 'quiz') # Welche Felder sollen in der Übersicht angezeigt werden
class QuizImageAdmin(admin.ModelAdmin):
list_display = ('image',)
class QuestionImageAdmin(admin.ModelAdmin):
list_display = ('image',)
# Registrierung der Modelle im Admin # Registrierung der Modelle im Admin
admin.site.register(QivipQuiz, QivipQuizAdmin) admin.site.register(QivipQuiz, QivipQuizAdmin)
admin.site.register(QivipQuestion, QivipQuestionAdmin) admin.site.register(QivipQuestion, QivipQuestionAdmin)
admin.site.register(QuizCategory, QuizCategoryAdmin) admin.site.register(QuizCategory, QuizCategoryAdmin)
admin.site.register(QivipQuizFavorite, QivipQuizFavoriteAdmin) admin.site.register(QivipQuizFavorite, QivipQuizFavoriteAdmin)
admin.site.register(QuizImage, QuizImageAdmin)
admin.site.register(QuestionImage, QuestionImageAdmin)

View File

@@ -4,7 +4,7 @@ from .models import QivipQuiz, QivipQuestion
class QuizForm(forms.ModelForm): class QuizForm(forms.ModelForm):
class Meta: class Meta:
model = QivipQuiz model = QivipQuiz
fields = ['name', 'description','image', 'status', 'category','difficulty','credits'] fields = ['name', 'description', 'status', 'category','difficulty','credits']
class QuestionForm(forms.ModelForm): class QuestionForm(forms.ModelForm):
class Meta: class Meta:

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.1.7 on 2025-04-22 16:04
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0049_alter_qivipquestion_image'),
]
operations = [
migrations.CreateModel(
name='QuestionImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(blank=True, null=True, upload_to='question_images/', verbose_name='Bild')),
],
),
migrations.CreateModel(
name='QuizImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(blank=True, max_length=256, null=True, upload_to='quiz_images/', verbose_name='Bild')),
],
),
migrations.AlterField(
model_name='qivipquestion',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='questions', to='library.questionimage'),
),
migrations.AlterField(
model_name='qivipquiz',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quizzes', to='library.QuizImage'),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.1.7 on 2025-04-23 12:04
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0050_questionimage_quizimages_alter_qivipquestion_image_and_more'),
]
operations = [
migrations.AlterField(
model_name='qivipquestion',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='library.questionimage'),
),
migrations.AlterField(
model_name='qivipquiz',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quizzes', to='library.quizimage'),
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.1.7 on 2025-04-23 12:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0051_alter_qivipquestion_image_alter_qivipquiz_image'),
]
operations = [
migrations.AlterField(
model_name='qivipquestion',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='question_images/', verbose_name='Bild'),
),
migrations.AlterField(
model_name='qivipquiz',
name='image',
field=models.ImageField(blank=True, max_length=256, null=True, upload_to='quiz_images/', verbose_name='Bild'),
),
migrations.DeleteModel(
name='QuestionImage',
),
migrations.DeleteModel(
name='QuizImage',
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.1.7 on 2025-04-23 13:15
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0052_alter_qivipquestion_image_alter_qivipquiz_image_and_more'),
]
operations = [
migrations.CreateModel(
name='QuestionImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(blank=True, null=True, upload_to='question_images/', verbose_name='Bild')),
],
),
migrations.CreateModel(
name='QuizImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(blank=True, max_length=256, null=True, upload_to='quiz_images/', verbose_name='Bild')),
],
),
migrations.AlterField(
model_name='qivipquestion',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='questions', to='library.questionimage'),
),
migrations.AlterField(
model_name='qivipquiz',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='quizzes', to='library.quizimage'),
),
]

View File

@@ -4,6 +4,15 @@ from django.utils.text import slugify
import uuid import uuid
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
class QuizImage(models.Model):
image = models.ImageField(max_length=256,verbose_name="Bild", upload_to='quiz_images/', blank=True, null=True) # Bild speichern in media/quiz_images/
class QuestionImage(models.Model):
image = models.ImageField(verbose_name="Bild", upload_to="question_images/", blank=True, null=True)
class QivipQuiz(models.Model): class QivipQuiz(models.Model):
STATUS_VALUES = { STATUS_VALUES = {
"öffentlich": "Öffentlich", "öffentlich": "Öffentlich",
@@ -27,7 +36,15 @@ class QivipQuiz(models.Model):
if value.strip() == "In dem Quiz geht es um ...": if value.strip() == "In dem Quiz geht es um ...":
raise ValidationError("Bitte gib eine aussagekräftige Beschreibung ein.") raise ValidationError("Bitte gib eine aussagekräftige Beschreibung ein.")
image = models.ImageField(max_length=256,verbose_name="Bild", upload_to='quiz_images/', blank=True, null=True) # Bild speichern in media/quiz_images/ image = models.ForeignKey(
QuizImage,
null=True,
blank=True,
on_delete=models.PROTECT,
related_name="quizzes"
)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz') user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz')
#creator = models.ForeignKey(User, on_delete=models.SET_NULL,blank=True, null=True, related_name='creator_quiz') #creator = models.ForeignKey(User, on_delete=models.SET_NULL,blank=True, null=True, related_name='creator_quiz')
@@ -74,7 +91,14 @@ class QivipQuestion(models.Model):
update_date = models.DateTimeField(auto_now=True) update_date = models.DateTimeField(auto_now=True)
data = models.TextField() data = models.TextField()
time_per_question = models.CharField(max_length=3, choices=list(TIME_VALUES.items()), default=30, help_text="Zeit pro Frage in Sekunden") time_per_question = models.CharField(max_length=3, choices=list(TIME_VALUES.items()), default=30, help_text="Zeit pro Frage in Sekunden")
image = models.ImageField(verbose_name="Bild", upload_to="question_images/", blank=True, null=True) image = models.ForeignKey(
QuestionImage,
null=True,
blank=True,
on_delete=models.PROTECT,
related_name="questions"
)
def __str__(self): def __str__(self):
return self.data[:50] return self.data[:50]

View File

@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.urls import reverse from django.urls import reverse
from django.contrib import messages from django.contrib import messages
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizImage
from .forms import QuizForm, QuestionForm from .forms import QuizForm, QuestionForm
import json import json
from django.core.paginator import Paginator from django.core.paginator import Paginator
@@ -193,10 +193,23 @@ def favorite_quiz(request, pk):
# Neues Quiz erstellen # Neues Quiz erstellen
@login_required @login_required
def new_quiz(request): def new_quiz(request):
if request.method == 'POST': if request.method == 'POST':
form = QuizForm(request.POST, request.FILES) form = QuizForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
quiz = form.save(commit=False) quiz = form.save(commit=False)
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.user_id = request.user
#quiz.creator = request.user #quiz.creator = request.user
quiz.save() quiz.save()
@@ -215,7 +228,27 @@ def edit_quiz(request, pk):
form = QuizForm(request.POST, instance=quiz, files=request.FILES) form = QuizForm(request.POST, instance=quiz, files=request.FILES)
if form.is_valid(): 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
form.save() 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 redirect('library:detail_quiz', pk=pk)
#return modified(request, pk) #return modified(request, pk)
@@ -229,6 +262,19 @@ def delete_quiz(request, pk):
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user) quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
if request.method == 'POST': if request.method == 'POST':
quiz.delete() 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 redirect('library:overview_quiz')
return render(request, 'library/delete_confirmation.html', {'object': quiz}) return render(request, 'library/delete_confirmation.html', {'object': quiz})
@@ -256,6 +302,7 @@ def copy_quiz(request, pk):
original_quiz = get_object_or_404(QivipQuiz, pk=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)
# Quiz kopieren (ohne ID, damit ein neues Objekt erstellt wird) # Quiz kopieren (ohne ID, damit ein neues Objekt erstellt wird)
@@ -264,11 +311,10 @@ def copy_quiz(request, pk):
new_quiz.pk = None # Setzt die ID auf None, damit Django es als neues Objekt erkennt 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.uuid = uuid.uuid4() # Neue UUID generieren
new_quiz.user_id = request.user # Neuer Besitzer ist der aktuelle User new_quiz.user_id = request.user # Neuer Besitzer ist der aktuelle User
new_quiz.name += " - Kopie" # Optional: Name anpassen new_quiz.name += " - Kopie"
new_quiz.base_quiz_id = pk new_quiz.base_quiz_id = pk
new_quiz.rating_count = 0 new_quiz.rating_count = 0
new_quiz.average_rating = 3.0 new_quiz.average_rating = 3.0
#new_quiz.creator = original_quiz.creator or request.user
new_quiz.save() # Speichern als neues Objekt new_quiz.save() # Speichern als neues Objekt
# Optional: Beschreibung anpassen # Optional: Beschreibung anpassen
# Alle zugehörigen Fragen kopieren # Alle zugehörigen Fragen kopieren
@@ -278,6 +324,7 @@ def copy_quiz(request, pk):
question.pk = None # Setze pk auf None, damit es als neues Objekt gespeichert wird 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.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.data = question.data # `data`-Feld wird explizit übernommen # Verknüpfe mit dem neuen Quiz
question.save() question.save()
messages.success(request, "Quiz wurde erfolgreich kopiert!") messages.success(request, "Quiz wurde erfolgreich kopiert!")
@@ -318,7 +365,6 @@ def new_question(request):
if request.method == 'POST': if request.method == 'POST':
question_text = request.POST.get('question', '') question_text = request.POST.get('question', '')
question_image = request.FILES.get('question_image', None)
# Handle different question types # Handle different question types
if question_type == 'true_false': if question_type == 'true_false':
@@ -358,7 +404,14 @@ def new_question(request):
question = QivipQuestion() question = QivipQuestion()
question.data = json.dumps(json_data) question.data = json.dumps(json_data)
question.quiz_id = quiz question.quiz_id = quiz
question.image = question_image 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') question.time_per_question = request.POST.get('time_per_question', '30')
question.save() question.save()
#return modified(request, pk=quiz.pk) #return modified(request, pk=quiz.pk)
@@ -400,6 +453,8 @@ def new_question(request):
@login_required @login_required
def edit_question(request, pk): def edit_question(request, pk):
question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user) 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: if question.quiz_id.user_id != request.user:
return redirect('library:question_list') return redirect('library:question_list')
@@ -453,7 +508,9 @@ def edit_question(request, pk):
if request.method == 'POST': if request.method == 'POST':
question_text = request.POST.get('question', '') question_text = request.POST.get('question', '')
try: try:
question_image = request.FILES['question_image'] question_image=request.FILES['question_image']
imagequestion=QuestionImage.objects.create(image=question_image)
question.image=imagequestion
except Exception: except Exception:
pass pass
@@ -498,8 +555,15 @@ def edit_question(request, pk):
question.data = json.dumps(json_data) question.data = json.dumps(json_data)
question.time_per_question = request.POST.get('time_per_question', '30') question.time_per_question = request.POST.get('time_per_question', '30')
question.image = question_image
question.save() 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 modified(request, question.quiz_id.pk)
return redirect('library:detail_quiz', pk=question.quiz_id.pk) return redirect('library:detail_quiz', pk=question.quiz_id.pk)
@@ -527,5 +591,11 @@ def delete_question(request, pk):
if request.method == 'POST': if request.method == 'POST':
question.delete() 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 redirect('library:detail_quiz', pk=question.quiz_id.pk)
return render(request, 'library/delete_confirmation.html', {'object': question}) return render(request, 'library/delete_confirmation.html', {'object': question})

View File

@@ -5,6 +5,21 @@
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
{% if form.instance.image %}
<img src="{{ form.instance.image.image.url }}" style="max-width: 100px;" alt="Bild der Frage">
<label for="reset_image">
<input type="checkbox" name="reset_image" id="reset_image">
Bild zurücksetzen
</label>
{% endif %}
<div>
<label class="font-bold" for="quiz_image">Bild:</label>
<input class="bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400" type="file" name="quiz_image" id="quiz_image">
</div>
<div class="flex justify-center space-x-3"> <div class="flex justify-center space-x-3">
<a href="{% if object.quiz_id %}{% url 'library:detail_quiz' object.quiz_id.id %}{% else %}{% url 'library:overview_quiz' %}{% endif %}" <a href="{% if object.quiz_id %}{% url 'library:detail_quiz' object.quiz_id.id %}{% else %}{% url 'library:overview_quiz' %}{% endif %}"
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"> class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">

View File

@@ -97,7 +97,7 @@
<div class="relative w-full h-full inset-0 bg-cover bg-center"> <div class="relative w-full h-full inset-0 bg-cover bg-center">
<!-- Hintergrundbild --> <!-- Hintergrundbild -->
<div class="absolute inset-0 bg-cover bg-center" <div class="absolute inset-0 bg-cover bg-center"
style="background-image: url({{ quiz.image.url }});"> style="background-image: url({{ quiz.image.image.url }});">
</div> </div>
</div> </div>
@@ -288,7 +288,7 @@
<div class="relative w-full h-full inset-0 bg-cover bg-center"> <div class="relative w-full h-full inset-0 bg-cover bg-center">
<!-- Hintergrundbild --> <!-- Hintergrundbild -->
<div class="absolute inset-0 bg-cover bg-center" <div class="absolute inset-0 bg-cover bg-center"
style="background-image: url({{ quiz.image.url }});"> style="background-image: url({{ quiz.image.image.url }});">
</div> </div>
</div> </div>
@@ -487,7 +487,7 @@
<div class="relative w-full h-full inset-0 bg-cover bg-center"> <div class="relative w-full h-full inset-0 bg-cover bg-center">
<!-- Hintergrundbild --> <!-- Hintergrundbild -->
<div class="absolute inset-0 bg-cover bg-center" <div class="absolute inset-0 bg-cover bg-center"
style="background-image: url({{ quiz.image.url }});"> style="background-image: url({{ quiz.image.image.url }});">
</div> </div>
</div> </div>

View File

@@ -25,7 +25,11 @@
{% if image %} {% if image %}
<img src="{{ image.url }}" style="max-width: 500px;" alt="Bild der Frage"> <img src="{{ image.image.url }}" style="max-width: 500px;" alt="Bild der Frage">
<label for="reset_image">
<input type="checkbox" name="reset_ques_image" id="reset_ques_image">
Bild zurücksetzen
</label>
{% endif %} {% endif %}
<div> <div>
<label class="font-bold" for="question_image">Bild:</label> <label class="font-bold" for="question_image">Bild:</label>

View File

@@ -25,7 +25,11 @@
</div> </div>
{% if image %} {% if image %}
<img src="{{ image.url }}" style="max-width: 500px;" alt="Bild der Frage"> <img src="{{ image.image.url }}" style="max-width: 500px;" alt="Bild der Frage">
<label for="reset_image">
<input type="checkbox" name="reset_ques_image" id="reset_ques_image">
Bild zurücksetzen
</label>
{% endif %} {% endif %}
<div> <div>
<label class="font-bold" for="question_image">Bild:</label> <label class="font-bold" for="question_image">Bild:</label>

View File

@@ -15,7 +15,7 @@
{% if question_image %} {% if question_image %}
<div class="flex justify-center"> <div class="flex justify-center">
<img class="m-4" src="{{ question_image.url }}" style="max-width: 500px;" alt="Bild der Frage"> <img class="m-4" src="{{ question_image.image.url }}" style="max-width: 500px;" alt="Bild der Frage">
</div> </div>
{% endif %} {% endif %}