diff --git a/api.py b/api.py index e56768a..67cc0ba 100644 --- a/api.py +++ b/api.py @@ -2,10 +2,10 @@ from ninja import NinjaAPI from resumes.api.views import router as resume_router from accounts.api.auth import auth_router from accounts.api.user import user_router -from accounts.api.authorize import router +from authorize.api.view import authorize_router api = NinjaAPI(title="简历管理 API") api.add_router("/resumes/", resume_router) api.add_router("/auth", auth_router) api.add_router("/users", user_router) -api.add_router("/authorize", router) \ No newline at end of file +api.add_router("/authorize", authorize_router) \ No newline at end of file diff --git a/authorize/__init__.py b/authorize/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authorize/admin.py b/authorize/admin.py new file mode 100644 index 0000000..ac59b80 --- /dev/null +++ b/authorize/admin.py @@ -0,0 +1,18 @@ +from django.contrib import admin +from authorize.models import WebsiteAccessRequest, ResumeDetailAccessRequest + + +# Register your models here. +@admin.register(WebsiteAccessRequest) +class WebsiteAccessRequestAdmin(admin.ModelAdmin): + list_display = ('user', 'website', 'status', 'reason', 'created_at', 'updated_at') + list_filter = ('status', 'website', 'created_at') + search_fields = ('user__username', 'website__name', 'reason') + ordering = ('-created_at',) + readonly_fields = ('created_at', 'updated_at') + +@admin.register(ResumeDetailAccessRequest) +class ResumeDetailAccessRequestAdmin(admin.ModelAdmin): + list_display = ('user', 'resume', 'reason', 'status', 'created_at') + list_filter = ('status', 'created_at') + search_fields = ('user__username', 'resume__id') \ No newline at end of file diff --git a/authorize/api/__init__.py b/authorize/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounts/api/authorize.py b/authorize/api/view.py similarity index 71% rename from accounts/api/authorize.py rename to authorize/api/view.py index 02d9fd1..b5ff7e5 100644 --- a/accounts/api/authorize.py +++ b/authorize/api/view.py @@ -1,41 +1,17 @@ -# ✅ 授权管理接口:manager 给 user 授权网站 + 普通用户申请接口 -from ninja import Router, Schema, Query -from pydantic import Field -from typing import List, Optional +from ninja import Router, Query from django.shortcuts import get_object_or_404 -from django.db import models - from accounts.models import User +from authorize.models import WebsiteAccessRequest, ResumeDetailAccessRequest +from authorize.schemas import ResumeAccessRequestIn, AccessRequestIn, AuthorizeIn +from resumes.models import ResumeDetail from websites.models import Website from utils.auth import jwt_auth from utils.permissions import manager_required, login_required -router = Router(tags=["授权管理"]) +authorize_router = Router(tags=["授权管理"]) -class WebsiteAccessRequest(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) - website = models.ForeignKey(Website, on_delete=models.CASCADE) - status = models.CharField( - max_length=20, - choices=[("pending", "待审批"), ("approved", "已通过"), ("rejected", "已拒绝")], - default="pending" - ) - reason = models.TextField(blank=True) - created_at = models.DateTimeField(auto_now_add=True) - - -class AuthorizeIn(Schema): - user_id: int = Field(..., description="被授权的用户ID") - website_ids: List[int] = Field(..., description="要授权的网站ID列表") - - -class AccessRequestIn(Schema): - website_id: int = Field(...) - reason: Optional[str] = Field(None, description="申请原因") - - -@router.post("/authorize", auth=jwt_auth) +@authorize_router.post("/authorize", auth=jwt_auth) @manager_required def authorize_user(request, data: AuthorizeIn): manager = request.user @@ -60,7 +36,7 @@ def authorize_user(request, data: AuthorizeIn): } -@router.post("/apply", auth=jwt_auth) +@authorize_router.post("/apply", auth=jwt_auth) @login_required def request_access(request, data: AccessRequestIn): user = request.user @@ -75,7 +51,7 @@ def request_access(request, data: AccessRequestIn): return {"success": True, "message": "申请已提交,等待分管理审批"} -@router.get("/pending", auth=jwt_auth) +@authorize_router.get("/pending", auth=jwt_auth) @manager_required def list_pending_requests(request): manager = request.user @@ -98,7 +74,7 @@ def list_pending_requests(request): } -@router.post("/approve", auth=jwt_auth) +@authorize_router.post("/approve", auth=jwt_auth) @manager_required def approve_request(request, request_id: int = Query(...), approve: bool = Query(True)): r = get_object_or_404(WebsiteAccessRequest, id=request_id) @@ -115,7 +91,7 @@ def approve_request(request, request_id: int = Query(...), approve: bool = Query return {"success": True, "message": f"已{'通过' if approve else '拒绝'} {r.user.username} 的访问申请"} -@router.get("/my-sites", auth=jwt_auth) +@authorize_router.get("/my-sites", auth=jwt_auth) @login_required def list_user_manager_websites(request): user = request.user @@ -130,7 +106,33 @@ def list_user_manager_websites(request): return {"success": True, "websites": list(sites)} -@router.get("/public-sites") +@authorize_router.get("/public-sites") def list_public_websites(request): websites = Website.objects.all().values("id", "name") return {"success": True, "websites": list(websites)} + + +@authorize_router.post("/apply-resume", auth=jwt_auth) +@login_required +def apply_resume_access(request, data: ResumeAccessRequestIn): + user = request.user + + if not user.is_user(): + return {"success": False, "message": "仅普通用户可申请查看简历"} + + resume = get_object_or_404(ResumeDetail, id=data.resume_id) + + exists = ResumeDetailAccessRequest.objects.filter( + user=user, resume=resume, status="pending" + ).exists() + + if exists: + return {"success": False, "message": "您已申请过该简历,正在等待审批"} + + ResumeDetailAccessRequest.objects.create( + user=user, + resume=resume, + reason=data.reason or "" + ) + + return {"success": True, "message": "申请已提交,等待审批"} diff --git a/authorize/apps.py b/authorize/apps.py new file mode 100644 index 0000000..5d2da55 --- /dev/null +++ b/authorize/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthorizeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'authorize' diff --git a/authorize/models.py b/authorize/models.py new file mode 100644 index 0000000..cb788e5 --- /dev/null +++ b/authorize/models.py @@ -0,0 +1,47 @@ +from django.db import models +from accounts.models import User +from websites.models import Website + +from resumes.models import ResumeDetail + + +# Create your models here. +class WebsiteAccessRequest(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + website = models.ForeignKey(Website, on_delete=models.CASCADE) + updated_at = models.DateTimeField(auto_now=True) + status = models.CharField( + max_length=20, + choices=[("pending", "待审批"), ("approved", "已通过"), ("rejected", "已拒绝")], + default="pending" + ) + reason = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + verbose_name = "网站访问申请" + verbose_name_plural = "网站访问申请" + + def __str__(self): + return f"{self.user.username} 申请网站 {self.website.name} ({self.status})" + + +class ResumeDetailAccessRequest(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="申请用户") + resume = models.ForeignKey(ResumeDetail, on_delete=models.CASCADE, verbose_name="目标简历") + reason = models.TextField(blank=True, verbose_name="申请理由") + status = models.CharField( + max_length=20, + choices=[("pending", "待审批"), ("approved", "已通过"), ("rejected", "已拒绝")], + default="pending", + verbose_name="审批状态" + ) + created_at = models.DateTimeField(auto_now_add=True, verbose_name="申请时间") + + class Meta: + unique_together = ("user", "resume") + verbose_name = "简历详情访问申请" + verbose_name_plural = "简历详情访问申请" + + def __str__(self): + return f"{self.user.username} 申请查看简历 {self.resume.id} ({self.status})" diff --git a/authorize/schemas.py b/authorize/schemas.py new file mode 100644 index 0000000..947129b --- /dev/null +++ b/authorize/schemas.py @@ -0,0 +1,18 @@ +from ninja import Schema, Query +from pydantic import Field +from typing import List, Optional + + +class AuthorizeIn(Schema): + user_id: int = Field(..., description="被授权的用户ID") + website_ids: List[int] = Field(..., description="要授权的网站ID列表") + + +class AccessRequestIn(Schema): + website_id: int = Field(...) + reason: Optional[str] = Field(None, description="申请原因") + + +class ResumeAccessRequestIn(Schema): + resume_id: int = Field(..., description="简历ID") + reason: Optional[str] = Field(None, description="申请理由") diff --git a/authorize/tests.py b/authorize/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/authorize/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/authorize/views.py b/authorize/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/authorize/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/core/settings.py b/core/settings.py index da8a872..0f92b62 100644 --- a/core/settings.py +++ b/core/settings.py @@ -50,7 +50,8 @@ INSTALLED_APPS = [ 'access_control', 'admin_panel', 'logs', - 'invites' + 'invites', + 'authorize' ] MIDDLEWARE = [