vault backup: 2025-12-10 11:37:35

This commit is contained in:
2023-05-15 17:16:05 +02:00
committed by Thomas Peetz
parent 91bf72fc87
commit 73f2162ddf
6049 changed files with 513094 additions and 227748 deletions
@@ -0,0 +1,356 @@
---
title: Build a CRUD Web App With Python and Flask - Part One
source: https://www.digitalocean.com/community/tutorials/build-a-crud-web-app-with-python-and-flask-part-one
---
This tutorial is out of date and no longer maintained.
### [Introduction](#introduction)
In this three-part tutorial, well build a CRUD (Create, Read, Update, Delete) employee management web app using [Flask](http://flask.pocoo.org/), a microframework for Python. Ive named the app Project Dream Team, and it will have the following features:
1. Users will be able to register and login as employees
2. The administrator will be able to create, update, and delete departments and roles
3. The administrator will be able to assign employees to a department and assign them roles
4. The administrator will be able to view all employees and their details
Part One will cover:
1. Database setup
2. Models
3. Migration
4. Homepage
5. Authentication
Ready? Here we go!
## [Prerequisites](#prerequisites)
This tutorial builds on my introductory tutorial, [Getting Started With Flask](https://scotch.io/tutorials/getting-started-with-flask-a-python-microframework), picking up where it left off. It assumes you have, to begin with, the following dependencies installed:
1. [Python 2.7](https://www.python.org/download/releases/2.7/)
2. [Flask](http://flask.pocoo.org/)
3. [virtualenv](https://virtualenv.pypa.io/en/stable/) (and, optionally, [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/))
You should have a virtual environment set up and activated. You should also have the following file and directory structure:
```
├── dream-team
   ├── app
   │   ├── __init__.py
   │   ├── templates
   │   ├── models.py
  │   └── views.py
   ├── config.py
    ├── requirements.txt
   └── run.py
```
This project structure groups the similar components of the application together. The `dream-team` directory houses all the project files. The `app` directory is the application package and houses different but interlinked modules of the application. All templates are stored in the `templates` directory, all models are in the `models.py` file, and all routes are in the `views.py` file. The `run.py` file is the applications entry point, the `config.py` file contains the application configurations, and the `requirements.txt` file contains the software dependencies for the application.
If you dont have these set up, please visit the introductory tutorial and catch up!
## [Database Setup](#database-setup)
Flask has support for several relational database management systems, including [SQLite](https://sqlite.org/), [MySQL](https://www.mysql.com/), and [PostgreSQL](https://www.postgresql.org/). For this tutorial, we will be using MySQL. Its popular and therefore has a lot of support, in addition to being scalable, secure, and rich in features.
We will install the following (remember to activate your virtual environment):
1. [Flask-SQLAlchemy](http://flask-sqlalchemy.pocoo.org/2.1/): This will allow us to use [SQLAlchemy](http://www.sqlalchemy.org/), a useful tool for SQL use with Python. SQLAlchemy is an Object Relational Mapper (ORM), which means that it connects the objects of an application to tables in a relational database management system. These objects can be stored in the database and accessed without the need to write raw SQL. This is convenient because it simplifies queries that may have been complex if written in raw SQL. Additionally, it reduces the risk of [SQL injection attacks](http://www.w3schools.com/sql/sql_injection.asp) since we are not dealing with the input of raw SQL.
2. [MySQL-Python](https://pypi.python.org/pypi/MySQL-python/1.2.5): This is a Python interface to MySQL. It will help us connect the MySQL database to the app.
Well then create the MySQL database. Ensure you have MySQL installed and running, and then log in as the root user:
```
mysql> CREATE USER 'dt_admin'@'localhost' IDENTIFIED BY 'dt2016';
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE DATABASE dreamteam_db;
Query OK, 1 row affected (0.00 sec)
mysql> GRANT ALL PRIVILEGES ON dreamteam_db . * TO 'dt_admin'@'localhost';
Query OK, 0 rows affected (0.00 sec)
```
We have now created a new user `dt_admin` with the password `dt2016`, created a new database `dreamteam_db`, and granted the new user all database privileges.
Next, lets edit the `config.py`. Remove any existing code and add the following:
config.py
It is good practice to specify configurations for different environments. In the file above, we have specified configurations for development, which we will use while building the app and running it locally, as well as production, which we will use when the app is deployed.
Some useful configuration variables are:
1. `TESTING`: setting this to `True` activates the testing mode of Flask extensions. This allows us to use testing properties that could for instance have an increased runtime cost, such as unit test helpers. It should be set to `True` in the configurations for testing. It defaults to `False`.
2. `DEBUG`: setting this to `True` activates the debug mode on the app. This allows us to use the Flask debugger in case of an unhandled exception, and also automatically reloads the application when it is updated. It should however always be set to `False` in production. It defaults to `False`.
3. `SQLALCHEMY_ECHO`: setting this to `True` helps us with debugging by allowing SQLAlchemy to log errors.
You can find more Flask configuration variables [here](http://flask.pocoo.org/docs/0.11/config/) and SQLAlchemy configuration variables [here](http://flask-sqlalchemy.pocoo.org/2.1/config/).
Next, create an `instance` directory in the `dream-team` directory, and then create a `config.py` file inside it. We will put configuration variables here that will not be pushed to version control due to their sensitive nature. In this case, we put the secret key as well as the database URI which contains the database user password.
instance/config.py
Now, lets edit the `app/__init__.py` file. Remove any existing code and add the following:
app/\_\_init\_\_.py
Weve created a function, `create_app` that, given a configuration name, loads the correct configuration from the `config.py` file, as well as the configurations from the `instance/config.py` file. We have also created a `db` object which we will use to interact with the database.
Next, lets edit the `run.py` file:
run.py
We create the app by running the `create_app` function and passing in the configuration name. We get this from the OS environment variable `FLASK_CONFIG`. Because we are in development, we should set the environment variable to `development`.
Lets run the app to ensure everything is working as expected. First, delete the `app/views.py` file as well as the `app/templates` directory as we will not be needing them going forward. Next, add a temporary route to the `app/__init__.py` file as follows:
app/\_\_init\_\_.py
Make sure you set the `FLASK_CONFIG` and `FLASK_APP` environment variables before running the app:
```
* Serving Flask app "run"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```
<img width="748" height="109" src="../_resources/ZARmTHcvR5WA9FENq2Nn_Screen_Shot_380267898b014e1bb.png"/>
We can see the “Hello, World” string we set in the route. The app is working well so far.
## [Models](#models)
Now to work on the models. Remember that a model is a representation of a database table in code. Well need three models: `Employee`, `Department`, and `Role`.
But first, lets install [Flask-Login](https://flask-login.readthedocs.io/en/latest/), which will help us with user management and handle logging in, logging out, and user sessions. The `Employee` model will inherit from Flask-Logins `UserMixin` class which will make it easier for us to make use of its properties and methods.
To use Flask-Login, we need to create a LoginManager object and initialize it in the `app/__init__.py` file. First, remove the route we added earlier, and then add the following:
app/\_\_init\_\_.py
In addition to initializing the LoginManager object, weve also added a `login_view` and `login_message` to it. This way, if a user tries to access a page that they are not authorized to, it will redirect to the specified view and display the specified message. We havent created the `auth.login` view yet, but we will when we get to authentication.
Now add the following code to the `app/models.py` file:
app/models.py
In the `Employee` model, we make use of some of Werkzeugs handy security helper methods, `generate_password_hash`, which allows us to hash passwords, and `check_password_hash`, which allows us to ensure the hashed password matches the password. To enhance security, we have a `password` method that ensures that the password can never be accessed; instead, an error will be raised. We also have two foreign key fields, `department_id` and `role_id`, which refer to the ID of the department and the role assigned to the employee.
Note that we have an `is_admin` field which is set to `False` by default. We will override this when creating the admin user. Just after the `Employee` model, we have a `user_loader` callback, which Flask-Login uses to reload the user object from the user ID stored in the session.
The `Department` and `Role` models are quite similar. Both have `name` and `description` fields. Additionally, both have a one-to-many relationship with the `Employee` model (one department or role can have many employees). We define this in both models using the `employees` field. `backref` allows us to create a new property on the `Employee` model such that we can use `employee.department` or `employee.role` to get the department or role assigned to that employee. `lazy` defines how the data will be loaded from the database; in this case, it will be loaded dynamically, which is ideal for managing large collections.
## [Migration](#migration)
Migrations allow us to manage changes we make to the models and propagate these changes in the database. For example, if later on, we make a change to a field in one of the models, all we will need to do is create and apply a migration, and the database will reflect the change.
Well begin by installing [Flask-Migrate](https://flask-migrate.readthedocs.io/en/latest/), which will handle the database migrations using Alembic, a lightweight database migration tool. Alembic emits `ALTER` statements to a database thus implementing changes made to the models. It also auto-generates minimalistic migration scripts, which may be complex to write.
Well need to edit the `app/__init__.py` file:
app/\_\_init\_\_.py
We have created a `migrate` object which will allow us to run migrations using Flask-Migrate. We have also imported the models from the `app` package. Next, well run the following command to create a migration repository:
This creates a `migrations` directory in the `dream-team` directory:
```
└── migrations
├── README
├── alembic.ini
├── env.py
├── script.py.mako
└── versions
```
Next, we will create the first migration:
Finally, well apply the migration:
Weve successfully created tables based on the models we wrote! Lets check the MySQL database to confirm this:
```
mysql> use dreamteam_db;
mysql> show tables;
+------------------------+
| Tables_in_dreamteam_db |
+------------------------+
| alembic_version |
| departments |
| employees |
| roles |
+------------------------+
4 rows in set (0.00 sec)
```
## [Blueprints](#blueprints)
Blueprints are great for organizing a flask app into components, each with its own views and forms. I find that blueprints make for a cleaner and more organized project structure because each blueprint is a distinct component that addresses a specific functionality of the app. Each blueprint can even have its own custom URL prefix or subdomain. Blueprints are particularly convenient for large applications.
Were going to have three blueprints in this app:
1. Home - this will have the homepage and dashboard views
2. Admin - this will have all administrator (department and role) forms and views
3. Auth - this will have all authentication (registration and login) forms and views
Create the relevant files and directories so that your directory structure resembles this:
```
└── dream-team
├── app
│   ├── __init__.py
│   ├── admin
│   │   ├── __init__.py
│   │   ├── forms.py
│   │   └── views.py
│   ├── auth
│   │   ├── __init__.py
│   │   ├── forms.py
│   │   └── views.py
│   ├── home
│   │   ├── __init__.py
│   │   └── views.py
│   ├── models.py
│   ├── static
│   └── templates
├── config.py
├── instance
│   └── config.py
├── migrations
│   ├── README
│   ├── alembic.ini
│   ├── env.py
│   ├── script.py.mako
│   └── versions
│   └── a1a1d8b30202_.py
├── requirements.txt
└── run.py
```
I chose not to have `static` and `templates` directories for each blueprint because all the application templates will inherit from the same base template and use the same CSS file. Instead, the `templates` directory will have sub-directories for each blueprint so that blueprint templates can be grouped together.
In each blueprints `__init__.py` file, we need to create a Blueprint object and initialize it with a name. We also need to import the views.
app/admin/\_\_init\_\_.py
app/auth/\_\_init\_\_.py
app/home/\_\_init\_\_.py
Then, we can register the blueprints on the app in the `app/__init__.py` file, like so:
app/\_\_init\_\_.py
We have imported each blueprint object and registered it. For the `admin` blueprint, we have added a URL prefix, `/admin`. This means that all the views for this blueprint will be accessed in the browser with the URL prefix `admin`.
## [Home Blueprint](#home-blueprint)
Time to work on fleshing out the blueprints! Well start with the `home` blueprint, which will have the homepage as well as the dashboard.
app/home/views.py
Each view function has a decorator, `home.route`, which has a URL route as a parameter (remember that `home` is the name of the blueprint as specified in the `app/home/__init__.py` file). Each view handles requests to the specified URL.
The `homepage` view renders the home template, while the `dashboard` view renders the dashboard template. Note that the `dashboard` view has a `login_required` decorator, meaning that users must be logged in to access it.
Now to work on the base template, which all other templates will inherit from. Create a `base.html` file in the `app/templates` directory and add the following code:
app/templates/base.html
Note that we use `#` for the Register and Login links. We will update this when we are working on the `auth` blueprint.
Next, create a `home` directory inside the `app/templates` directory. The homepage template, `index.html`, will go inside it:
app/templates/home/index.html
Inside the `static` directory, add `css` and `img` directories. Add the following CSS file, `style.css`, to your `static/css` directory (note that you will need a background image, `intro-bg.jpg`, as well as a favicon in your `static/img` directory):
app/static/css/style.css
Run the app; you should be able to see the homepage now.
<img width="748" height="467" src="../_resources/1FIiIIkGR9eQ3PAJrNeu_Screen_Shot_76895f229b65404ea.png"/>
## [Auth Blueprint](#auth-blueprint)
For the `auth` blueprint, well begin by creating the registration and login forms. Well use [Flask-WTF](https://flask-wtf.readthedocs.io/en/stable/), which will allow us to create forms that are secure (thanks to CSRF protection and reCAPTCHA support).
Now to write the code for the forms:
app/auth/forms.py
Flask-WTF has a number of validators that make writing forms much easier. All the fields in the models have the `DataRequired` validator, which means that users will be required to fill all of them in order to register or log in.
For the registration form, we require users to fill in their email address, username, first name, and last name. Users will also be required to enter their password twice. We use the `Email` validator to ensure valid email formats are used (e.g., `example@example.com`). We use the `EqualTo` validator to confirm that the `password` and `confirm_password` fields in the `RegistrationForm` match. We also create methods (`validate_email` and `validate_username`) to ensure that the email and username entered have not been used before.
The `submit` field in both forms will be represented as a button that users will be able to click to register and login respectively.
With the forms in place, we can write the views:
app/auth/views.py
Just like in the `home` blueprint, each view here handles requests to the specified URL. The `register` view creates an instance of the `Employee` model class using the registration form data to populate the fields and then adds it to the database. This essentially registers a new employee.
The `login` view queries the database to check whether an employee exists with an email address that matches the email provided in the login form data. It then uses the `verify_password` method to check that the password in the database for the employee matches the password provided in the login form data. If both of these are true, it proceeds to log the user in using the `login_user` method provided by Flask-Login.
The `logout` view has the `login_required` decorator, which means that a user must be logged in to access it. It calls the `logout_user` method provided by Flask-Login to log the user out.
Note the use of `flash` method, which allows us to use Flasks [message flashing](http://flask.pocoo.org/docs/0.11/patterns/flashing/) feature. This allows us to communicate feedback to the user, such as informing them of successful registration or unsuccessful login.
Finally, lets work on the templates. First, well install [Flask-Bootstrap](https://pythonhosted.org/Flask-Bootstrap/index.html) so we can use its `wtf` and `utils` libraries. The `wtf` library will allow us to quickly generate forms in the templates based on the forms in the `forms.py` file. The `utils` library will allow us to display the flash messages we set earlier to give feedback to the user.
We need to edit the `app/__init__.py` file to use Flask-Bootstrap:
app/\_\_init\_\_.py
Weve made quite a number of edits to the `app/__init__.py` file. This is the final version of the file and how it should look at this point (note that I have re-arranged the imports and variables in alphabetical order):
app/\_\_init\_\_.py
We need two templates for the `auth` blueprint: `register.html` and `login.html`, which well create in an `auth` directory inside the `templates` directory.
app/templates/auth/register.html
app/templates/auth/login.html
The forms are loaded from the `app/auth/views.py` file, where we specified which template files to display for each view. Remember the Register and Login links in the base template? Lets update them now so we can access the pages from the menus:
Run the app again and click on the Register and Login menu links. You should see the templates loaded with the appropriate form.
Try to fill out the registration form; you should be able to register a new employee. After registration, you should be redirected to the login page, where you will see the flash message we configured in the `app/auth/views.py` file, inviting you to login.
<img width="748" height="407" src="../_resources/YNe42GM3REC5V89JEUQ4_Screen_Shot_834151213325415ab.png"/>
<img width="748" height="286" src="../_resources/MYG8y2mPRju5r4seFyMw_Screen_Shot_66e0f5d748f94ef09.png"/>
Logging in should be successful; however you should get a `Template Not Found` error after logging in, because the `dashboard.html` template has not been created yet. Lets do that now:
app/templates/home/dashboard.html
Refresh the page. Youll notice that the navigation menu still has the register and login links, even though we are already logged in. Well need to modify it to show a logout link when a user is already authenticated. We will also include a `Hi, username!` message in the nav bar:
app/templates/base.html
Note how we use `if-else` statements in the templates. Also, take note of the `current_user` proxy provided by Flask-Login, which allows us to check whether the user is authenticated and to get the users username.
<img width="748" height="468" src="../_resources/p5AiTJWHSHd9fleVKrxA_Screen_Shot_fc843f9c62304276b.png"/>
Logging out will take you back to the login page:
<img width="748" height="291" src="../_resources/fvZWaNdqRiGaWAr4cotK_Screen_Shot_b1f537d5d4f047069.png"/>
Attempting to access the dashboard page without logging in will redirect you to the login page and display the message we set in the `app/__init__.py` file:
<img width="748" height="327" src="../_resources/nbibjCDVR82P6XRsLB01_Screen_Shot_059c846478de4da79.png"/>
Notice that the URL is configured such that once you log in, you will be redirected to the page you initially attempted to access, which in this case is the dashboard.
## [Conclusion](#conclusion)
Thats it for Part One! Weve covered quite a lot: setting up a MySQL database, creating models, migrating the database, and handling registration, login, and logout. Good job for making it this far!
Watch this space for Part Two, which will cover the CRUD functionality of the app, allowing admin users to add, list, edit, and delete departments and roles, as well as assign them to employees.