Saving image relations on save in editor

This commit is contained in:
Mawoka
2023-05-28 12:03:13 +02:00
parent 10668e420b
commit 0a73c3a811
7 changed files with 77 additions and 11 deletions
+14
View File
@@ -196,3 +196,17 @@ def check_image_string(image: str) -> (bool, uuid.UUID | None):
return True, None
except ValueError:
return False, None
def extract_image_ids_from_quiz(quiz: Quiz) -> list[str | uuid.UUID]:
quiz_images = []
if quiz.background_image is not None:
quiz_images.append(quiz.background_image)
if quiz.cover_image is not None:
quiz_images.append(quiz.cover_image)
for question in quiz.questions:
if question["image"] is None:
continue
quiz_images.append(question["image"])
return quiz_images
+6 -3
View File
@@ -12,7 +12,7 @@ import bleach
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from classquiz.config import settings, redis, storage, meilisearch, ALLOWED_TAGS_FOR_QUIZ
from classquiz.config import settings, redis, storage, meilisearch, ALLOWED_TAGS_FOR_QUIZ, arq
from classquiz.db.models import Quiz, QuizInput, User, QuizQuestionType
from classquiz.auth import get_current_user
import os
@@ -111,6 +111,7 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
images_to_delete = []
old_quiz_data: Quiz = await Quiz.objects.get_or_none(id=session_data.quiz_id, user_id=session_data.user_id)
print(old_quiz_data)
def mark_image_for_deletion(new: str | None, index: int, old_quiz: Quiz | None):
if old_quiz is None:
@@ -133,7 +134,7 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
question.image = None
if image is None:
mark_image_for_deletion(question.image, i, old_quiz_data)
elif check_image_string(quiz_input.cover_image)[0]:
elif check_image_string(question.image)[0]:
mark_image_for_deletion(question.image, i, old_quiz_data)
else:
raise HTTPException(status_code=400, detail="Image URL(s) aren't valid!")
@@ -151,6 +152,7 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
raise HTTPException(status_code=400, detail="image url is not valid")
if session_data.edit:
await arq.enqueue_job("quiz_update", old_quiz_data, old_quiz_data.id, _defer_by=2)
quiz = old_quiz_data
meilisearch.index(settings.meilisearch_index).update_documents([await get_meili_data(quiz)])
if not quiz_input.public:
@@ -174,7 +176,8 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
await redis.srem("edit_sessions", edit_id)
await redis.delete(f"edit_session:{edit_id}")
await redis.delete(f"edit_session:{edit_id}:images")
return await quiz.update()
await quiz.update()
return quiz
else:
quiz = Quiz(
**quiz_input.dict(),
+10 -3
View File
@@ -1,7 +1,6 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import re
from datetime import datetime
from fastapi import APIRouter, HTTPException, UploadFile, File, Depends
@@ -10,6 +9,7 @@ from fastapi.responses import StreamingResponse, RedirectResponse
from classquiz.auth import get_current_user
from classquiz.config import settings, storage, arq
from classquiz.db.models import User, StorageItem, PublicStorageItem, UpdateStorageItem
from classquiz.helpers import check_image_string
from classquiz.storage.errors import DownloadingFailedError
from uuid import uuid4, UUID
@@ -22,10 +22,17 @@ file_regex = r"^[a-z0-9]{8}-[a-z0-9-]{27}--[a-z0-9-]{36}$"
@router.get("/download/{file_name}")
async def download_file(file_name: str):
if not re.match(file_regex, file_name):
checked_image_string = check_image_string(file_name)
if not checked_image_string[0]:
raise HTTPException(status_code=400, detail="Invalid file name")
if checked_image_string[1] is not None:
item = await StorageItem.objects.get_or_none(id=checked_image_string[1])
if item is None:
raise HTTPException(status_code=404, detail="File not found")
file_name = item.storage_path
if file_name is None:
file_name = item.id.hex
if storage.backend == "s3":
print("redir")
return RedirectResponse(url=await storage.get_url(file_name, 300))
try:
download = await storage.download(file_name)
+2 -2
View File
@@ -6,7 +6,7 @@ from arq.connections import RedisSettings
from classquiz import settings
from classquiz.db import database
from classquiz.worker.storage import clean_editor_images_up, calculate_hash
from classquiz.worker.storage import clean_editor_images_up, calculate_hash, quiz_update
async def startup(ctx):
@@ -22,7 +22,7 @@ async def shutdown(ctx):
class WorkerSettings:
# functions = [add_track]
functions = [calculate_hash]
functions = [calculate_hash, quiz_update]
cron_jobs = [cron(clean_editor_images_up, hour={0, 6, 12, 18}, minute=0)]
on_startup = startup
on_shutdown = shutdown
+43 -1
View File
@@ -3,13 +3,15 @@
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import uuid
import ormar.exceptions
from arq.worker import Retry
import xxhash
from classquiz.config import redis, storage
from tempfile import SpooledTemporaryFile
from classquiz.db.models import StorageItem
from classquiz.db.models import StorageItem, Quiz
from classquiz.helpers import extract_image_ids_from_quiz
from classquiz.storage.errors import DeletionFailedError
@@ -50,3 +52,43 @@ async def calculate_hash(ctx, file_id_as_str: str):
print("Got hash!")
await file_data.update()
file.close()
async def quiz_update(ctx, old_quiz: Quiz, quiz_id: uuid.UUID):
new_quiz: Quiz = await Quiz.objects.get(id=quiz_id)
old_images = extract_image_ids_from_quiz(old_quiz)
new_images = extract_image_ids_from_quiz(new_quiz)
# If images are identical, then return
if sorted(old_images) == sorted(new_images):
print("Nothing's changed")
return
print("Change detected")
removed_images = list(set(old_images) - set(new_images))
added_images = list(set(new_images) - set(old_images))
change_made = False
# print("added:", added_images)
# print("removed:", removed_images)
for image in removed_images:
if "--" in image:
await storage.delete([image])
else:
item = await StorageItem.objects.get_or_none(id=uuid.UUID(image))
if item is None:
continue
# print("removed item")
try:
await new_quiz.storageitems.remove(item)
except ormar.exceptions.NoMatch:
continue
change_made = True
for image in added_images:
if "--" not in image:
item = await StorageItem.objects.get_or_none(id=uuid.UUID(image))
if item is None:
continue
# print("added item")
await new_quiz.storageitems.add(item)
change_made = True
if change_made:
await new_quiz.update()
+1 -1
View File
@@ -110,7 +110,7 @@
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button"
on:click={() => {
data.questions[selected_question].image = '';
data.questions[selected_question].image = null;
}}
>
<svg
+1 -1
View File
@@ -72,7 +72,7 @@
alt="not available"
class="max-h-72 h-auto w-auto"
on:contextmenu|preventDefault={() => {
data.cover_image = '';
data.cover_image = null;
}}
/>
</div>