1
+ import time
2
+ from datetime import timedelta
3
+ from itertools import islice
4
+
1
5
from django .db import migrations
2
6
from django .db .models import Q
3
7
4
8
5
9
def delete_non_migrated_users (apps , schema_editor ):
6
10
"""
7
11
Delete users where is_fxa_migrated is False and who aren't creators/owners/users of
8
- any important content
12
+ any content.
9
13
"""
10
- User = apps .get_model ('auth' , 'User' )
11
-
12
- users_to_delete = User .objects .filter (
13
- profile__is_fxa_migrated = False
14
- ).exclude (
15
- Q (answer_votes__isnull = False ) |
16
- Q (answers__isnull = False ) |
17
- Q (award_creator__isnull = False ) |
18
- Q (badge__isnull = False ) |
19
- Q (created_revisions__isnull = False ) |
20
- Q (gallery_images__isnull = False ) |
21
- Q (gallery_videos__isnull = False ) |
22
- Q (inboxmessage__isnull = False ) |
23
- Q (outbox__isnull = False ) |
24
- Q (poll_votes__isnull = False ) |
25
- Q (post__isnull = False ) |
26
- Q (question_votes__isnull = False ) |
27
- Q (questions__isnull = False ) |
28
- Q (readied_for_l10n_revisions__isnull = False ) |
29
- Q (reviewed_revisions__isnull = False ) |
30
- Q (thread__isnull = False ) |
31
- Q (wiki_post_set__isnull = False ) |
32
- Q (wiki_thread_set__isnull = False )
14
+ User = apps .get_model ("auth" , "User" )
15
+ users_to_delete = User .objects .filter (profile__is_fxa_migrated = False ).exclude (
16
+ Q (answer_votes__isnull = False )
17
+ | Q (answers__isnull = False )
18
+ | Q (award_creator__isnull = False )
19
+ | Q (badge__isnull = False )
20
+ | Q (created_revisions__isnull = False )
21
+ | Q (gallery_images__isnull = False )
22
+ | Q (gallery_videos__isnull = False )
23
+ | Q (inboxmessage__isnull = False )
24
+ | Q (outbox__isnull = False )
25
+ | Q (poll_votes__isnull = False )
26
+ | Q (post__isnull = False )
27
+ | Q (question_votes__isnull = False )
28
+ | Q (questions__isnull = False )
29
+ | Q (readied_for_l10n_revisions__isnull = False )
30
+ | Q (reviewed_revisions__isnull = False )
31
+ | Q (thread__isnull = False )
32
+ | Q (wiki_post_set__isnull = False )
33
+ | Q (wiki_thread_set__isnull = False )
34
+ | Q (locales_leader__isnull = False )
35
+ | Q (locales_reviewer__isnull = False )
36
+ | Q (locales_editor__isnull = False )
37
+ | Q (wiki_contributions__isnull = False )
38
+ )
39
+
40
+ total_users = users_to_delete .count ()
41
+ if total_users == 0 :
42
+ print ("No users to delete" )
43
+ return
44
+
45
+ print (f"Starting deletion of { total_users :,} users" )
46
+ start_time = time .time ()
47
+ deleted_count = 0
48
+ batch_size = 2000
49
+
50
+ user_ids = users_to_delete .values_list ("id" , flat = True ).iterator (chunk_size = batch_size )
51
+ current_batch = []
52
+
53
+ for user_id in user_ids :
54
+ current_batch .append (user_id )
55
+
56
+ if len (current_batch ) >= batch_size :
57
+ # Delete the batch using _base_manager to avoid the overridden managers
58
+ # of each model through the cascade.
59
+ # We don't care about the extra logic b/c the accounts that are being deleted are empty
60
+ deletion_counts = User ._base_manager .filter (id__in = current_batch ).delete ()
61
+ # get the user, not the cascaded deletes
62
+ user_deletes = deletion_counts [1 ].get ("auth.User" , 0 )
63
+ deleted_count += user_deletes
64
+ current_batch = []
65
+
66
+ elapsed_time = time .time () - start_time
67
+ avg_time_per_user = elapsed_time / deleted_count if deleted_count > 0 else 0
68
+ current_rate = deleted_count / elapsed_time * 60 if elapsed_time > 0 else 0
69
+ remaining_time = (
70
+ (total_users - deleted_count ) * avg_time_per_user if deleted_count > 0 else 0
71
+ )
72
+
73
+ print (
74
+ f"""
75
+ Progress Report:
76
+ ---------------
77
+ Users Deleted: { deleted_count :,} of { total_users :,} ({ (deleted_count / total_users * 100 ):.1f} %)
78
+ Elapsed Time: { timedelta (seconds = int (elapsed_time ))}
79
+ Average Time per User: { avg_time_per_user :.3f} seconds
80
+ Current Rate: { current_rate :.1f} users/minute
81
+ Estimated Time Remaining: { timedelta (seconds = int (remaining_time ))}
82
+ """
83
+ )
84
+
85
+ if current_batch :
86
+ deletion_counts = User ._base_manager .filter (id__in = current_batch ).delete ()
87
+ user_deletes = deletion_counts [1 ].get ("auth.User" , 0 )
88
+ deleted_count += user_deletes
89
+
90
+ total_time = time .time () - start_time
91
+ print (
92
+ f"""
93
+ Deletion Complete:
94
+ -----------------
95
+ Total Users Deleted: { deleted_count :,} of { total_users :,}
96
+ Total Time: { timedelta (seconds = int (total_time ))}
97
+ Average Time per User: { (total_time / deleted_count if deleted_count > 0 else 0 ):.3f} seconds
98
+ Overall Rate: { (deleted_count / total_time * 60 if total_time > 0 else 0 ):.1f} users/minute
99
+ """
33
100
)
34
-
35
- users_to_delete .delete ()
36
101
37
102
38
103
def reverse_migration (apps , schema_editor ):
@@ -44,12 +109,12 @@ def reverse_migration(apps, schema_editor):
44
109
45
110
class Migration (migrations .Migration ):
46
111
dependencies = [
47
- (' users' , ' 0032_profile_account_type_alter_profile_user' ),
112
+ (" users" , " 0032_profile_account_type_alter_profile_user" ),
48
113
]
49
114
50
115
operations = [
51
116
migrations .RunPython (
52
117
delete_non_migrated_users ,
53
118
reverse_migration ,
54
119
),
55
- ]
120
+ ]
0 commit comments