• Flask + Angular Full-Stack Tutorial Part 5 - Forms (5-25-16)

    Contents:

    1. Intro
    2. Writing to Database
    3. Installing WTForms
    4. Using WTForms in Routes
    5. Generating a Secret Key
    6. Submitting the Form
    7. Sanitizing Input
    8. Flashing Messages and Redirects
    9. Hashing Passwords
    10. Pgcrypto
    11. Wrapping Up

    Intro

    In Part 4, we learned how to use PostgreSQL to create a database and access it via PSQL in the terminal and select data using Pyscopg2 in the server code. In this section of the tutorial, we will learn how to insert data into the database using forms and more postgresql code.

    Writing to Database

    Now that we know how to read from the database, we’ll learn how to write data to the database. Writing data is a little more involved code-wise than reading data, because: 1) we have to make sure the data input does not compromise our app’s security and 2) we have to make sure that if our app is comporomised, super important information (like passwords and emails) are not able to be stolen.

    Installing WTForms

    First, let’s install a Flask extension called Flask-WTForms that will help us create forms that automatically handle input checking (like throwing an error if a new user does not type a password and tries to submit the signup form). Using WTForms will save us a lot of time because it provides a lot of built-in functionality related to form validation, meaning we don’t have to code those things from scratch. With the pet-app virtualenv activated, install the Flask-WTF package by typing in the terminal:

    
      pip install Flask-WTF
      

    Now we can start creating a form. WTForms work differently from normal HTML forms in that WTForms are defined first as a form class in python, passed as a parameter to the route that will use the form, and finally rendered by Jinja in the html. In exchange for the added complexity of WTForms (vs using normal html forms), our users will gain automatic feedback generation from the forms they submit based on the options we set in our form class.

    We will start by adding imports of Flask-WTF dependencies to our server.py file:

    
      from flask_wtf import Form
      from wtforms import StringField, BooleanField, validators
      from wtforms.validators import DataRequired
      

    These lines will import the Form base class that we will use to define our own forms, classes to represent text and checkbox form fields (inputs), and validation classes. Next, we will write a class for our registration form. In server.py, before the code for the routes add this code:

    
      class SignUpForm(Form):
          name = StringField('name', [validators.Length(min=4, max=25)])
          password = StringField('password', [validators.Length(min=8, max=35)])
          acceptRules = BooleanField('I accept the site ', [validators.InputRequired()])
      

    This code creates a new class called SignUpForm, which is a derived class based on the Form class we imported (it is passed as the parameter to our new class). The next lines defined what fields we want to appear in our form. The line that defines “name” creates a StringField, which corresponds to a regular html text input field, with a label of “name” as the first argument and a validators argument that will enforce a minimum character length of 4 and a maximum of 25. The password and acceptRules attributes are similar, except acceptRules is defined as a BooleanField, which corresponds to a regular html checkbox field, and has an input required validator attached. The StringField and BooleanField objects will render their corresponding html components and the validators will prevent the form from being submitted if the user has not satisfied their requirements. For example, if a user types their name but does not enter a password or check the ‘I accept the site (terms and conditions)’ box, then WTForms will generate error statements indicating that the user needs to fix these problems before the form can be submitted.

    Using WTForms in Routes

    Since we have our registration form defined as a class, we can now use it in a registration route. We currently do not have a route for registering new users, so let’s make one that is attached to the URL ‘/signup’:

    
        @app.route('/signup')
        def signupRoute():
            form = SignUpForm()
            return render_template("signup.html", form=form)
      

    This code simply defines a route that will take our users to signup.html, where they will be able to use our form we are passing as the second parameter in render_template. Next, let’s create signup.html, saving it in the templates directory and populating it with the following code:

    
        {% extends "layout.html" %}
        {% block content %}
            <div class="signin-div">
                <h2>New user sign up:</h2>
                <br>
                <form method="post" action="/signup-submit">
                    {{ form.hidden_tag() }}
                    
                    <div class="input-group">
                      <span class="input-group-addon">{{form.name.label}}:</span>
                      {{form.name(placeholder='username', type='text', class='form-control')}}
                    </div>
                    <br>
                    <div class="input-group">
                      <span class="input-group-addon">{{form.password.label}}:</span>
                      {{form.password(placeholder='password', type='password', class='form-control')}}
                    </div>
                    <br>
                    <div class="input-group">
                        <div>{{form.acceptRules.label}} <b>terms of service:</b> {{form.acceptRules}}</div>
                    </div>
                    <br>
                    <input type="submit" class="btn btn-default" value="Sign up!">
                </form>
            </div>
        {% endblock %}
      

    There is a lot going on here, so we’ll break it down step by step. First, we’ve already seen “{% extends “layout.html” %}” and the Jinja tags that define our html as being the block “content”. At the line “<form method=”post” action=”/signup-submit”>”, we are defining an html form with an HTTP method of POST and a submit action of activating the ‘/signup-submit’ route. What this means is that our form data is going to be submitted using an HTTP POST method, which will send the information to our server code, and that when the form is submitted the server will execute the code inside the ‘/signup-submit’ route (which we will create shortly).

    Inside the form, the {{ form.hidden_tag() }} line hides certain fields that WTForms automatically generates to enable things like CSRF protection. This line does not impact any of the fields we defined in our SignUpForm() python class. Next, the “<div class=”input-group”>” line creates a div with a bootstrap class that styles it as an input-group. Inside this div, we have two things: Jinja expressions that will render the label of our form’s name field and the name field itself. Notice that if you compare this code to the SignUpForm() code in the server.py file, we are using the form’s field names exactly as the variables in the SignUpForm class appear. So “{form.name.label}}” means use the variable called “name” from the “form” we passed to this template and get its “label”. When we want to render the actual field itself, we just put {{form.name}}; notice that you can pass attributes of normal html forms (like placeholder and type attributes) as parameters to the form field. The password and acceptRules fields are being rendered in the same process as the name field.

    Generating a Secret Key

    Before we can even display this form, we need to define a configuration variable for our app called “SECRET_KEY” in our server.py file. SECRET_KEY” is required for using sessions in Flask and also for enabling CSRF protection in WTForms. Since the key should ideally be extremely difficult to guess (it is a “secret” key afterall), we are going to generate a Unique User ID (UUID) in python’s interactive mode which we will copy and paste into our server code. UUIDs are medium-length strings of random numbers and letters joined with hyphens, making them a good choice for our secret key. To enter python interactive mode, in the terminal type:

    
      python
      

    Next, we need to import the uuid module by typing:

    
      import uuid
      

    Then we can print a generated UUID with this command:

    
      print(uuid.uuid4())
      

    “uuid4()” is one of several different methods that the uuid module provides for generating uuids. Copy the resulting UUID and exit out of python interactive mode:

    
      exit()
      

    In server.py, place the following directly beneath where we first define our app:

    
      app.config['SECRET_KEY'] = 'your secret key'
      

    Replace ‘your secret key’ with the UUID you copied from the python interactive mode. Since ‘SECRET_KEY’ is a configuration variable, we are setting it as a key-value pair in our app’s config attribute. The one last thing we should do before we view our form running is create a new menu option in layout.html that directs users to the signup page. Inside the ul tag that contains our navbar menu options, add:

    
      <li><a href="signup"><span class="fa fa-plus"></span> Sign Up</a></li>
      

    Now we are finally ready to view our form! Start the server and click on the Sign Up button. The form should appear similar to this:

    If you get an error page that says something like “psycopg2.OperationalError: could not connect to server: Connection refused”, this means that the postgresql service may be stopped, so start the postgresql service from the terminal with this command and try reloading the page:

    
      sudo service postgresql start
      

    Submitting the Form

    We saw that in the form of our signup.html file that there was an action defined as the route ‘/signup-submit’. This will be the route that handles the information being submitted from the form, and will insert that information into the database. This route will also notify the user if the username being submitted already exists, and if so it will reject the information being submitted and send an error message to be displayed in the form template. Before we can write our new route, we need to import some more modules that the signup function will utilize. In the imports section of server.py, add the following dependencies:

    
      from flask import request, redirect, url_for, flash
      

    These modules enable us to check which HTTP method was used with form submissions (request), redirect the user to different routes, get the url of a specified route function (url_for), and send messages to Jinja templates with flash. Next, in the routes section of server.py, add the following route:

    
        @app.route('/signup-submit', methods=['POST'])
        def signupSubmit():
            form = SignUpForm()
            if request.method == 'POST' and form.validate_on_submit():
                conn = connectToDb()
                cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
                data = (form.name.data,)
                cur.execute("SELECT user_name FROM users WHERE user_name = %s", data)
                if cur.fetchone():
                    print (form.name.data + ' failed to sign up')
                    flash('The username ' + form.name.data + ' already exists!', 'alert-danger')
                    return render_template("signup.html", form=form)
                else:
                    data = (form.name.data, form.password.data,)
                    cur.execute("INSERT INTO users (user_name, user_password) VALUES (%s, %s)", data)
                    conn.commit()
                    print (form.name.data + ' signed up successfully')
                    flash('You were successfully signed up as ' + str(form.name.data) + '!', 'alert-success')
                    return redirect(url_for('indexRoute'))
            else:
                flash('Error:')
                print(form.errors)
                print(type(form.errors))
                for key, value in form.errors.items():
                    errorString = str(key) + ": "
                    for message in value:
                        errorString += str(message) + '\n'
                    flash(errorString, 'alert-danger')
            return render_template("signup.html", form=form)
      

    A lot happens in this route. First, in the “@app.route(…)” line we are specifying that this route is to be reached using the POST HTTP method, which corresponds to the method we set our form to be submitted with. Since this route is going to be validating the information passed from the sign up form, we need to include our WTForms SignUpForms class as an object, which we name “form” (this happens in the line “form = SignUpForm()”). The next line is a conditional that specifies to execute the first block of code if the method used was POST and if the data being submitted satisfies the validation constraints of our form object. If both cases are true, then we connect to the database using the connectToDb() function and create a cursor object from the connection object (“cur = conn.cursor(…)”).

    Sanitizing Input

    The next two lines comprise a method that we use to safely pass user input to postgresql commands. First, we specify the variables we want passed to the SQL query we will be making in the order that they will appear in the SQL query. We do this by using a variable called “data” and setting it equal to the series of variables we will put in the SQL query inside parentheses. The last variable being used inside the parentheses must have a comma afterwards, even if there is only one variable. Next, we use cur.execute(“SELECT … WHERE user_name = %s”, data) to perform an SQL query using the variables we listed in the “data” variable. Notice that “WHERE user_name = %s” uses ‘%s’ as a placeholder for a variable we listed in “data”. The reason we are using a placeholder where the actual variable should be located is to use a technique called data binding, which allows us to separate the SQL query from the input that users provide. Data binding allows the cursor object to safely “escape” all data being passed into the SQL query via the “data” variable, meaning that regardless of what is typed into the input field the user will not have the ability to create a malformed query string (which could lead to SQL Injection attacks). Regardless of the types of SQL queries being used, if it involves user input it MUST be sanitized in this manner. Not properly escaping user input when sending it to the server poses serious, expensive security risks.

    Flashing Messages and Redirects

    Continuing down the function code, “if cur.fetchone()” checks if the query we just performed returned at least one result. In this particular case, the query checks the database to see if the user_name submitted with the form already exists. If the user_name already exists, then we log a message to our console using print(), flash() a message to the user, and redirect the user back to the sign up route. flash() works by providing Jinja a message object which it will display once to the user; if the user refreshes the page or navigates to another route, the message will disappear (hence the name “flash”). The first parameter in flash() is the message itself, in which we are notifying the user that the name they tried to submit already exists. The second parameter is an optional category that can be assigned to the message, which is useful if you want different categories of messages (like success messages and error messages). In order to see messages, we need to explicitly check for them using Jinja expressions. In layout.html, modify the container div by adding the following code that will check for any flashed messages and display them:

    
      <div class="container">
        <div class="jumbotron">
          <h1><span class="fa fa-paw"></span> Pet App</h1>
          {% with messages = get_flashed_messages(with_categories=true) %}
              {% if messages %}
                  <div class="flash-div">
                  {% for category, message in messages %}
                      <div class="alert {{category}} alert-dismissible" role="alert">
                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                          <span aria-hidden="true">×</span>
                        </button>
                        {{message}}
                      </div>
                  {% endfor %}
                  </div>
              {% endif %}
          {% endwith %}
          {% block content %}{% endblock %}
        </div>
      </div>
      

    The code we added will display any flashed messages above where our block content will be rendered. “{% with messages = get_flashed_messages(with_categories=true) %}” checks if there are any flashed messages when the page is rendered and will take into account any categories passed with the messages. Next, if there are any messages (“{% if messages %}”), they will be looped through (“{% for category, message in messages %}”) and displayed. The code inside the “for” loop displays a bootstrap dismiss-able “alert” div that displays each message (“{{message}}”). Note that since we can pass the message category as a class inside the div tag, we took advantage of the bootstrap-specific classes that define whether an alert represents an error message (“alert-danger”) or success message (“alert-success”). Now when the page is loaded and a user tries to submit an empty form, the result should look like this:

    And when a user successfully signs up, they should see a success message:

    Now that our message flashing is working, let’s continue to examine the code in signupSubmit(). We left off at the flash(…) function where we are notifying the user that the user_name they submitted is already in the database. The next line, “return render_template(“signup.html”, form=form)”, will re-render the signup.html template saving any data the user already entered in addition to the flashed error message.

    If the user_name being submitted is not currently in the database, then we will execute another SQL query to insert the data into the database. We use data-binding like we did in the earlier SELECT statement to safely sanitize our input in the INSERT statement, passing the form name and the form password to the query inside the “data” variable. For INSERT statements to take effect, they need to be comitted to the database, which is accomplished by the “conn.commit()” function. Then, we log a message to the console, flash a success message to the user, and redirect them to the indexRoute. The redirect() function takes another route as a parameter. Inside the redirect() function, we are using “url_for(‘indexRoute’)” to grab the url for the indexRoute function (which is ‘/’).

    The ‘else’ branch of the first ‘if’ conditional (which checks if the form is being submitted with POST and is valid) flashes error messages to the user and redirects them to the signupRoute. Since there may be multiple error messages, we are constructing each message to be flashed by formatting the key (containing which field in the form has an error) and value (containing the corresponding error message) of the form.errors. Since the form object will generate form errors as a python dictionary, we use the “.items()” function to iterate through them. To view the structure of the form.errors dictionary, we log it to the console with a print() function. After the error messages are flashed, the user is displayed the error messages above the data they already entered in the form.

    Hashing Passwords

    So far, we have a pretty decent form-submission process coded in our server and template files, but there is a big security issue that we still have to deal with: passwords. Imagine that a malicious user somehow manages to gain access to our database, and they proceed to download all of the data from our users table. With the username/password combinations for each user compromised, our app would be screwed! But now imagine that instead of each user’s password, the attacker was only able to download strings like this: $2a$06$0qAH6pBxIARbA2ips8a4CuTpnxvzCGF8Gr5hnB5bC.OZKs2R2Lbti. That is what hashing passwords enables us to do: it provides a last line of defense against any attackers who manage to gain access to our database by obscuring encrypted columns.

    Pgcrypto

    To encrypt our user’s passwords, we will need to install and create an extension called pgcrypto in our database. To install the pgcrypto module, use “sudo apt-get install” in the terminal:

    
      sudo apt-get install postgresql-contrib-9.3
      

    This will install pgcrypto. Note that since the version of postgresql we are using is 9.3 (you can check which version you are using by the text that displays when you log into psql), we specified postgresql-contrib-9.3. Next, we can add the following line to the petapp.sql file after the CREATE TABLE commands and before the GRANT commands:

    
      CREATE EXTENSION pgcrypto
      

    Now, we will re-import our database by changing into the sql directory, logging into psql, and executing the import of our sql file at the psql command prompt:

    
      \i petapp.sql
      

    If psql responds with a message “CREATE EXTENSION” while it is importing the sql file, that means that pgcrypto was successfully enabled for the database. We can now perform encryption of passwords when writing to the database and decryption when reading from the database by modifying our current SQL queries. In the signupSubmit() function in server.py, locate the cur.execute(“INSERT…”) statement and modify it to include encryption code for the password:

    
      cur.execute("INSERT INTO users (user_name, user_password) VALUES (%s, crypt(%s, gen_salt('bf')))", data)
      

    The “crypt” function takes two parameters: the value to encrypt (%s, which is the second bound-variable in our data, password) and the encryption method. You can read here about the different encryption methods available, but ‘bf’ (short for ‘blowfish’), which is the generated salt (“gen_salt()”) we are choosing, provides very strong encryption. To decrypt encrypted passwords, we will use “crypt(%s, user_password)”, which compares the user_password provided to the encrypted password in the database. When we go over how to log-in users, we will implement the decryption phase.

    We can see the results of enabling password encryption by registering a new user then logging in to psql, changing into the petapp database, and selecting all users:

    
      petapp=# select * from users;
       user_id | user_name |                        user_password                         
      ---------+-----------+--------------------------------------------------------------
             1 | billybill | $2a$06$X1JCLWDilYLZiVUh/AuD9.QxEICUNDikVVlHPrSYleBTc5VFHA4JK
      (1 row)
      

    The password I actually typed for billybill was just “password”, but the encryption totally hides that fact. Now our app has three cyber-security precautions helping to protect us and our users: CSRF protection, SQL Injection prevention, and password hashing.

    Wrapping Up

    At this point, we can successfully and safely register new users! It takes a lot of effort to do that seemingly simple thing, but the effort we put in will pay dividends when our app’s security precautions thwart certain types of cyber attacks. Here is the complete code so far for our layout.html, server.py (with comments added), and petapp.sql files:

    
      <!doctype html>
      <html>
          <head>
              <title>Pet App</title>
              
              <script src="https://code.jquery.com/jquery-1.12.3.min.js" integrity="sha256-aaODHAgvwQW1bFOGXMeX+pC4PZIPsvn2h1sArYOhgXQ=" crossorigin="anonymous"></script>
              
              <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
              
              <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
              
              <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
         
             <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
          
          </head>
          <body>
    
              <nav class="navbar navbar-default navbar-static-top">
                <div class="container">
                  <div class="navbar-header">
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                      <span class="sr-only">Toggle navigation</span>
                      <span class="icon-bar"></span>
                      <span class="icon-bar"></span>
                      <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" href="/">Pet App</a>
                  </div>
                  <div id="navbar" class="navbar-collapse collapse">
                    <ul class="nav navbar-nav">
                      <li><a href="/"><span class="fa fa-home"></span> Home</a></li>
                      <li><a href="signup"><span class="fa fa-plus"></span> Sign Up</a></li>
                      <li><a href="users"><span class="fa fa-users"></span> Users</a></li>
                      <li><a href="pets"><span class="fa fa-paw"></span> Pets</a></li>
                    </ul>
                  </div>
                </div>
              </nav>
          
          
              <div class="container">
                <div class="jumbotron">
                  <h1><span class="fa fa-paw"></span> Pet App</h1>
                  {% with messages = get_flashed_messages(with_categories=true) %}
                      {% if messages %}
                          <div class="flash-div">
                          {% for category, message in messages %}
                              <div class="alert {{category}} alert-dismissible" role="alert">
                                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                                  <span aria-hidden="true">×</span>
                                </button>
                                {{message}}
                              </div>
                          {% endfor %}
                          </div>
                      {% endif %}
                  {% endwith %}
                  {% block content %}{% endblock %}
                </div>
              </div>
              
          </body>
      </html>
      
    
      # APP DEPENDENCY IMPORTS ----------------------------------------------------------------
      # import flask modules for our app, Jinja templates, flashing messages, and routes
      from flask import Flask, render_template
      from flask import request, redirect, url_for, flash
      #import psycopg2 modles for using postgresql
      import psycopg2
      import psycopg2.extras
      # import flask-WTF modules for form creation and validation
      from flask_wtf import Form
      from wtforms import StringField, BooleanField, validators
      from wtforms.validators import DataRequired
      # -----------------------------------------------------------------------------------------
    
      # APP CONFIGURATION -----------------------------------------------------------------------
      # create an instance of a Flask application named app
      app = Flask(__name__)
      # give our app a secret_key for enabling sessions and CSRF protection in WTForms
      app.config['SECRET_KEY'] = '75a9309e-0c32-11e6-9145-0242ac113b44'
      # -----------------------------------------------------------------------------------------
    
      # DATABASE --------------------------------------------------------------------------------
      # returns connection to the postgresql database specified using a particular role
      def connectToDb():
        connectionString = 'dbname=petapp user=petapp_admin password=woofwoof host=localhost'
        print (connectionString)
        return psycopg2.connect(connectionString)
      # ------------------------------------------------------------------------------------------
    
      # WTFORMS ----------------------------------------------------------------------------------
      # sign up form
      class SignUpForm(Form):
        name = StringField('name', [validators.Length(min=4, max=25)])
        password = StringField('password', [validators.Length(min=8, max=35)])
        acceptRules = BooleanField('I accept the site ', [validators.InputRequired()])
      # ------------------------------------------------------------------------------------------
    
      # APP ROUTES -------------------------------------------------------------------------------
      @app.route('/')
      def indexRoute():
        return render_template("index.html")
        
      @app.route('/signup')
      def signupRoute():
        form = SignUpForm()
        return render_template("signup.html", form=form)
        
      @app.route('/signup-submit', methods=['POST'])
      def signupSubmit():
        form = SignUpForm()
        # if the form was submitted via a POST HTTP request and is successfully validated:
        if request.method == 'POST' and form.validate_on_submit():
          conn = connectToDb()
          cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
          data = (form.name.data,)
          cur.execute("SELECT user_name FROM users WHERE user_name = %s", data)
          # if the user_name submitted already exists in the database, flash error messages
          # and re-render the sign up form with user input saved
          if cur.fetchone():
            print (form.name.data + ' failed to sign up')
            flash('The username ' + form.name.data + ' already exists!', 'alert-danger')
            return render_template("signup.html", form=form)
          # else, save the user information to the database, hashing the password,
          # flash a success message, and redirect to the indexRoute
          else:
            data = (form.name.data, form.password.data,)
            cur.execute("INSERT INTO users (user_name, user_password) VALUES (%s, crypt(%s, gen_salt('bf')))", data)
            conn.commit()
            print (form.name.data + ' signed up successfully')
            flash('You were successfully signed up as ' + str(form.name.data) + '!', 'alert-success')
            return redirect(url_for('indexRoute'))
        # else, flash error messages and re-render the sign up form with user input saved
        else:
          print(form.errors)
          print(type(form.errors))
          for key, value in form.errors.items():
            errorString = str(key) + ": "
            for message in value:
              errorString += str(message) + '\n'
            flash(errorString, 'alert-danger')
        return render_template("signup.html", form=form)
        
      @app.route('/users')
      def usersRoute():
        conn = connectToDb()
        cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
        cur.execute("SELECT * FROM users")
        results = cur.fetchall()
        print (results)
        return render_template("users.html", users=results)
        
      @app.route('/pets')
      def petsRoute():
        return render_template("pets.html")
        
      @app.route('/pets/<pet_id>')
      def petRoute(pet_id):
        return render_template("pet.html", pet_id=pet_id)
        
      # -----------------------------------------------------------------------------------------
    
      # RUN APP ---------------------------------------------------------------------------------
      if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8080, debug=True)
      
    
      DROP DATABASE IF EXISTS petapp;
      CREATE DATABASE petapp;
    
      \c petapp;
    
      DROP OWNED BY petapp_admin;
      DROP ROLE IF EXISTS petapp_admin;
      CREATE ROLE petapp_admin LOGIN PASSWORD 'woofwoof';
    
      CREATE TABLE users (
          user_id BIGSERIAL PRIMARY KEY,
          user_name VARCHAR(32) NOT NULL,
          user_password VARCHAR(64) NOT NULL
      );
    
      CREATE TABLE pets (
          pet_id BIGSERIAL PRIMARY KEY,
          pet_name VARCHAR(32) NOT NULL,
          pet_breed VARCHAR(32) NOT NULL
      );
    
      CREATE EXTENSION pgcrypto;
    
      GRANT SELECT, INSERT, UPDATE, DELETE on users, pets TO petapp_admin;
      GRANT SELECT, UPDATE ON users_user_id_seq, pets_pet_id_seq TO petapp_admin;
      

    In Part 6, we will learn how to sign in users using another Flask extension called Flask-login as well as further organize our app’s structure. Thanks for reading, hope this was helpful to some people!