diff --git a/account/api.py b/account/api.py index 5f34e74..69c9597 100644 --- a/account/api.py +++ b/account/api.py @@ -1,4 +1,3 @@ -import random from typing import List from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required diff --git a/account/decorators.py b/account/decorators.py index 06f3906..10eedb9 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -1,28 +1,28 @@ -from ninja.errors import HttpError from ninja import NinjaAPI from functools import wraps +from typing import Callable, List, Any +from django.http import HttpRequest +from django.contrib.auth.decorators import login_required, user_passes_test from .models import User, RoleChoices api = NinjaAPI() -def _require(roles): - def decorator(func): +def _require(roles: List[RoleChoices]) -> Callable: + def check_role(user: User) -> bool: + return user.is_authenticated and hasattr(user, "role") and user.role in roles + + def decorator(func: Callable) -> Callable: @wraps(func) - def wrapper(request, *args, **kwargs): - if not request.user.is_authenticated: - raise HttpError(401, "用户未登录") - try: - if request.user.role not in roles: - raise HttpError(403, "你没有权限") - except User.DoesNotExist: - raise HttpError(404, "用户不存在") + @login_required + @user_passes_test(check_role, login_url=None) + def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> Any: return func(request, *args, **kwargs) return wrapper return decorator -admin_required = _require([RoleChoices.ADMIN, RoleChoices.SUPER]) +admin_required = _require([RoleChoices.ADMIN, RoleChoices.SUPER]) super_required = _require([RoleChoices.SUPER]) diff --git a/api/settings.py b/api/settings.py index 1453ed8..ac6b404 100644 --- a/api/settings.py +++ b/api/settings.py @@ -183,3 +183,7 @@ CORS_ALLOW_CREDENTIALS = True NINJA_PAGINATION_CLASS = "ninja.pagination.PageNumberPagination" NINJA_PAGINATION_PER_PAGE = 10 + +# Media files +MEDIA_URL = "/media/" +MEDIA_ROOT = BASE_DIR.parent / "media" diff --git a/api/urls.py b/api/urls.py index c4d2e08..cec5f6f 100644 --- a/api/urls.py +++ b/api/urls.py @@ -17,6 +17,8 @@ Including another URLconf from django.contrib import admin from django.urls import path +from django.conf import settings +from django.conf.urls.static import static from ninja import NinjaAPI api = NinjaAPI() @@ -24,8 +26,12 @@ api = NinjaAPI() api.add_router("account/", "account.api.router") api.add_router("tutorial/", "task.tutorial.router") api.add_router("submission/", "submission.api.router") +api.add_router("upload/", "utils.upload.router") -urlpatterns = [ + +apis = [ path("admin/", admin.site.urls), path("api/", api.urls), ] + +urlpatterns = apis + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/data/hitokoto b/data/hitokoto deleted file mode 160000 index b503a98..0000000 --- a/data/hitokoto +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b503a98f236a3737df3615316c08052e6f99ca07 diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/upload.py b/utils/upload.py new file mode 100644 index 0000000..ff50a4c --- /dev/null +++ b/utils/upload.py @@ -0,0 +1,29 @@ +from ninja import Router, File +from ninja.files import UploadedFile +from pathlib import Path +from django.conf import settings +import uuid +from account.decorators import super_required + +router = Router() + + +@router.post("") +@super_required +def upload_to_media(request, image: File[UploadedFile]): + # 生成唯一的文件名 + ext = Path(image.name).suffix + filename = f"{uuid.uuid4()}{ext}" + + # 确保 media 目录存在 + media_root = Path(settings.MEDIA_ROOT) + media_root.mkdir(exist_ok=True) + + # 保存文件 + file_path = media_root / filename + with open(file_path, "wb+") as f: + for chunk in image.chunks(): + f.write(chunk) + + # 返回文件URL + return {"url": f"{settings.MEDIA_URL}{filename}"}