[ Python ] Flask 基于 Web开发 大型程序的结构实例解析
作为一个编程入门新手,Flask是我接触到的第一个Web框架。想要深入学习,就从《FlaskWeb开发:基于Python的Web应用开发实战》这本书入手,本书由于是翻译过来的中文版,理解起来不是很顺畅。但是对着代码理解也是能应对的,学到 第七章:大型程序结构 这章节的时候,发现难度有所提升,网上能参考的完整实例没有,于是根据自己的理解记下来。
程序结构图:
README
(1)本程序是基于Flask微型Web框架开发,使用Jinja2模版引擎
(2)页面展示了一个文本框和一个按钮,输入文本框点击按钮提交,文本框为空无法提交(输入文本框的数据为一个模拟用户);
(3)当在文本框中输入新用户提交,欢迎词和文本框中输入老用户提交不一致;
(4)文本框输入新用户提交后,将新用户保存至SQLite数据库,并使用异步发送邮件至管理员邮箱;
(5)页面刷新,浏览器不会再次提示:是否提交 项目结构 flasky # 程序根目录
├── app # 核心模块目录
│ ├── email.py # 邮件发送模版
│ ├── __init__.py
│ ├── main # 蓝图模块目录
│ │ ├── errors.py # 错误处理模块
│ │ ├── forms.py # 页面表单模块
│ │ ├── __init__.py
│ │ └── views.py # 正常处理模块
│ ├── models.py # 对象关系映射模块
│ ├── static # 页面静态资源目录
│ │ └── favicon.ico # 页面收藏夹图标
│ └── templates # 默认存放页面模版目录
│ ├── 404.html
│ ├── base.html
│ ├── index.html
│ ├── mail # 邮件模块目录
│ │ ├── new_user.html
│ │ └── new_user.txt
│ └── user.html
├── config.py # 程序配置文件
├── data-dev.sqlite # 程序数据库文件
├── manage.py # 程序管理启动文件
├── migrations # 数据库迁移目录
│ ├── alembic.ini
│ ├── env.py
│ ├── README
│ ├── script.py.mako
│ └── versions
├── requirements.txt # 所有依赖包文件
└── tests # 测试文件目录
├── __init__.py
└── test_basics.py
README
程序代码总汇
"/"
# -*- coding: utf-8 -*-
# Author: hkey
import os basedir = os.path.abspath(os.path.dirname(__file__)) class Config(object): # 所有配置类的父类,通用的配置写在这里
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
SQLALCHEMY_TRACK_MODIFICATIONS = True
FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
FLASKY_MAIL_SENDER = 'Flasky Admin <xxx@126.com>'
FLASKY_ADMIN = 'xxx@qq.com' @staticmethod
def init_app(app): # 静态方法作为配置的统一接口,暂时为空
pass class DevelopmentConfig(Config): # 开发环境配置类
DEBUG = True
MAIL_SERVER = 'smtp.126.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = 'xxx@126.com'
MAIL_PASSWORD = 'xxxxxx'
SQLALCHEMY_DATABASE_URI = \
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite') class TestingConfig(Config): # 测试环境配置类
TESTING = True
SQLALCHEMY_DATABASE_URI = \
'sqlite:///' + os.path.join(basedir, 'data-test.sqlite') class ProductionConfig(Config): # 生产环境配置类
SQLALCHEMY_DATABASE_URI = \
'sqlite:///' + os.path.join(basedir, 'data.sqlite') config = { # config字典注册了不同的配置,默认配置为开发环境,本例使用开发环境
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
config.py
# -*- coding: utf-8 -*-
# Author: hkey
import os
from app import create_app, db
from app.models import User, Role
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app)
migrate = Migrate(app, db) def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role) manager.add_command('shell', Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand) @manager.command
def test():
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests) if __name__ == '__main__':
manager.run()
manage.py
"/app"
# -*- coding: utf-8 -*-
# Author: hkey
from flask import Flask, render_template
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
from config import config # 由于尚未初始化所需的程序实例,所以没有初始化扩展,创建扩展类时没有向构造函数传入参数。
bootstrap = Bootstrap()
mail = Mail()
db = SQLAlchemy() def create_app(config_name):
'''工厂函数'''
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app) # 通过config.py统一接口
bootstrap.init_app(app) # 该init_app是bootstrap实例的方法调用,与上面毫无关系
mail.init_app(app) # 同上
db.init_app(app) # 同上 # 附加路由和自定义错误页面,将蓝本注册到工厂函数
from .main import main as main_blueprint
app.register_blueprint(main_blueprint) return app
app/__init__.py
# -*- coding: utf-8 -*-
# Author: hkey
from threading import Thread
from flask import render_template, current_app
from flask_mail import Message
from . import mail def send_async_mail(app, msg):
'''创建邮件发送函数'''
with app.app_context():
mail.send(msg) def send_mail(to, subject, template, **kwargs):
app = current_app._get_current_object()
if app.config['FLASKY_ADMIN']:
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
thr = Thread(target=send_async_mail, args=(app, msg))
thr.start() # 通过创建子线程实现异步发送邮件
return thr
app/email.py
# -*- coding: utf-8 -*-
# Author: hkey # 对象关系映射类 from . import db class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, index=True)
users = db.relationship('User', backref='role', lazy='dynamic')
def __repr__(self):
return '<Role %r>' % self.name class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) def __repr__(self):
return '<User %r>' % self.username
app/models.py
''/app/main"
# -*- coding: utf-8 -*-
# Author: hkey
from flask import Blueprint
# 定义蓝本
main = Blueprint('main', __name__) from . import views, errors
app/main/__init__.py
# -*- coding: utf-8 -*-
# Author: hkey
from flask import render_template
from . import main @main.app_errorhandler(404) # 路由装饰器由蓝本提供,这里要调用 app_errorhandler 而不是 errorhandler
def page_not_found(e):
return render_template('404.html'), 404 @main.app_errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
app/main/errors.py
# -*- coding: utf-8 -*-
# Author: hkey
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import Required class NameForm(FlaskForm):
'''通过 flask-wtf 定义表单类'''
name = StringField('What is your name ?', validators=[Required()]) # 文本框
submit = SubmitField('Submit') # 按钮
app/main/forms.py
# -*- coding: utf-8 -*-
# Author: hkey
from flask import render_template, session, redirect, url_for, current_app
from . import main
from .forms import NameForm
from .. import db
from ..models import User
from ..email import send_mail @main.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first() # 查询数据库是否有该用户
if user is None: # 如果没有该用户,就保存到数据库中
user = User(username=form.name.data)
db.session.add(user)
session['known'] = False # 通过session保存 known为False,通过web渲染需要
if current_app.config['FLASKY_ADMIN']: # 如果配置变量有flasky管理员就发送邮件
# 异步发送邮件
send_mail(current_app.config['FLASKY_ADMIN'], 'New User', 'mail/new_user', user=user)
else:
session['known'] = True
session['name'] = form.name.data
form.name.data = ''
return redirect(url_for('.index')) # 通过redirect避免用户刷新重复提交
return render_template('index.html', form=form, name=session.get('name'),
known=session.get('known', False))
app/main/views.py
"/app/main/templates" 页面
<!DOCTYPE html>
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename = 'favicon.ico')}}"
type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename = 'favicon.ico')}}"
type="image/x-icon">
{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
app/templates/base.html
<!DOCTYPE html>
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
<p>Happy to see you again!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
app/templates/index.html
<!DOCTYPE html>
{% extends "base.html" %}
{% block title %}Flasky - Page Not Found{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Not Found!</h1>
</div>
{% endblock %}
app/templates/404.html
"/app/main/templates/mail" 邮件模版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
User <b>{{ user.username }}</b> has joined.
</head>
<body> </body>
</html>
app/templates/mail/new_user.html
User {{ user.username }} has joined.
app/templates/mail/new_user.txt
"/app/main/static/favicon.ico" 静态 icon 图片文件
创建需求文件
程序中必须包含一个 requirements.txt 文件,用于记录所有依赖包及其精确的版本号。如果要在另一台电脑上重新生成虚拟环境,这个文件的重要性就体现出来了,例如部署程序时使用的电脑。
(venv) E:\flasky>pip3 freeze > requirements.txt
创建数据库
(venv) E:\flasky>python manage.py shell
>>> db.create_all()
>>> exit()
生成数据库迁移文件
(venv) E:\flasky>python manage.py db init
Creating directory E:\flasky\migrations ... done
Creating directory E:\flasky\migrations\versions ... done
Generating E:\flasky\migrations\alembic.ini ... done
Generating E:\flasky\migrations\env.py ... done
Generating E:\flasky\migrations\README ... done
Generating E:\flasky\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'E:\\flasky\\migrations\\alembic.ini' before proceeding. (venv) E:\flasky>python manage.py db migrate -m "initial migration"
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.env] No changes in schema detected. (venv) E:\flasky>python manage.py db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
运行测试
(venv) E:\flasky>python manage.py test
test_app_exists (test_basics.BasicsTestCase)
确保程序实例存在 ... ok
test_app_is_testing (test_basics.BasicsTestCase)
确保程序在测试中运行 ... ok ----------------------------------------------------------------------
Ran 2 tests in 2.232s OK
启动程序
(venv) E:\flasky>python manage.py runserver
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 138-639-525
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
浏览器输入 http://127.0.0.1:5000
输入用户名并提交:
程序会异步发送邮件,程序控制台会打印发送日志。已收到邮件:
[ Python ] Flask 基于 Web开发 大型程序的结构实例解析的更多相关文章
- pycharm+python+Django之web开发环境的搭建(windows)
转载:https://blog.csdn.net/yjx2323999451/article/details/53200243/ pycharm+python+Django之web开发环境的搭建(wi ...
- BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第3章节--SharePoint 2013 开发者工具 SharePoint中基于Web开发
BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第3章节--SharePoint 2013 开发者工具 SharePoint中基于Web开发 之前提到过, ...
- 使用eclipse搭建第一个python+Django的web开发实例
python+Django的web开发实例 一.创建一个项目如果这是你第一次使用Django,那么你必须进行一些初始设置.也就是通过自动生成代码来建立一个Django项目--一个Django项目的 ...
- python——flask常见接口开发(简单案例)
python——flask常见接口开发(简单案例)原创 大蛇王 发布于2019-01-24 11:34:06 阅读数 5208 收藏展开 版本:python3.5+ 模块:flask 目标:开发一个只 ...
- Flask从入门到精通之大型程序的结构一
尽管在单一脚本中编写小型Web 程序很方便,但这种方法并不能广泛使用.程序变复杂后,使用单个大型源码文件会导致很多问题.不同于大多数其他的Web 框架,Flask 并不强制要求大型项目使用特定的组织方 ...
- 【Python】【Web开发】
# [[Web开发]] ''' 最早的软件都是运行在大型机上的,软件使用者通过“哑终端”登陆到大型机上去运行软件.后来随着PC机的兴起,软件开始主要运行在桌面上,而数据库这样的软件运行在服务器端,这种 ...
- Python flask模块接口开发学习总结
引言 Flask 是一个简单且十分强大的Python web 框架.它被称为微框架,“微”并不是意味着把整个Web应用放入到一个Python文件,微框架中的“微”是指Flask旨在保持代码简洁且易于扩 ...
- python +Django 搭建web开发环境初步,显示当前时间
1.python 的安装 网上很多关于django跟python 开发的资料,这块我正在实习准备用这个两个合起来搞一个基于web 的东西出来现在开始学习,写点东西记录一下心得. 开发环境是window ...
- Flask从入门到精通之大型程序的结构二
一.程序包 程序包用来保存程序的所有代码.模板和静态文件.我们可以把这个包直接称为app(应用),如果有需求,也可使用一个程序专用名字.templates 和static 文件夹是程序包的一部分,因此 ...
随机推荐
- HDU 1073
http://acm.hdu.edu.cn/showproblem.php?pid=1073 模拟oj判题 随便搞,开始字符串读入的细节地方没处理好,wa了好久 #include <iostre ...
- crm--01
需求: 将课程名称与班级综合起来 class ClassListConfig(ModelSatrk): # 自定义显示方式 def display_class(self,obj=None,is_hea ...
- 【剑指offer15】二进制中1的个数(位运算),C++实现
原创博文,转载请注明出处! # 本文是牛客网<剑指offer>刷题笔记 1.题目 # 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示.例如,把9表示成二进制是1001,有两 ...
- Java 堆和栈 垃圾回收 2015/9/16
http://www.cnblogs.com/whgw/archive/2011/09/29/2194997.html Java内存: 1.堆内存:基本类型的变量和对象的引用变量. 2.栈内存:由ne ...
- cordova学习-基础篇
Cordova 学习笔记(一):快速开始 1.安装cordova Cordova安装可以通过node.js从npm上获取.npm install -g cordova 通过这个命令可以安装cordov ...
- angularJS 全选反选批量删除
<th> <label for="flag"> <span ng-hide="master">全选</span> ...
- Django 打印
转自:http://bbs.chinaunix.net/archiver/tid-1227401.html fentin 发表于 2008-07-28 17:52:44 请教Django Python ...
- 【angularJS】定义模块angular.module
模块定义了一个应用程序.控制器通常属于一个模块. JavaScript 中应避免使用全局函数.因为他们很容易被其他脚本文件覆盖. AngularJS 模块让所有函数的作用域在该模块下,避免了该问题. ...
- [Aizu2784]Similarity of Subtrees
vjudge Description 给一棵\(n\)个节点的有根树,定义两棵树同构当且仅当他们每个深度的节点个数相同.问这个树上有多少对子树满足同构.\(n\le100000\). sol 树\(h ...
- 使用vigil 监控微服务系统包含可视化界面
1. 安装 a. rust cargo cargo install vigil-server b. docker docker pull valeriansaliou/vigil:v1.3.0 2. ...