# flask_demo **Repository Path**: njbinbin/flask_demo ## Basic Information - **Project Name**: flask_demo - **Description**: A simple flask demo for learning. - **Primary Language**: Python - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-03-28 - **Last Updated**: 2024-12-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 今天开始研究Flask,感觉比Django更容易明白自己的代码在干什么,而不是在别人装修好的房子里摆家俱。可能要学习的内容包括(简书的Markdown不支持目录[TOC]???,也不支持页内链接自己手动做目录都不可能???明白为什么叫“简”书了): # 一、环境与安装 基于Docker开发环境配置,能跑起来的第一个应用Hello World! #二、MVT 基于Model,View和Template的应用 ##1. 模板 ##2. View与路由 ##3. ORM与数据库 #三、登录与认证 处理登录与认证 ##1. 表单与flask-wtf ##2. 会话 #四、RESTful API 将前后端分离所需要的工作 #五、部署 wsgi的几种部署(Gunicorn 和 Tornado,Nginx) ——这里有一个参考网址准备研究,不过只有基于模板的开发:[http://www.pythondoc.com/flask-mega-tutorial/index.html](http://www.pythondoc.com/flask-mega-tutorial/index.html) Let's GO #一、环境与安装 网上的教程一般从虚拟环境开始,跟之前的django开发环境一样,这里还是直接搭建docker环境用于开发,保证开发到部署的一致性。PyCharm的好处之一就是其支持配置Python解释器到docker-compose.yml文件设置的远程容器中的python。 - 建立一个目录放项目 在里面写好Dockerfile和requirements.txt - Dockerfile: ``` FROM python:3.7 ADD requirements.txt /tmp/requirements.txt RUN pip install -r /tmp/requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ EXPOSE 5000 WORKDIR /myapp ``` - requirements.txt,参照网址教程中所需要的包,flask核心很小,其他功能是通过松散的包实现所的,所以比django要装的多。 ``` flask==1.1.1 flask-login==0.5.0 flask-openid==1.2.5 flask-mail==0.9.1 flask-whooshalchemy flask-wtf==0.14.3 flask-babel==1.0.0 guess_language==0.2 flipflop==1.0 coverage==5.0.4 pymysql==0.9.3 mysqlclient==1.4.2.post1 flask_mysql==1.5.1 flask_sqlalchemy==2.4.1 sqlalchemy-migrate==0.13.0 pytz==2018.9 gunicorn==19.9.0 urllib3==1.25.3 transitions==0.7.1 drf-access-policy==0.5.1 Markdown==3.0.1 drf-nested-routers==0.91 # Developer Tools mkdocs==1.0.4 # Static and Media Storage boto3==1.9.93 oss2==2.9.1 ``` - docker-compose.yml: 还是准备用MySQL数据库,所以docker-compose.yml仍使用三个容器,其中一个mkdocs服务,一个MySQL服务,一个作为开发项目。MySQL容器配置的用户名密码等放在.envs/mysql.env文件中。开发容器映射文件夹/myapp到当前项目目录。 ``` version: '3' services: mysql_server: image: mysql:5.7.26 container_name: flask_mysql_container restart: always command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci env_file: - .envs/mysql.env ports: - "3302:3306" volumes: - ./mysql_data:/var/lib/mysqldocker flask_server: build: . container_name: flask_container env_file: - .envs/flask.env - .envs/mysql.env depends_on: - mysql_server - doc_server volumes: - .:/myapp ports: - 8080:5000 command: > bash -c "python wait_db_ready.py flask run -h 0.0.0.0" doc_server: restart: always build: . container_name: flask_docs command: "mkdocs serve" volumes: - .:/myapp ports: - "8081:80" ``` - 容器所需要的环境变量放在.envs目录下: mysql.env,弱密码,仅用于试验 ```bash # root password MYSQL_ROOT_PASSWORD=root # user MYSQL_USER=myapp # user password MYSQL_PASSWORD=myapp # db_name MYSQL_DATABASE=myapp ``` flask.env ```bash # For wait_db_ready.py MYSQL_CHECK_TIMEOUT=30 MYSQL_CHECK_INTERVAL=1 # Tell 'flask run' where app is FLASK_APP=apps.site # Url for SQLALCHEMY SQLALCHEMY_DATABASE_URI=mysql://myapp:myapp@flask_mysql_container/myapp?charset=utf8mb4 ``` - 先写一个最简单的Helloworld 当前目录下建立一个apps包用来管理应用,增加site.py源码: ```python from flask import Flask #导入Flask类 app = Flask(__name__) #1、实例化Flask app @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run(host='0.0.0.0') ``` - 初始的目录结构 ```sh $ tree -a . ├── apps #建立一个包管理应用 │   ├── static #静态文件目录 │   ├── templates #模板目录 │   ├── __init__.py │   └── site.py #第一个应用 ├── docker-compose.yml #docker-compose.yml ├── Dockerfile #dockerfile ├── docs #mkdocs容器的映射数据目录 │   ├── index.md #mkdocs文档首页,内容空 │   └── api │   └── helloworld.md #mkdocs测试文档,内容空 ├── .envs │   ├── flask.env #环境变量 │   └── mysql.env #mysql环境变量设置 ├── mysql_data #映射的mysql容器数据目录,容器启动后自动生成(docker组权限) ├── requirements.txt #docker的包需求 ├── mkdocs.yml #mkdoc服务配置 └── wait_db_ready.py #防止mysql服务还没启动完成app容器先启动了 ``` - 给项目mkdocs容器一个初始配置,用来提供以后的文档 ```yml site_name: myappdocs site_description: myapp mkdocs site site_dir: site copyright: Copyright © 2019, myapp site. dev_addr: 0.0.0.0:80 nav: - Home: 'index.md' - API: - helloworld: 'api/helloworld.md' ``` - 试验docker容器是否正常: ```bash $ docker-compose up ``` 正常的话浏览器访问http://localhost:8080应该能看到Hello World!了,MySQL容器起来后mysql_data会生成数据库的文件及myapp数据库。http://localhost:8081是mkdocs服务首页。 - 继续配置PyCharm 打开当前项目文件夹,File->Settings中当前项目的Project Interpreter增加一个Docker Compose项,设置好Docker以及当前目录的docker-compose.yml,选择Service为flask_server,调试器Edit Configurations新增一个Flask Server模板,Additional options:填写`-h 0.0.0.0`以让外部访问。在app.py中下断点,调试看是否能断下,以后就可以直接在PyCharm调试启动容器了。 应用的启动入口是docker-compose.yml中flask_server指定的 ` command: > bash -c "python wait_db_ready.py flask run -h 0.0.0.0" `,wait_db_ready.py是实现了一个死循环直到数据库容器能连接,再启动flask应用。`flask run`从环境变量中获得`FLASK_APP`变量的值并加载(这里是app.app,放在了app包里),Flask官方不建议用`python app.py`方式启动,可能是要强调跟部署的一致性。 - 如果启动版本控制,在项目目录运行: ```bash $ git init ``` 之后可以在PyCharm进行操作了。在码云上建了一个仓库,随学习更新。 https://gitee.com/njbinbin/flask_demo.git #二、MVT 基于Model,View和Template的应用。 首先对项目进行一些改造 - static目录下存放静态文件,如site.css全局css,先做一个简单的备用: ```css body { background: #f0f0f0; } ``` - templates目录下存放模板 ##1. 模板 模板默认在项目templates目录下,先建立base.html ```html {{Title}} {% block contents %} {% endblock %} ``` 再继承一个首页模板index.html: ``` {% extends 'base.html' %} {% block contents %} {{ Welcome }} {% endblock %} ``` 现在使用模板实现Hello World,修改site.py: ```python from flask import Flask, render_template app = Flask(__name__, static_folder='static', static_url_path='/static', template_folder='templates') @app.route('/') def hello_world(): # return 'Hello World!' return render_template('index.html', Title='My flask site', Welcome='Hello World!') if __name__ == '__main__': app.run(host='0.0.0.0') ``` ##2. View与路由 flask的路由用装饰器实现,就像在前面site.py中实现的,在函数前面修饰后即将url与处理函数绑定: ```python # 这里将根url绑定到hello_world函数上 @app.route('/') def hello_world(): return 'Hello World!' ``` `@app.route('/api/v1//')`这样带参数也是可以的,绑定的函数对应接收两个参数:`def func(user, action)`。与django不同,flask的路由没有像urls.py这样统一的配置入口,如果直接用装饰器与函数在一起生成则可能随模块分散在各处管理起来不太方便。大型项目建议使用Blueprint来管理。项目下新建一个test_route包,其中加一个views.py: ``` from flask import Blueprint, render_template test = Blueprint('test', __name__) @test.route('/hello') def test_hello(): return render_template('index.html', Title="My flask site - test route", Welcome="Hello again!") ``` site.py现在用来集中管理各模块的路由: ``` from flask import Flask, render_template #引入test_route包views模块中的test对象 from apps.test_route.views import test app = Flask(__name__, static_folder='static', static_url_path='/static', template_folder='templates') #将引入的test_route.views/test其注册到蓝图中统一管理,前缀test app.register_blueprint(test, url_prefix='/test') @app.route('/') def hello_world(): # return 'Hello World!' return render_template('index.html', Title='My flask site', Welcome='Hello World!') if __name__ == '__main__': app.run(host='0.0.0.0') ``` 实际的路由为前缀+模块用装饰器注册的路由,这里apps.test_route.views/test_hello函数对应的URL是`/test/hello/` ##3. ORM与数据库 - 配置config.py,更新flask.env和site.py 将相关的参数单独放到一个config.py中,与site.py相同目录: ```python import os # 静态文件配置 STATIC_DIR = os.getenv('STATIC_DIR', 'static') STATIC_URL = os.getenv('STATIC_URL', '/static') TEMPLATE_DIR = os.getenv('TEMPLATE_DIR', 'templates') # 读取static目录下的css文件存入列表,在base.html中演示使用模板循环 STYLE_SHEETS = [] for f in os.listdir(os.path.join(os.path.dirname(__file__), STATIC_DIR)): if f.endswith('.css'): STYLE_SHEETS.append(f) # 数据庫配置 SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI', '') SQLALCHEMY_TRACK_MODIFICATIONS = os.getenv('SQLALCHEMY_TRACK_MODIFICATIONS', '') == 'True' ``` 参数来自环境变量,更新flask.env ```sh # For wait_db_ready.py MYSQL_CHECK_TIMEOUT=30 MYSQL_CHECK_INTERVAL=1 # Tell flask run where apps in FLASK_APP=apps.site # For SQLALCHEMY SQLALCHEMY_DATABASE_URI=mysql+pymysql://myapp:myapp@flask_mysql_container/myapp?charset=utf8mb4 SQLALCHEMY_MIGRATE_REPO=./migrations SQLALCHEMY_TRACK_MODIFICATIONS=True # For static files and templates STATIC_DIR=static STATIC_URL=/static TEMPLATE_DIR=templates ``` site.py增加`from flask_sqlalchemy import SQLAlchemy`和`import apps.config`两个引入 修改app对象的建立代码: ``` app = Flask(__name__, static_folder=apps.config.STATIC_DIR, static_url_path=apps.config.STATIC_URL, template_folder=apps.config.TEMPLATE_DIR) app.config.from_object(apps.config) ``` - 建立数据库对象 site.py中app对象建立后,增加一行代码建立一个数据库对象与app绑定:`db = SQLAlchemy(app)` - Models与ORM 建立user包和post包,用来存放用户及贴子的模型与视图,先建立model user和post包中增加models.py user/models.py: ``` from apps.site import db class User(db.Model): id = db.Column(db.Integer, primary_key=True) nickname = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) posts = db.relationship('Post', backref='author', lazy='dynamic') def __repr__(self): return '' % self.nickname ``` post/models.py: ``` from apps.site import db class Post(db.Model): id = db.Column(db.Integer, primary_key=True) body = db.Column(db.String(140)) timestamp = db.Column(db.DateTime) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) def __repr__(self): return '' % self.body ``` - 数据迁移与管理,之前遗漏了相关包,需要在requirements.txt增加两个包,多余的drf-nested-routers==0.91包去掉(这是django用的),镜像更新重建: ``` flask-script==2.0.6 flask-migrate==2.5.3 ``` 写一个manage.py: ```python from flask_script import Manager from flask_migrate import Migrate, MigrateCommand from apps.site import app from apps.site import db #要迁移维护的模型,引入即可 import apps.user.models import apps.post.models manager = Manager(app) migrate = Migrate(app, db) manager.add_command('db', MigrateCommand) if __name__ == '__main__': manager.run() ``` 为三条数据库命令建立对应的PyCharm运行调试参数配置并分析运行(Run/Debug configurations) Script path为`manage.py` Parameters分别为`db init`、`db migrate`和`db upgrade` Working directory是项目所在主机目录的绝对路径,我的是`/home/zzz/Projects/myFlask` 分别运行后数据库应该迁移完成,可以尝试在python环境操作数据库并查询,看是否正常工作: 进入PyCharm的Python Console标签页,将激活容器中的Python环境: ```python /usr/bin/docker-compose -f /home/zzz/Projects/myFlask/docker-compose.yml -f /home/zzz/.PyCharm2019.3/system/tmp/docker-compose.override.1323.yml run --rm -p 0.0.0.0:35361:35361 flask_server Starting flask_docs ... Starting flask_mysql_container ... import sys; print('Python %s on %s' % (sys.version, sys.platform)) sys.path.extend(['/myapp', '/myapp']) PyDev console: starting. Python 3.7.7 (default, Mar 11 2020, 00:27:03) [GCC 8.3.0] on linux >>> from apps.site import db >>> from apps.post.models import Post >>> from apps.user.models import User >>> u = User(nickname='john', email='john@email.com') >>> db.session.add(u) >>> db.session.commit() >>> users = User.query.all() >>> for u in users: ... print(u.id, u.nickname) ... 1 john >>> u = User.query.get(1) >>> u >>> import datetime >>> p = Post(body='my first post!', timestamp=datetime.datetime.utcnow(), author=u) >>> db.session.add(p) >>> db.session.commit() ```