diff --git a/core/api.py b/core/api.py index e1b3bbbd..47601a05 100644 --- a/core/api.py +++ b/core/api.py @@ -1,23 +1,30 @@ +from io import BytesIO +from pathlib import Path from typing import Annotated +from uuid import uuid4 import annotated_types from django.conf import settings +from django.core.files.base import ContentFile +from django.db import transaction from django.db.models import F from django.http import HttpResponse -from ninja import Query +from ninja import Query, UploadedFile 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 Image, UnidentifiedImageError from club.models import Mailing -from core.auth.api_permissions import CanAccessLookup, CanView +from core.auth.api_permissions import CanAccessLookup, CanView, IsOldSubscriber from core.models import Group, SithFile, User from core.schemas import ( FamilyGodfatherSchema, GroupSchema, MarkdownSchema, SithFileSchema, + UploadedFileSchema, UserFamilySchema, UserFilterSchema, UserProfileSchema, @@ -33,6 +40,59 @@ class MarkdownController(ControllerBase): return HttpResponse(markdown(body.text), content_type="text/html") +@api_controller("/upload") +class UploadController(ControllerBase): + @route.post("/images", response=UploadedFileSchema, permissions=[IsOldSubscriber]) + def upload_assets(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=400 + ) + + def convert_image(file: UploadedFile) -> ContentFile: + content = BytesIO() + Image.open(BytesIO(file.read())).save( + fp=content, format="webp", optimize=True + ) + return ContentFile(content.getvalue()) + + try: + converted = convert_image(file) + except UnidentifiedImageError: + return self.create_response( + message=f"{file.name} can't be processed", status_code=400 + ) + + with transaction.atomic(): + parent = SithFile.objects.filter(parent=None, name="upload").first() + if parent is None: + root = User.objects.get(id=settings.SITH_ROOT_USER_ID) + parent = SithFile.objects.create( + parent=None, + name="upload", + owner=root, + ) + image = SithFile( + parent=parent, + name=f"{Path(file.name).stem}_{uuid4()}.webp", + file=converted, + owner=self.context.request.user, + is_folder=False, + mime_type="img/webp", + size=converted.size, + moderator=self.context.request.user, + is_moderated=True, + ) + image.file.name = image.name + image.clean() + image.save() + image.view_groups.add( + Group.objects.filter(id=settings.SITH_GROUP_PUBLIC_ID).first() + ) + image.save() + return image + + @api_controller("/mailings") class MailingListController(ControllerBase): @route.get("", response=str) diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 21fde2e5..c32a1b4b 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -856,6 +856,10 @@ Welcome to the wiki page! ] ) + # Upload folder + SithFile.objects.create(name="upload", owner=root) + (settings.MEDIA_ROOT / "upload").mkdir(parents=True, exist_ok=True) + def _create_profile_pict(self, user: User): path = self.SAS_FIXTURE_PATH / "Family" / f"{user.username}.jpg" file = resize_image(Image.open(path), 400, "WEBP") diff --git a/core/schemas.py b/core/schemas.py index f4080c90..57d40610 100644 --- a/core/schemas.py +++ b/core/schemas.py @@ -9,6 +9,7 @@ from django.utils.text import slugify from haystack.query import SearchQuerySet from ninja import FilterSchema, ModelSchema, Schema from pydantic import AliasChoices, Field +from pydantic_core import Url from core.models import Group, SithFile, User @@ -47,6 +48,18 @@ class UserProfileSchema(ModelSchema): return reverse("core:download", kwargs={"file_id": obj.profile_pict_id}) +class UploadedFileSchema(ModelSchema): + class Meta: + model = SithFile + fields = ["id", "name", "mime_type", "size"] + + href: str + + @staticmethod + def resolve_href(obj: SithFile) -> Url: + return reverse("core:download", kwargs={"file_id": obj.id}) + + class SithFileSchema(ModelSchema): class Meta: model = SithFile