8
8
from django .db .models .functions import Concat
9
9
from django .core .management .base import BaseCommand
10
10
11
-
12
11
log = getLogger (__name__ )
13
12
User = get_user_model ()
14
13
15
14
16
15
class Command (BaseCommand ):
17
- """ """
16
+ """
17
+ Django management command to sync user usernames with Wikimedia usernames.
18
+
19
+ This command matches the usernames in the local database with the Wikimedia
20
+ usernames, and updates the local usernames if they differ from the ones provided
21
+ by Wikimedia.
22
+ """
18
23
19
24
help = "Matches the usernames with Wikimedia and updates the changed ones in Wikilearn"
20
25
21
26
def add_arguments (self , parser ):
27
+ """
28
+ Adds command-line arguments to the management command.
29
+
30
+ Arguments:
31
+ parser (ArgumentParser): The argument parser to which arguments should be added.
32
+ - usernames (list): List of specific usernames to sync. If not provided,
33
+ all usernames will be checked and synced.
34
+ """
22
35
parser .add_argument (
23
36
"usernames" ,
24
37
nargs = "*" ,
@@ -28,29 +41,39 @@ def add_arguments(self, parser):
28
41
29
42
def handle (self , * args , ** options ):
30
43
"""
31
- Get all TPA users whose usernames were cleaned and filter by usernames if provided.
32
- Get their usernames from the third party provider in this case wikimedia
33
- and update their username in Wikilearn.
44
+ Main entry point for the command.
45
+
46
+ Retrieves the users whose usernames need to be checked and updated.
47
+ Filters the users if specific usernames are provided. Logs the process and
48
+ prints the statistics of the operation.
49
+
50
+ Arguments:
51
+ args: Additional positional arguments.
52
+ options: Command-line options, including the list of usernames.
34
53
"""
35
54
usernames = options ["usernames" ]
36
55
users = self ._get_tpa_users ()
37
56
38
57
if usernames :
39
58
users = users .filter (username__in = usernames )
40
-
59
+
41
60
total = users .count ()
42
61
43
- log .info ("Syncing %s users with wikimedia usernames" , total )
62
+ log .info ("Syncing %s users with Wikimedia usernames" , total )
44
63
45
64
stats = self ._update_user_with_wikimedia_username (users )
46
65
self ._print_stats (total , stats )
47
66
48
- def _get_tpa_users (self ):
67
+ def _get_tpa_users (self ) -> QuerySet [ User ] :
49
68
"""
50
- Gets all the platform users who registered with the third party provider.
69
+ Retrieves users who registered with the third-party authentication (TPA) provider.
70
+
71
+ These are users whose usernames need to be potentially updated based on
72
+ Wikimedia usernames. Excludes users whose first name is missing or whose
73
+ username already matches their wiki_username.
51
74
52
- It is assumed that users without first name were registered before TPA was required.
53
- Also excludes where username is the combination first and last name already .
75
+ Returns:
76
+ QuerySet[User]: A queryset of users to be processed .
54
77
"""
55
78
users = (
56
79
User .objects .select_related ("profile" )
@@ -66,15 +89,27 @@ def _get_tpa_users(self):
66
89
.exclude (first_name = "" )
67
90
.exclude (
68
91
username = F ("wiki_username" )
69
- ) # because no point in updating username with wiki_username later if it is already same .
92
+ ) # Excludes users whose username already matches the computed wiki_username .
70
93
)
71
94
72
95
return users
73
96
74
97
def _update_user_with_wikimedia_username (self , users : QuerySet [User ]) -> dict :
75
98
"""
76
- Checks if "wiki_username" was the original username provided by Wikimedia
77
- and updates the Wikilearn username with it.
99
+ Updates the usernames of users based on their Wikimedia username.
100
+
101
+ This method checks if the current username matches the Wikimedia username.
102
+ If the usernames differ, the local username is updated. It logs each operation
103
+ and returns statistics of the update process.
104
+
105
+ Arguments:
106
+ users (QuerySet[User]): A queryset of users to be processed.
107
+
108
+ Returns:
109
+ dict: A dictionary containing statistics about the update process.
110
+ - correct_username: Number of usernames that were already correct.
111
+ - updated_username: Number of usernames that were updated.
112
+ - skipped_username: Number of usernames that were skipped.
78
113
"""
79
114
total = len (users )
80
115
stats = {
@@ -85,23 +120,29 @@ def _update_user_with_wikimedia_username(self, users: QuerySet[User]) -> dict:
85
120
for i , user in enumerate (users ):
86
121
index = i + 1
87
122
if self ._username_exists (user .username ):
88
- # This check is to avoid updating username if it is already same as Wikimedia's .
89
- log .info (f' { index } /{ total } : SKIPPED: { user .username } is CORRECT' )
123
+ # This check is to avoid updating username if it is already correct according to Wikimedia.
124
+ log .info (f" { index } /{ total } : SKIPPED: { user .username } is CORRECT" )
90
125
stats ["correct_username" ] += 1
91
126
elif self ._username_exists (user .wiki_username ):
92
127
log .info (f"{ index } /{ total } : UPDATING: { user .username } with { user .wiki_username } " )
93
128
self ._update_user (user )
94
129
stats ["updated_username" ] += 1
95
130
else :
96
- # This means both the username and our guess (first_name+last_name) is wrong .
97
- log .info (f' { index } /{ total } : SKIPPED: { user .username } ' )
131
+ # This means both the username and the computed wiki_username are incorrect .
132
+ log .info (f" { index } /{ total } : SKIPPED: { user .username } " )
98
133
stats ["skipped_username" ] += 1
99
-
134
+
100
135
return stats
101
136
102
137
def _username_exists (self , username : str ) -> bool :
103
138
"""
104
- Checks if username exists in the Wikimedia's global account.
139
+ Checks if a username exists in Wikimedia's global account database.
140
+
141
+ Arguments:
142
+ username (str): The username to be checked.
143
+
144
+ Returns:
145
+ bool: True if the username exists in Wikimedia, False otherwise.
105
146
"""
106
147
USERNAME_VERIFY_URL = f"https://en.wikipedia.org/wiki/Special:CentralAuth?target={ username } "
107
148
ERROR_MSG = "There is no global account for"
@@ -111,10 +152,23 @@ def _username_exists(self, username: str) -> bool:
111
152
return ERROR_MSG not in response .text
112
153
113
154
def _update_user (self , user : User ):
155
+ """
156
+ Updates the username of a user in the local database.
157
+
158
+ Arguments:
159
+ user (User): The user object whose username is to be updated.
160
+ """
114
161
user .username = user .wiki_username
115
162
user .save ()
116
163
117
164
def _print_stats (self , total : int , stats : dict ):
165
+ """
166
+ Prints statistics about the update process.
167
+
168
+ Arguments:
169
+ total (int): The total number of users processed.
170
+ stats (dict): A dictionary containing statistics about the update process.
171
+ """
118
172
log .info (f"Total mismatched users: { total } " )
119
173
log .info (f"Correct usernames: { stats ['correct_username' ]} " )
120
174
log .info (f"Updated usernames: { stats ['updated_username' ]} " )
0 commit comments