baseline: 批次A-D 成果 + membership 半成品(测试红)

This commit is contained in:
agent
2026-09-11 23:11:35 +08:00
commit b3f3095d53
311 changed files with 40540 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
"""演示账套与免注册体验(批次 C2)。
- `POST /api/v1/demo/enter/` 免注册进入演示账套(返回一次性 JWT,租户固定 demo)
- 演示租户默认只读:写操作被 `DemoReadOnlyMiddleware` 拦截(见 middleware.py)
安全:演示账号权限最小(非 staff/superuser),token 短时效;只读拦截按"租户 + 方法"
判定,不依赖前端自觉。
"""
from asgiref.sync import sync_to_async
from adrf.decorators import api_view
from rest_framework import status
from rest_framework.decorators import authentication_classes, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
DEMO_TENANT = "demo"
DEMO_USER = "demo"
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
async def enter_demo(request):
"""免注册进入演示账套:签发一次性 JWT(2 小时)。
若演示租户尚未初始化,返回 503 并提示先跑 `manage.py seed_demo`。
"""
from django.conf import settings
from django.contrib.auth import get_user_model
from rest_framework_simplejwt.tokens import RefreshToken
from apps.core.models import Tenant
def _prepare():
tenant = Tenant.objects.filter(code=DEMO_TENANT, is_active=True).first()
if tenant is None:
return None, None
user = get_user_model().objects.filter(username=DEMO_USER).first()
if user is None:
return tenant, None
return tenant, user
tenant, user = await sync_to_async(_prepare)()
if tenant is None or user is None:
return Response({
"code": "demo_not_ready",
"detail": "演示账套尚未初始化,请管理员执行:python manage.py seed_demo",
}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
def _token():
refresh = RefreshToken.for_user(user)
refresh.set_exp(lifetime=__import__("datetime").timedelta(hours=2))
return str(refresh.access_token)
access = await sync_to_async(_token)()
return Response({
"access": access,
"tenant": tenant.code,
"tenant_name": tenant.name,
"username": user.username,
"read_only": True,
"expires_in": 7200,
"message": "演示账套为只读模式:可以随意试用查询、报表、AI 功能,写入会被拒绝",
})