Use UploadedImage to check image correctness and better error responses

This commit is contained in:
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
from django.conf import settings
from django.db.models import F
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.exceptions import PermissionDenied
from ninja_extra.pagination import PageNumberPaginationExtra
from ninja_extra.schemas import PaginatedResponseSchema
from PIL import UnidentifiedImageError
from club.models import Mailing
from core.auth.api_permissions import CanAccessLookup, CanView, IsOldSubscriber
@ -20,6 +19,7 @@ from core.schemas import (
MarkdownSchema,
SithFileSchema,
UploadedFileSchema,
UploadedImage,
UserFamilySchema,
UserFilterSchema,
UserProfileSchema,
@ -39,25 +39,18 @@ class MarkdownController(ControllerBase):
class UploadController(ControllerBase):
@route.post(
"/image",
response=UploadedFileSchema,
response={
200: UploadedFileSchema,
422: dict[Literal["detail"], list[dict[str, Any]]],
403: dict[Literal["detail"], str],
},
permissions=[IsOldSubscriber],
url_name="quick_upload_image",
)
def upload_image(self, file: UploadedFile):
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(
file, uploader=self.context.request.user
)
except UnidentifiedImageError:
return self.create_response(
message=f"{file.name} can't be processed", status_code=415
)
def upload_image(self, file: File[UploadedImage]):
image = QuickUploadImage.create_from_uploaded(
file, uploader=self.context.request.user
)
return image

View File

@ -6,7 +6,11 @@ import { inheritHtmlElement, registerComponent } from "#core:utils/web-component
import type CodeMirror from "codemirror";
// biome-ignore lint/style/useNamingConvention: This is how they called their namespace
import EasyMDE from "easymde";
import { markdownRenderMarkdown, uploadUploadImage } from "#openapi";
import {
type UploadUploadImageErrors,
markdownRenderMarkdown,
uploadUploadImage,
} from "#openapi";
const loadEasyMde = (textarea: HTMLTextAreaElement) => {
const easymde = new EasyMDE({
@ -21,8 +25,18 @@ const loadEasyMde = (textarea: HTMLTextAreaElement) => {
file: file,
},
});
if (response.response.status !== 200) {
onError(gettext("Invalid file"));
if (!response.response.ok) {
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;
}
onSuccess(response.data.href);

View File

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