Flask blog app tutorial 8 : Deploy
This is the tutorial for Flask blog app deployment.
We have 8 parts of tutorials including this one. However, in this production version, we exclude the previous part which is mostly related to the "Likes" button and count on dashboard. So, we're deploying from part 1 to part 2 of the series.
In this part of the series, we'll deploy the Blog app to CentOS 7 server (etaman.com).
The source code is available from FlaskApp/p-deploy.
Download the source from FlaskApp/p-deploy:
$ git clone https://github.com/Einsteinish/FlaskBlogApp.git
We'll use virtualenvwrapper. So, let's install it using pip:
$ sudo pip install virtualenvwrapper
We need to find virtualenvwrapper.sh. It could be in either /usr/local/bin/ or /usr/bin. Let's put env into ~/.bashrc:
# virtuanvwrapper export WORKON_HOME=~/Envs source /usr/local/bin/virtualenvwrapper.sh
Then run the virtualenvwrapper.sh by running ~/.bashrc:
$ source ~/.bashrc virtualenvwrapper.user_scripts creating /home/sfvue/Envs/initialize virtualenvwrapper.user_scripts creating /home/sfvue/Envs/premkvirtualenv virtualenvwrapper.user_scripts creating /home/sfvue/Envs/postmkvirtualenv virtualenvwrapper.user_scripts creating /home/sfvue/Envs/prermvirtualenv virtualenvwrapper.user_scripts creating /home/sfvue/Envs/postrmvirtualenv virtualenvwrapper.user_scripts creating /home/sfvue/Envs/predeactivate virtualenvwrapper.user_scripts creating /home/sfvue/Envs/postdeactivate virtualenvwrapper.user_scripts creating /home/sfvue/Envs/preactivate virtualenvwrapper.user_scripts creating /home/sfvue/Envs/postactivate virtualenvwrapper.user_scripts creating /home/sfvue/Envs/get_env_details virtualenvwrapper.user_scripts creating /home/sfvue/Envs/premkproject virtualenvwrapper.user_scripts creating /home/sfvue/Envs/postmkproject
Let's create our first virtualenv:
[sfvue@sf bin]$ mkvirtualenv env1 New python executable in env1/bin/python Installing setuptools, pip, wheel...done. virtualenvwrapper.user_scripts creating /home/sfvue/Envs/env1/bin/predeactivate virtualenvwrapper.user_scripts creating /home/sfvue/Envs/env1/bin/postdeactivate virtualenvwrapper.user_scripts creating /home/sfvue/Envs/env1/bin/preactivate virtualenvwrapper.user_scripts creating /home/sfvue/Envs/env1/bin/postactivate virtualenvwrapper.user_scripts creating /home/sfvue/Envs/env1/bin/get_env_details (env1)[sfvue@sf ~]$
We'll need 3 tables and 12 stored procedures as listed in Python Flask Blog App Tutorial : Appendix.
Let's go into MySQL and create a database for our app, FlaskBlogApp:
[sfvue@sf ~]$ mysql -u root -p Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 27566 Server version: 5.5.50-MariaDB MariaDB Server Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> CREATE DATABASE FlaskBlogApp; Query OK, 1 row affected (0.02 sec)
The root user has full access to all of the databases, however, in the cases where more restrictions may be required, we should create users with custom permissions.
Let's create a new user within the MySQL shell:
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
The newuser has no permissions to do anything with the databases. In fact, if newuser even tries to login (with the password, password), they will not be able to reach the MySQL shell.
So, the first thing to do is to provide the user with access to the information they will need:
GRANT ALL PRIVILEGES ON * . * TO 'newuser'@'localhost';
The asterisks in this command refer to the database and table (respectively) that they can access. The command allows to the user to read, edit, execute and perform all tasks across all the databases and tables.
Once we have finalized the permissions that we want to set up for our new users, always be sure to reload all the privileges:
FLUSH PRIVILEGES;
With the "FLUSH" command, our changes will now be in effect.
We need to create a table (blog_user):
CREATE TABLE `FlaskBlogApp`.`blog_user` ( `user_id` BIGINT NOT NULL AUTO_INCREMENT, `user_name` VARCHAR(45) NULL, `user_username` VARCHAR(45) NULL, `user_password` VARCHAR(85) NULL, PRIMARY KEY (`user_id`)); MariaDB [(none)]> use FlaskBlogApp; MariaDB [FlaskBlogApp]> desc blog_user; +---------------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------------+-------------+------+-----+---------+----------------+ | user_id | bigint(20) | NO | PRI | NULL | auto_increment | | user_name | varchar(45) | YES | | NULL | | | user_username | varchar(45) | YES | | NULL | | | user_password | varchar(85) | YES | | NULL | | +---------------+-------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
tbl_blog:
CREATE TABLE `tbl_blog` ( `blog_id` int(11) NOT NULL AUTO_INCREMENT, `blog_title` varchar(45) DEFAULT NULL, `blog_description` varchar(5000) DEFAULT NULL, `blog_user_id` int(11) DEFAULT NULL, `blog_date` datetime DEFAULT NULL, PRIMARY KEY (`blog_id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1; ALTER TABLE `FlaskBlogApp`.`tbl_blog` ADD COLUMN `blog_file_path` VARCHAR(200) NULL AFTER `blog_date`, ADD COLUMN `blog_accomplished` INT NULL DEFAULT 0 AFTER `blog_file_path`, ADD COLUMN `blog_private` INT NULL DEFAULT 0 AFTER `blog_accomplished`; MariaDB [FlaskBlogApp]> desc tbl_blog; +-------------------+---------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------------+---------------+------+-----+---------+----------------+ | blog_id | int(11) | NO | PRI | NULL | auto_increment | | blog_title | varchar(45) | YES | | NULL | | | blog_description | varchar(5000) | YES | | NULL | | | blog_user_id | int(11) | YES | | NULL | | | blog_date | datetime | YES | | NULL | | | blog_file_path | varchar(200) | YES | | NULL | | | blog_accomplished | int(11) | YES | | 0 | | | blog_private | int(11) | YES | | 0 | | +-------------------+---------------+------+-----+---------+----------------+ 8 rows in set (0.00 sec)
CREATE TABLE `FlaskBlogApp`.`tbl_likes` ( `blog_id` INT NOT NULL, `like_id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NULL, `blog_like` INT NULL DEFAULT 0, PRIMARY KEY (`like_id`)); MariaDB [FlaskBlogApp]> desc tbl_likes; +-----------+---------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+---------+------+-----+---------+----------------+ | blog_id | int(11) | NO | | NULL | | | like_id | int(11) | NO | PRI | NULL | auto_increment | | user_id | int(11) | YES | | NULL | | | blog_like | int(11) | YES | | 0 | | +-----------+---------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
Here are the three tables we've created so far:
MariaDB [FlaskBlogApp]> show tables; +------------------------+ | Tables_in_FlaskBlogApp | +------------------------+ | blog_user | | tbl_blog | | tbl_likes | +------------------------+ 3 rows in set (0.00 sec)
Here is the list of the stored procedures that should be created:
- sp_addBlog:
USE `FlaskBlogApp`; DROP procedure IF EXISTS `sp_addBlog`; DELIMITER $$ USE `FlaskBlogApp`$$ CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_addBlog`( IN p_title varchar(45), IN p_description varchar(1000), IN p_user_id bigint, IN p_file_path varchar(200), IN p_is_private int, IN p_is_done int ) BEGIN insert into tbl_blog( blog_title, blog_description, blog_user_id, blog_date, blog_file_path, blog_private, blog_accomplished ) values ( p_title, p_description, p_user_id, NOW(), p_file_path, p_is_private, p_is_done ); END$$ DELIMITER ;
- sp_createUser:
USE `FlaskBlogApp`; DROP procedure IF EXISTS `sp_createUser`; DELIMITER $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_createUser`( IN p_name VARCHAR(20), IN p_username VARCHAR(20), IN p_password VARCHAR(85) ) BEGIN IF ( select exists (select 1 from blog_user where user_username = p_username) ) THEN select 'Username Exists !!'; ELSE insert into blog_user ( user_name, user_username, user_password ) values ( p_name, p_username, p_password ); END IF; END$$ DELIMITER ;
- sp_deleteBlog:
USE `FlaskBlogApp`; DROP procedure IF EXISTS `sp_deleteBlog`; DELIMITER $$ USE `FlaskBlogApp`$$ CREATE PROCEDURE `sp_deleteBlog` ( IN p_blog_id bigint, IN p_user_id bigint ) BEGIN delete from tbl_blog where blog_id = p_blog_id and blog_user_id = p_user_id; END$$ DELIMITER ;
- sp_GetAllBlogs:
USE `FlaskBlogApp`; DROP procedure IF EXISTS `sp_GetAllBlogs`; DELIMITER $$ USE `FlaskBlogApp`$$ CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetAllBlogs`() BEGIN select blog_id,blog_title,blog_description,blog_file_path from tbl_blog where blog_private = 0; END$$ DELIMITER ;
- sp_GetBlogById:
USE `FlaskBlogApp`; DROP procedure IF EXISTS `sp_GetBlogById`; DELIMITER $$ USE `FlaskBlogApp`$$ CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetBlogById`( IN p_blog_id bigint, In p_user_id bigint ) BEGIN select blog_id,blog_title,blog_description,blog_file_path,blog_private,blog_accomplished from tbl_blog where blog_id = p_blog_id and blog_user_id = p_user_id; END$$ DELIMITER ;
- sp_GetBlogByUser:
USE `FlaskBlogApp`; DROP procedure IF EXISTS `sp_GetBlogByUser`; DELIMITER $$ USE `FlaskBlogApp`$$ CREATE PROCEDURE `sp_GetBlogByUser` ( IN p_user_id bigint ) BEGIN select * from tbl_blog where blog_user_id = p_user_id; END$$ DELIMITER ;
- sp_updateBlog:
USE `FlaskBlogApp`; DROP procedure IF EXISTS `sp_updateBlog`; DELIMITER $$ USE `FlaskBlogApp`$$ CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_updateBlog`( IN p_title varchar(45), IN p_description varchar(1000), IN p_blog_id bigint, In p_user_id bigint, IN p_file_path varchar(200), IN p_is_private int, IN p_is_done int ) BEGIN update tbl_blog set blog_title = p_title, blog_description = p_description, blog_file_path = p_file_path, blog_private = p_is_private, blog_accomplished = p_is_done where blog_id = p_blog_id and blog_user_id = p_user_id; END$$ DELIMITER ;
- sp_validateLogin:
USE `FlaskBlogApp`; DROP procedure IF EXISTS `sp_validateLogin`; DELIMITER $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_validateLogin`( IN p_username VARCHAR(20) ) BEGIN select * from blog_user where user_username = p_username; END$$ DELIMITER ;
Here is the stored procedures created so far:
MariaDB [FlaskBlogApp]> select db,name,definer from mysql.proc WHERE db = 'FlaskBlogApp'; +--------------+------------------+----------------+ | db | name | definer | +--------------+------------------+----------------+ | FlaskBlogApp | sp_addBlog | root@localhost | | FlaskBlogApp | sp_createUser | root@localhost | | FlaskBlogApp | sp_deleteBlog | root@localhost | | FlaskBlogApp | sp_GetAllBlogs | root@localhost | | FlaskBlogApp | sp_GetBlogById | root@localhost | | FlaskBlogApp | sp_GetBlogByUser | root@localhost | | FlaskBlogApp | sp_updateBlog | root@localhost | | FlaskBlogApp | sp_validateLogin | root@localhost | +--------------+------------------+----------------+ 8 rows in set (0.00 sec)
We make calls to the MySQL stored procedure to create the new user etc.
To connect with MySQL from Flask, we need to install Flask-MySQL:
(env1)[sfvue@sf p-deploy]$ pip install flask-mysql
Let's install Flask with pip package manager:
$ pwd /home/sfvue/MySites/etaman/FlaskBlogApp/FlaskApp/p-deploy [sfvue@sf p-deploy]$ workon env1 (env1)[sfvue@sf p-deploy]$ pip install flask
Here is the directory structure:
(env1)[sfvue@sf p-deploy]$ tree -L 2 . |-- app.py |-- config.py |-- static | |-- css | |-- images | |-- js | |-- Uploads | |-- templates |-- addBlog.html |-- dashboard.html |-- error.html |-- index.html |-- signin.html |-- signup.html |-- userHome.html
Here is the configuration file, config.py:
#secret key for signing cookies SECRET_KEY = "spooky action at a distance-Einstein" # MySQL configurations MYSQL_DATABASE_USER = 'khong' MYSQL_DATABASE_PASSWORD = 'khong' MYSQL_DATABASE_DB = 'FlaskBlogApp' MYSQL_DATABASE_HOST = 'localhost' # file upload configurations UPLOAD_FOLDER = 'static/Uploads'
In app.py, we need the following a line of code to get the configuration:
app = Flask(__name__) app.config.from_object('config')
We need to set proper permission to apache user:
(env1)[sfvue@sf p-deploy]$ sudo chown -R apache:apache static
We use Apache with port forwarding - forward a request on port 80 to 5050, and here the the configuration file, /etc/httpd/sites-available/etaman.com.conf:
<VirtualHost *:80> ServerName www.etaman.com ServerAlias etaman.com ProxyPreserveHost On ProxyPass / http://127.0.0.1:5050/ ProxyPassReverse / http://127.0.0.1:5050/ ErrorLog /var/www/etaman.com/logs/error.log CustomLog /var/www/etaman.com/logs/access.log combined LogLevel warn ServerSignature Off </VirtualHost>
Set a soft-link and restart the server:
$ sudo ln -s /etc/httpd/sites-available/etaman.com.conf /etc/httpd/sites-enabled/etaman.com.conf $ sudo service httpd restart Redirecting to /bin/systemctl restart httpd.service
We'll run our app using pm2:
(env1)[sfvue@sf p-deploy]$ pm2 start etaman.sh
The etaman.sh looks like this:
workon env1 /home/sfvue/Envs/env1/bin/python app.py &
Check the status of app:
Actually, at this point, the pm2 run keeps failing. So, I had to use the traditional "nohup" instead:
[sfvue@sf p-deploy]$ nohup /home/sfvue/Envs/env1/bin/python /home/sfvue/MySites/etaman/FlaskBlogApp/FlaskApp/p-deploy/app.py & [1] 20772
The screen shots of the run:
If not errors in the sign-up, we may want to go to sign-in page to login.
Once signed in, we can see the dashboard with the posts:
WE can edit/delete the posts:
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization