作为一个编程入门新手,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">&times;</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开发 大型程序的结构实例解析的更多相关文章

  1. pycharm+python+Django之web开发环境的搭建(windows)

    转载:https://blog.csdn.net/yjx2323999451/article/details/53200243/ pycharm+python+Django之web开发环境的搭建(wi ...

  2. BEGINNING SHAREPOINT&#174; 2013 DEVELOPMENT 第3章节--SharePoint 2013 开发者工具 SharePoint中基于Web开发

    BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第3章节--SharePoint 2013 开发者工具 SharePoint中基于Web开发         之前提到过, ...

  3. 使用eclipse搭建第一个python+Django的web开发实例

    python+Django的web开发实例   一.创建一个项目如果这是你第一次使用Django,那么你必须进行一些初始设置.也就是通过自动生成代码来建立一个Django项目--一个Django项目的 ...

  4. python——flask常见接口开发(简单案例)

    python——flask常见接口开发(简单案例)原创 大蛇王 发布于2019-01-24 11:34:06 阅读数 5208 收藏展开 版本:python3.5+ 模块:flask 目标:开发一个只 ...

  5. Flask从入门到精通之大型程序的结构一

    尽管在单一脚本中编写小型Web 程序很方便,但这种方法并不能广泛使用.程序变复杂后,使用单个大型源码文件会导致很多问题.不同于大多数其他的Web 框架,Flask 并不强制要求大型项目使用特定的组织方 ...

  6. 【Python】【Web开发】

    # [[Web开发]] ''' 最早的软件都是运行在大型机上的,软件使用者通过“哑终端”登陆到大型机上去运行软件.后来随着PC机的兴起,软件开始主要运行在桌面上,而数据库这样的软件运行在服务器端,这种 ...

  7. Python flask模块接口开发学习总结

    引言 Flask 是一个简单且十分强大的Python web 框架.它被称为微框架,“微”并不是意味着把整个Web应用放入到一个Python文件,微框架中的“微”是指Flask旨在保持代码简洁且易于扩 ...

  8. python +Django 搭建web开发环境初步,显示当前时间

    1.python 的安装 网上很多关于django跟python 开发的资料,这块我正在实习准备用这个两个合起来搞一个基于web 的东西出来现在开始学习,写点东西记录一下心得. 开发环境是window ...

  9. Flask从入门到精通之大型程序的结构二

    一.程序包 程序包用来保存程序的所有代码.模板和静态文件.我们可以把这个包直接称为app(应用),如果有需求,也可使用一个程序专用名字.templates 和static 文件夹是程序包的一部分,因此 ...

随机推荐

  1. Android虚拟、实体键盘不能同时使用?

    /****************************************************************************** * Android虚拟.实体键盘不能同时 ...

  2. JAVA多线程----用--死锁

    (1) 互斥条件:一个资源每次只能被一个进程使用.(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放.(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺.(4) ...

  3. TOF 初探

    TOF 简介 TOF是Time of flight的简写,直译为飞行时间的意思.所谓飞行时间法3D成像,是通过给目标连续发送光脉冲,然后用传感器接收从物体返回的光,通过探测光脉冲的飞行(往返)时间来得 ...

  4. Java 多线程 2015/9/21

    http://lavasoft.blog.51cto.com/62575/27069   http://blog.csdn.net/aboy123/article/details/38307539   ...

  5. ios 延迟调用 && UIImageView && UILabel && UISegmentedControl && UISwitch && UISlider

    // //  ViewController.m //  UI_Lesson3 // //  Created by archerzz on 15/8/13. //  Copyright (c) 2015 ...

  6. 值类型struct在foreach中的陷阱

    最近踩了一个坑,为了优化代码,把class改为了struct,结果发现原来的初始化语句没有预期的运行,伪代码如下: public struct A { bool _isActive; public v ...

  7. Could Not Launch Appium Inspector

    环境: macOS High Sierra 10.13.2 appium GUI 1.5.3 出现如上图报错时,尝试将App Path和Device Name勾选,如下图:

  8. 利用 ReSharper 自定义代码中的错误模式,在代码审查之前就发现并修改错误

    多人协作开发的项目总会遇到代码编写风格上的差异.一般工具都能帮我们将常见的差异统一起来——例如 if 的换行:但也有一些不那么通用,但项目中却经常会出现的写法也需要统一. 例如将单元测试中的 Asse ...

  9. ubuntu下Python的安装和使用

    版权声明 更新:2017-04-13-上午博主:LuckyAlan联系:liuwenvip163@163.com声明:吃水不忘挖井人,转载请注明出处! 1 文章介绍 本文介绍了Python的开发环境. ...

  10. java和c++中的DES\3DES\Base64

    首先来看一段java中对字符串加解密的代码: //密钥 private String key = "123456789012345678901234"; //解密过程,先用Base ...