82 lines
4.3 KiB
Python
82 lines
4.3 KiB
Python
"""پاکسازی کلیدهای «آلوده» دفتر یکتاسازی یادآوری بازدید.
|
|
|
|
چرا لازم است؟ کلید یکتاسازیِ یادآوری بازدید، در نسخهٔ قدیمی ``visit:<id>:<شمارهٔ چرخه>``
|
|
بود که شمارهٔ چرخه با تقسیم روز بر یک عدد ثابت محاسبه میشد. وقتی آن عدد صفر میشد،
|
|
عددهای بیمعنا (مثل ``visit:1601:24663``) ساخته و برای همیشه در ``SMSDedup`` ثبت
|
|
میشدند؛ نتیجهاش قفل دائمی یادآوری برای همان مشتری بود.
|
|
|
|
قاعدهٔ جدید کلید را با «موعد تقویمی» میسازد: ``visit:<id>:<YYYY-MM>``. کلیدهای قدیمی
|
|
هرگز با کلید جدید برخورد نمیکنند، بنابراین بیضرر ولی بازدارندهاند.
|
|
|
|
این دستور **فقط** کلیدهای بیمعنا/قدیمیِ دفتر را حذف میکند و هیچ مشتری، اقدام یا
|
|
پیامکی را تغییر نمیدهد.
|
|
|
|
python manage.py cleanup_visit_dedup --dry-run # فقط گزارش
|
|
python manage.py cleanup_visit_dedup # حذف کلیدهای بیمعنا (cycle > حد)
|
|
python manage.py cleanup_visit_dedup --all-legacy # حذف همهٔ کلیدهای قالب قدیمی
|
|
"""
|
|
import re
|
|
|
|
from django.core.management.base import BaseCommand
|
|
from django.db import transaction
|
|
|
|
from ...models import SMSDedup
|
|
|
|
# سقف منطقی شمارهٔ چرخه؛ هر عدد بزرگتر از این، نتیجهٔ باگ تقسیم قدیمی است.
|
|
MAX_SANE_CYCLE = 50
|
|
LEGACY_PATTERN = re.compile(r"^visit:(\d+):(\d+)$")
|
|
NEW_PATTERN = re.compile(r"^visit:(\d+):(\d{4}-\d{2})$")
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "حذف کلیدهای آلوده/قدیمی دفتر یکتاسازی یادآوری بازدید (بدون تغییر دادهٔ مشتری)."
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument("--dry-run", action="store_true",
|
|
help="فقط گزارش؛ چیزی حذف نمیشود.")
|
|
parser.add_argument("--all-legacy", action="store_true",
|
|
help="همهٔ کلیدهای قالب قدیمی (visit:<id>:<عدد>) حذف شوند، "
|
|
"نه فقط عددهای بیمعنا.")
|
|
|
|
def handle(self, *args, **options):
|
|
dry_run = options["dry_run"]
|
|
all_legacy = options["all_legacy"]
|
|
|
|
absurd, sane_legacy, current = [], [], []
|
|
for key in SMSDedup.objects.filter(key__startswith="visit:").values_list(
|
|
"key", flat=True
|
|
).iterator():
|
|
if NEW_PATTERN.match(key):
|
|
current.append(key)
|
|
continue
|
|
match = LEGACY_PATTERN.match(key)
|
|
if match:
|
|
cycle = int(match.group(2))
|
|
(absurd if cycle > MAX_SANE_CYCLE else sane_legacy).append(key)
|
|
else:
|
|
absurd.append(key)
|
|
|
|
doomed = absurd + (sane_legacy if all_legacy else [])
|
|
|
|
self.stdout.write(self.style.MIGRATE_LABEL("گزارش کلیدهای یادآوری بازدید:"))
|
|
self.stdout.write(f" کلیدهای قالب جدید (موعد تقویمی): {len(current):,}")
|
|
self.stdout.write(f" کلیدهای بیمعنا (چرخه > {MAX_SANE_CYCLE} یا ناشناخته): {len(absurd):,}")
|
|
self.stdout.write(f" کلیدهای قدیمیِ سالم (چرخه ≤ {MAX_SANE_CYCLE}): {len(sane_legacy):,}")
|
|
if absurd:
|
|
self.stdout.write(" نمونهٔ کلیدهای بیمعنا: " + ", ".join(absurd[:5]))
|
|
self.stdout.write(f" قابل حذف در این اجرا: {len(doomed):,}")
|
|
|
|
if dry_run:
|
|
self.stdout.write(self.style.WARNING(
|
|
"\n[شبیهسازی] هیچ کلیدی حذف نشد."
|
|
))
|
|
return
|
|
if not doomed:
|
|
self.stdout.write(self.style.SUCCESS("\nانجام شد: چیزی برای حذف نبود."))
|
|
return
|
|
with transaction.atomic():
|
|
deleted, _ = SMSDedup.objects.filter(key__in=doomed).delete()
|
|
self.stdout.write(self.style.SUCCESS(
|
|
f"\nانجام شد: {deleted:,} کلید حذف شد (فقط از دفتر یکتاسازی)."
|
|
))
|