模特库标签分页与全能创作收口:藏长视频、选择器分页
角色库导入打标签并支持筛选;模特库与全能创作角色/商品选择改为每页 20 条分页。临时限制成片 ≤60 秒,过滤对话里的超长时长选项,并收拢本地全能创作与后台用户相关修复。
This commit is contained in:
@@ -117,7 +117,7 @@ class AdminUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"id", "username", "status", "is_platform_admin", "date_joined", "teams",
|
||||
"id", "username", "first_name", "status", "is_platform_admin", "date_joined", "teams",
|
||||
"balance", "wallet_team", "wallet_team_name", "wallet_shared",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
@@ -871,7 +871,12 @@ class AdminUserProvisioningTests(TestCase):
|
||||
self.nc.force_authenticate(self.normal)
|
||||
|
||||
def _create(self, **overrides):
|
||||
payload = {"username": "created-user", "password": "strong-pass-1", "initial_credits": "500"}
|
||||
payload = {
|
||||
"username": "creat1",
|
||||
"display_name": "测试用户",
|
||||
"password": "strong-pass-1",
|
||||
"initial_credits": "500",
|
||||
}
|
||||
payload.update(overrides)
|
||||
return self.ac.post("/api/admin/users/", payload, format="json")
|
||||
|
||||
@@ -882,7 +887,8 @@ class AdminUserProvisioningTests(TestCase):
|
||||
self.assertFalse(r.data["wallet_shared"])
|
||||
self.assertEqual(r.data["teams"], []) # 个人团队是钱包实现细节,不当「所属团队」露出
|
||||
|
||||
user = User.objects.get(username="created-user")
|
||||
user = User.objects.get(username="creat1")
|
||||
self.assertEqual(user.first_name, "测试用户")
|
||||
team = Team.objects.get(owner=user)
|
||||
self.assertTrue(team.is_personal)
|
||||
self.assertEqual(team.credit_account.balance, Decimal("500"))
|
||||
@@ -892,22 +898,30 @@ class AdminUserProvisioningTests(TestCase):
|
||||
|
||||
def test_created_user_can_login(self):
|
||||
self._create()
|
||||
r = APIClient().post("/api/auth/login/", {"username": "created-user", "password": "strong-pass-1"}, format="json")
|
||||
r = APIClient().post("/api/auth/login/", {"username": "creat1", "password": "strong-pass-1"}, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_create_user_validation_and_permission(self):
|
||||
self.assertEqual(self.nc.post("/api/admin/users/", {}, format="json").status_code, 403)
|
||||
self.assertEqual(self._create(username="").status_code, 400)
|
||||
self.assertEqual(self._create(display_name="").status_code, 400)
|
||||
self.assertEqual(self._create(username="abc").status_code, 400) # 不足 6 位
|
||||
self.assertEqual(self._create(username="abc-12").status_code, 400) # 含非法字符
|
||||
self.assertEqual(self._create(username="abcdefg").status_code, 400) # 超过 6 位
|
||||
self.assertEqual(self._create(password="short").status_code, 400)
|
||||
self.assertEqual(self._create(initial_credits="-5").status_code, 400)
|
||||
self.assertEqual(self._create(username="normal").status_code, 400) # 重名
|
||||
self.assertEqual(User.objects.filter(username="created-user").count(), 0)
|
||||
# 准备一个 6 位账号占用,验证重名(大小写不敏感)
|
||||
User.objects.create_user(username="taken1", password="x")
|
||||
self.assertEqual(self._create(username="taken1").status_code, 400)
|
||||
self.assertEqual(self._create(username="TAKEN1").status_code, 400)
|
||||
self.assertEqual(User.objects.filter(username="creat1").count(), 0)
|
||||
|
||||
def test_create_user_without_initial_credits(self):
|
||||
r = self._create(initial_credits="")
|
||||
self.assertEqual(r.status_code, 201)
|
||||
self.assertEqual(r.data["balance"], "0.0000")
|
||||
team = Team.objects.get(owner__username="created-user")
|
||||
team = Team.objects.get(owner__username="creat1")
|
||||
self.assertEqual(team.name, "测试用户")
|
||||
self.assertFalse(CreditLedger.objects.filter(team=team).exists()) # 0 积分不落空流水
|
||||
|
||||
def test_adjust_user_credit(self):
|
||||
|
||||
@@ -269,31 +269,43 @@ def admin_users(request):
|
||||
qs = qs.filter(status=st)
|
||||
search = (request.query_params.get("search") or "").strip()
|
||||
if search:
|
||||
qs = qs.filter(username__icontains=search)
|
||||
qs = qs.filter(Q(username__icontains=search) | Q(first_name__icontains=search))
|
||||
paginator = DefaultPagination()
|
||||
page = paginator.paginate_queryset(qs, request)
|
||||
return paginator.get_paginated_response(AdminUserSerializer(page, many=True).data)
|
||||
|
||||
|
||||
def _admin_create_user(request):
|
||||
"""后台直建用户:{username, password, initial_credits?}。
|
||||
"""后台直建用户:{username, display_name?, password, initial_credits?}。
|
||||
|
||||
username = 登录账号(仅英文+数字,固定 6 位);display_name = 展示用用户名(可中文)。
|
||||
积分账户是 OneToOne 挂 Team 的,所以这里给新用户配一个 is_personal 的一人团队当专属钱包 ——
|
||||
计费链路(预留/实扣/流水/额度)一行不用改,后台却能按「用户」视角管人和钱。
|
||||
后续要恢复多人协作,把人拉进同一个团队即可,不需要迁数据。"""
|
||||
import re
|
||||
|
||||
username = str(request.data.get("username") or "").strip()
|
||||
display_name = str(
|
||||
request.data.get("display_name")
|
||||
or request.data.get("name")
|
||||
or ""
|
||||
).strip()
|
||||
password = str(request.data.get("password") or "").strip()
|
||||
if not display_name:
|
||||
return Response({"display_name": ["请填写用户名"]}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if len(display_name) > 64:
|
||||
return Response({"display_name": ["用户名不能超过 64 字"]}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not username:
|
||||
return Response({"username": ["请填写用户名"]}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if len(username) > 150:
|
||||
return Response({"username": ["用户名不能超过 150 字"]}, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
for validator in User._meta.get_field("username").validators:
|
||||
validator(username)
|
||||
except DjangoValidationError as exc:
|
||||
return Response({"username": list(exc.messages)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if User.objects.filter(username=username).exists():
|
||||
return Response({"username": ["该用户名已存在"]}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response({"username": ["请填写登录账号"]}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not re.fullmatch(r"[A-Za-z0-9]{6}", username):
|
||||
return Response(
|
||||
{"username": ["登录账号须为 6 位英文或数字,不能含其他字符"]},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
# 统一小写入库,避免 Abc123 / abc123 被当成两个账号
|
||||
username = username.lower()
|
||||
if User.objects.filter(username__iexact=username).exists():
|
||||
return Response({"username": ["该登录账号已存在,不能重复"]}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if len(password) < 8:
|
||||
return Response({"password": ["密码至少 8 位"]}, status=status.HTTP_400_BAD_REQUEST)
|
||||
initial, err = _parse_points(request.data.get("initial_credits") or 0, field="initial_credits", allow_negative=False)
|
||||
@@ -301,8 +313,13 @@ def _admin_create_user(request):
|
||||
return err
|
||||
|
||||
with transaction.atomic():
|
||||
user = User.objects.create_user(username=username, password=password)
|
||||
team = Team.objects.create(name=username, owner=user, is_personal=True)
|
||||
user = User.objects.create_user(
|
||||
username=username,
|
||||
password=password,
|
||||
first_name=display_name,
|
||||
)
|
||||
# 个人团队名用展示名,侧栏第一行显示用户名、第二行显示登录账号
|
||||
team = Team.objects.create(name=display_name, owner=user, is_personal=True)
|
||||
TeamMember.objects.create(team=team, user=user, role=TeamMember.Role.OWNER)
|
||||
CreditAccount.objects.create(team=team, balance=initial)
|
||||
if initial > 0:
|
||||
@@ -323,7 +340,11 @@ def _admin_create_user(request):
|
||||
target_type="user",
|
||||
target_id=user.id,
|
||||
target_name=user.username,
|
||||
after={"initial_credits": str(initial), "wallet_team": str(team.id)},
|
||||
after={
|
||||
"initial_credits": str(initial),
|
||||
"wallet_team": str(team.id),
|
||||
"display_name": display_name,
|
||||
},
|
||||
)
|
||||
return Response(AdminUserSerializer(_admin_user_qs().get(id=user.id)).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user