0

Custom User Model (models.py):

class Users(AbstractBaseUser):
    user_id            = models.AutoField(primary_key=True)
    user_email         = models.EmailField(max_length=100, unique=True)
    password           = models.CharField(max_length=100)
    registration_date  = models.DateField(auto_now_add=True)
    last_login         = models.DateField(auto_now_add=True)
    is_administrator   = models.BooleanField(default=False)
    is_active          = models.BooleanField(default=True)
    email_verified     = models.BooleanField(default=False)
    objects = UserManager()

    USERNAME_FIELD = 'user_email'

Custom User Manager (models.py)

class UserManager(BaseUserManager):
    def create_user(self, user_email, password):
        user = self.model(
            email=self.normalize_email(user_email)
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

Registration Form (forms.py):

class RegistrationForm(forms.ModelForm):
    password         = forms.CharField(widget=forms.PasswordInput)
    password_confirm = forms.CharField(widget=forms.PasswordInput)

    class Meta:
        model = get_user_model()
        fields = ('user_email',)

    def clean_password(self):
        password1 = self.cleaned_data.get("password")
        password2 = self.cleaned_data.get("password_confirm")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError( self.error_messages['password_mismatch'],
                                         code='password_mismatch' )
        return password2

    def save(self, commit=True):
        user = super(RegistrationForm, self).save(commit=False)
        user.registration_date = datetime.date.today()
        user.last_login = datetime.date.today()
        user.set_password("password")
        if commit:
            user.save()
        return user

Register view (views.py)

def register(request):
    if  request.method == "POST":
        form = RegistrationForm(request.POST)
        if form.is_valid:
            form.save()
    else:
        form = RegistrationForm()
    return render(request, 'web/page.html', {'form' : form})

This is my first question on .. anything online (I'm an avid user of the search facility), but I've been struggling with this for days and I feel like it shouldn't be so difficult.

For my question, it is more like a two part query.

1) I want a custom user model, but want to use as much of what Django is offering. But getting rid of the username field and adding a couple other fields seems to require me to make "custom" everything else. Including a custom user manager and custom forms - not ideal! The registration form above works but I was hoping for something a bit more simple, along the lines of:

class RegistrationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = get_user_model()
        fields = ('user_email', 'password1', 'password2')

but I seem to be struggling, running it gives me the error:

The Users could not be created because the data didn't validate.

Ideally I would want something simple like that without the error. I'm hoping it to give fewer errors and hopefully more future proof if Django adds new security features.

2) Why tf won't the login code below work?

Login Form (forms.py):

class LoginForm(forms.Form):
    user_email = forms.EmailField(max_length=100)
    password   = forms.CharField(max_length=100)

Login View (views.py):

def login_user(request):
    if  request.method == "POST":
        user_email = request.POST.get('email')
        password   = request.POST.get('password')
        UserModel = get_user_model()
        user = UserModel.objects.get(user_email=user_email)
        if user.check_password(password):
            login(request, user)
    return render(request, 'login.html')

I'm pretty sure the problem lies with the second if statement. It just won't give a True value when the correct password is entered. Having tried print(user.user_id), the correct id is given for the email and password entered. Having played with some plaintext passwords, it works, but I rather like the set_password and check_password functions - provided they work!

Many thanks!

aak
  • 11
  • 1
  • 2
  • Why exactly do you need a custom user class, and why do you base it on AbstractBaseUser and not on AbstractUser? You say "I want a custom user model", but I skimmed through your code and I see no good reasons for that. Have you read [this doc](https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#extending-the-existing-user-model)? – Frax May 24 '18 at 22:30
  • Hi Frax. I did read through the document you linked. The plan is to have 2 models for the user, one purely for authentication and the other for more details, linked with onetoonefield. As such I don't want first/last_name. Secondly the website won't have a username field, the user will purely register and authenticate with email. Finally, it comes with a bunch of stuff I will never use (groups/permissions/is_staff). Thought it would make sense based on that to use the AbstractBaseUser over the AbstractUser – aak May 24 '18 at 22:47
  • Nah, [additional stuff doesn't matter](https://xkcd.com/1988/). Use the standard User class (or thin wrapper based on AbstractUser, as suggested [here](https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project)). First and last name can be left empty, username can be set to the email. Handle that in the registration form or view, and you are done. – Frax May 24 '18 at 22:58
  • As for your login view, you use `UserModel` class there, but your user class seems to be called `Users`, are you using the proper class? And, btw, one more pointer: [Django - Login with Email](https://stackoverflow.com/questions/37332190/django-login-with-email). – Frax May 24 '18 at 23:02
  • @Frax worked out solution below. With just a 6 months C programming course 10 years ago, and MATLAB for 4 years (engineering background), I definitely cannot claim to have achieved anywhere near software enlightenment! Thanks for the ideas, it did make me think in detail (looking at source code detail), what the difference between AbstractUser and AbstractBaseUser are, and what they inherit. – aak May 25 '18 at 11:13

2 Answers2

1

I worked it out. Both naive errors!

For point two, the Login View (views.py) code works well, the problem was the Registration Form (forms.py). In particular:

user.set_password("password")

was literally saving 'password' as the password. Changed to:

user.set_password(self.cleaned_data["password_confirm"])

made it work.

Not that it matters (for point one), because the 4 line Registration Form inheriting the UserCreationForm worked all along and will replace the first one. Turns out the issue was I wasn't putting a valid password (minimum 8 letters). Why it validates the data in views.py during:

if form.is_valid:

but then tells me off later I have no idea. I wish Django would be a bit more detailed on what the issue was!

aak
  • 11
  • 1
  • 2
0

I would recommend that you look into Django all-auth. It's an open source solution to user registration and management that offers a lot of customization, and works well with custom user models.

https://github.com/pennersr/django-allauth