User42 User42 - 2 months ago 27
Python Question

Custom user model and custom authentication not working in django. Authentication form is not passing validation tests

I am trying to implement the custom User model and custom authentication for my application.

I am able to create models and make migrations. But when I created a form and implemented the login template, I am getting this error -

login
User model with this Login already exists.


View where I am authenticating user -

from django.shortcuts import render_to_response, redirect, render
from django.template import RequestContext
from django.contrib.auth import login, logout , authenticate
from accounts.forms import AuthenticationForm
from django.contrib.auth.decorators import login_required
def accounts_login(request):
context = {}
if request.method == "POST":
form = AuthenticationForm(request.POST) #getting error here
print(form)
if form.is_valid():
user = authenticate(login = request.POST["login"], password = request.POST["password"])
if user is not None:
print(user)
login(request,user)
next_url = request.POST["next"]
return redirect(next_url, args=(), kwargs={})
#more code - not required here. Let me know if needed.


Authentication form:

from django import forms
from accounts.models import UserModel

class AuthenticationForm(forms.Form):
login = forms.CharField(max_length=128, required=True)
password = forms.CharField(max_length=128, required=True)


My custom user model and user manager -

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.utils import timezone
from django.db.models import Max

class MyUserManager(BaseUserManager):
use_in_migrations = True
def create_user(self,login, parent_type, last_name, first_name, password):
return create_superuser(self,login, parent_type, last_name, first_name, password)


def create_superuser(self,login, parent_type, last_name, first_name, password):
maxx = self.model.objects.all().aggregate(Max('sys_id'))
print(maxx)
user = self.model(
sys_id = maxx["sys_id__max"] + 1,
login = login,
password = password,
parent_type = parent_type,
last_name = last_name,
first_name = first_name,
display_name = last_name + " " + first_name,
created_when = timezone.now()
)
user.save(using=self._db)
# no difference here...actually can set is_admin = True or something like that.
return user

class UserModel(AbstractBaseUser):
# custom user class
SYSTEM = 0
TENANT = 1
parent_type_choices = (
(SYSTEM, 'System'),
(TENANT, 'Tenant')
)
sys_id = models.BigIntegerField(primary_key=True, blank=True)
parent_type = models.PositiveIntegerField(choices=parent_type_choices, null=False, blank=False)
parent_sys_id = models.ForeignKey('tenant.TenantModel', on_delete = models.SET_NULL, null=True, blank=True)
last_name = models.CharField(null=False, blank=False, max_length=40)
first_name = models.CharField(max_length=40, null=False, blank=False)
display_name = models.CharField(max_length=80, unique=True, null=False, blank=True)
login = models.CharField(max_length=40, unique=True, null=False, blank=False)
authentication_method = models.CharField(max_length=80, null=True, blank=True)
access_valid_start = models.DateTimeField(null=True, blank=True)
access_valid_end = models.DateTimeField(null=True, blank=True)
created_when = models.DateTimeField(null=True, blank=True, )
created_by = models.BigIntegerField(null=True, blank=True)
last_updated_when = models.DateTimeField(null=True, blank=True)
last_updated_by = models.BigIntegerField(null=True, blank=True)
notes = models.CharField(max_length=2048, null=True, blank=True)
is_active = models.BooleanField(default=True)

objects = MyUserManager()

USERNAME_FIELD = "login"
# REQUIRED_FIELDS must contain all required fields on your User model,
# but should not contain the USERNAME_FIELD or password as these fields will always be prompted for.
REQUIRED_FIELDS = ['parent_type', 'last_name', 'first_name']

class Meta:
app_label = "accounts"
db_table = "Users"

def __str__(self):
return self.display_name

def get_full_name(self):
return self.display_name

def get_short_name(self):
return self.last_name

def check_password(self,password):
return True
if self.password ==password:
return true


Custom backend

from django.conf import settings
from accounts.models import UserModel

class MyAuthBackend(object):
def authenticate(self, login, password):
try:
user = UserModel.objects.get(login=login)
if user.check_password(password):
return user
else:
print("wrong password")
return None
except User.DoesNotExist:
return None
except Exception as e:
print(repr(e))
return None

def get_user(self, user_id):
try:
user = User.objects.get(pk=user_id)
if user.is_active:
return user
return None
except User.DoesNotExist:
return None


Added these two variables in setting.py

AUTH_USER_MODEL = 'accounts.UserModel'
AUTHENTICATION_BACKENDS = ('accounts.backends.MyAuthBackend',)


Getting backend from command line

>>> get_backends()
[<accounts.backends.MyAuthBackend object at 0x7f2c438afe80>]


A user with username lets say xyz and password 'ABC' exists in db.

1. When I am trying to login with existing username and password it throws me error
User model with this Login already exists.


2. When I try to login with non-existing username and password (lets say xyz123) it throws me error
authenticate() takes 0 positional arguments but 2 were given


I followed multiple articles but mainly django official documentation for this. Read multiple articles to solve the issue but failed.

Please suggest what I am doing wrong.

Update 1: chnged the AuthenticationForm from ModelForm to normal Form class. Error 1 is solved.

Update 2: Not using my custom backend as pointed by Daniel, it is useless because I am not doing anything new in it. Hence error 2 is also solved.

Update 3: Now hashing password before saving user to db in create super user function.
user.set_password(password)

Answer

Firstly, you are storing passwords in plain text in your overwritten create_superuser method. You must absolutely not do this. Quite apart from the security issues, it won't actually work, as the authentication backend will hash a submitted password before comparing it with the saved version, so it will never match.

Secondly, your actual problem is that your AuthenticationForm is a ModelForm; as such it will attempt to see if it can create a user with that email and password. There is no reason to make it a modelform, you should just have a plain form with CharFields for email and password.

Note though that you won't actually be able to log in, until you have fixed the plain text password issue. Note also that your custom auth backend - as well as breaking the authenticate method by not conforming to the interface described in the docs - does not do anything that the built-in one does not do; it is pointless and you should remove it.