Opened 8 years ago

Closed 8 years ago

Last modified 8 years ago

#27111 closed Bug (fixed)

UserCreationForm.__init__() crashes if USERNAME_FIELD not in self.fields

Reported by: Peter Campbell Owned by: Berker Peksag
Component: Forms Version: 1.10
Severity: Normal Keywords: custom user, django admin, UserCreationForm
Cc: berker.peksag@… Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Between the latest 1.9.x release and 1.10 release the following method was added to django.contrib.auth.forms.UserCreationForm lines 95-97

def __init__(self, *args, **kwargs):
        super(UserCreationForm, self).__init__(*args, **kwargs)
        self.fields[self._meta.model.USERNAME_FIELD].widget.attrs.update({'autofocus': ''})

which sets the username field to autofocus when the form is rendered.

This is quite probably a very specific use case but in my current project I am subclassing from AbstractUser to create my own custom user:

    class User(AbstractBaseUser, PermissionsMixin):
        """
        A custom user
        """
        uuid = models.UUIDField(
            _('UUID'),
            max_length=36,
            unique=True,
            help_text=_('The unique identifier used to login to the system.')
        )
        ...
        USERNAME_FIELD = 'uuid'

Note that this project uses a uuid field as its username field. This project will only enable internal and external systems to connect to it via an API (DRF) so authentication will be token based using the username and associated password as the entry point into the system. As such I do not want django usernames to be created manually, instead creating them automatically as the user object is saved. The exact method of user creation may vary but for this particular issue I am focusing on subclassing the UserCreationForm:

    import uuid

    from django.contrib import admin
    from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
    from django.contrib.auth.forms import UserCreationForm as DjangoUserCreationForm
    from django.utils.translation import ugettext_lazy as _
    
    from .models import User


    class UserCreationForm(DjangoUserCreationForm):
        """
        Custom user creation form enables the password to be set but handles the uuid creation internally on save.
        """
        class Meta(DjangoUserCreationForm.Meta):
            model = User
            fields = ('description',)
    
        def save(self, commit=True):
            user = super().save(commit=False)
            user.uuid = uuid.uuid4()
            user.set_password(self.cleaned_data["password1"])
            if commit:
                user.save()
            return user
     
    
    class UserAdmin(DjangoUserAdmin):
        add_form = UserCreationForm
        fieldsets = (
            (None, {'fields': ['password', 'description']}),
            (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                           'groups', 'user_permissions')}),
            (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
        )
        add_fieldsets = (
            (
                None, {
                    'classes': ('wide',),
                    'fields': ('password1', 'password2')
                }
            ),
        )
        list_display = ('uuid', 'description', 'is_active', 'is_staff', 'is_superuser')
        search_fields = ('uuid',)
        ordering = ('uuid',)

    admin.site.register(User, UserAdmin)

Note in my subclassed UserCreationForm.save method I set the username value to a newly created uuid and in my subclassed UserAdmin class the only fields available for creation are password1 and password2, effectively disabling username input.

Going back to the init method above and in this specific case in the admin console, attempting to load the user create form results in the USERNAME_FIELD being inaccessible (in fact the fields attribute is also unavailable at this point), resulting in the following error:

KeyError at /admin/users/user/add/

'uuid'

which points directly to this line (django.contrib.auth.forms.UserCreationForm.init line - 97):

    self.fields[self._meta.model.USERNAME_FIELD].widget.attrs.update({'autofocus': ''})

To work around this in my subclassed UserCreationForm I have added:

    def __init__(self, *args, **kwargs):
        if hasattr(self, 'fields') and self._meta.model.USERNAME_FIELD in self.fields:
            self.fields[self._meta.model.USERNAME_FIELD].widget.attrs.update({'autofocus': ''})

        super(DjangoUserCreationForm, self).__init__(*args, **kwargs)

Note that in order to ensure the init method of the parent of django.contrib.auth.forms.UserCreationForm (django.forms.models.BaseModelForm) is called I call the super implementation directly, which probably isn't wise but in this case works for me - I cannot call the immediate parent as it will result in the same error.

I understand my logic might be edge case but I feel this is bug, hence why it is reported as such.

Change History (4)

comment:1 by Tim Graham, 8 years ago

Summary: django.contrib.auth.forms.UserCreationForm.__init__ method expects USERNAME_FIELD to be in self.fieldsUserCreationForm.__init__() crashes if USERNAME_FIELD not in self.fields
Triage Stage: UnreviewedAccepted

Feel free to submit a patch and we could backport to 1.10. Tests would go in tests/auth_tests/test_forms.py.

comment:2 by Berker Peksag, 8 years ago

Cc: berker.peksag@… added
Has patch: set
Owner: changed from nobody to Berker Peksag
Status: newassigned

comment:3 by Tim Graham <timograham@…>, 8 years ago

Resolution: fixed
Status: assignedclosed

In 3c18f8a3:

Fixed #27111 -- Fixed KeyError if USERNAME_FIELD isn't in UserCreationForm.fields.

comment:4 by Tim Graham <timograham@…>, 8 years ago

In c4ee9312:

[1.10.x] Fixed #27111 -- Fixed KeyError if USERNAME_FIELD isn't in UserCreationForm.fields.

Backport of 3c18f8a3d2f61669493f9ff2015ddc027eada3d6 from master

Note: See TracTickets for help on using tickets.
Back to Top