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

https://gist.github.com/Porter97/d078313f6f902b4d281d2bed226e1916

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

app/__init__.py

https://gist.github.com/Porter97/11752200c1856efad91178fd9831987c

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

https://gist.github.com/Porter97/f9123ae77928e22d5e61b0582ba7e2bc

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

https://gist.github.com/Porter97/deb583adc1f90454fd225a28d28ad141

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

https://gist.github.com/Porter97/6bc2de72705cd1dfae57c8cb5466de8b

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

https://gist.github.com/Porter97/66f7c349fc100043863caa75e647af00

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

https://gist.github.com/Porter97/92dabc0bd7869be556da487455af63a4

We will also create a text based version as well:

app/templates/auth/email/confirm_user.txt

https://gist.github.com/Porter97/4d4756df3b3051ff169ccaff1e367742

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

https://gist.github.com/Porter97/c1e5c7d70490fddb56630cd8e575b09e

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

https://gist.github.com/Porter97/77638ddcbc298ed6e38301386c345587

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

app/auth/views.py

https://gist.github.com/Porter97/06b44bc5732a4847ce14d17b97def8e6

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

https://gist.github.com/Porter97/7f9527159ff70d512abcf1cdcbc44d7b

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

https://gist.github.com/Porter97/6a57393318a885e279a22a38b79af956

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

https://gist.github.com/Porter97/53bbfae8fa348dcce722da82ccea73e7

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

app/auth/forms.py

https://gist.github.com/Porter97/d7b370c1f26290fc1600072197a456fd

app/auth/views.py

https://gist.github.com/Porter97/e7a813e83d54e63a675ba0c5f2154b80

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

https://gist.github.com/Porter97/14e5fc3df1ff2f93dfce8a895f48858b

app/template/auth/reset_password.html

https://gist.github.com/Porter97/f9a53a8204d17018f5c6b51a0311ffdd

app/template/auth/email/reset_password.html

https://gist.github.com/Porter97/9473b91a5c8824e731434846b723c3b1

app/template/auth/email/reset_password.txt

https://gist.github.com/Porter97/04db5efcc94b1b821b6e9a46437a676d

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

https://gist.github.com/Porter97/775da0e297877d48527e41b01e244dfd

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

https://gist.github.com/Porter97/5eaa8e16510c168368d9280e0a9c1164

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

https://gist.github.com/Porter97/14280ff6359b450727a42b008fb53329

app/templates/auth/email/change_email.html

https://gist.github.com/Porter97/47fe51fb8e3a12d0ea9d437d71462a22

app/templates/auth/email/change_email.txt

https://gist.github.com/Porter97/6fa3c13ebcfe008ac688f11f1aca0c29

app/templates/auth/change_password.html

https://gist.github.com/Porter97/416d6f6048a7e09648f0a0f98643e98c

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

https://gist.github.com/Porter97/c709c059eb903c85a0b8b042643bd655

app/templates/404.html

https://gist.github.com/Porter97/0b92a9b64d7ce258e59bf9f01f10600d

app/templates/500.html

https://gist.github.com/Porter97/72ce788a1918d3f00690a5326e8fe676

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.

Tutorials

Feeds