Use UploadedImage to check image correctness and better error responses

This commit is contained in:
Antoine Bartuccio 2025-04-09 22:15:12 +02:00
parent 67bc49fb21
commit 6e39b59dd5
4 changed files with 42 additions and 29 deletions

View File

@ -1,15 +1,14 @@
from typing import Annotated from typing import Annotated, Any, Literal
import annotated_types import annotated_types
from django.conf import settings from django.conf import settings
from django.db.models import F from django.db.models import F
from django.http import HttpResponse from django.http import HttpResponse
from ninja import Query, UploadedFile from ninja import File, Query
from ninja_extra import ControllerBase, api_controller, paginate, route from ninja_extra import ControllerBase, api_controller, paginate, route
from ninja_extra.exceptions import PermissionDenied from ninja_extra.exceptions import PermissionDenied
from ninja_extra.pagination import PageNumberPaginationExtra from ninja_extra.pagination import PageNumberPaginationExtra
from ninja_extra.schemas import PaginatedResponseSchema from ninja_extra.schemas import PaginatedResponseSchema
from PIL import UnidentifiedImageError
from club.models import Mailing from club.models import Mailing
from core.auth.api_permissions import CanAccessLookup, CanView, IsOldSubscriber from core.auth.api_permissions import CanAccessLookup, CanView, IsOldSubscriber
@ -20,6 +19,7 @@ from core.schemas import (
MarkdownSchema, MarkdownSchema,
SithFileSchema, SithFileSchema,
UploadedFileSchema, UploadedFileSchema,
UploadedImage,
UserFamilySchema, UserFamilySchema,
UserFilterSchema, UserFilterSchema,
UserProfileSchema, UserProfileSchema,
@ -39,25 +39,18 @@ class MarkdownController(ControllerBase):
class UploadController(ControllerBase): class UploadController(ControllerBase):
@route.post( @route.post(
"/image", "/image",
response=UploadedFileSchema, response={
200: UploadedFileSchema,
422: dict[Literal["detail"], list[dict[str, Any]]],
403: dict[Literal["detail"], str],
},
permissions=[IsOldSubscriber], permissions=[IsOldSubscriber],
url_name="quick_upload_image", url_name="quick_upload_image",
) )
def upload_image(self, file: UploadedFile): def upload_image(self, file: File[UploadedImage]):
if file.content_type.split("/")[0] != "image":
return self.create_response(
message=f"{file.name} isn't a file image", status_code=415
)
try:
image = QuickUploadImage.create_from_uploaded( image = QuickUploadImage.create_from_uploaded(
file, uploader=self.context.request.user file, uploader=self.context.request.user
) )
except UnidentifiedImageError:
return self.create_response(
message=f"{file.name} can't be processed", status_code=415
)
return image return image

View File

@ -6,7 +6,11 @@ import { inheritHtmlElement, registerComponent } from "#core:utils/web-component
import type CodeMirror from "codemirror"; import type CodeMirror from "codemirror";
// biome-ignore lint/style/useNamingConvention: This is how they called their namespace // biome-ignore lint/style/useNamingConvention: This is how they called their namespace
import EasyMDE from "easymde"; import EasyMDE from "easymde";
import { markdownRenderMarkdown, uploadUploadImage } from "#openapi"; import {
type UploadUploadImageErrors,
markdownRenderMarkdown,
uploadUploadImage,
} from "#openapi";
const loadEasyMde = (textarea: HTMLTextAreaElement) => { const loadEasyMde = (textarea: HTMLTextAreaElement) => {
const easymde = new EasyMDE({ const easymde = new EasyMDE({
@ -21,8 +25,18 @@ const loadEasyMde = (textarea: HTMLTextAreaElement) => {
file: file, file: file,
}, },
}); });
if (response.response.status !== 200) { if (!response.response.ok) {
onError(gettext("Invalid file")); if (response.response.status === 422) {
onError(
(response.error as UploadUploadImageErrors[422]).detail
.map((err: Record<"ctx", Record<"error", string>>) => err.ctx.error)
.join(" ; "),
);
} else if (response.response.status === 403) {
onError(gettext("Not authorized, you need to have subscribed at least once"));
} else {
onError(gettext("Could not upload image"));
}
return; return;
} }
onSuccess(response.data.href); onSuccess(response.data.href);

View File

@ -306,19 +306,19 @@ def test_apply_rights_recursively():
SimpleUploadedFile( SimpleUploadedFile(
"test.jpg", content=b"invalid", content_type="image/jpg" "test.jpg", content=b"invalid", content_type="image/jpg"
), ),
415, 422,
), ),
( (
lambda: old_subscriber_user.make(), lambda: old_subscriber_user.make(),
SimpleUploadedFile( SimpleUploadedFile(
"test.jpg", content=RED_PIXEL_PNG, content_type="invalid" "test.jpg", content=RED_PIXEL_PNG, content_type="invalid"
), ),
415, 200, # PIL can guess
), ),
( (
lambda: old_subscriber_user.make(), lambda: old_subscriber_user.make(),
SimpleUploadedFile("test.jpg", content=b"invalid", content_type="invalid"), SimpleUploadedFile("test.jpg", content=b"invalid", content_type="invalid"),
415, 422,
), ),
], ],
) )

View File

@ -7,7 +7,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-08 11:42+0200\n" "POT-Creation-Date: 2025-04-09 22:12+0200\n"
"PO-Revision-Date: 2024-09-17 11:54+0200\n" "PO-Revision-Date: 2024-09-17 11:54+0200\n"
"Last-Translator: Sli <antoine@bartuccio.fr>\n" "Last-Translator: Sli <antoine@bartuccio.fr>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -64,12 +64,18 @@ msgid "No results found"
msgstr "Aucun résultat trouvé" msgstr "Aucun résultat trouvé"
#: core/static/bundled/core/components/easymde-index.ts #: core/static/bundled/core/components/easymde-index.ts
msgid "Invalid file" msgid "Not authorized, you need to have subscribed at least once"
msgstr "Fichier invalide" msgstr ""
#: core/static/bundled/core/components/easymde-index.ts
msgid "Could not upload image"
msgstr "L'image n'a pas pu être téléversée"
#: core/static/bundled/core/components/easymde-index.ts #: core/static/bundled/core/components/easymde-index.ts
msgid "Attach files by drag and dropping or pasting from clipboard." msgid "Attach files by drag and dropping or pasting from clipboard."
msgstr "Ajoutez des fichiez en glissant déposant ou collant depuis votre presse papier." msgstr ""
"Ajoutez des fichiez en glissant déposant ou collant depuis votre presse "
"papier."
#: core/static/bundled/core/components/easymde-index.ts #: core/static/bundled/core/components/easymde-index.ts
msgid "Drop image to upload it." msgid "Drop image to upload it."