Series: Building a Social Network with Flask & Stream – Part 4

This is the fourth installment of a tutorial series focusing on how to create a full-stack application using Flask and Stream. This is the second part of the User and Permissions set up. This article is going to cover setting up the views and forms to allow users to register, login, and logout, as well as functions, to allow them to change their email or password. Check out the Github repo here to help you follow along!

Getting Started

Last week we got our database ready to go complete with user models as well as permissions and anonymous users. We also went over initializing Flask-Migrate to handle changes to the database.

Init to Win It

Now that we have our database set up and ready, it would be pretty nice to have a way for people to sign up and log in. This next section is going to deal specifically with that.

We are going to modularize the authentication portion of the site by creating a specific auth folder. After creating the auth directory, create an __init__.py file.

app/auth/__init__.py

Before we move on to views and forms, let’s initialize the blueprint in our main __init__.py file:

app/__init__.py

Next Steps

Our Authentication folder is now integrated with the rest of our app, so we can start creating our endpoints.

Auth To The Races

Our first step is to allow users to register, so we will have to make a form and endpoint for it. First of which will be the form:

app/auth/forms.py

This form has fields for username, email, and two for confirming a password. Additionally, there are custom validators to ensure that the email and username aren’t already in use when a user signs up.

After that, we can create the endpoint that will provide and return the information on the form in auth/views.py.

app/auth/views.py

As you can no doubt tell, there are already quite a few issues here. First and foremost, we haven’t created the HTML document that we are going to return with this page. After that, how can we be sure that the email the user entered belongs to them. Finally, short of making a user physically type in the address for registration, there isn’t any practical way to navigate our site. We are going to tackle each of these problems next.

Early Registration

First, the HTML file – since we will have a few different pages for authentication (register, login, logout, etc.) we will create our folder for it in templates. Once that’s been created, we’ll create the register.html file.

app/templates/auth/register.html

In this file, we import a function (quick_form) from the bootstrap/wtf package. This handy bit of code allows you to quickly render all the fields of the form class that you passed through the render_template function.

Cannot Confirm or Deny

Although now we can return a web page for registration, we still can’t be sure that the email used for signup belongs to the user. While there are a few ways around this (like third party authentication, but that’s a story for another day), confirmation emails are the standard. Implementing an email system in Flask is pretty easy with Flask-Mail as well.

If you remember from the previous article, we have already installed, initialized, and configured the Flask-Mail package, the only thing left to do is set the email account that we will be sending from. If this is your first time using this kind of functionality, I’m going to point you again to Miguel Grinberg’s tutorial, as he explains the process of allowing a Gmail account to act as a proxy for your app. In the repo, you will see that I have set my Offbrand Mail Sender configuration as admin@offbrand.co; however, you should change to whichever email you are using. Additionally, you will need to set your environmental variables (using “set” in the command line) for your email username and password.

Once that is set up, we have to create an email.py folder to hold the email function. Our email function will operate asynchronously using the threading package, so sending the email doesn’t block the rest of the application.

app/email.py

Next, we are going to create our new user email templates in our auth template folder to keep them neat.

app/templates/auth/mail/confirm_user.html

We will also create a text based version as well:

app/templates/auth/email/confirm_user.txt

After this, we will have to integrate this function when a user registers, as well as a confirmation endpoint that we included in the email (that fancy little url_for() function) to validate the confirmation token. To top it all off, we will add a resend confirmation route as well, just in case it gets lost in the mail. If you recall, we set up a confirmation token as part of the models in the previous article, as well as the function to validate them, so this should be a snap.

app/auth/views.py

Known Unknowns

Before we can get to our confirmations, though, we run into another issue: login_required. To validate the user, the user has to be logged in, but we don’t have a login screen yet. This next step is going to go over a lot of the same material we just covered in setting up the registration page.

First, let’s create our login form.

app/auth/forms.py

Next we’ll construct the endpoint and pass the form to it.

app/auth/views.py

The next argument in this route will come in handy later as we start to build more pages for the site, allowing users who had previously not authenticated and had detoured to do so the ability to continue after authentication instead of being forced back to the index page.

I also took the opportunity here to put together a logout route, an unconfirmed route quickly, and a before_request check to see if the user is unconfirmed to direct them there.

Last but not least, we need the pages to return. As you can see from render_template(), we are going to name this file login.html.

app/templates/auth/login.html

Just one more page for those who have yet to confirm their account, and we should be just about finished.

app/templates/auth/unconfirmed.html

Last Hurdles

Now that we have wrapped up registration, confirmation, login, and logout, there are a few more things to get into to make sure that we have all of the functionality you would expect – such as the ability to reset a forgotten password, change your password, or change your email. Each of these will require some functions to be added to the User class in models.py.

app/models.py

We will also need the forms for those requests, as well as the routes for them:

app/auth/forms.py

app/auth/views.py

The changing email and password functions will stay buried for now, but the forgotten password can easily be put into our login form, along with a quick template for the reset password page and email.

app/template/auth/login.html

app/template/auth/reset_password.html

app/template/auth/email/reset_password.html

app/template/auth/email/reset_password.txt

Base Jumping

Now that we have all of these pages, we need a way to navigate between them. As we will need this navigation on every page, and I don’t particularly like writing navigation bars on every page, we will once again modularize it and import it using Jinja. We can also use it as a way to centralize any of our other components that will be used on every page, such as flashed messages and Moment.js

app/template/base.html

Going back to the HTML documents that we’ve made before (login, register, reset_password, unconfirmed, index, etc.), we can simply import it at the top like so:

app/template/login.html

Loose Change

Since we now have a way to access our change email and change password functions, we can build those now too!

The only thing left is to build the HTML templates to return, as well as the emails.

app/templates/auth/change_email.html

app/templates/auth/email/change_email.html

app/templates/auth/email/change_email.txt

app/templates/auth/change_password.html

Erring On The Side Of Caution

The last thing we will have to deal with for now is errors. Since we’ve constructed the capability for permission routes, we will also have to gracefully deal with users trying to access things that they aren’t authorized for. Your most common errors are going to be 403’s (Forbidden), 404’s (Page Not Found), and 500’s (Internal Server Errors), so we will make those now:

app/templates/403.html

app/templates/404.html

app/templates/500.html

Sanity Check

Booting up our Flask server again and navigating to localhost, we can see our page is starting to come together.

Final Thoughts

Phew! That was a lot.

Now though, we have all of the pieces in place to start creating our app. Users have a landing page, can register, sign-in and sign-out, as well as change their password or email. You also now can use permission routes, return custom error pages, and have a nice little navbar for your users to navigate the site.

Next week we are going to start getting into the meat of the project, creating user profile pages with their information and allowing them to edit their profiles! We will also start diving into developing testing strategies to ensure that everything is working correctly.

Thank you for following along and happy coding!

Note: The next post in this series can be found here.