Authenticate works but login does not. It's stuck on pending

149 views Asked by At

The issue is as explained in the title. I am able to create and authenticate users through forms or the cli but once the user/patient object is passed to the login(request, user) function, the entire application gets stuck and the pages keep loading. Whether I am logging in directly from the sign in page or registering a new member then trying to redirect them based on successful authentication. What could the issue be?

(Note, I attempted creating a custom auth backend that also pitfalls into the same issue as the default.) Thanks in advance.

This is a snippet of my Patient model along with its manager from the booking app

class PatientManager(BaseUserManager):
    """ Manager for patient profiles """
    use_in_migrations = True
     
    def _create_user(self, email, password, is_superuser, birth_date, phone_no, **extra_fields):
        """ Create a new user profile """
        if not email:
            raise ValueError('User must have an email address')
        
        email = self.normalize_email(email)
        user = self.model(email=email, password=password, is_superuser=is_superuser, birth_date=birth_date, phone_no=phone_no, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        
        return user

    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.')

        user = self._create_user(email=email, password=password, is_superuser=True, birth_date=date(1990, 1, 1), phone_no="0711111111")
        
        return user
class Patient(AbstractUser, PermissionsMixin):
    uuid = models.UUIDField(primary_key=True, auto_created=True, default=uuid.uuid4())
    username = None
    name = models.CharField(max_length=50, null=False)
    email = models.EmailField(max_length=255, unique=True, validators=[EmailValidator(message="Please enter a valid email address in the format"), RegexValidator(regex='^[email protected]', inverse_match=True, message="Please provide a valid email address.")])
    birth_date = models.DateField(null=False)
    gender = models.CharField(choices=GENDER_CHOICES, default="Prefer Not To Say", max_length=20)
    phone_no = models.IntegerField(null=False)
    account_type = models.CharField(max_length=10, default='PATIENT')
    kin_name = models.CharField(max_length=50, help_text="*Optional", null=True, blank=True)
    kin_contact = models.IntegerField(help_text="*Optional", null=True, blank=True)
    password = models.CharField(max_length=30, validators=[MinLengthValidator(limit_value=8, message="Please ensure the password is at least 8 characters"), RegexValidator(regex='^password', inverse_match=True, message="Please use a different password")], default="password")
    is_superuser = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    last_update = models.DateTimeField(_('last updated'), auto_now=True)

    objects = PatientManager()
    
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['password']
    
    def __str__(self):
        return self.email
    
    def save(self, *args, **kwargs):
        while True:
            if (datetime.today().date() - self.birth_date).days < 6390:
                raise ValidationError(f"You are a {(datetime.today().date() - self.birth_date).days} year old. Account holders should be adults who have attained at least 18 years of age.")
            if Patient.objects.filter(email = self).count() < 1:
                break
        super().save(*args, **kwargs) 

This is a snippet of my members app views.py

def login_user(request):
    if request.method == "POST":
        username = request.POST['email']
        password = request.POST['password']
        user = authenticate(request, username=username, password=password)
        if user:
            print(f"{username} {password} is being logged in as\n {user} {request.POST}")
            login(request, user)
            print(f"Has been authenticated {user}")
            # Redirect to a success page.
            return redirect('index')
        else:
            # Return an 'invalid login' error message.
            messages.error(request, "There Was An Error!")
            return redirect('login')
    else:
        return render(request, 'authenticate/login.html', {})

def register_user(request):
    if request.method == "POST":
        form = RegisterUserForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data['email']
            password = form.cleaned_data['password1']
            print(f"{username} {password}")
            user = authenticate(username=username, password = password)
            print(f"{user}")
            login(request, user)
            messages.success(request, "Sign Up Completed!")
            return redirect('index')
    else:
        form = RegisterUserForm()
    return render(request, 'authenticate/register_user.html', {
        'form':form,
    })

I was hoping once the user gets authenticated they would get redirected to the index page. I have logged output from the cli and this proves that the users are authenticated but my login function is buggy. This result below is from: print(f"{username} {password} is being logged in as\n {user} {request.POST}")

[email protected] @octo808 is being logged in as
 [email protected] <QueryDict: {'csrfmiddlewaretoken': ['7NVGmhwyH2J2tT07jtt6SKNhOWyJl9xYUWlCtjL6lX5hJrjIvpJ7Edth8is3GAxp'], 'email': ['[email protected]'], 'password': ['@octo808']}>

EDIT: I have included the register module from my views. I have also noticed that the user_logged_in signal from django\contrib\auth\__init__.py never fires up in the login function.

user_logged_in.send(sender=user.__class__, request=request, user=user)

1

There are 1 answers

1
willeM_ Van Onsem On BEST ANSWER

Likely the save() method is a culprit: it can get in an infinite loop, and actually quite fast.

Indeed, if you save the Person again, then for the query Patient.objects.filter(email = self).count() it will return one record, indeed, the one record you are saving again.

But regardless, enforcing uniqueness with queries is inefficient and inelegant anyway. You can do this through validators, you already marked email as unique=True, so normally the database will handle this. For the birthday, you can work with:

from datetime import date


def at_least_age_18(value):
    today = date.today()
    age = (
        today.year
        - value.year
        - ((today.month, today.day) < (value.month, value.year))
    )
    if age < 18:
        raise ValidationError(
            f'You are a {age} year old. Account holders should be adults who have attained at least 18 years of age.'
        )


class Patient(AbstractUser, PermissionsMixin):
    # …
    email = models.EmailField(
        max_length=255,
        unique=True,
        validators=[
            EmailValidator(
                message="Please enter a valid email address in the format"
            ),
            RegexValidator(
                regex='^[email protected]',
                inverse_match=True,
                message="Please provide a valid email address.",
            ),
        ],
    )
    birth_date = models.DateField(null=False, validators=[at_least_age_18])
    # …

    objects = PatientManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['password']

    def __str__(self):
        return self.email

    # no save override

Nice thing is that Django's ModelForms will also look for the validators of the fields, and thus print the error in the corresponding field if people fill in a birthday that is less than 18 years ago.