--- title: Build a CRUD Web App With Python and Flask - Part Three source: https://www.digitalocean.com/community/tutorials/build-a-crud-web-app-with-python-and-flask-part-three --- This tutorial is out of date and no longer maintained. ### [Introduction](#introduction) This is the last part of a three-part tutorial to build an employee management web app, named Project Dream Team. In [Part Two](https://www.digitalocean.com/community/tutorials/build-a-crud-web-app-with-python-and-flask-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: 1. Custom error pages 2. Unit tests 3. Deployment on PythonAnywhere ## [Custom Error Pages](#custom-error-pages) 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: 1. 403 Forbidden: this occurs when a user is logged in (authenticated) but does not have sufficient permissions to access the resource. This is the error we have been throwing when non-admins attempt to access an admin view. 2. 404 Not Found: this occurs when a user attempts to access a non-existent resource such as an invalid URL, e.g `http://127.0.0.1:5000/nothinghere`. 3. 500 Internal Server Error: this is a general error thrown when a more specific error cannot be determined. It means that for some reason, the server cannot process the request. We’ll start by writing the views for the custom error pages. In your `app/__init__.py` file, add the following code: app/\_\_init\_\_.py 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`. app/templates/errors/403.html app/templates/errors/404.html app/templates/errors/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: app/\_\_init\_\_.py 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. ## [Tests](#tests) 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](https://www.digitalocean.com/community/tutorials/build-a-crud-web-app-with-python-and-flask-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: config.py 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](https://pythonhosted.org/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: tests.py 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. tests.py 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](http://www.seleniumhq.org/projects/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. ## [Deploy!](#deploy) 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](https://www.pythonanywhere.com/), a Platform as a Service (PaaS) that is easy to set up, secure, and scalable, not to mention free for basic accounts! ### [PythonAnywhere Set-Up](#pythonanywhere-set-up) Create a free PythonAnywhere account [here](https://www.pythonanywhere.com/registration/register/beginner/) 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](https://bitbucket.org/), [GitLab](https://about.gitlab.com/) or [GitHub](https://www.digitalocean.com/community/tutorials/github.com/), 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: .gitignore ``` *.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](https://virtualenvwrapper.readthedocs.io/en/latest/) 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: ### [Database Configuration](#database-configuration) 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==` ![](https://assets.digitalocean.com/articles/build-a-crud-web-app-with-python-and-flask-part-three/3waokLcVQrSXbO36a6MF_database.jpg) ### [WSGI File](#wsgi-file) 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: app/\_\_init\_\_.py 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. ### [Admin User](#admin-user) 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. ![](https://assets.digitalocean.com/articles/build-a-crud-web-app-with-python-and-flask-part-three/Gxv6N95WSQexztQ9OeSe_add%20dept.jpg) ![](https://assets.digitalocean.com/articles/build-a-crud-web-app-with-python-and-flask-part-three/Si288uqbT3yB72K44wMD_add%20roles.jpg) ![](https://assets.digitalocean.com/articles/build-a-crud-web-app-with-python-and-flask-part-three/acb1icFxTEK9iV5qwVl0_assign%20roles%20employees.jpg) ## [Conclusion](#conclusion) 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.