This tutorial is out of date and no longer maintained.
Note: This is Part Three of a three-part series. Build a CRUD Web App With Python and Flask - Part One Build a CRUD Web App With Python and Flask - Part Two Build a CRUD Web App With Python and Flask - Part Three
This is the last part of a three-part tutorial to build an employee management web app, named Project Dream Team. In Part Two of the tutorial, we built out the CRUD functionality of the app.
We created forms, views, and templates to list, add, edit and delete departments and roles. By the end of Part Two, we could assign (and re-assign) departments and roles to employees.
In Part Three, we will cover:
Web applications make use of HTTP errors to let users know that something has gone wrong. Default error pages are usually quite plain, so we will create our own custom ones for the following common HTTP errors:
http://127.0.0.1:5000/nothinghere
.We’ll start by writing the views for the custom error pages. In your app/__init__.py
file, add the following code:
We make use of Flask’s @app.errorhandler
decorator to define the error page views, where we pass in the status code as a parameter.
Next, we’ll create the template files. Create an app/templates/errors
directory, and in it, create 403.html
, 404.html
, and 500.html
.
All the templates give a brief description of the error and a button that links to the homepage.
Run the app and log in as a non-admin user, then attempt to access http://127.0.0.1:5000/admin/departments
. You should get the following page:
Now attempt to access this non-existent page: http://127.0.0.1:5000/nothinghere
. You should see:
To view the internal server error page, we’ll create a temporary route where we’ll use Flask’s abort()
function to raise a 500 error. In the app/__init__.py
file, add the following:
Go to http://127.0.0.1:5000/500
; you should see the following page:
Now you can remove the temporary route we just created for the internal server error.
Now, let’s write some tests for the app. The importance of testing software can’t be overstated. Tests help ensure that your app is working as expected, without the need for you to manually test all of your app’s functionality.
We’ll begin by creating a test database, and give the database user we created in Part One all privileges on it:
mysql> CREATE DATABASE dreamteam_test;
Query OK, 1 row affected (0.00 sec)
mysql> GRANT ALL PRIVILEGES ON dreamteam_test . * TO 'dt_admin'@'localhost';
Query OK, 0 rows affected (0.00 sec)
Now we need to edit the config.py
file to add configurations for testing. Delete the current contents and replace them with the following code:
We have put DEBUG = True
in the base class, Config
so that it is the default setting. We override this in the ProductionConfig
class. In the TestingConfig
class, we set the TESTING
configuration variable to True
.
We will be writing unit tests. Unit tests are written to test small, individual, and fairly isolated units of code, such as functions. We will make use of Flask-Testing, an extension that provides unit testing utilities for Flask.
Next, create a tests.py
file in the root directory of your app. In it, add the following code:
In the base class above, TestBase
, we have a create_app
method, where we pass in the configurations for testing.
We also have two other methods: setUp
and tearDown
. The setUp
method will be called automatically before every test we run. In it, we create two test users, one admin and one non-admin, and save them to the database. The tearDown
method will be called automatically after every test. In it, we remove the database session and drop all database tables.
To run the tests, we will run the tests.py
file:
Output----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
The output above lets us know that our test setup is OK. Now let’s write some tests.
We’ve added three classes: TestModels
, TestViews
and TestErrorPages
.
The first class has methods to test that each of the models in the app is working as expected. This is done by querying the database to check that the correct number of records exists in each table.
The second class has methods that test the views in the app to ensure the expected status code is returned. For non-restricted views, such as the homepage and the login page, the 200 OK
code should be returned; this means that everything is OK and the request has succeeded. For restricted views that require authenticated access, a 302 Found
code is returned. This means that the page is redirected to an existing resource, in this case, the login page. We test both that the 302 Found
code is returned and that the page redirects to the login page.
The third class has methods to ensure that the error pages we created earlier are shown when the respective error occurs.
Note that each test method begins with test
. This is deliberate, because unittest
, the Python unit testing framework, uses the test
prefix to automatically identify test methods. Also note that we have not written tests for the front-end to ensure users can register and login, and to ensure administrators can create departments and roles and assign them to employees. This can be done using a tool like Selenium Webdriver; however, this is outside the scope of this tutorial.
Run the tests again:
Output..............
----------------------------------------------------------------------
Ran 14 tests in 2.313s
OK
Success! The tests are passing.
Now for the final part of the tutorial: deployment. So far, we’ve been running the app locally. In this stage, we will publish the application on the internet so that other people can use it. We will use PythonAnywhere, a Platform as a Service (PaaS) that is easy to set up, secure, and scalable, not to mention free for basic accounts!
Create a free PythonAnywhere account here if you don’t already have one. Be sure to select your username carefully since the app will be accessible at your-username.pythonanywhere.com
.
Once you’ve signed up, your-username.pythonanywhere.com
should show this page:
We will use git to upload the app to PythonAnywhere. If you’ve been pushing your code to cloud repository management systems like Bitbucket, GitLab or GitHub, that’s great! If not, now’s the time to do it. Remember that we won’t be pushing the instance
directory, so be sure to include it in your .gitignore
file, like so:
*.pyc
instance/
Also, ensure that your requirements.txt
file is up to date using the pip freeze
command before pushing your code:
Now, log in to your PythonAnywhere account. In your dashboard, there’s a Consoles
tab; use it to start a new Bash console.
In the PythonAnywhere Bash console, clone your repository.
Next, we will create a virtualenv, then install the dependencies from the requirements.txt
file. Because PythonAnywhere installs virtualenvwrapper for all users by default, we can use its commands:
We’ve created a virtualenv called dream-team
. The virtualenv is automatically activated. We then entered the project directory and installed the dependencies.
Now, in the Web tab on your dashboard, create a new web app.
Select the Manual Configuration option (not the Flask option), and choose Python 2.7 as your Python version. Once the web app is created, its configurations will be loaded. Scroll down to the Virtualenv section, and enter the name of the virtualenv you just created:
Next, we will set up the MySQL production database. In the Databases tab of your PythonAnywhere dashboard, set a new password and then initialize a MySQL server:
The password above will be your database user password. Next, create a new database if you wish. PythonAnywhere already has a default database that you can use.
By default, the database user is your username and has all privileges granted on any databases created. Now, we need to migrate the database and populate it with the tables. In a Bash console on PythonAnywhere, we will run the flask db upgrade
command, since we already have the migrations directory that we created locally. Before running the commands, ensure you are in your virtualenv as well as in the project directory.
When setting the SQLALCHEMY_DATABASE_URI
environment variable, remember to replace your-username
, your-password
, your-host-address
and your-database-name
with their correct values. The username, host address and database name can be found in the MySQL settings in the Databases tab on your dashboard. For example, using the information below, my database URI is: mysql://projectdreamteam:password@projectdreamteam.mysql.pythonanywhere-services.com/projectdreamteam$dreamteam_db
Now we will edit the WSGI file, which PythonAnywhere uses to serve the app. Remember that we are not pushing the instance
directory to version control. We, therefore, need to configure the environment variables for production, which we will do in the WSGI file.
In the Code section of the Web tab on your dashboard, click on the link to the WSGI configuration file.
Delete all the current contents of the file, and replace them with the following:
In the file above, we tell PythonAnywhere to get the variable app
from the run.py
file and serve it as the application. We also set the FLASK_CONFIG
, SECRET_KEY
, and SQLALCHEMY_DATABASE_URI
environment variables. Feel free to alter the secret key. Note that the path
variable should contain your username and project directory name, so be sure to replace it with the correct values. The same applies to the database URI environment variable.
We also need to edit our local app/__init__.py
file to prevent it from loading the instance/config.py
file in production, as well as to load the configuration variables we’ve set:
Push your changes to version control, and pull them on the PythonAnywhere Bash console:
Now let’s try loading the app on PythonAnywhere. First, we need to reload the app on the Web tab in the dashboard:
Now go to your app URL:
Great, it works! Try registering a new user and logging in. This should work just as it did locally.
We will now create an admin user the same way we did locally. Open the Bash console, and run the following commands:
Output>>> from app.models import Employee
>>> from app import db
>>> admin = Employee(email="admin@admin.com",username="admin",password="admin2016",is_admin=True)
>>> db.session.add(admin)
>>> db.session.commit()
Now you can log in as an admin user and add departments and roles, and assign them to employees.
Congratulations on successfully deploying your first Flask CRUD web app! From setting up a MySQL database to creating models, blueprints (with forms and views), templates, custom error pages, tests, and finally deploying the app on PythonAnywhere, you now have a strong foundation in web development with Flask. I hope this has been as fun and educational for you as it has for me! I’m looking forward to hearing about your experiences in the comments below.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!