diff --git a/account/api.py b/account/api.py index e339e54..8971192 100644 --- a/account/api.py +++ b/account/api.py @@ -1,4 +1,5 @@ from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.decorators import login_required from ninja import Router from ninja.errors import HttpError from .schemas import UserRegistrationSchema, UserLoginSchema @@ -7,40 +8,36 @@ from .models import RoleChoices, User router = Router() -@router.get("/") -def account(request): - return {"message": "success"} - - @router.post("/register") def user_register(request, payload: UserRegistrationSchema): if User.objects.filter(username=payload.username).exists(): - raise HttpError(400, "Username already exists") + raise HttpError(400, "用户已存在") User.objects.create_user( username=payload.username, email=payload.email, password=payload.password, ) - return {"message": "User created successfully"} + return {"message": "创建成功"} @router.post("/login") def user_login(request, payload: UserLoginSchema): user = authenticate(username=payload.username, password=payload.password) - if user is not None: + if user: login(request, user) return {"username": user.username, "role": user.role} else: - raise HttpError(401, "Invalid credentials") + raise HttpError(401, "账号密码错误") @router.post("/logout") +@login_required def user_logout(request): logout(request) @router.get("/profile") -def current_user_profile(request): +def my_profile(request): # 暂时这样写 if request.user.is_authenticated: return {"username": request.user.get_username(), "role": request.user.role} diff --git a/account/decorators.py b/account/decorators.py new file mode 100644 index 0000000..06f3906 --- /dev/null +++ b/account/decorators.py @@ -0,0 +1,28 @@ +from ninja.errors import HttpError +from ninja import NinjaAPI +from functools import wraps +from .models import User, RoleChoices + +api = NinjaAPI() + + +def _require(roles): + def decorator(func): + @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, "用户不存在") + return func(request, *args, **kwargs) + + return wrapper + + return decorator + +admin_required = _require([RoleChoices.ADMIN, RoleChoices.SUPER]) + +super_required = _require([RoleChoices.SUPER]) diff --git a/account/models.py b/account/models.py index 8aef0ed..ac204bc 100644 --- a/account/models.py +++ b/account/models.py @@ -4,7 +4,7 @@ from django.contrib.auth.models import AbstractUser class RoleChoices(models.TextChoices): - SUPER = "sup er", "超级管理员" + SUPER = "super", "超级管理员" ADMIN = "admin", "管理员" NORMAL = "normal", "普通" diff --git a/api/settings.py b/api/settings.py index 7351483..d99c0f2 100644 --- a/api/settings.py +++ b/api/settings.py @@ -42,7 +42,9 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", "ninja", "corsheaders", + "django_extensions", "account", + "tutorial", ] MIDDLEWARE = [ diff --git a/api/urls.py b/api/urls.py index 1b9f47f..6d081b5 100644 --- a/api/urls.py +++ b/api/urls.py @@ -22,6 +22,7 @@ from ninja import NinjaAPI api = NinjaAPI() api.add_router("account/", "account.api.router") +api.add_router("tutorial/", "tutorial.api.router") urlpatterns = [ path("admin/", admin.site.urls), diff --git a/requirements.txt b/requirements.txt index 0ad3953..d013c53 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/tutorial/__init__.py b/tutorial/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tutorial/admin.py b/tutorial/admin.py new file mode 100644 index 0000000..87a26f2 --- /dev/null +++ b/tutorial/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from .models import Tutorial + +admin.site.register(Tutorial) diff --git a/tutorial/api.py b/tutorial/api.py new file mode 100644 index 0000000..8bf0eb5 --- /dev/null +++ b/tutorial/api.py @@ -0,0 +1,41 @@ +from ninja import Router +from ninja.errors import HttpError + +from account.decorators import super_required + +# from account.decorators import super_required +from .schemas import TutorialAll, TutorialIn, TutorialReturn +from .models import Tutorial + +router = Router() + + +@router.get("/", response=TutorialReturn) +@super_required +def tutorial(request): + return { + "total": Tutorial.objects.count(), + "list": Tutorial.objects.all(), + "first": Tutorial.objects.first(), + } + + +@router.get("/{display}", response=TutorialAll) +def get(request, display: str): + return Tutorial.objects.get(display=display) + + +@router.post("/") +@super_required +def create(request, payload: TutorialIn): + if Tutorial.objects.filter(display=payload.display): + raise HttpError(400, "有序号相同的教程存在") + Tutorial.objects.create(**payload.dict()) + return {"message": "创建成功"} + + +@router.delete("/{display}") +@super_required +def remove(request, display: str): + Tutorial.objects.filter(display=display).delete() + return {"message": "删除成功"} diff --git a/tutorial/apps.py b/tutorial/apps.py new file mode 100644 index 0000000..868f019 --- /dev/null +++ b/tutorial/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TutorialConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'tutorial' diff --git a/tutorial/migrations/0001_initial.py b/tutorial/migrations/0001_initial.py new file mode 100644 index 0000000..604aa94 --- /dev/null +++ b/tutorial/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 5.1.6 on 2025-03-03 11:38 + +import django_extensions.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Tutorial', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')), + ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')), + ('display', models.CharField(max_length=10, unique=True)), + ('title', models.CharField(max_length=100)), + ('content', models.TextField()), + ('is_public', models.BooleanField(default=False)), + ], + options={ + 'get_latest_by': 'modified', + 'abstract': False, + }, + ), + ] diff --git a/tutorial/migrations/__init__.py b/tutorial/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tutorial/models.py b/tutorial/models.py new file mode 100644 index 0000000..4986058 --- /dev/null +++ b/tutorial/models.py @@ -0,0 +1,12 @@ +from django.db import models +from django_extensions.db.models import TimeStampedModel + + +class Tutorial(TimeStampedModel): + display = models.CharField(max_length=10, unique=True) + title = models.CharField(max_length=100) + content = models.TextField() + is_public = models.BooleanField(default=False) + + def __str__(self): + return self.title diff --git a/tutorial/schemas.py b/tutorial/schemas.py new file mode 100644 index 0000000..a0497a3 --- /dev/null +++ b/tutorial/schemas.py @@ -0,0 +1,28 @@ +from ninja import Schema, ModelSchema +from typing import List, Optional +from .models import Tutorial + + +class TutorialSlim(Schema): + display: str + title: str + is_public: bool + + +class TutorialAll(ModelSchema): + class Meta: + model = Tutorial + fields = "__all__" + + +class TutorialReturn(Schema): + total: int + list: List[TutorialSlim] + first: Optional[TutorialAll] + + +class TutorialIn(Schema): + display: str + title: str + content: str + is_public: bool = False