Menu Close

Objectives

  • Add a login page to your inclass site (login.html)
  • Create a navigation bar for your site
  • Create a database with a table called ‘user’
  • Add a dashboard page to your site and redirect when logged in (dashboard.html)
  • Change the navigation if you are logged in
  • Create a log out route

NOTE: You need to make sure you install MySQLdb at some point. You will get an error message that says you need it. If you are on a Mac you need to do the following to get it to work (this is from the Mac terminal not PyCharm). The mysqldb is a BIG install and may take some time. This may be different for Windows users. You may just need to install mysqldb.

brew install mysqldb

Then you need to go back to the PyCharm virtual environment and install the following package (pymysql), you can do it either way:

pip install pymysql

You will add some more code about this in step 2.


Step 1. Let’s start by creating two new pages in your templates folder. These will extend off the base.html file, just like the index.html file. So copying the code from the index file is a great idea. You should also make sure there is a css and js folder in your static folder. Inside the css folder create a styles.css and inside the js folder create a scripts.js file.

  • templates > login.html
  • templates > dashboard.html
  • static > css > styles.css
  • static > js > scripts.js

We need to create several more .py files inside the app folder.

  • app > forms.py
  • models.py
  • db_connect.py

We also need to create a new file inside the main folder by the app.py file. Call it config.py and add the following code. You can put any text into your secret key.

  • project folder > config.py
class Config(object):
	SECRET_KEY = "hello-this-is-the-main-key"

Step 2. (__INIT__.PY) We need to make some changes to the __init__.py file.

First we are going to be using a couple of “managers” for the database and users. So lets add in two libraries for the site. Put these up at the top around line 3 and 4.

from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

Notice when you add these lines that they are underlined in red. That means you need to add them to the project. You should search in the project settings for the libraries. I used flask-sqlalchemy and flask-login as my search terms. Make sure you pip freeze > requirements.txt when you are done. This will tell the heroku server that they need to use both of these libraries as well.

Towards the bottom of the page, but before the “if name” statement, add two more lines of code.

db = SQLAlchemny(app)
login = LoginManager(app

Another code change for the __init__.py file is to include the new models and routes. Put this at the bottom also, but above the if name statement too.

from app import routes, models

Let’s make a small change to the app.py file. Let’s add ,db to the end of the first line, and a new line to import our new user model so we can use it everywhere. (Note the capital U in user, this is because it is a class.)

from app import app, db
from app.models import User

The __init__.py file needs access to the secret key we created in step 1. Up at the very top, maybe around line 2, add:

from config import Config

Then we can use the secret key. Below the app = Flask line add the following three lines.

app.config.from_object(Config)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

You need to add two more lines of code to get your MySQLdb working (add this up at the top – line 6 – 7).

import pymysql
pymysql.install_as_MySQLdb()

Step 3. Create your new database. I have emailed your credentials. You will need to download the MySQL Workbench. I created a database for each of you. It is a MySQL database. Create a new table called ‘user’, keep it all lowercase. It should have three fields (id, username, and password). The id field should be AI and PK. You need to use ‘id’ not userid, in this instance where we are using the LoginManager library it requires it. Be sure to add a tuple (or record) admin and pass should be fine for the username and password.

We need to create our connection for SQLAlchemy to connect into the database. We do this in the __init__.py file. Add the following line of code (with your credentials).

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://um6ztuqgbxjrz:emvel9mqd0nd@bryanmarshall.com/dbtep6pngqg4oe'

After the // it goes user:pass@bryanmarshall.com/database.


Step 4. (MODELS.PY) Let’s make our model next. This model is going to match EXACTLY to our database table. Open the models.py file and add the following code.

from app import db
from flask_login import UserMixin
from app import login

@login.user_loader
def load_user(id):
    return User.query.get(int(id))

##### USER MODEL #####
# The user_id needs to be named id for the UserMixin to work correctly
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    password = db.Column(db.String(128))

    def get_id(self):
        return (self.id)

Step 5. (FORMS.PY) Now we add our form in the forms.py so we can use them in our routes. Open the forms.py file and add the following code. You will need to add Flask-WTF to your libraries. Again, make sure you freeze your requirements.

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired


class LoginForm(FlaskForm):
	username = StringField('Username', validators=[DataRequired()])
	password = PasswordField('Password', validators=[DataRequired()])

	submit = SubmitField('Sign In')

Step 6. (ROUTE.PY) We are finally going to add some routes. This is the controller or brain of our application. Open the routes.py file and let’s start adding some code. First let’s add some code to use our installed libraries. Add this to the top of the file (lines 2 – 5).

from app import app, db
from app.forms import LoginForm
from app.models import User
from flask_login import login_user, logout_user, current_user, login_required, LoginManager

Now we want to add some user management. Notice the @ decorator in front of the login_manager.

##### USER MANAGEMENT ##### FOUND: https://www.youtube.com/watch?v=2dEM-s3mRLE
login_manager = LoginManager()
login_manager.init_app(app)

# This method is used to as part of the flask_login code.
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

We want to create a new route when a person tries to login. It is going to use the Get and Post methods since it is a form submission.

@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    form = LoginForm()

    # set form data to variables
    form_username = form.username.data
    form_password = form.password.data

    if form.validate_on_submit():
        user = User.query.filter_by(username=form_username).first()
        
        if user is not None:
            password = user.password

        if user is None or password != form_password:
            flash('Invalid username or passsword')
            return redirect(url_for('login'))
        login_user(user)
        session.permanent = True
        session['user_id'] = user.id
        print(session['user_id'])
        return redirect(url_for('dashboard'))
    return render_template('login.html',  title='Sign In', form=form)

Notice how we get red lines for flash, url_for, session, and redirect. We need to add these libraries to the top line for flask.

from flask import render_template, redirect, url_for, flash, session

We may want to log out so at the very bottom of routes we add the following code:

##### LOGIN STUFF #####
@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('index'))

# This route redirects unauthrozied users to the login page.
@login_manager.unauthorized_handler
def unauthorized():
    # do stuff
    return redirect(url_for('login'))

Step 7. We still need to make changes to our webpages (index.html, dashboard.html, login.html, and base.html). Here is the code I used for the block body of the three websites. You will still need to adjust the titles.

dashboard.html (block body)

    <!-- Header-->
        <header class="py-4">
            <div class="container px-lg-5">
                <div class="p-4 p-lg-1 bg-light rounded-3 text-center">
                    <div class="m-4 m-lg-5">
                        <h1 class="display-5 fw-bold">Dashboard</h1>
                        <p class="fs-4">This is the dashboard page. You are now logged in. </p>

                    </div>
                </div>
            </div>
        </header>

index.html (block body)

<!-- Header-->
        <header class="py-4">
            <div class="container px-lg-5">
                <div class="p-4 p-lg-1 bg-light rounded-3 text-center">
                    <div class="m-4 m-lg-5">
                        <h1 class="display-5 fw-bold">This is the inclass project. </h1>

                    </div>
                </div>
            </div>
        </header>

login.html (block body) – notice I added the flash message code here using “with”.

<!-- Header-->
        <header class="py-4">
            <div class="container px-lg-5">
                <div class="p-4 p-lg-1 bg-light rounded-3 text-center">
                    <div class="m-4 m-lg-5">
                        <h1 class="display-5 fw-bold">Login Page</h1>

                        {% with messages = get_flashed_messages() %}
                            {% if messages %}

                                {% for message in messages %}
                                    <div class="alert alert-danger alert-dismissible fade show" role="alert">
                                        {{ message }}
                                        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>

                                    </div>
                                {% endfor %}
                            {% endif %}
                        {% endwith %}

                        <form action="" method="POST" novalidate>
                            {{ form.hidden_tag() }}
                        <p>
                            {{ form.username.label }}: admin<br>
                            {{ form.username(size=32) }}
                            {% for error in form.username.errors %}
                            <span style="color: red;">[{{ error }}] </span>
                            {% endfor %}
                        </p>
                        <p>
                            {{ form.password.label }}: pass<br>
                            {{ form.password(size=32) }}
                            {% for error in form.password.errors %}
                            <span style="color: red;">[{{ error }}] </span>
                            {% endfor %}
                        </p>
                        <p> {{ form.submit() }} </p>
                        </form>
                    </div>
                </div>
            </div>
        </header>

base.html (head and navigation changes) – notice we use if the person is logged in use this navigation, otherwise use this navigation.

This is the head code – code at the top of the page, not to be confused with the header. Notice the call to the css styles. You can grab the css from my site (https://sp-2023-inclass-marshall.herokuapp.com/). You can also get a lot of other code from that site, like the js file. Notice how I use the url_for to call the static file, so no matter where this base file is being used from it still works.

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <meta name="description" content="" />
    <meta name="author" content="" />
    <!-- Favicon-->
    <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='assets/favicon.ico') }}" />
    <!-- Bootstrap icons-->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet" />
    <!-- Core theme CSS (includes Bootstrap)-->
    <!-- CSS only -->
    <link href="{{ url_for('static', filename='css/styles.css') }}" rel="stylesheet" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
    {% block head %} {% endblock %}
</head>

Here is the body code for the base.html file.

<body>
    <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container px-lg-5">
                <a class="navbar-brand" href="{{ url_for('index') }}">Inclass Project</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        {% if current_user.is_anonymous %}
                        <li class="nav-item"><a class="nav-link {% block nav_home %}{% endblock %}" href="{{ url_for('index') }}">Home</a></li>
                        <li class="nav-item"><a class="nav-link {% block nav_login %}{% endblock %}" href="{{ url_for('login') }}">Login</a></li>
                        {% else %}
                        <li class="nav-item"><a class="nav-link {% block nav_logout %}{% endblock %}" href="{{ url_for('logout') }}">Logout</a></li>


                        {% endif %}

                    </ul>
                    <hr>

                </div>
            </div>
        </nav>
    {% block body %} {% endblock %}
</body>

Turn In

To turn in, upload your project to your GitHub repository and bring in your latest commit to Render. Turn the link in to your Render site and the GitHub repository. It should still be shared with bamarshall06.