7.1 大型程序的结构

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

尽管在单一脚本文件中编写小型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 os

basedir = 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 = [email protected]

    FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')

#为不同的开发环境设置不同的数据库

class DevelopmentConfig(Config):  #开发配置类

    MAIL_SERVER = 'smtp.qq.com'

    MAIL_PORT = 587

    MAIL_USE_TLS = True

    MAIL_USERNAME = '[email protected]'

    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 Flask

from config import config

from flask_bootstrap import Bootstrap

from flask_mail import Mail

from flask_moment import Moment

from flask_sqlalchemy import SQLAlchemy


bootstrap = 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 Message

from flask import current_app, render_template

from . import mail

def 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 db

class 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.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)

    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 Blueprint

main = 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_template

from . 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 main

from . forms import NameForm

from ..models import User

from .. import db

from ..email import send_mail

from 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 FlaskForm

from wtforms import StringField, SubmitField

from wtforms.validators import DataRequired

class 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, db

from app.models import User, Role

from flask_script import Manager, Shell

from flask_migrate import Migrate, MigrateCommand


app = 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. 文件内容是我们下载的扩展及其版本号

7.1 大型程序的结构_第1张图片

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

&pip install -r requirements.txt

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

五. |-tests/

 1.|-tests/test_basics.py

import unittest

from app import create_app, db

from flask import current_app

class 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

7.1 大型程序的结构_第2张图片


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

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

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


你可能感兴趣的:(flask,web,flask,web开发)