"""P1-10 默认数据 seed 命令 用法: python manage.py seed_affinity # 写入默认数据,已存在则跳过 python manage.py seed_affinity --force # 强制覆盖已存在的默认规则/等级 写入内容(与「好感度系统功能与规则设计.md」§4.2 / §6.2 一致): 1. AffinitySetting 单例(如不存在) 2. 9 条默认互动规则(含 1 条 companion_time 规则 — WR-005) 3. 5 个默认等级 幂等性: 默认按 rule_key(规则)/ level(等级)查询,已存在则跳过。 --force 模式下覆盖已存在记录的字段(不删旧记录)。 WR-007 修正: 每条 spec 独立事务,部分失败不影响其他记录(避免 force 模式下第 N 条崩 导致前 N-1 条全部回滚但 stdout 已打印 "成功"的语义错位)。 """ from django.core.management.base import BaseCommand from django.db import transaction from userapp.models import AffinityLevel, AffinityRule, AffinitySetting from userapp.affinity.defaults import DEFAULT_LEVELS, DEFAULT_RULES, DEFAULT_SETTING class Command(BaseCommand): help = '初始化好感度系统默认数据(AffinitySetting / AffinityRule / AffinityLevel)' def add_arguments(self, parser): parser.add_argument( '--force', action='store_true', help='强制覆盖已存在记录的字段(不删旧记录)', ) def handle(self, *args, **options): force = options['force'] # WR-007:每条独立事务,部分失败可重跑;不再用全局 @transaction.atomic self._seed_setting() rules_created, rules_updated, rules_failed = self._seed_rules(force) levels_created, levels_updated, levels_failed = self._seed_levels(force) style = self.style.WARNING if (rules_failed + levels_failed) else self.style.SUCCESS self.stdout.write(style( f'\n[seed_affinity] 完成:' f'规则 创建 {rules_created} 更新 {rules_updated} 失败 {rules_failed},' f'等级 创建 {levels_created} 更新 {levels_updated} 失败 {levels_failed}' )) def _seed_setting(self): try: with transaction.atomic(): if AffinitySetting.objects.exists(): self.stdout.write('AffinitySetting 已存在,跳过') return # 从 DEFAULT_SETTING 取所有字段(含 IN-003 改名后的 global_daily_cap) AffinitySetting.objects.create(**DEFAULT_SETTING) self.stdout.write(self.style.SUCCESS('AffinitySetting 已创建(默认值)')) except Exception as e: self.stderr.write(self.style.ERROR(f'AffinitySetting 创建失败:{e}')) def _seed_rules(self, force): created = 0 updated = 0 failed = 0 for spec in DEFAULT_RULES: rule_key = spec['rule_key'] try: with transaction.atomic(): existing = AffinityRule.objects.filter(rule_key=rule_key).first() if existing is None: AffinityRule.objects.create(**spec) created += 1 self.stdout.write(f' + 规则 {rule_key} 已创建') elif force: for k, v in spec.items(): setattr(existing, k, v) existing.save() updated += 1 self.stdout.write(f' ~ 规则 {rule_key} 已覆盖') else: self.stdout.write(f' - 规则 {rule_key} 已存在,跳过') except Exception as e: failed += 1 self.stderr.write(self.style.ERROR(f' ! 规则 {rule_key} 处理失败:{e}')) continue return created, updated, failed def _seed_levels(self, force): created = 0 updated = 0 failed = 0 for spec in DEFAULT_LEVELS: level_num = spec['level'] try: with transaction.atomic(): existing = AffinityLevel.objects.filter(level=level_num).first() if existing is None: # 用 skip_clean=True 跳过 save 内 full_clean(区间重叠校验依赖已存在 levels, # 但当前批量 seed 是按 level 升序逐条 commit,跨记录关系会随插入逐步成立) # 这里仍执行 clean 以保证区间合法 AffinityLevel.objects.create(**spec) created += 1 self.stdout.write(f' + 等级 Lv{level_num} 已创建') elif force: for k, v in spec.items(): setattr(existing, k, v) existing.save() updated += 1 self.stdout.write(f' ~ 等级 Lv{level_num} 已覆盖') else: self.stdout.write(f' - 等级 Lv{level_num} 已存在,跳过') except Exception as e: failed += 1 self.stderr.write(self.style.ERROR(f' ! 等级 Lv{level_num} 处理失败:{e}')) continue return created, updated, failed