0

I have a custom user model and a user manager defined as follows:

/accounts/models.py

from django.contrib.auth.models import (
    AbstractBaseUser,
    BaseUserManager,
    PermissionsMixin
)
from django.db import models
from django.utils import timezone


class UserManager(BaseUserManager):
    def create_user(self, email, first_name, last_name, username=None, password=None):
        if not email:
            raise ValueError("Users must have a valid email address")

        if not first_name and last_name:
            raise ValueError("Users must have a first and last name")

        created_username = ''.join([first_name.lower(), last_name[:1].lower()])
        i=2
        while User.objects.filter(username=created_username).exists():
            created_username = ''.join([first_name.lower(), last_name[:i].lower()])
            i+=1

        user = self.model(
            email=self.normalize_email(email),
            first_name=first_name,
            last_name=last_name,
            username=created_username
        )

        user.set_password(password)
        user.save()

        return user

    def create_superuser(self, email, first_name, last_name, password):
        user = self.create_user(
            email,
            first_name,
            last_name,
            password
        )

        user.is_staff = True
        user.is_admin = True
        user.is_superuser = True

        user.save()
        return user


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    first_name = models.CharField(max_length=40, blank=True)
    last_name = models.CharField(max_length=40, blank=True)
    username = models.CharField(max_length=40, unique=True, blank=True, editable=False)
    # display_name = models.CharField(max_length=150)
    bio = models.TextField(blank=True, null=True)
    avatar = models.ImageField(blank=True, null=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name','last_name']

    def __str__(self):
        return "{} @{}".format(self.email, self.username)

    def get_short_name(self):
        return self.first_name

    def get_full_name(self):
        return ' '.join([self.first_name, self.last_name])

This seems to work perfectly when registering a superuser from the shell. I have a form and a view set up to register regular users on my site as follows:

/accounts/forms.py

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _

auth_code = 'hamburger'

def validate_authorization(value):
    if value != auth_code:
        raise ValidationError(
            _('Must have valid authorization code in order to register.')
        )


class UserCreateForm(UserCreationForm):
    authorization_code = forms.CharField(max_length=10, required=True, validators=[validate_authorization])

    class Meta:
        model = get_user_model()
        fields = ("email", "first_name", "last_name", "password1", "password2", "authorization_code")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["email"].label = "Email Address"
        self.fields["first_name"].label = "First Name"
        self.fields["last_name"].label = "Last Name"
        self.fields["password1"].label = "Password"
        self.fields["password2"].label = "Password Confirmation"
        self.fields["authorization_code"].label = "Authorization Code"

/accounts/views.py

from django.shortcuts import render

from django.template import RequestContext
from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.urlresolvers import reverse_lazy
from django.views import generic
from django.http import HttpResponseRedirect

from django.contrib.auth import get_user_model

from . import forms


class SigninView(generic.FormView):
    form_class = AuthenticationForm
    success_url = '/dashboard/' #reverse_lazy('index')
    template_name = 'accounts/signin.html'

    def get_form(self, form_class=None):
        if form_class is None:
            form_class = self.get_form_class()
        return form_class(self.request, **self.get_form_kwargs())

    def form_valid(self, form):
        login(self.request, form.get_user())
        return super().form_valid(form)


class SignoutView(generic.RedirectView):
    url = '/' #reverse_lazy("home")

    def get(self, request, *args, **kwargs):
        logout(request)
        return super().get(request, *args, **kwargs)


class RegisterView(generic.CreateView):
    form_class = forms.UserCreateForm
    success_url = '/'
    template_name = 'accounts/register.html'

    def form_valid(self, form):
        self.object = form.save(commit=False)
        form.instance.username = ''.join([form.instance.first_name.lower(), form.instance.last_name[:1].lower()])
        i=2
        while get_user_model().objects.filter(username=form.instance.username).exists():
            form.instance.username = ''.join([form.instance.first_name.lower(), form.instance.last_name[:i].lower()])
            i+=1
        form.save()
        return HttpResponseRedirect(self.get_success_url())
        # return super(RegisterView, self).form_valid(form)

I am at a loss as to why my superuser cannot log into the website but my regular users can. Also you will notice I have a while statement that auto generates a username based on the entered first and last name. Initially I had this only in the UserManager however, the form was bypassing the user manager and so I had to add the same block of code to my view. So there seems to be a disconnect between users created from the form versus users created from the shell (UserManager).

The authorization_code is in place because I don't want just anybody to be able to register on my site and I didn't know a better way. I am open to better suggestions.

Additional information that may be helpful

settings.py

# Set user authentication model
AUTH_USER_MODEL = 'accounts.User'

Python 3.5, Django 1.10

Thank you in advance for any advice or insight.

JillianSN
  • 73
  • 1
  • 8

1 Answers1

0

Problem solved.

In

def create_superuser(self, email, first_name, last_name, password):
    user = self.create_user(
        email,
        first_name,
        last_name,
        password
    )

I was forgetting to set password=password,. From looking at the password field in the database, it seems this was also resulting in (as close as I can tell) bypassing <algorithm>$<iterations>$<salt> (per the Django docs https://docs.djangoproject.com/en/1.10/topics/auth/passwords/) though the password was still being hashed in some way (not being stored in plain text) the password field for superusers was considerably shorter than the password field for normal users. Whatever it was doing, it was not storing the actual password and was giving me an invalid username/password when attempting to log in with a superuser account.

So the proper way is

def create_superuser(self, email, first_name, last_name, password):
    user = self.create_user(
        email,
        first_name,
        last_name,
        password=password,
    )

I still don't understand why created_username is being bypassed in the UserManager when saving a user from the AuthenticationForm but I found a workaround by adding the same while statement to the view. At least all is functional now. I'm still interested to learn if anybody has further insight into this matter.

JillianSN
  • 73
  • 1
  • 8