From 2f1e2575336a3e82fb26670f81053adccb7e72d5 Mon Sep 17 00:00:00 2001 From: Zareian Date: Fri, 2 Oct 2026 06:20:05 +0330 Subject: [PATCH] delete_users: only project models; Django admin history (LogEntry.user_id is staff) was being counted Account deletion also keeps the status log of the payment records it keeps. Co-Authored-By: Claude Opus 5.5 --- accounts/management/commands/delete_users.py | 16 +++++++++++++++- accounts/tests_delete_users.py | 12 ++++++++++++ accounts/views.py | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/accounts/management/commands/delete_users.py b/accounts/management/commands/delete_users.py index c76301d..12c3cea 100644 --- a/accounts/management/commands/delete_users.py +++ b/accounts/management/commands/delete_users.py @@ -34,11 +34,25 @@ USER_COLUMNS = ('user_id', 'referred_user_id') def user_scoped_models(): - """[(model, column)] برای هر مدلی که به کاربر ارجاع می‌دهد، جز خودِ User.""" + """ + [(model, column)] برای هر مدلِ خودِ پروژه که به کاربرِ اپ ارجاع می‌دهد، جز خودِ User. + + فقط اپ‌هایی که داخلِ پوشه‌ی پروژه‌اند. `admin.LogEntry` هم ستونِ user_id دارد ولی آن + کارمندِ ادمینِ جنگوست، نه کاربرِ اپ؛ بارِ اول که این دستور روی سرور گزارش گرفت، همان را + هم شمرده بود و اگر اجرا می‌شد تاریخچه‌ی ادمینِ شماره‌ی ۱ پاک می‌شد. + """ + from pathlib import Path + + from django.conf import settings + + base = Path(settings.BASE_DIR).resolve() found = [] for model in apps.get_models(): if model is User: continue + app_path = Path(model._meta.app_config.path).resolve() + if base not in app_path.parents or 'site-packages' in app_path.parts: + continue names = {f.attname for f in model._meta.concrete_fields} found += [(model, column) for column in USER_COLUMNS if column in names] return found diff --git a/accounts/tests_delete_users.py b/accounts/tests_delete_users.py index c08b113..8ccaa0e 100644 --- a/accounts/tests_delete_users.py +++ b/accounts/tests_delete_users.py @@ -63,3 +63,15 @@ class DeleteUsersTests(TestCase): self.run_command('--ids', str(self.doomed.pk), str(self.kept.pk), '--yes', '--skip-paid') self.assertTrue(User.objects.filter(pk=self.doomed.pk).exists()) self.assertFalse(User.objects.filter(pk=self.kept.pk).exists()) + + def test_django_admin_history_is_never_touched(self): + from django.contrib.admin.models import LogEntry + from django.contrib.auth.models import User as StaffUser + + from accounts.management.commands.delete_users import user_scoped_models + + self.assertNotIn(LogEntry, [model for model, _ in user_scoped_models()]) + staff = StaffUser.objects.create(id=self.doomed.pk, username='staff') + LogEntry.objects.create(user=staff, action_flag=1, object_repr='x') + self.run_command('--ids', str(self.doomed.pk), '--yes') + self.assertEqual(LogEntry.objects.count(), 1) diff --git a/accounts/views.py b/accounts/views.py index 5a753b8..f4cfbe2 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -424,7 +424,7 @@ def _upgradable_guest(request, phone: str): DELETE_CONFIRM_WORD = 'حذف' # سوابقِ پرداخت سندِ مالی‌اند و با حذفِ حساب نمی‌روند (تراکنش‌ها با سفارش می‌مانند). -KEPT_ON_ACCOUNT_DELETE = ('payment.Order',) +KEPT_ON_ACCOUNT_DELETE = ('payment.Order', 'statuslog.StatusChangeLog') class DeleteAccountView(APIView):