上一篇介绍了总体的设计和技术选型,这一篇将开始构建项目。

工厂函数

首先来构造工厂函数,编写app/__init__.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Yujichang

import os
import logging
import click
from flask import Flask, make_response
from flask_cors import CORS
from config import config
from app.extensions import db, migrate
from logging.handlers import RotatingFileHandler

basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))


def create_app(config_name=None):
    if config_name is None:
        config_name = os.getenv('FLASK_CONFIG', 'development')

    """ 使用工厂函数初始化程序实例"""
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_object(config[config_name])
    CORS(app, supports_credentials=True)

    db.init_app(app)
    migrate.init_app(app, db)

    register_logging(app)
    register_request_handlers(app)
    register_blueprints(app)
    register_shell_context(app)
    register_commands(app)

    return app


def register_logging(app):
    """
    配置日志信息
    """
    # ensure the logs folder exists
    try:
        os.makedirs(os.path.join(basedir, 'logs'))
    except OSError:
        pass

    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    file_handler = RotatingFileHandler(os.path.join(os.path.join(basedir, 'logs'), 'results.log'),
                                       maxBytes=10 * 1024 * 1024, backupCount=10)
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)

    if not app.debug:
        app.logger.addHandler(file_handler)


def register_request_handlers(app):
    @app.after_request
    def allow_cors(response):
        response = make_response(response)
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE'
        response.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type,authorization'

        return response


def register_blueprints(app):
    # 注册蓝本 api
    from .api import api as api_blueprint
    app.register_blueprint(api_blueprint, url_prefix='/api')

    # 注册蓝本 main
    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint, url_prefix='/user')


def register_commands(app):
    @app.cli.command()
    @click.option('--drop', is_flag=True, help='Create after drop.')
    def initdb(drop):
        """Initialize the database."""
        if drop:
            click.confirm('This operation will delete the database, do you want to continue?', abort=True)
            db.drop_all()
            click.echo('Drop tables.')
        db.create_all()
        click.echo('Initialized database.')


def register_shell_context(app):
    @app.shell_context_processor
    def make_shell_context():
        from app.models import Class, Student, Result, Statistics, class_student, User
        return dict(db=db, Class=Class, Student=Student, Result=Result, Statistics=Statistics, User=User,
                    class_student=class_student)

在工厂函数中添加了以下功能:

  • 使用了flask_cors来处理跨域问题。
  • 注册扩展模块SQLAlchemy、Migrate。
  • 添加了日志logger。
  • 注册蓝图main和api。
  • 注册cli command,通过comand初始化数据库。
  • 注册flask shell上下文,引入数据库模型。

配置文件

工厂函数调用配置文件,配置全局配置,编写config.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Yujichang

import os
import hashlib

basedir = os.path.abspath(os.path.dirname(__file__))


class BaseConfig(object):
    SECRET_KEY = os.getenv('SECRET_KEY') or hashlib.new(name='md5', data=b'you secret_key').hexdigest()

    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_COMMIT_ON_TEARDOWN = True
    SQLALCHEMY_RECORD_QUERIES = True
    SQLALCHEMY_ECHO = False

    ALLOWED_FILE_EXTENSIONS = ['xls', 'xlsx', 'csv', 'txt']
    FILE_UPLOAD_PATH = os.path.join(basedir, 'uploads')


class DevelopmentConfig(BaseConfig):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(os.path.join(basedir, 'instance'), 'data-dev.db')


class TestingConfig(BaseConfig):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'  # in-memory database


class ProductionConfig(BaseConfig):
    SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL',
                                        'sqlite:///' + os.path.join(os.path.join(basedir, 'instance'), 'data.db'))


config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig
}

主要配置了sqlite数据库的连接路径和相关参数,上传文件允许的后缀。

扩展文件

扩展文件主要是引入SQLAlchemy、Migrate,实现数据库的管理,编写app/exsentions.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Yujichang

from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()

构建蓝图

构建api蓝图,编写app/api/__init__.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Yujichang

from flask import Blueprint

api = Blueprint('api', __name__)

from . import errors, classes, students, results, statistics, auth

文件最后引入了classes、students、results、statistics等模块,将班级管理、学生管理、成绩管理、统计分析的路由添加到api蓝图里。

构建main蓝图,编写app/main/__init__.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Yujichang

from flask import Blueprint

main = Blueprint('main', __name__, template_folder="templates", static_url_path='', static_folder='static')

from . import view, errors

main蓝图是主路由,包含登陆和注册两个路由。

构建完配置文件、扩展文件、工厂函数、蓝图后,基本框架已构建完成,下一篇将整理数据库模型,初始化数据库。