public user IDs, plans priced by tutor cost, age-based tutor tone, self-service account deletion
- profile publicId ("User482917", accounts/public_id.py); admin shows and searches it
- plans API reports aiDailyQuota per plan and freeAiDailyQuota; migration 0007 sets the
temporary prices (monthly 99,000 / yearly 990,000 toman, 5 tutor questions a day) unless
an admin already edited the yearly plan
- tutor tone follows the learner's age (18+ gets an adult tone at any grade); notation
still follows the grade's book; answer cache keyed by tone (prompt tutor-v12-age-tone)
- POST /api/user/delete: phone users confirm with a fresh SMS code (same checks as login,
now shared in verify_phone_otp), guests type "حذف"; payment records are kept, demo
accounts refused
- delete_users --skip-paid
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,10 +54,13 @@ def plan(user_ids):
|
||||
return counts
|
||||
|
||||
|
||||
def delete_users(user_ids):
|
||||
def delete_users(user_ids, keep=()):
|
||||
"""keep: برچسبِ مدلهایی که نمیمانند پاک شوند (مثلاً 'payment.Order' برای حذفِ حسابِ خودِ کاربر)."""
|
||||
phones = list(User.objects.filter(pk__in=user_ids).exclude(phone=None).values_list('phone', flat=True))
|
||||
with transaction.atomic():
|
||||
for model, column in user_scoped_models():
|
||||
if model._meta.label in keep:
|
||||
continue
|
||||
model.objects.filter(**{f'{column}__in': user_ids}).delete()
|
||||
OTPCode.objects.filter(phone__in=phones).delete()
|
||||
User.objects.filter(pk__in=user_ids).delete()
|
||||
@@ -74,6 +77,8 @@ class Command(BaseCommand):
|
||||
parser.add_argument('--yes', action='store_true', help='واقعاً پاک کن.')
|
||||
parser.add_argument('--include-paid', action='store_true',
|
||||
help='کاربرِ دارای سفارشِ پرداختشده را هم پاک کن.')
|
||||
parser.add_argument('--skip-paid', action='store_true',
|
||||
help='کاربرِ دارای سفارشِ پرداختشده را کنار بگذار و بقیه را پاک کن.')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
phones = [normalize_phone(p) or p for p in options['phones']]
|
||||
@@ -93,7 +98,12 @@ class Command(BaseCommand):
|
||||
from payment.models import Order
|
||||
paid = set(Order.objects.filter(user_id__in=[u.pk for u in users], status='paid')
|
||||
.values_list('user_id', flat=True))
|
||||
if paid and not options['include_paid']:
|
||||
if paid and options['skip_paid']:
|
||||
self.stdout.write(f'کنار گذاشته شدند (سفارشِ پرداختشده دارند): {sorted(paid)}')
|
||||
users = [u for u in users if u.pk not in paid]
|
||||
if not users:
|
||||
raise CommandError('بعد از کنار گذاشتنِ پرداختکنندهها کسی نماند.')
|
||||
elif paid and not options['include_paid']:
|
||||
raise CommandError(f'کاربرهای {sorted(paid)} سفارشِ پرداختشده دارند؛ با --include-paid پاک میشوند.')
|
||||
|
||||
ids = [u.pk for u in users]
|
||||
|
||||
Reference in New Issue
Block a user