Include default password in Welcome email for new users

At the moment I am creating new user accounts through the admin panel. When a new user is created, I set up a post save signal which will automatically send a welcome email to the new user’s registered email. My signals.py for this function looks like this:

#sends welcome email to new users with their username and default password

def send_welcome_email(sender,instance,created, **kwargs):

    user=instance

    template = render_to_string('accounts/welcome_email_template.html', {'name':user.first_name,'new_username':user.username,'default_password':user.password})

    if created:

        email = EmailMessage(

            'Welcome to Isow Investors team' , 

            template , 

            settings.EMAIL_HOST_USER,

            ['an email address'],

        )

        email.fail_silently = False

        email.send()

        print("Welcome email sent to user")

post_save.connect(send_welcome_email, sender=User)

And this is an example of the email template that is sent to the user:


Hello {{name}},

Welcome to the team,

Below are the details you need to login for the first time. We highly recommend you to change your password after logging in for the first time. The link to change your password 
can be found in your profile settings page. 

username  : {{new_username}}

default password  : {{default_password}}

At the moment, when I create a new user, an email is sent to the user’s email successfully. The only issue I have is that instead of showing the default password that I registered their account with, I get what I assume to be the hashed password. A list of characters,symbols and numbers. So how can I include the default password in the welcome email so that the new user will be able to login for the first time? Thank You

You can’t, shouldn’t, and really don’t want to. There is no reason for you to know the user’s password, and it’s extremely problematic for you to do so.

Django has the “Forgot password” facility which emails a one-time token to a web page allowing the user to select their own password.

See the Authentication Views docs for information on how to implement this.

Okay so I’ve been trying to use the builtin password reset feature for django with the signals but I can’t attach the link to the reset page in the custom email.

I first created a new url for setting up the new password.

#allows new users to set their new passwords. 

     path("set_password/", auth_views.PasswordResetView.as_view(template_name="accounts/set_password.html"),

       name="set_password"),

I already have a similar url but this one was for registered users who forgot or want to change their passwords. I wanted a different form for users who wanted to set their password for the first time so thats why I created another url for resetting password.

Url path for users who forgot their password:

path("reset_password/",
      auth_views.PasswordResetView.as_view(template_name="accounts/password_reset.html"),
       name="reset_password"),

So my idea was that, when I create a new user, an email will be sent to them, with their username and email as well as a link to the set password page. From that link, they will enter their email, receive the one time token which will take them to the password reset form where they can set their password.

welcome_email_template.html:

Hello {{name}},

We are pleased to inform you that your account is now active.

Please click on the link below, which will guide you into setting up your password. The details for your account are also listed below.
<a href="{{set_password_url should go here}}"> Set new password</a>


username  : {{new_username}}
email  : {{user_email}}

My issue is that I can’t pass the url for the password reset page in the email. I tried passing the {% url %} tag for the page in the email template but it wouldn’t render the anchor tag and instead I will get plain text. I searched online for solutions but couldn’t find one that worked. I experimented with using {{protocol}} and {{domain}} in the email template but I couldn’t set them in my signals.py. I’m guessing I have to somehow set up an absolute url in the signals.py file and pass that as context into the template but I’m not sure how.

That could be an issue of how you’re sending the email. If you’re sending the email as text, that’s what you’re going to get. If you’re sending the email as html email, it should render correctly, unless there’s some other problem.

I am using render_to_string on the html email template before passing it into EmailMessage.

#sends welcome email to new users with their username and defualt password
def send_welcome_email(sender,instance,created, **kwargs):
    user=instance

    template = render_to_string('accounts/welcome_email_template.html', {
        'name':user.first_name,
        'new_username':user.username, 
        'user_email': user.email})

    if created:
        email = EmailMessage(
            'subject' , 
            template , 
            settings.EMAIL_HOST_USER,
            ['email'],

        )

        email.fail_silently = False
        email.send()
        print("Welcome email sent to user")


post_save.connect(send_welcome_email, sender=User)

See the docs for send_email for how to send html email.

Read the mdn Django tutorial…
It has section dedicated for this issue.
And it linked to the django docs if you really want to understand Django.

Okay, so I went through the documentation and switched to using EmailMultiAlternatives instead of emailmessage to send my emails. Now the email sent does contain a link but I can’t set that link to the url that will send the user to the password reset page. I tried the solutions suggested here and here but they didn’t work (maybe because I didn’t fully understand the steps) and now I am confused as to what to do.

Signals.py

#sends welcome email to new users with their username and defualt password
def send_welcome_email(sender,instance,created, **kwargs):
    user= instance
    current_site = get_current_site(request=None)
    domain = current_site.domain
    page_url = reverse('set_password')
    protocol = "http"
    url = '{protocol}://{domain}/{page_url}'.format(protocol=protocol, domain=domain, page_url = page_url)

    subject,from_email , to = 'welcome to isow investors team', 'settings.EMAIL_HOST_USER',user.email
    context =  {'name':user.first_name,'new_username':user.username, 'user_email': user.email , 'url':url}
    text_content = render_to_string('accounts/welcome_email_template.txt',context)
    html_content = render_to_string('accounts/welcome_email_template.html', context)

    msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
    msg.attach_alternative(html_content, "text/html")
    msg.send()
    print("Welcome email sent to user")

post_save.connect(send_welcome_email, sender=User)

So what are you getting? (What is the value of url being passed in the context?) What does your “welcome_email_template.html” look like? What is being rendered?

My welcome_email_template.html looks like this :

Hello {{name}},


We are pleased to inform you that your online  account is now active.

Please click on the link below, which will guide you into setting up your password. The details for your account are also listed below.
<a href="{{url}}"> Set new password</a>


username  : {{new_username}}
email  : {{user_email}}


If you have any further questions or clarification on this matter, kindly reach out to us at name@gmail.com


The last email I sent came out like this :

Hello ,


We are pleased to inform you that your online account is now active.

Please click on the link below, which will guide you into setting up your password. The details for your account are also listed below.
<a href="http://example.com//set_password/"> Set new password</a>


username  : tebodo
email  : tebodo3718@enpaypal.com


If you have any further questions or clarification on this matter, kindly reach out to us at name@gmail.com.

As you can see, the url in the context is outputting "http://example.com//set_password/" instead of my local host or whichever domain I might be on.

Are you currently using the “sites” framework? Oh, wait, you’re using a signal for this, which means you don’t have the request available. (Mistake #1 - a signal isn’t your friend here.)

Best suggestions I can offer is to either get rid of using signals for this or hard-code your domain name, directly or through a setting. (I’m willing to almost guarantee that you’re not getting the benefits of using a signal that you might think you’re getting.)

If signals is not friend, what do you suggest I use instead to achieve the same functionality.

Add a call to your function at the point where you want the email sent. No need to make it any more complex than that. Or, if you’re really tied to using the admin for this, just hard code the domain name for now.

But I am creating the users from the admin page so I don’t have a view function that relates to the usercreation

Then go ahead and hard code the domain name. That’s going to be the easiest way to address this.

Alright. But How should I go about dynamically changing the domain name depending on whether I am on local server or in production server?

Make it a setting in your settings file. You’ll likely have a number of settings different between the two environments, this just becomes one more.

By the way, it’s this type of situation why I frequently quote this paragraph from the Django Admin docs

If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views.

You do have other options, like creating your own model admin and form for the User object - or even using something like an admin action. There are various ways to handle this.

I took your advice and switched to registering the user from a page and sending the email through a view.

views.py:

def registerPage(request):
    if not request.user.is_authenticated or request.user.username == 'admin_username':
        form = CreateUserForm()

        if request.method == "POST":
            form=CreateUserForm(request.POST)
            if form.is_valid():
                
                user = form.save()
                user.set_password(get_random_string())
                user.save()


                username = form.cleaned_data.get("username")
                email = form.cleaned_data.get("email")

                password_reset_form  = PasswordResetForm({'email':email} , subject_template_name = "Welcome" , email_template_name= "accounts/welcome_email_template.html")

                assert password_reset_form.is_valid()
                password_reset_form.save()
                

                messages.success(request,"Account was created for " + username )
                return redirect("login")


        context= {"form":form}
        return render(request, 'accounts/register.html', context)

So now I register a user through the register page without setting a password. The view validates the form, saves the user and I then set a default password for the user using get_random_string. After that, I plan on using passwordresetform to send the one time link for the user to reset his password through a custom email template. At the moment the email is sending automatically, but the issue is with the custom email template and getting the url for the one time link.

The default password_reset_email.html looks like this:

{% load i18n %}{% autoescape off %}
{% blocktranslate %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktranslate %}

{% translate "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% translate 'Your username, in case you’ve forgotten:' %} {{ user.get_username }}

{% translate "Thanks for using our site!" %}

{% blocktranslate %}The {{ site_name }} team{% endblocktranslate %}

{% endautoescape %}

I don’t want to override the default password_reset_email.html because the content in the email sent for new users need to be a different from that of logged users who want to reset their password. So instead, I know I have to use my own email template which I already created. But I don’t know How I should structure it terms of how to pass in the protocol and domain tags and also which parts from the default template I have to keep and which parts I can change. Thank you.

.

All data is passed to a template through the context.

But now, I’ll point out that since you’re rendering this in your view (where you have the request available), you can use the build_absolute_uri method on the request to help build the complete url needing to be sent and put that into the context, rather than constructing it in the template.

I built the url using the build_absolute_uri as you suggested:

@staff_member_required 
def registerPage(request):
    if not request.user.is_authenticated or request.user.username == 'admin_username':
        form = CreateUserForm()

        if request.method == "POST":
            form=CreateUserForm(request.POST)
            if form.is_valid():
                
                user = form.save()
                user.set_password(get_random_string())
                user.save()


                username = form.cleaned_data.get("username")
                email = form.cleaned_data.get("email")
                name = form.cleaned_data.get("first_name")

                url = request.build_absolute_uri('reset/<uidb64>/<token>/')

                set_password_form  = PasswordResetForm({'email':email,'url':url , 'username':username,"name":name})

                assert set_password_form.is_valid()
                set_password_form.save(
                    request=request,
                    from_email="email",
                    email_template_name="accounts/welcome_email_template.html",
                    html_email_template_name="accounts/welcome_email_template.html",
                )
                   

                messages.success(request,"Account was created for " + username )
                return redirect("login")


        context= {"form":form}
        return render(request, 'accounts/register.html', context)
    else:
        return redirect('home')

Even though I added the data I needed into the context, the email sent doesn’t show the url, username nor the first name.

My email template

Hello {{name}},

The  team would like to officially welcome you on board as investor.

We are pleased to inform you that your online investment account is now active.

Please click on the link below, which will guide you into setting up your password. The details for your account are also listed below.

{{url}}


username  : {{username}}
email  : {{email}}


If you have any further questions or clarification on this matter, kindly reach out to us at email.

Happy Investing!

The email I actually receive

Hello ,

The  team would like to officially welcome you on board as investor.

We are pleased to inform you that your online investment account is now active.

Please click on the link below, which will guide you into setting up your password. The details for your account are also listed below.




username  :
email  : fajab48425@rebation.com


If you have any further questions or clarification on this matter, kindly reach out to us at email.

Happy Investing!

I think it’s something to do with how I am setting up the passwordresetform context. I tried to put the context in the save method but then I get the error that it doesn’t expect that as an argument.