fix: 集团任职硬删除,杜绝 NULL 重复冒出多条

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-29 06:53:06 +08:00
parent 52e714b9b2
commit c3a0143eff
3 changed files with 122 additions and 126 deletions

View File

@@ -1,4 +1,8 @@
"""admin_assignment 任职 CRUD修复停用不生效、重复插入、IsSuperuser 绕过)。"""
"""admin_assignment 任职 CRUD
集团任职用 club_id=''(禁止 NULLMySQL 对 NULL 唯一约束失效,会无限复制,
导致「停一条冒出五六条」。停用改为硬删除。
"""
from django.db.models import Q
from django.utils import timezone
@@ -14,10 +18,19 @@ GROUP_ROLE_CODES = frozenset({
})
def normalize_club_id(club_id):
"""集团级统一成空串,禁止 NULL避免唯一索引失效"""
if club_id is None:
return ''
s = str(club_id).strip()
return s
def _club_filter(qs, club_id):
if club_id is None or club_id == '':
cid = normalize_club_id(club_id)
if not cid:
return qs.filter(Q(club_id__isnull=True) | Q(club_id=''))
return qs.filter(club_id=club_id)
return qs.filter(club_id=cid)
def find_assignment(yonghuid, club_id, role_code):
@@ -46,56 +59,7 @@ def _clear_other_primary(yonghuid, except_id=None):
clear_other_primary_assignments(yonghuid, except_id=except_id)
def create_or_reactivate_assignment(
yonghuid,
club_id,
role_code,
data_scope,
is_primary,
granted_by,
):
active = find_active_assignment(yonghuid, club_id, role_code)
if active:
return None, '该任职记录已存在且有效'
if is_primary:
_clear_other_primary(yonghuid)
inactive_qs = _club_filter(
AdminAssignment.objects.filter(yonghuid=yonghuid, role_code=role_code).exclude(status=1),
club_id,
).order_by('-id')
inactive = inactive_qs.first()
if inactive:
if is_primary:
_clear_other_primary(yonghuid)
inactive_qs.exclude(pk=inactive.pk).update(status=0, UpdateTime=timezone.now())
AdminAssignment.objects.filter(pk=inactive.id).update(
status=1,
data_scope=data_scope,
is_primary=is_primary,
granted_by=granted_by,
UpdateTime=timezone.now(),
)
return inactive.id, None
row = AdminAssignment.query.create(
yonghuid=yonghuid,
club_id=club_id,
role_code=role_code,
data_scope=data_scope,
is_primary=is_primary,
granted_by=granted_by,
status=1,
)
return row.id, None
def _strip_system_super_if_needed(yonghuid):
"""
停用集团权限后:非白名单账号必须摘掉 IsSuperuser。
否则 build_admin_club_context 仍把 is_group_admin=True停用等于没停。
"""
yonghuid = (yonghuid or '').strip()
if not yonghuid:
return
@@ -108,31 +72,62 @@ def _strip_system_super_if_needed(yonghuid):
return
if (user.Phone or '') in SUPER_ADMIN_PHONES:
return
if user.IsSuperuser:
if user.IsSuperuser or user.IsStaff:
user.IsSuperuser = False
user.save(update_fields=['IsSuperuser'])
user.IsStaff = False
user.save(update_fields=['IsSuperuser', 'IsStaff'])
def deactivate_all_group_assignments_for_user(yonghuid):
"""停用该用户全部集团级任职(空俱乐部 / ALL_CLUBS / GROUP_* 角色)。"""
yonghuid = (yonghuid or '').strip()
if not yonghuid:
return 0
qs = AdminAssignment.objects.filter(yonghuid=yonghuid, status=1).filter(
Q(club_id__isnull=True)
| Q(club_id='')
| Q(data_scope=DATA_SCOPE_ALL)
| Q(role_code__in=list(GROUP_ROLE_CODES))
def create_or_reactivate_assignment(
yonghuid,
club_id,
role_code,
data_scope,
is_primary,
granted_by,
):
club_id = normalize_club_id(club_id)
if not club_id:
data_scope = DATA_SCOPE_ALL
# 创建前先清同键垃圾(含 NULL/空串/status=0 重复)
junk = _club_filter(
AdminAssignment.objects.filter(yonghuid=yonghuid, role_code=role_code),
club_id,
)
active = junk.filter(status=1).order_by('-id')
if active.exists():
keep = active.first()
junk.exclude(pk=keep.pk).delete()
return None, '该任职记录已存在且有效'
junk.delete()
if is_primary:
_clear_other_primary(yonghuid)
row = AdminAssignment.objects.create(
yonghuid=yonghuid,
club_id=club_id,
role_code=role_code,
data_scope=data_scope,
is_primary=is_primary,
granted_by=granted_by or '',
status=1,
)
return row.id, None
def _is_group_level(row):
return (
not normalize_club_id(row.club_id)
or (row.data_scope or '') == DATA_SCOPE_ALL
or (row.role_code or '') in GROUP_ROLE_CODES
)
return qs.update(status=0, UpdateTime=timezone.now())
def deactivate_assignment(assignment_id):
"""
停用任职。
- 同人同角色同俱乐部(含 NULL 重复行)全部停
- 若是集团级任职:再停掉该人全部集团任职,并摘掉非白名单 IsSuperuser
"""
"""停用=硬删除。集团级则删光该人全部集团任职并摘 IsSuperuser。"""
try:
pk = int(assignment_id)
except (TypeError, ValueError):
@@ -140,45 +135,49 @@ def deactivate_assignment(assignment_id):
row = AdminAssignment.objects.filter(pk=pk).first()
if not row:
return False, '任职记录不存在'
# 可能已被删,幂等成功
return True, None
qs = AdminAssignment.objects.filter(
yonghuid=row.yonghuid,
role_code=row.role_code,
status=1,
)
qs = _club_filter(qs, row.club_id)
qs.update(status=0, UpdateTime=timezone.now())
yonghuid = row.yonghuid
role_code = row.role_code
club_id = row.club_id
group_level = _is_group_level(row)
is_group_level = (
not (row.club_id or '').strip()
or (row.data_scope or '') == DATA_SCOPE_ALL
or (row.role_code or '') in GROUP_ROLE_CODES
)
if is_group_level:
deactivate_all_group_assignments_for_user(row.yonghuid)
_strip_system_super_if_needed(row.yonghuid)
# 同人同角色同俱乐部(含 NULL/'')全部硬删
_club_filter(
AdminAssignment.objects.filter(yonghuid=yonghuid, role_code=role_code),
club_id,
).delete()
if group_level:
AdminAssignment.objects.filter(yonghuid=yonghuid).filter(
Q(club_id__isnull=True)
| Q(club_id='')
| Q(data_scope=DATA_SCOPE_ALL)
| Q(role_code__in=list(GROUP_ROLE_CODES))
).delete()
_strip_system_super_if_needed(yonghuid)
return True, None
def deactivate_all_assignments_for_user(yonghuid):
"""账号禁用时:停用该用户全部有效任职,并摘掉非白名单 IsSuperuser"""
"""账号禁用:硬删全部任职 + 摘超管"""
yonghuid = (yonghuid or '').strip()
if not yonghuid:
return 0
n = AdminAssignment.objects.filter(yonghuid=yonghuid, status=1).update(
status=0,
UpdateTime=timezone.now(),
)
deleted, _ = AdminAssignment.objects.filter(yonghuid=yonghuid).delete()
_strip_system_super_if_needed(yonghuid)
return n
return deleted
def dedupe_active_assignments():
"""停用重复的有效任职,每组 (yonghuid, club_id, role_code) 只保留一条"""
"""硬删重复任职;并把 club_id=NULL 归一成 ''"""
# 归一 NULL -> ''
AdminAssignment.objects.filter(club_id__isnull=True).update(club_id='')
rows = list(
AdminAssignment.query.filter(status=1).order_by('yonghuid', 'club_id', 'role_code', 'id')
AdminAssignment.objects.filter(status=1).order_by('yonghuid', 'club_id', 'role_code', 'id')
)
seen = {}
dup_ids = []
@@ -189,8 +188,5 @@ def dedupe_active_assignments():
else:
seen[key] = row.id
if dup_ids:
AdminAssignment.objects.filter(pk__in=dup_ids).update(
status=0,
UpdateTime=timezone.now(),
)
AdminAssignment.objects.filter(pk__in=dup_ids).delete()
return len(dup_ids)