from django.contrib.contenttypes.models import ContentType from django.core.management.base import BaseCommand from django.db import transaction from printing import models as printing_models from stateflow import services as stateflow_services class Command(BaseCommand): help = ( "修复 printing 相关模型(PrintingJob/PlateOrder)所关联的 BusinessObject 的 " "content_type/object_id/name 绑定(用于历史数据回填与一致性修复)。" ) def add_arguments(self, parser): parser.add_argument( '--dry-run', action='store_true', help='只输出统计,不写入数据库', ) parser.add_argument( '--limit', type=int, default=None, help='最多处理多少条记录(用于分批回填)', ) parser.add_argument( '--only', choices=['printingjob', 'plateorder', 'all'], default='all', help='仅修复指定类型(默认 all)', ) def handle(self, *args, **options): dry_run: bool = options['dry_run'] limit: int | None = options['limit'] only: str = options['only'] # 提前确保 ContentType 可用(也用于输出校验信息) ct_job = ContentType.objects.get_for_model(printing_models.PrintingJob) ct_po = ContentType.objects.get_for_model(printing_models.PlateOrder) self.stdout.write(f"dry_run={dry_run} only={only} limit={limit}") self.stdout.write(f"ContentType: PrintingJob={ct_job.id}, PlateOrder={ct_po.id}") stats = { 'printingjob_total': 0, 'printingjob_updated': 0, 'plateorder_total': 0, 'plateorder_updated': 0, } def _iter_qs(qs): if limit: return qs.order_by('id')[:limit] return qs.order_by('id') # 用事务包住,避免部分更新(dry-run 仍使用事务,最后标记回滚) with transaction.atomic(): if only in ('printingjob', 'all'): qs = ( printing_models.PrintingJob.objects .select_related('business_object') .filter(business_object__isnull=False) ) for job in _iter_qs(qs): stats['printingjob_total'] += 1 bo = job.business_object should_fix = ( bo.content_type_id != ct_job.id or bo.object_id != job.id or not (bo.name or '').strip() ) if not should_fix: continue if dry_run: stats['printingjob_updated'] += 1 continue if stateflow_services.ensure_business_object_bound_to_instance( bo, job, default_name=f"PrintingJob-{job.id}", ): stats['printingjob_updated'] += 1 if only in ('plateorder', 'all'): qs = ( printing_models.PlateOrder.objects .select_related('business_object') .filter(business_object__isnull=False) ) for po in _iter_qs(qs): stats['plateorder_total'] += 1 bo = po.business_object should_fix = ( bo.content_type_id != ct_po.id or bo.object_id != po.id or not (bo.name or '').strip() ) if not should_fix: continue if dry_run: stats['plateorder_updated'] += 1 continue if stateflow_services.ensure_business_object_bound_to_instance( bo, po, default_name=f"PlateOrder-{po.id}", ): stats['plateorder_updated'] += 1 if dry_run: # dry-run:显式回滚(即便未来误加了写逻辑,也不会落库) transaction.set_rollback(True) self.stdout.write(self.style.SUCCESS("Done.")) for k in sorted(stats.keys()): self.stdout.write(f"{k}={stats[k]}")