Django PasswordResetForm not sending emails

I have the following function manages adding a new employee to the selected workshop,

def add_employee(request, shop_number):
    workshop = get_object_or_404(Workshop, shop_number=shop_number)
    context = {"workshop": workshop}
    
    if request.method == "POST":
        form = ProfileForm(request.POST, files=request.FILES)
        if form.is_valid():
            employee = form.save(commit=False)
            employee.added_by = request.user
            
           # Create the user associated with the employee with an unusable password
            employee_user = get_user_model().objects.create_user(
                username=request.POST['email'],
                email=request.POST['email'],
                password=None,  # No password is set
                first_name=request.POST['first_name'],
                last_name=request.POST['last_name']
            )
            employee_user.set_unusable_password()
            employee_user.save()

            employee.user = employee_user
            employee.save()

            # Add the new employee to the workshop's employees
            workshop.employees.add(employee)

            # Conditionally add the employee to groups based on their role
            if employee.is_craftman:
                group, created = Group.objects.get_or_create(name='craftmen')
                group.user_set.add(employee_user)
            elif employee.is_salesman:
                group, created = Group.objects.get_or_create(name='salesmen')
                group.user_set.add(employee_user)


            # Send a password reset email to the user
            form = PasswordResetForm({'email': employee_user.email})
            print(employee_user.email)
           
            if form.is_valid():
                request.current_site = get_current_site(request)
                form.save(
                    request=request,
                    use_https=request.is_secure(),
                    email_template_name='registration/password_reset_email.html',
                    subject_template_name='registration/password_reset_subject.txt',
                    from_email=settings.EMAIL_HOST_USER,
             
                )
            else:
                print(form.errors)
                print(form)


            messages.success(request, f"You have successfully added {\
                             employee}")
            return redirect('manage_employees', shop_number=shop_number)
        else:
            context['form'] = form
    else:
        context['form'] = ProfileForm()        

    return render(request, 'employees/employees_form.html', context)

The code correctly prints the employee user’s email, but the user does not receive the password reset email. Other parts of the code send emails successfully, but this section and the default password reset view both fail to deliver emails. I have confirmed that the specific email exists in the database. Could anyone help identify potential issues preventing the reset email from being delivered?

Are you running this locally in development, or are you asking about your production environment?

Do you get any error messages logged when the emails are being sent?

Does sendtestemail work?

What are your EMAIL_-related settings in your settings.py file?

Thank you Sir for responding, I’m running this locally, no error is shown, sendtestemail works well,below are my email settings,

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = os.environ.get('HOST_MAIL')
EMAIL_HOST_PASSWORD = os.environ.get('MAIL_PASS')
EMAIL_PORT = 587
EMAIL_USE_TLS = True

I’m a bit confused by the models you’re using here.

What’s the difference between an employee and an employee_user? (What models are they?)

Also, you’re creating a form in the POST section of your view and then testing it for validity. What are you trying to accomplish by these lines of code?

The employee is an instance of a specialized Profile model containing additional attributes specific to an employee, while employee_user is the basic user account that allows this person to log into the system. Below are some of its fields,

class Profile(models.Model):

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

    is_owner = models.BooleanField(default=False)

    is_craftman = models.BooleanField(default=False)
    is_salesman = models.BooleanField(default=False)
  
    photo = ThumbnailerImageField(
        upload_to='humans/%Y/%m/%d/', blank=True, null=True)

    phone = models.CharField(help_text='+254723456789', unique=True)
    added_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                 on_delete=models.SET_NULL,
                                 related_name='%(class)s_users', null=True, blank=True)

    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)```

The snippet you've highlighted is part of a function that creates a new employee user and sends them a password reset email. Here's an explanation of each step:

1. Create Password Reset Form:
   - ` form = PasswordResetForm({'email': employee_user.email})`
   - A `PasswordResetForm` is created and initialized with the new employee's email address. This form is used later to handle the password reset email. It's necessary to wrap the email address inside a dictionary, as this is how the form expects its input.

2. Print Employee Email (for debugging purposes):
   - `print(employee_user.email)`
   - This will output the employee's email to the console, which might be useful for debugging or verifying that the right email is being used. With this I get the right email used to setup the employee.

3. Check Form Validity:
   - `if form.is_valid():`
   - The code checks if the form input is valid that is, if the email address exists in the database and is associated with a user.

4. Prepare Email Content and Send Email:
   - `request.current_site = get_current_site(request)`
   - The site information associated with the request is used to build links that point back to the same site.
   - `form.save(...):`
     - This function sends the actual password reset email. It provides various parameters:
       - `request`: Used to help generate the correct links in the email.
       - `use_https`: Determines if the email links should use `https` (secure) or `http` (insecure).
       - `email_template_name` and `subject_template_name`: These specify the templates for the email body and subject, respectively.
       - `from_email`: The email address used as the sender.

The purpose of this entire section is to ensure that the newly created user receives a secure password reset email to set their password, as no usable password was initially set.

Very helpful, thank you.

Is the PasswordResetForm that you are using a form that you have created, or are you using the system-provided form?

Side Note, the is_valid test for that form does not verify that the email address is in the database. It only verifies that the email address entered passes the EmailField validator.

So the next thing you want to verify is that the email address you’re testing with actually does exist, and that the is_active attribute for the user account is True. (The email-send feature of the PasswordResetForm will only send an email to active users.)

The PasswordResetForm is the system-provided one. I agree with your side note. I confirmed the email existed and that the is_active attribute for the user account is True

As I was troubleshooting, I realized for accounts that I have added manually in the Django admin, PasswordResetForm was sending the password reset link. Following this, the issue is most likely being with how the user is created

I think I found it.

The get_users method in the PasswordResetForm also filters out users with an unusable password, so those users don’t get emails either. You’ll have to either assign some type of password to those users, or override that method in the form to not filter out by that condition.

1 Like

Yes, you are right, I went with the option of sending a link to the users to reset the password. Thank you very much for the guidance. I do appreciate.