How To Create Users Without Setting Their PasswordΒΆ

When creating a new user through Django’s admin interface, you are asked to enter the new user’s password. This is less than ideal, because it requires the admin to think of a password for someone else, communicate it to them somehow, and then the user must remember to change it. A better way would be to send a password-reset email to the new user, allowing them to enter their own password.

To implement this, we need to provide a user-creation form that has an optional (instead of required, like the built-in form) password field and a User admin that uses the form and sends the password-reset email when creating a new user.

We’ll subclass UserCreationForm to create a form with optional password fields:

from django import forms
from authtools.forms import UserCreationForm

class UserCreationForm(UserCreationForm):
    """
    A UserCreationForm with optional password inputs.
    """

    def __init__(self, *args, **kwargs):
        super(UserCreationForm, self).__init__(*args, **kwargs)
        self.fields['password1'].required = False
        self.fields['password2'].required = False
        # If one field gets autocompleted but not the other, our 'neither
        # password or both password' validation will be triggered.
        self.fields['password1'].widget.attrs['autocomplete'] = 'off'
        self.fields['password2'].widget.attrs['autocomplete'] = 'off'

    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = super(UserCreationForm, self).clean_password2()
        if bool(password1) ^ bool(password2):
            raise forms.ValidationError("Fill out both fields")
        return password2

Then an admin class that uses our form and sends the email:

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import PasswordResetForm
from django.utils.crypto import get_random_string
from authtools.admin import NamedUserAdmin

User = get_user_model()

class UserAdmin(NamedUserAdmin):
    """
    A UserAdmin that sends a password-reset email when creating a new user,
    unless a password was entered.
    """
    add_form = UserCreationForm
    add_fieldsets = (
        (None, {
            'description': (
                "Enter the new user's name and email address and click save."
                " The user will be emailed a link allowing them to login to"
                " the site and set their password."
            ),
            'fields': ('email', 'name',),
        }),
        ('Password', {
            'description': "Optionally, you may set the user's password here.",
            'fields': ('password1', 'password2'),
            'classes': ('collapse', 'collapse-closed'),
        }),
    )

    def save_model(self, request, obj, form, change):
        if not change and (not form.cleaned_data['password1'] or not obj.has_usable_password()):
            # Django's PasswordResetForm won't let us reset an unusable
            # password. We set it above super() so we don't have to save twice.
            obj.set_password(get_random_string())
            reset_password = True
        else:
            reset_password = False

        super(UserAdmin, self).save_model(request, obj, form, change)

        if reset_password:
            reset_form = PasswordResetForm({'email': obj.email})
            assert reset_form.is_valid()
            reset_form.save(
                request=request,
                use_https=request.is_secure(),
                subject_template_name='registration/account_creation_subject.txt',
                email_template_name='registration/account_creation_email.html',
            )

Using django.contrib.auth.forms.PasswordResetForm allows us to share the email-sending code with Django. If you wanted to change the template the email uses, email_template_name would be the place to do it.

Now we can replace the installed UserAdmin with our own.

from django.contrib import admin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

You can view the complete admin.py file here.