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 .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuizCategory
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizCategory, QuizImage
# Für das QivipQuiz Modell
class QivipQuizAdmin(admin.ModelAdmin):
@@ -23,8 +23,17 @@ class QuizCategoryAdmin(admin.ModelAdmin):
class QivipQuizFavoriteAdmin(admin.ModelAdmin):
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
admin.site.register(QivipQuiz, QivipQuizAdmin)
admin.site.register(QivipQuestion, QivipQuestionAdmin)
admin.site.register(QuizCategory, QuizCategoryAdmin)
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 Meta:
model = QivipQuiz
fields = ['name', 'description','image', 'status', 'category','difficulty','credits']
fields = ['name', 'description', 'status', 'category','difficulty','credits']
class QuestionForm(forms.ModelForm):
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
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):
STATUS_VALUES = {
"öffentlich": "Öffentlich",
@@ -27,7 +36,15 @@ class QivipQuiz(models.Model):
if value.strip() == "In dem Quiz geht es um ...":
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)
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')
@@ -74,7 +91,14 @@ class QivipQuestion(models.Model):
update_date = models.DateTimeField(auto_now=True)
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")
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):
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.urls import reverse
from django.contrib import messages
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizImage
from .forms import QuizForm, QuestionForm
import json
from django.core.paginator import Paginator
@@ -193,10 +193,23 @@ def favorite_quiz(request, pk):
# 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)
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()
@@ -215,7 +228,27 @@ def edit_quiz(request, pk):
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
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)
@@ -229,6 +262,19 @@ 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})
@@ -256,6 +302,7 @@ 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)
@@ -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.uuid = uuid.uuid4() # Neue UUID generieren
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.rating_count = 0
new_quiz.average_rating = 3.0
#new_quiz.creator = original_quiz.creator or request.user
new_quiz.save() # Speichern als neues Objekt
# Optional: Beschreibung anpassen
# 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.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!")
@@ -318,7 +365,6 @@ def new_question(request):
if request.method == 'POST':
question_text = request.POST.get('question', '')
question_image = request.FILES.get('question_image', None)
# Handle different question types
if question_type == 'true_false':
@@ -358,7 +404,14 @@ def new_question(request):
question = QivipQuestion()
question.data = json.dumps(json_data)
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.save()
#return modified(request, pk=quiz.pk)
@@ -400,6 +453,8 @@ def new_question(request):
@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')
@@ -454,6 +509,8 @@ def edit_question(request, pk):
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
@@ -498,8 +555,15 @@ def edit_question(request, pk):
question.data = json.dumps(json_data)
question.time_per_question = request.POST.get('time_per_question', '30')
question.image = question_image
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)
@@ -527,5 +591,11 @@ def delete_question(request, pk):
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})

View File

@@ -5,6 +5,21 @@
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ 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">
<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">

View File

@@ -97,7 +97,7 @@
<div class="relative w-full h-full inset-0 bg-cover bg-center">
<!-- Hintergrundbild -->
<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>
@@ -288,7 +288,7 @@
<div class="relative w-full h-full inset-0 bg-cover bg-center">
<!-- Hintergrundbild -->
<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>
@@ -487,7 +487,7 @@
<div class="relative w-full h-full inset-0 bg-cover bg-center">
<!-- Hintergrundbild -->
<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>

View File

@@ -25,7 +25,11 @@
{% 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 %}
<div>
<label class="font-bold" for="question_image">Bild:</label>

View File

@@ -25,7 +25,11 @@
</div>
{% 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 %}
<div>
<label class="font-bold" for="question_image">Bild:</label>

View File

@@ -15,7 +15,7 @@
{% if question_image %}
<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>
{% endif %}