完善用户注册流程,支持注册码验证与管理,新增用户授权网站列表接口
This commit is contained in:
parent
7557735ee7
commit
5235fe1e77
@ -3,6 +3,8 @@ from django.contrib.auth import get_user_model
|
|||||||
from rest_framework_simplejwt.tokens import RefreshToken
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from invites.models import RegistrationCode
|
||||||
|
|
||||||
auth_router = Router(tags=["认证"])
|
auth_router = Router(tags=["认证"])
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
@ -13,19 +15,28 @@ def register(
|
|||||||
username: str = Form(...),
|
username: str = Form(...),
|
||||||
password: str = Form(...),
|
password: str = Form(...),
|
||||||
email: str = Form(...),
|
email: str = Form(...),
|
||||||
role: str = Form("user") # 可选:默认 user
|
code: str = Form(None)
|
||||||
):
|
):
|
||||||
if User.objects.filter(username=username).exists():
|
if User.objects.filter(username=username).exists():
|
||||||
return {"success": False, "message": "用户名已存在"}
|
return {"success": False, "message": "用户名已存在"}
|
||||||
|
|
||||||
if role != "user":
|
user = User(username=username, email=email, role="user")
|
||||||
return {"success": False, "message": "不能注册管理员或分管理账号"}
|
|
||||||
|
|
||||||
user = User(username=username, email=email, role=role)
|
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
if code:
|
||||||
|
try:
|
||||||
|
reg = RegistrationCode.objects.get(code=code)
|
||||||
|
if not reg.is_available():
|
||||||
|
return {"success": False, "message": "注册码已达使用上限"}
|
||||||
|
user.authorized_websites.set(reg.manager.managed_websites.all())
|
||||||
|
reg.used_count += 1
|
||||||
|
reg.save()
|
||||||
|
except RegistrationCode.DoesNotExist:
|
||||||
|
return {"success": False, "message": "注册码无效"}
|
||||||
|
|
||||||
refresh = RefreshToken.for_user(user)
|
refresh = RefreshToken.for_user(user)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "注册成功",
|
"message": "注册成功",
|
||||||
@ -40,7 +51,6 @@ def register(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@auth_router.post("/login")
|
@auth_router.post("/login")
|
||||||
def login(
|
def login(
|
||||||
request,
|
request,
|
||||||
|
@ -12,9 +12,7 @@ from utils.permissions import manager_required, login_required
|
|||||||
|
|
||||||
router = Router(tags=["授权管理"])
|
router = Router(tags=["授权管理"])
|
||||||
|
|
||||||
# =========================
|
|
||||||
# 模型(授权申请)
|
|
||||||
# =========================
|
|
||||||
class WebsiteAccessRequest(models.Model):
|
class WebsiteAccessRequest(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
website = models.ForeignKey(Website, on_delete=models.CASCADE)
|
website = models.ForeignKey(Website, on_delete=models.CASCADE)
|
||||||
@ -26,20 +24,17 @@ class WebsiteAccessRequest(models.Model):
|
|||||||
reason = models.TextField(blank=True)
|
reason = models.TextField(blank=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
# =========================
|
|
||||||
# 请求结构
|
|
||||||
# =========================
|
|
||||||
class AuthorizeIn(Schema):
|
class AuthorizeIn(Schema):
|
||||||
user_id: int = Field(..., description="被授权的用户ID")
|
user_id: int = Field(..., description="被授权的用户ID")
|
||||||
website_ids: List[int] = Field(..., description="要授权的网站ID列表")
|
website_ids: List[int] = Field(..., description="要授权的网站ID列表")
|
||||||
|
|
||||||
|
|
||||||
class AccessRequestIn(Schema):
|
class AccessRequestIn(Schema):
|
||||||
website_id: int = Field(...)
|
website_id: int = Field(...)
|
||||||
reason: Optional[str] = Field(None, description="申请原因")
|
reason: Optional[str] = Field(None, description="申请原因")
|
||||||
|
|
||||||
# =========================
|
|
||||||
# 授权接口(POST)
|
|
||||||
# =========================
|
|
||||||
@router.post("/authorize", auth=jwt_auth)
|
@router.post("/authorize", auth=jwt_auth)
|
||||||
@manager_required
|
@manager_required
|
||||||
def authorize_user(request, data: AuthorizeIn):
|
def authorize_user(request, data: AuthorizeIn):
|
||||||
@ -64,9 +59,7 @@ def authorize_user(request, data: AuthorizeIn):
|
|||||||
"message": f"已授权 {target_user.username} 访问 {len(data.website_ids)} 个网站",
|
"message": f"已授权 {target_user.username} 访问 {len(data.website_ids)} 个网站",
|
||||||
}
|
}
|
||||||
|
|
||||||
# =========================
|
|
||||||
# 用户发起申请(POST)
|
|
||||||
# =========================
|
|
||||||
@router.post("/apply", auth=jwt_auth)
|
@router.post("/apply", auth=jwt_auth)
|
||||||
@login_required
|
@login_required
|
||||||
def request_access(request, data: AccessRequestIn):
|
def request_access(request, data: AccessRequestIn):
|
||||||
@ -81,9 +74,7 @@ def request_access(request, data: AccessRequestIn):
|
|||||||
|
|
||||||
return {"success": True, "message": "申请已提交,等待分管理审批"}
|
return {"success": True, "message": "申请已提交,等待分管理审批"}
|
||||||
|
|
||||||
# =========================
|
|
||||||
# 分管理查看待审批列表
|
|
||||||
# =========================
|
|
||||||
@router.get("/pending", auth=jwt_auth)
|
@router.get("/pending", auth=jwt_auth)
|
||||||
@manager_required
|
@manager_required
|
||||||
def list_pending_requests(request):
|
def list_pending_requests(request):
|
||||||
@ -106,9 +97,7 @@ def list_pending_requests(request):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
# =========================
|
|
||||||
# 分管理审批接口
|
|
||||||
# =========================
|
|
||||||
@router.post("/approve", auth=jwt_auth)
|
@router.post("/approve", auth=jwt_auth)
|
||||||
@manager_required
|
@manager_required
|
||||||
def approve_request(request, request_id: int = Query(...), approve: bool = Query(True)):
|
def approve_request(request, request_id: int = Query(...), approve: bool = Query(True)):
|
||||||
@ -124,3 +113,17 @@ def approve_request(request, request_id: int = Query(...), approve: bool = Query
|
|||||||
r.user.authorized_websites.add(r.website)
|
r.user.authorized_websites.add(r.website)
|
||||||
|
|
||||||
return {"success": True, "message": f"已{'通过' if approve else '拒绝'} {r.user.username} 的访问申请"}
|
return {"success": True, "message": f"已{'通过' if approve else '拒绝'} {r.user.username} 的访问申请"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/my-sites", auth=jwt_auth)
|
||||||
|
@login_required
|
||||||
|
def list_my_authorized_websites(request):
|
||||||
|
user = request.user
|
||||||
|
sites = user.authorized_websites.all().values("id", "name", "db_alias")
|
||||||
|
return {"success": True, "websites": list(sites)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/public-sites")
|
||||||
|
def list_public_websites(request):
|
||||||
|
websites = Website.objects.all().values("id", "name", "db_alias", "description")
|
||||||
|
return {"success": True, "websites": list(websites)}
|
||||||
|
@ -4,3 +4,6 @@ from django.apps import AppConfig
|
|||||||
class AccountsConfig(AppConfig):
|
class AccountsConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'accounts'
|
name = 'accounts'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import accounts.signals
|
19
accounts/signals.py
Normal file
19
accounts/signals.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from accounts.models import User
|
||||||
|
from invites.models import RegistrationCode
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=User)
|
||||||
|
def create_registration_code_for_manager(sender, instance, created, **kwargs):
|
||||||
|
if instance.role == "manager":
|
||||||
|
if created or not RegistrationCode.objects.filter(manager=instance).exists():
|
||||||
|
RegistrationCode.objects.create(
|
||||||
|
code=str(uuid.uuid4()).replace("-", "")[:12],
|
||||||
|
manager=instance,
|
||||||
|
description=f"{instance.username} 的默认邀请码",
|
||||||
|
usage_limit=10
|
||||||
|
)
|
||||||
|
elif instance.role == "user":
|
||||||
|
RegistrationCode.objects.filter(manager=instance).update(usage_limit=0)
|
@ -50,6 +50,7 @@ INSTALLED_APPS = [
|
|||||||
'access_control',
|
'access_control',
|
||||||
'admin_panel',
|
'admin_panel',
|
||||||
'logs',
|
'logs',
|
||||||
|
'invites'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
0
invites/__init__.py
Normal file
0
invites/__init__.py
Normal file
10
invites/admin.py
Normal file
10
invites/admin.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from invites.models import RegistrationCode
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(RegistrationCode)
|
||||||
|
class RegistrationCodeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("code", "manager", "usage_limit", "used_count", "created_at")
|
||||||
|
list_filter = ("manager",)
|
||||||
|
search_fields = ("code", "manager__username")
|
||||||
|
readonly_fields = ("used_count", "created_at")
|
6
invites/apps.py
Normal file
6
invites/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class InvitesConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "invites"
|
0
invites/migrations/__init__.py
Normal file
0
invites/migrations/__init__.py
Normal file
21
invites/models.py
Normal file
21
invites/models.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.db import models
|
||||||
|
from accounts.models import User
|
||||||
|
|
||||||
|
class RegistrationCode(models.Model):
|
||||||
|
code = models.CharField(max_length=32, unique=True, verbose_name="注册码")
|
||||||
|
manager = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
limit_choices_to={"role": "manager"},
|
||||||
|
verbose_name="对应分管理"
|
||||||
|
)
|
||||||
|
description = models.CharField(max_length=100, blank=True, verbose_name="说明")
|
||||||
|
usage_limit = models.IntegerField(default=1, verbose_name="最多使用次数")
|
||||||
|
used_count = models.IntegerField(default=0, verbose_name="已使用次数")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.code} ({self.used_count}/{self.usage_limit})"
|
||||||
|
|
||||||
|
def is_available(self):
|
||||||
|
return self.used_count < self.usage_limit
|
3
invites/tests.py
Normal file
3
invites/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
invites/views.py
Normal file
3
invites/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
@ -1,36 +1,32 @@
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from ninja.errors import HttpError
|
from ninja.errors import HttpError
|
||||||
|
|
||||||
|
|
||||||
from functools import wraps
|
|
||||||
from ninja.errors import HttpError
|
|
||||||
|
|
||||||
|
|
||||||
def login_required(func):
|
def login_required(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(request, *args, **kwargs):
|
def wrapper(request, *args, **kwargs):
|
||||||
user = getattr(request, 'user', None)
|
user = getattr(request, 'auth', None)
|
||||||
if not user or not user.is_authenticated:
|
if not user or not user.is_authenticated:
|
||||||
raise HttpError(401, "请先登录")
|
raise HttpError(401, "请先登录")
|
||||||
|
request.user = user
|
||||||
return func(request, *args, **kwargs)
|
return func(request, *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def manager_required(func):
|
def manager_required(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(request, *args, **kwargs):
|
def wrapper(request, *args, **kwargs):
|
||||||
user = getattr(request, 'user', None)
|
user = getattr(request, 'auth', None)
|
||||||
if not user or not user.is_authenticated or user.role not in ['admin', 'manager']:
|
if not user or not user.is_authenticated or user.role not in ['admin', 'manager']:
|
||||||
raise HttpError(403, "仅分管理或管理员可访问")
|
raise HttpError(403, "仅分管理或管理员可访问")
|
||||||
|
request.user = user
|
||||||
return func(request, *args, **kwargs)
|
return func(request, *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def admin_required(func):
|
def admin_required(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(request, *args, **kwargs):
|
def wrapper(request, *args, **kwargs):
|
||||||
user = getattr(request, 'user', None)
|
user = getattr(request, 'auth', None)
|
||||||
if not user or not user.is_authenticated or user.role != 'admin':
|
if not user or not user.is_authenticated or user.role != 'admin':
|
||||||
raise HttpError(403, "仅管理员可访问")
|
raise HttpError(403, "仅管理员可访问")
|
||||||
|
request.user = user
|
||||||
return func(request, *args, **kwargs)
|
return func(request, *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
Loading…
x
Reference in New Issue
Block a user