@@ -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,48 @@ 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+ user = models .User (email = form .cleaned_data ["email" ])
253+ user .set_unusable_password ()
254+ user .save ()
255+ messages .success (
256+ request ,
257+ f"Passwordless user created: { user .email } " ,
258+ )
259+ return redirect ("admin:core_user_changelist" )
260+ else :
261+ form = PasswordlessUserForm ()
262+
263+ context = {
264+ ** self .admin_site .each_context (request ),
265+ "title" : "Add passwordless user" ,
266+ "form" : form ,
267+ "opts" : self .model ._meta , # noqa: SLF001
268+ }
269+ return TemplateResponse (
270+ request , "admin/core/user/add_passwordless.html" , context
271+ )
272+
216273
217274class MailDomainAccessInline (admin .TabularInline ):
218275 """Inline class for the MailDomainAccess model"""
0 commit comments