博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
7.1 大型程序的结构
阅读量:4095 次
发布时间:2019-05-25

本文共 7607 字,大约阅读时间需要 25 分钟。

——————————————————————前言————————————————————————————

尽管在单一脚本文件中编写小型web程序很方便, 但是程序变复杂后, 使用单个大型源码文件会导致很多问题。

Flask并不要求大型项目使用特定的组织方式, 程序结构的组织方式完全由开发者决定。

本节我们介绍一种使用包和模块组织大型程序的方式。

————————————————————————————————————————————————————

|-flasky

  |-app/  #web程序相关的内容在app包里,包括以下内容:

    |-templates/  #响应返回的模板

    |-static/  #响应返回的静态文件

    |-main/  #蓝本

      |-__init__.py

      |-errors.py

      |-forms.py

      |-views.py

    |-__init__.py 

    |-email.py  #发送邮件的函数:发送邮件也是web程序的一个功能

    |-models.py  #数据库中的表, 也是web程序的一部分

  |-migrations/  #迁移仓库和迁移脚本都在该包里

  |-tests/  #测试包

    |-__init__.py

    |-test*.py

  |-venv/  #虚拟环境

  |-requirements.txt   #安装扩展的名称和版本号

  |-config.py   #程序配置脚本

  |-manage.py  #程序启动脚本

后面带有'/'的都是文件夹, 在flasky文件夹中包括7部分, 下面我们来逐一介绍:

一. config.py

    在之前的hello.py脚本中, 我们是用config字典来存储程序实例的配置, 现在我们改用配置类:

import osbasedir = os.path.abspath(os.path.dirname(__file__))  #返回本脚本所在目录class Config(object):  #配置基类: 通用配置    #设置密钥, web表单防止CSRF攻击    SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'    #请求后数据库的改动会自动提交    SQLALCHEMY_COMMIT_ON_TEARDOWN = True    #邮件通用设置    FLASKY_MAIL_SUBJECT_PREFIX = '[FLASK]'    FLASKY_MAIL_SENDER = 1660705191@qq.com    FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')#为不同的开发环境设置不同的数据库class DevelopmentConfig(Config):  #开发配置类    MAIL_SERVER = 'smtp.qq.com'    MAIL_PORT = 587    MAIL_USE_TLS = True    MAIL_USERNAME = '1896785231@qq.com'    MAIL_PASSWORD = 'zwfafgudahfalehic'    SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or\'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')class TestingConfig(Config):  #测试配置类    TESTING = True    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or\'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')class ProductionConfig(Config):  #生产配置类    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or\'sqlite:///' + os.path.join(basedir, 'data.sqlite')config = {  #配置字典'development': DevelopmentConfig,'testing': TestingConfig,'production': ProductionConfig,'default': DevelopmentConfig}

二. |-app/

 1. |-app/__init__.py  #提供创建app的函数并且初始化一些扩展

from flask import Flaskfrom config import configfrom flask_bootstrap import Bootstrapfrom flask_mail import Mailfrom flask_moment import Momentfrom flask_sqlalchemy import SQLAlchemybootstrap = Bootstrap()  #先创建扩展的实例mail = Mail()moment = Moment()db = SQLAlchemy()def create_app(config_name):    app = Flask(__name__)  #创建程序实例    app.config.from_object(config[config_name])  #本句作用是设置所有的config变量    config[config_name].init_app(app)    bootstrap.init_app(app)  #初始化扩展    mail.init_app(app)    moment.init_app(app)    db.init_app(app)    return app

 2. |-app/email.py

from flask_mail import Messagefrom flask import current_app, render_templatefrom . import maildef send_email(subject, to, template, **kwargs):    msg =Message(current_app.config['FLASKY_MAIL_SUBJECT_PREFIX'], sender=current_app.config['FLASKY_MAIL_SENDER']), recipients=[to]  #创建邮件    msg.body = render_template(template + '.txt', **kwargs)  #设置邮件    msg.html = render_template(template + '.html', **kwargs)    mail.send(msg)  #发送邮件

 3. |-app/models.py

from . import dbclass Role(db.Model):  #角色表    __tablename__ = 'roles'    id = db.Column(db.Integer, primary_key=True)    name = db.Column(db.String(64), unique=True)    users = db.relationship('User', backref='role', lazy='dynamic') #1    def __repr__(self):        return '
' %self.nameclass User(db.Model): #用户表 __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) roles_id = db.Column(db.Integer, db.ForeignKey('roles.id')) #2 def __repr__(self): return '
' %self.username

1, 2两句表示一对多关系, 上节已有介绍。

 4. |-app/main/

  1) |-app/main/__init__.py  #定义蓝本

from flask import Blueprintmain = Blueprint('main', __name__)  #定义蓝本, 第一个参数为蓝本名, 第二个参数代码所在模块或者包from . import views. errors  #把蓝本与路由和错误处理程序关联起来

关联起来以后, 再把蓝本注册到程序实例上, 路由就注册到了程序实例上。

和在单一脚本文件中编写小型web程序不同, 我们只有在调用create_app后程序实例app才存在, 此时再用app定义路由显然来不及, 这是我们可以用蓝本定义路由, 路由处于休眠状态, 然后再在create_app函数中把蓝本注册到程序实例app上, 路由就注册到了程序实例上:

def create_app(config_name):    #...    from .main import main as main_blueprint    app.register_blueprint(main_blueprint)    return app

  2) |-app/main/errors.py

from flask import render_templatefrom . import main@main.app_errorhandler(404)  #404错误处理程序def page_not_found(e):    return render_template('404.html'), 404@main.app_errorhandler(500)  #500错误处理程序def internal_server_error(e):    return render_template('500.html'), 500

  3) |-app/main/views.py

from . import mainfrom . forms import NameFormfrom ..models import Userfrom .. import dbfrom ..email import send_mailfrom flask import session, render_template, url_for, redirect, current_app@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 not user:            user = User(form.name.data)            db.session.add(user)            session['known'] = False            if current_app.config['FLASKY_ADMIN']:                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('main.index'))    return render_template('index.html', name=session.get('name'), known=session.get('known'), form=form)

    蓝本定义路由时有两点不同, 一是@main.route('/', methods=['GET', 'POST']), 二是url_for函数的参数, 原来是'index', 现在要加上命名空间——蓝本名(ps:定义蓝本的第一个参数)

   

  4) |-app/main/forms.py

from flask_wtf import FlaskFormfrom wtforms import StringField, SubmitFieldfrom wtforms.validators import DataRequiredclass NameForm(FlaskForm):  #web表单定义    name = StringField('what is your name?', validators=[DataRequired()])  #文本框, 第二个参数是该字段的验证函数    submit = SubmitField('submit')  #提交按钮

三. manage.py  #创建程序实例app, 初始化flask-script, flask-migrate扩展

from app import create_app, dbfrom app.models import User, Rolefrom flask_script import Manager, Shellfrom flask_migrate import Migrate, MigrateCommandapp = create_app( os.environ.get('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():    """Run the unit test."""    import unittest    tests = unittest.TestLoader.discover('tests')    unittest.TestRunner(verbosity=2).run(tests)if __name__=='__main__':    manager.run()

四. requirements.txt  #存储我们下载的扩展及其版本号

 1. 生成该文件

&pip freeze >requirements.txt

 2. 文件内容是我们下载的扩展及其版本号

 3.此文件的作用是, 当我们想创建其它的web程序并且刚好用到这些扩展, 我们可以在该web程序的虚拟环境下执行:

&pip install -r requirements.txt

由此创建之前虚拟环境的完全副本。

五. |-tests/

 1.|-tests/test_basics.py

import unittestfrom app import create_app, dbfrom flask import current_appclass BasicsTestCase(unittest.TestCase):    def setUp(self):  #测试函数执行前执行:创建程序实例, 推送上下文, 创建数据库        self.app = create_app('testing')        self.app_context = self.app.app_context()        self.app_context.push()        db.create_all()    def tearDown(self):  #测试函数执行完毕后执行, 删除数据库, 退出上下文        db.session.remove()        db.drop_all()        self.app_context.pop()    def test_app_exists(self):  #以test开头的都是测试函数        self.assertFalse(current_app is None)    def test_app_is_testing(self):        self.assertTrue(TESTING)

 2. |-test/__init__.py

可以为空, 存在是test才是包, 不存在test只是一个普通的文件夹。

 3. 执行测试

& python manage.py test

——————————————————————————结束语———————————————————————

到现在我们已经学到了使用Flask开发Web程序的必备基础知识。

之后我们要解决的问题就是如何把这些知识融贯起来开发一个真正的程序。

你可能感兴趣的文章
No devices detected. Fatal server error: no screens found
查看>>
db db2_monitorTool IBM Rational Performace Tester
查看>>
postgresql监控工具pgstatspack的安装及使用
查看>>
【JAVA数据结构】双向链表
查看>>
【JAVA数据结构】先进先出队列
查看>>
谈谈加密和混淆吧[转]
查看>>
乘法逆元
查看>>
Objective-C 基础入门(一)
查看>>
找工作准备的方向(4月22日写的)
查看>>
关于fwrite写入文件后打开查看是乱码的问题
查看>>
用结构体指针前必须要用malloc,不然会出现段错误
查看>>
Linux系统中的美
查看>>
一些实战项目(linux应用层编程,多线程编程,网络编程)
查看>>
STM32CubeMX 真的不要太好用
查看>>
不要买铝合金机架的无人机,不耐摔,易变形弯曲。
查看>>
ACfly也是基于FreeRTOS的
查看>>
carzepony也在想往FreeRTOS上迁移
查看>>
我发觉,不管是弄ROS OPENCV T265二次开发 SDK开发 caffe PX4 都是用的C++
查看>>
ROS的安装(包含文字和视频教程,我的ROS安装教程以这篇为准)
查看>>
原来我之前一直用的APM固件....现在很多东西明白了。
查看>>