"""演示账套与免注册体验(批次 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 功能,写入会被拒绝", })