72 lines
2.7 KiB
Python
72 lines
2.7 KiB
Python
"""演示账套与免注册体验(批次 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, TenantMembership
|
||
|
||
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
|
||
# The token flow bypasses the normal login path, so make sure the
|
||
# membership exists even when the demo tenant was built by hand.
|
||
TenantMembership.objects.get_or_create(
|
||
user=user, tenant=tenant,
|
||
defaults={"role": "owner", "is_active": True},
|
||
)
|
||
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 功能,写入会被拒绝",
|
||
})
|