Flask to Heroku: The Roadblocks

Unspoken challenges that you face while working with Flask & deploying the app in Heroku

Flask to Heroku: The Roadblocks

Introduction

For quite some time, I have been thinking of deploying a website as a project, nothing massive but mostly utilizing the important functionalities of a webpage. Personally, I always loved using the Flask framework. It's easy, a lot of libraries to make things even easier, and who doesn't love a lightweight framework!

Youtube videos are great to start with but when it needs customization based on your requirement it's quite the challenge, not to mention the videos are 30 minutes to 1 hour long, while your project could span for days or weeks. Here's me summarizing the challenges that no video prepared you for!

A Brief - Idea behind the webpage

Since I started learning to program, I was quite fascinated by the way GitHub shows the contribution graph. The graph shows how consistent you are. While the color intensity depends on the number of contributions you do, the primary aim is to show if you have contributed or not. I hoped to create something similar. While researching I found that Pixela uses an API to make use of a similar format of the graph. The idea of personalizing it to any activities did seem interesting to me. After using it for a week, I wondered if I could actually make a website that wraps around the API for others to use too and track their goals and other habits. Here's how I thought to approach it:

  • Sign Up and Login Page
  • A Profile page
  • A Graph display page
  • An About page
  • Hosting it in Heroku

You can view the website here: TracBox

The Challenges

Before I talk about the challenges, here are the libraries I used to make this a successful project:

Since it's a personal project, the idea was to store as little data as possible, while using Flask and login sessions to work with.

1. Gathering multiple WTForms data in a single route

When: I faced this while creating the Profile route. I wanted to create a page where once the user logs in successfully, they can choose to Create a Graph or View any previously created Graph.

Why: I needed two separate data as any user can create Graph1 and choose to view Graph2. For this, I had to use two different WTForms - one, which takes in the required data to create the graph passing the parameters to the Create Graph API call & two, which takes the Graph ID of the respective graph that the User wants to view, as Pixela uses that to return an SVG format of a graph.

This is where a bit of HTML comes in handy. Using name allows us to specify the name of the form are we referring to. This is how to approach the problem:

# HTML code
...
<input name ="form1" type="submit" value="Create Graph">
...
...
<input name ="form2" type="submit" value="View Graph">

Now to catch it inside the Flask route, we need to address the unique name of each form. Here's how:

# FLASK code
@app.route("/profile/", methods=['GET', 'POST'])
def profile():
    form1 = GraphForm()
    form2 = ViewGraph()

    if "form1" in request.form and form1.validate_on_submit():
        --Do something--
    if "form2" in request.form and form2.validate_on_submit():
        --Something more--

How: request.form generates an immutable MultiDict , a data structure, which looks something like this
ImmutableMultiDict([('csrf_token', 'something'), ('graph_id', 'graph1'), ('form2', 'View Graph')]) allowing us to catch hold of the name of the form.

2. Escaping HTML elements

When: I needed to show the graph on the graph.html page as an easy way to view or work with the plot that they themselves generated using the Pixela API.

Why: When passing the Graph ID from the View Graph using the form on the Profile page, the Pixela API returns and SVG format of the graph to be viewed. I needed to render it on the webpage but the format it returned was just a massive HTML SVG tag.

There are two ways to escape HTML characters as I passed the API response to the template. Here's how:

# HTML code

<!-- Way: 1 -->
<div>
{% autoescape false %}
{{ image }}
{% endautoescape %}
<div>

<!-- Way: 2 -->
<div>
{{ image|safe }}
<div>

I recommend the second way more, as you aren't having to force Jinja to explicitly turn off autoescape as autoescape is generally always True inside Jinja.

3. Validators: The Superpower

When: In quite a few instances, I required validators, they were my Han Solo to Luke Skywalker. Without them, I'm sure this project would have been a bit more complicated than I wished it to be.

Why: Pixela uses a set format when registering users inside their database, requiring a dictionary format of a few parameters acknowledging their Terms and Services. It would be easy, if I could set the parameters to 'Yes' while they were on the Sign Up page but I needed that they were mandatorily checked, else it would be impossible to proceed further.

Here's how you can use validators to make life easier for you:

class SignUpForm(FlaskForm):
    username = StringField(label='Username', validators=[InputRequired()])
    password = PasswordField(label='Password', validators=[InputRequired(), Length(min=4)])
    age = BooleanField(label='Yes, I accept the Terms and Conditions & I\'m above age 18.', validators=[InputRequired()])
  • InputRequired(): This mandatorily makes sure that there is some kind of input inside the form field, else it would not allow the User to proceed any further.
  • Length(): I used the Length field to make sure there were no one-word passwords as that's not only less secure but ruins the hashing too.

However, the most useful of these fields were the age variable holding the BooleanField which allowed a checkbox with the label, making it mandatory to provide input to proceed further.

4. Login Session & Catching Errors

Login Session: If you have previously worked with any API, you'd know how it requires a token or key to access the API, like a password. Quite rightly, this was one of the challenges I had to face as Pixela uses the token at every moment where we are creating the graph or updating some parameters like pixels in the graph. I didn't want any User to have to type the password every time they update a pixel, cause it's tiresome as well as results in a bad user experience.

The way to avoid it was to create a login session where the user would securely log in with their username and password and they would be able to access their account and any graph that they create. Using a session for login helped me re-use the token from the current session. Every time I needed the token for any action the User chose, I could simply do token = current_user.password and had the required token saved in the variable, which I could then pass as an API call, given I wrapped the decorator @login_required on the route. This was entirely done by Flask-Login, a simple Flask library that handles Login sessions.

Errors: Creating a Login session is prone to errors, by errors I mean if someone tried to access a page that was wrapped under @login_required without logging in, it would show the below image:

This doesn't result in a great user experience as the User doesn't understand what this is all about and would probably blame the creator of the application on how badly it is made. The way to catch these errors and redirect the user gives a better user experience. To catch the error:

@app.errorhandler(401)
def custom_401(error):
    return render_template('404.html')

I created a separate 404.html template that doesn't return a dull-looking page so as to make the webpage interaction even more humane.

5. Heroku: The Final Boss

Heroku provides a free server to host a webpage for you to show your work to the world. However, it does come with some good-to-know things. One of the major challenges I faced while using Heroku is the setting up of a database.

Using the webpage in my local server, I started with SQLite, which is generally a good starting point for you to understand a database view it, and work with it. However, using SQLite inside Heroku isn't safe, not to mention your data would probably be deleted daily, having quite a few unhappy users. Here's Heroku themselves explaining why this happens.

The alternative is to use PostgresSQL, it's exactly the same commands that SQLite uses with the added benefit that it's industry-proven. Once you add it, you can simply add the secret key to your program and it will start working. This is what it looks like:

Screenshot from 2022-03-24 22-41-25.png

However, with the recent changes to Postgres, the value that Heroku uses results in an error. Here's what you should do instead to avoid that:

app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL').replace("postgres://", "postgresql://", 1)

We are simply replacing the postgres:// with postgresql:// as you wouldn't be able to change that inside of Heroku.

Conclusion

I wouldn't sugarcoat and tell you that these are the only troubles you would face but I can tell you that there is almost every solution available out there on the web. You only require patience to search for it. Heroku does generate unseen errors compared to the local server but on the positive side, you get to see the logs and fix the errors.

If you get stuck or face a new challenge, I would love to know about it. You can share that with me on my Twitter or Linkedin. I would really appreciate it if you could visit the website here and share some feedback or comment. I swear it does help you control your daily life better! Thank you for reaching the end! If you'd like to view the code, it's openly hosted here on GitHub.