Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django create new user without password

I'm creating new users using a custom UserCreationForm and then using password_reset from auth.views with a customized email template to send a signup mail to the user.

This process is working fine, users are able to successfully complete the signup process and start using the app. I now want to create users without having to set a password for the user.

I tried setting self.fields['password1'].required and self.fields['password2'].required to False in the UserCreationForm init method as recommended here but that hasn't worked for me. Unfortunately, it's proving difficult to debug. I'm not seeing any error messages but I know the RegistrationView register method isn't getting called.

Here's my UserCreationForm

class UserCreateForm(UserCreationForm):
    email = forms.EmailField(required=True)

    class Meta:
        model = User
        fields = ("email",)

    def __int__(self):
        self.fields['password1'].required = False
        self.fields['password2'].required = False

    def save(self, commit=True):
        user = super(UserCreateForm, self).save(commit=False)
        clean_email = self.cleaned_data["email"]
        user.email = clean_email
        if commit:
            user.save()
        custom_user = CustomUser()
        custom_user.id = user
        custom_user.save()
        return user

Here's my RegistrationView

class UserRegistration(RegistrationView):
    def __init__(self):
        self.form_class = UserCreateForm
        self.success_url = 'login'
        self.template_name = 'register.html'

    def register(self, request, form):
        new_user = form.save()

I'm using Django 1.8. Please let me know if any other information would be useful to share.

like image 485
Tomás Collins Avatar asked Feb 29 '16 00:02

Tomás Collins


2 Answers

Programmatically, you can create/save a new User without a password argument, and it will not raise any exceptions. In fact, you can even create a user without any arguments. This is explained here.

The trick is to use create() instead of create_user(), as discussed here.

For example, you can do this in your code:

User.objects.create(username='user without password')

The database field password will then contain an empty string. Because the user now exists, the admin will show the UserChangeForm, and the password will show up as a ReadOnlyPasswordHashField which says "No password set.", as you can see in the image below. You can proceed normally from there.

screenshot of admin site

Note that this empty password is not the same as an unusable password. The PasswordResetView does not work with an unusable password, but it does work with an empty password.

Also note that User.objects.create(password='') works, but User.objects.create(password=None) does not: the latter raises a IntegrityError due to the NOT NULL constraint in the database.

Just to illustrate the options, here's a test with different ways to create a user:

from django.test import TestCase
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password
import django.db
import django.db.transaction

RAW_PASSWORD = 'mypassword'


class UserTests(TestCase):
    def tearDown(self) -> None:
        # print passwords as stored in test database
        for user in User.objects.all():
            print(f'username: {user.username}\tpassword: {user.password}')

    def test_user_create_missing_required_fields(self):
        # none of these raise any exceptions, despite required model fields
        user_kwargs = [dict(),
                       dict(username='a'),
                       dict(username='b', password=''),
                       dict(username='c', password=RAW_PASSWORD)]
        for kwargs in user_kwargs:
            user = User.objects.create(**kwargs)
            # password is "usable" ...
            self.assertTrue(user.has_usable_password())
            if 'password' in kwargs:
                # stored password is still raw (not hashed)
                self.assertEqual(kwargs['password'], user.password)
                # ... but password-check fails
                self.assertFalse(user.check_password(kwargs['password']))

    def test_user_set_password(self):
        # can set a usable password after user creation
        user = User(username='d')
        user.set_password(RAW_PASSWORD)  # does not save...
        user.save()
        self.assertTrue(user.has_usable_password())
        self.assertNotEqual(RAW_PASSWORD, user.password)
        self.assertTrue(user.check_password(RAW_PASSWORD))

    def test_user_create_hashed_password(self):
        # we can initialize with a hashed password
        hashed_password = make_password(RAW_PASSWORD)
        user = User.objects.create(username='e', password=hashed_password)
        self.assertTrue(user.has_usable_password())
        self.assertTrue(user.check_password(RAW_PASSWORD))

    def test_user_set_unusable_password(self):
        # can set an unusable password
        user = User(username='f')
        user.set_unusable_password()  # does not save...
        user.save()
        self.assertFalse(user.has_usable_password())

    @django.db.transaction.atomic
    def test_user_create_password_none(self):
        # cannot initialize with password=None (violates the NOT NULL constraint)
        with self.assertRaises(django.db.IntegrityError) as a:
            User.objects.create(username='g', password=None)
        self.assertIn('not null', str(a.exception).lower())

    def test_user_set_password_none(self):
        # can set None in set_password(), but that becomes unusable
        user = User(username='h')
        user.set_password(None)
        user.save()
        self.assertFalse(user.has_usable_password())
like image 118
djvg Avatar answered Sep 20 '22 22:09

djvg


As you described, you modified the form fields to not require password, but what about the model? The build-in User model enforces use of password (it is required model field), and will give errors if you try to save a User objects without one. There's a special method for when you don't want to set a real password - django.contrib.auth.models.User.set_unusable_password(). Use it in your view before saving the form data.

like image 35
Nikita Avatar answered Sep 19 '22 22:09

Nikita