Home > Back-end >  How can I get my custom user model logged in? AuthenticationForm or my own custom login form won
How can I get my custom user model logged in? AuthenticationForm or my own custom login form won

Time:12-28

I've created a custom user model, subclassing AbstractUser.

I have a registration view, template, and a CustomUserCreationForm that seems to work fine, and can register users no problem via the front end.

My issue is getting the user logged in. I can't seem to pass the form validation to authenticate them with. I'm always returned with a None user object

With this line for example, I always get None, this failing verification

            user = authenticate(request, email=email, password=password)
            # user = User.objects.get(email=email, password=hashed_pass)

            # Check if authentication successful
            if user is not None:
                login(request, user)
                return HttpResponseRedirect(reverse("clientcare:portal/dashboard"))
            else:
                return render(request, "clientcare/login.html", {
                    "message": "Invalid email and/or password.",
                    'login_form':LoginForm,
                })

Forms

class CustomUserCreationForm(UserCreationForm):

    class Meta(UserCreationForm.Meta):
        model = User
        fields = ('email', 'first_name','last_name' ,'city', 'province','age','gender','phone_number','password1', 'password2',)

class LoginForm(forms.Form):
    email = forms.EmailField(max_length=100)
    password = forms.CharField(widget = forms.PasswordInput())

class CustomUserChangeForm(UserChangeForm):

    class Meta:
        model = User
        fields = ('email', 'first_name','last_name' ,'city', 'province','age','gender','phone_number',)


Models

# Create your models here.
class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError('Users require an email field')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        extra_fields.setdefault('is_patient', False)
        extra_fields.setdefault('is_provider', True)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class User(AbstractUser):
    username = None
    email = models.EmailField(_('email address'), unique=True)
    image_height = models.PositiveIntegerField(null=True, blank=True, editable=False, default="200")
    image_width = models.PositiveIntegerField(null=True, blank=True, editable=False, default="200")
    date_joined = models.DateTimeField(auto_now=True, null=False, blank=False)
    city = models.CharField(null=False, blank=False, max_length=20)
    province = models.CharField(null=False, blank=False, max_length=20)
    profile_image_url = models.ImageField(null=True, blank=True, upload_to='images/', editable=True)    
    paid = models.BooleanField(default=False)
    phone_number = PhoneField(blank=True, null=True, help_text='Contact phone number', E164_only=False)
    in_trial = models.BooleanField(default=True)
    recently_active = models.BooleanField(default=True)
    gender = models.CharField(choices=(("Male", "Male"),("Female", "Female"), ("Other", "Other")), max_length=6, default="Male", null=False, blank=False)
    age = models.SmallIntegerField(max_length=3,null=False, blank=False)
    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name',]
    # classify if the user is a provider or a patient
    is_patient = models.BooleanField('Patient status',default=False)
    is_provider = models.BooleanField('Provider status',default=False)

    def __str__(self):
        return f"{self.get_full_name()}"

Login View

def login_view(request):

    if request.method == "POST":

        login_form = LoginForm(data=request.POST)
        if login_form.is_valid():
            email = login_form.cleaned_data['email']
            password = login_form.cleaned_data['password']

            # hashed_pass = bcrypt.hashpw(raw_pass, salt)
            # if bcrypt.checkpw(raw_pass, hashed_pass):
            user = authenticate(request, email=email, password=password)
            # user = User.objects.get(email=email, password=hashed_pass)

            # Check if authentication successful
            if user is not None:
                login(request, user)
                return HttpResponseRedirect(reverse("clientcare:portal/dashboard"))
            else:
                return render(request, "clientcare/login.html", {
                    "message": "Invalid email and/or password.",
                    'login_form':LoginForm,
                })
        else:
            return render(request, "clientcare/login.html", {
                "message": "Invalid login data. Please try again",
                'login_form':LoginForm,
            })
    else:
        return render(request, "clientcare/login.html", {
            'login_form':LoginForm,
        })

Registration view

def register(request):

    # Adding the salt to password

    if request.method == "POST":

        register_form = CustomUserCreationForm(request.POST)
        if register_form.is_valid():
            email = register_form.cleaned_data['email']
            city = register_form.cleaned_data["city"]
            province = register_form.cleaned_data["province"]
            first_name = register_form.cleaned_data["first_name"]
            last_name = register_form.cleaned_data["last_name"]
            phone_number = register_form.cleaned_data["phone_number"]
            age = register_form.cleaned_data["age"]
            gender = register_form.cleaned_data["gender"]

            # Ensure password matches confirmation
            password = register_form.cleaned_data["password1"]
            confirmation = register_form.cleaned_data["password2"]
            if password != confirmation:
                return render(request, "clientcare/register.html", {
                    "messsage": "Passwords must match."
                })

            # Hashing the password
            # hashed = bcrypt.hashpw(password, salt)
            # password = hashed

            # Attempt to create new user
            try:
                user = User.objects.create(email=email, city=city, province=province, password=password, first_name=first_name, last_name=last_name, phone_number=phone_number, age=age, gender=gender)
                user.save()
            except IntegrityError:
                return render(request, "clientcare/register.html", {
                    "message": "ERROR. TRY AGAIN",
                })
            login(request, user)
            return HttpResponseRedirect(reverse("clientcare:index"))
        else:
            return render(request, "clientcare/register.html", {
                "message": "ERROR. PLEASE CONFIRM REGISTRATION INFO",
            })
    else:
        return render(request, "clientcare/register.html",{
            'registration_form':CustomUserCreationForm
        })

I have my user in settings.py as such:

AUTH_USER_MODEL = 'clientcare.User'

I'm well aware I can use AllAuth or other auth libraries. But I'm trying to understand things on a lower level before using such libraries. Any help would be appreciated.

Nothing I try seems to work in getting my custom user model logged in. Do I need to write a custom backend? AuthenticationForm doesn't seem to work just as my own login form doesn't seem to validate

HOWEVER, if I update a users password via the admin(with superuser), then the user can login no problem with the updated password.. so my CustomUserChangeForm does the job. What am I missing?

CodePudding user response:

I'm putting a model of my old project here as an example. Here I used the user model as it is and added the fields I wanted to add to the new class that user is also a field there. Django's auth module works as is and no additional coding was required.

from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import ugettext_lazy as _
from backend.custom_fields import AutoOneToOneField
from backend import definitions
from django.urls import reverse
from django.db.models.signals import post_save
from django.dispatch import receiver
from backend.convert import convert_string_to_url_safe
from random import randint
from backend.image_operation import image_resize, image_convert_to_jpg


EMAIL_PRIVACY_CHOICES = (
    (0, _('Display your e-mail address.')),
    (1, _('Hide your e-mail address but allow form e-mail.')),
    (2, _('Hide your e-mail address and disallow form e-mail.')),
)

PHONE_PRIVACY_CHOICES = (
    (0, _('Display your phone no.')),
    (1, _('Hide your phone no.')),
)


def avatar_upload_file_name_path(instance, filename_ext):
    ext = filename_ext.split('.')[-1]
    return f'profile_uploads/{convert_string_to_url_safe(instance.get_full_name)}-{randint(1000, 10000)}.{ext}'


def photo_upload_file_name_path(instance, filename_ext):
    ext = filename_ext.split('.')[-1]
    return f'profile_uploads/{convert_string_to_url_safe(instance.get_full_name)}-{randint(1000, 10000)}.{ext}'


def resized_photo_upload_file_name_path(instance, filename_ext):
    ext = filename_ext.split('.')[-1]
    return f'profile_uploads/resized-{convert_string_to_url_safe(instance.get_full_name)}-{randint(1000, 10000)}.{ext}'


class PlatformUser(models.Model):
    user = AutoOneToOneField(User, on_delete=models.CASCADE)
    slug = models.CharField(max_length=100, null=True, blank=True)
    title = models.CharField(max_length=60, blank=True, default='')
    description = models.TextField(blank=True, default='')
    phone = models.CharField(max_length=25, blank=True, default='')
    facebook_address = models.URLField(blank=True, default='')
    twitter_address = models.URLField(blank=True, default='')
    instagram_address = models.URLField(blank=True, default='')
    linkedin_address = models.URLField(blank=True, default='')
    youtube_address = models.URLField(blank=True, default='')
    site = models.URLField(_('Personal Site'), blank=True)
    skype_name = models.CharField(_('Skype Name'), max_length=100, blank=True, default='')
    birth_date = models.DateTimeField(_('Birth Date'), blank=True, null=True)
    location = models.CharField(_('Location'), max_length=30, blank=True)
    photo = models.ImageField(upload_to=photo_upload_file_name_path, blank=True, default='')
    photo_resized = models.ImageField(upload_to=resized_photo_upload_file_name_path, blank=True, null=True)
    show_photo = models.BooleanField(_('Show avatar'), blank=True, default=True)
    show_signatures = models.BooleanField(_('Show signatures'), blank=True, default=True)
    show_smilies = models.BooleanField(_('Show smilies'), blank=True, default=True)
    email_privacy_permission = models.IntegerField(_('Privacy permission'), choices=EMAIL_PRIVACY_CHOICES, default=1)
    phone_privacy_permission = models.IntegerField(_('Privacy permission'), choices=PHONE_PRIVACY_CHOICES, default=1)
    auto_subscribe = models.BooleanField(_('Auto subscribe'),
                                         help_text=_("Auto subscribe all topics you have created or reply."),
                                         blank=True, default=False)
    post_count = models.IntegerField(_('Post count'), blank=True, default=0)
    likes_count = models.IntegerField(default=0)
    view_count = models.IntegerField(default=0)
    signature = models.TextField(_('Sign'), blank=True, default='', max_length=definitions.SIGNATURE_MAX_LENGTH)
    signature_html = models.TextField(_('Sign as HTML'), blank=True, default='',
                                      max_length=definitions.SIGNATURE_MAX_LENGTH)
    verification_code = models.CharField(_('Verify Code'), blank=True, default='', max_length=40)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    updated = models.DateTimeField(_('Updated'), auto_now=True, null=True)

    class Meta:
        ordering = ['user']
        get_latest_by = 'created'
        verbose_name = _('Platform Member')
        verbose_name_plural = _('Platform Members')

    def __init__(self, *args, **kwargs):
        super(PlatformUser, self).__init__(*args, **kwargs)
        self.__original_image_filename = self.photo.name

    @property
    def email(self):
        return self.user.email

    @property
    def get_full_name(self):
        full_name = str(self.user.first_name.title())
        if len(full_name) > 0:
            full_name  = " "
        full_name  = str(self.user.last_name.title())
        if len(full_name) == 0:
            full_name = self.user.username
        return full_name

    @property
    def username(self):
        return self.user.username

    @property
    def first_name(self):
        return self.user.first_name

    @property
    def last_name(self):
        return self.user.last_name

    def __str__(self):
        return f"[{self.user.first_name} {self.user.last_name}] - ({self.user.email})"

    def get_absolute_url(self):
        return reverse('profile',
                       args=[str(self.get_slug())])

    def get_slug(self):
        if not self.slug:
            self.slug = self.user.username
        return self.slug

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = self.get_slug()

        if self.photo and (self.photo.name != self.__original_image_filename or not self.photo_resized):
            self.photo_resized = image_resize(400, 400, self.photo)
            self.photo = image_convert_to_jpg(self.photo)

        super(PlatformUser, self).save(*args, **kwargs)


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        PlatformUser.objects.create(user=instance)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    platform_users = PlatformUser.objects.filter(user=instance)

    if len(platform_users) > 0:
        platform_user = platform_users[0]
    else:
        platform_user = PlatformUser.objects.create(user=instance)

    platform_user.save()

as you could see there are receivers for creating my own platform user row on database when user created.

This way is simple but useful in my opinion. Your way is more correct but more complex.

BTW I dont use and dont like django forms at all.

CodePudding user response:

Solved. I realized I wasn't using the create_user method I wrote in my registration view.

Updated the line below:

user = User.objects.create_user(email=email, city=city, province=province, password=password, first_name=first_name, last_name=last_name, phone_number=phone_number, age=age, gender=gender)

After this, my login form and validation worked. So for anyone having a similar issue, make sure you're using the create_user method if you wrote one/if you're using a custom user model/if you call user.set_password(password) in that method and not in your view.

  • Related