@@ -133,10 +133,25 @@ def retry_send_messages_action(__, request, queryset):
133133)
134134
135135
136+ class PasswordlessUserForm (forms .Form ):
137+ """Minimal form to create a passwordless (sub-less) User from the admin."""
138+
139+ email = forms .EmailField (label = "Email" , required = True )
140+
141+ def clean_email (self ):
142+ """Reject emails that are already in use."""
143+ email = self .cleaned_data ["email" ]
144+ if models .User .objects .filter (email = email ).exists ():
145+ raise forms .ValidationError ("A user with this email already exists." )
146+ return email
147+
148+
136149@admin .register (models .User )
137150class UserAdmin (auth_admin .UserAdmin ):
138151 """Admin class for the User model"""
139152
153+ change_list_template = "admin/core/user/change_list.html"
154+
140155 fieldsets = (
141156 (
142157 None ,
@@ -213,6 +228,56 @@ class UserAdmin(auth_admin.UserAdmin):
213228 )
214229 search_fields = ("id" , "sub" , "admin_email" , "email" , "full_name" )
215230
231+ def get_urls (self ):
232+ urls = super ().get_urls ()
233+ custom_urls = [
234+ path (
235+ "add-passwordless/" ,
236+ self .admin_site .admin_view (self .add_passwordless_view ),
237+ name = "core_user_add_passwordless" ,
238+ ),
239+ ]
240+ return custom_urls + urls
241+
242+ def add_passwordless_view (self , request ):
243+ """Create a passwordless (sub-less) user from a single email field.
244+
245+ These users cannot authenticate locally (unusable password, no
246+ ``admin_email``) and will be claimed on first OIDC login by
247+ ``UserManager.get_user_by_sub_or_email``.
248+ """
249+ if request .method == "POST" :
250+ form = PasswordlessUserForm (request .POST )
251+ if form .is_valid ():
252+ email = form .cleaned_data ["email" ]
253+ user = models .User .objects .filter (email = email ).first ()
254+ if user is None :
255+ user = models .User (email = email )
256+ user .set_unusable_password ()
257+ user .save ()
258+ messages .success (
259+ request ,
260+ f"Passwordless user created: { user .email } " ,
261+ )
262+ else :
263+ messages .info (
264+ request ,
265+ f"User already exists: { user .email } " ,
266+ )
267+ return redirect ("admin:core_user_changelist" )
268+ else :
269+ form = PasswordlessUserForm ()
270+
271+ context = {
272+ ** self .admin_site .each_context (request ),
273+ "title" : "Add passwordless user" ,
274+ "form" : form ,
275+ "opts" : self .model ._meta , # noqa: SLF001
276+ }
277+ return TemplateResponse (
278+ request , "admin/core/user/add_passwordless.html" , context
279+ )
280+
216281
217282class MailDomainAccessInline (admin .TabularInline ):
218283 """Inline class for the MailDomainAccess model"""
0 commit comments