Flask使用总结

  • Flask常见项目结构
  • flask程序编写流程
    • 编写配置文件configpy
    • 编写app初始化工厂函数在app中的init文件中
    • 编写我们的管理文件或者说启动文件managepy
    • 编写数据库模型在app中的modelspy中
    • 编写表单蓝本下的formspy中
    • 创建蓝本app下auth包中initpy文件
    • 编写视图viewspy

1. Flask常见项目结构


|-Flasky
    |app                    程序的应用模块,有且只有一个
        |-templates/        网页模板                    base.html       bootstrap/wtf.html | moment函数
        |-static/           静态文件                    js/css
        |-auth/
            |-__init__.py                               实例化验证蓝本auth
            |-errors.py
            |-forms.py                                  ValidationError/validate_email,validate_username
            |-views.py                                  @login_required login_user(user,form.remember.data) logout_user() current_user
        |-main/             main蓝本
            |-__init__.py   设置main为蓝本               Blueprint       flask_blueprint|导入views和errors
            |-errors.py     错误处理                     main.app_errorhander(404)
            |-forms.py      表单                         flask_wtf/wtforms/wtforms.validators  FlaskForm, StingFied,DataReqirede
            |-views.py      视图函数 main.route          flask        render_template, url_for,request,redirect,flash
        |-__init__.py       初始化app,并建立工厂函数,建立扩展     Bootstrap/Moment/Mail/SQLAlchemy/LoginManager
        |-email.py          邮箱发送函数                  Message(subject,sender,to), msg.body, msg.html/mail.send(msg)
        |-models.py         数据库                        werkzeug.security/generate_password_hash,check_password_hash   @login_manager.user_loader
    |-migrations/           数据迁移仓库                  init-migrate-upgrade
    |-tests/                测试模块
         |-__init__.py
         |-test*.py
    |-venv/                 虚拟环境                    virtualenv venv|. venv/bin/activate|pip freeze >requirements.txt|pip install -r requirements.txt
    |-requirements.txt      依赖包
    |-config.py             程序配置信息                SECRET_KEY/SQLALCHEMY_COMMIT_ON_TEARDOWN/BASE_DIR/SQLALCHEMY_DATABASE_URI
    |-manage.py             程序管理文件,实例化app.     Migrate/Manager add_command('db', MigrateCommand)  add_command('shell', Shell(make_context=make_shell_context))

2. flask程序编写流程

1. 编写配置文件config.py

import pymysql
import os

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

class Config():
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    SECRET_KEY = 'A232K3LJBFSLJ23O0VS024J3KSFKP'
    REDIS_URL = "redis://127.0.0.1:6379/0"

    # 上传文件目录配置
    UP_DIR = os.path.join(BASE_DIR, "static/uploads/")
    FC_DIR = os.path.join(BASE_DIR, "static/uploads/users/")

    # 配置邮箱信息
    MAIL_SERVER = 'smtp.126.com'
    MAIL_PORT = 25
    MAIL_USE_TLS = True

    # 以下两个一般设置在环境变量environ中,实现保密
    MAIL_USERNAME = '邮箱账号'
    MAIL_PASSWORD = '邮箱密码'

    @staticmethod
    def init_app(app):
        pass


class DevelopmentConfig(Config):
    DEBUG = True
    # 在flask中使用mysql
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:[email protected]:3306/movie_dev'

class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:[email protected]:3306/movie_test'

class ProductConfig(Config):
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:[email protected]:3306/movie_pro'


config = {
    'development':DevelopmentConfig,
    'testing':TestingConfig,
    'product':ProductConfig,
    'default': DevelopmentConfig
}

2. 编写app初始化工厂函数(在app中的init文件中)

from flask import Flask

# bootstrap对表单进行渲染
from flask_bootstrap import Bootstrap

# 发送邮件
from flask_mail import Mail

# 本地化时间
from flask_moment import Moment

# 引入封装好的数据库框架
from flask_sqlalchemy import SQLAlchemy

# from flask_redis import FlaskRdis
from config import config

from flask_login import LoginManager

bootstrap = Bootstrap()
moment = Moment()
mail = Mail()
db = SQLAlchemy()
redis = FlaskRdis()

login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'


# 工厂函数,初始化程序实例以及扩展
def creat_app(config_name='default'):
    app = Flask(__name__)
    # 通过对象配置app
    app.config.from_object(config[config_name])

    # 初始化所有flask扩展
    config[config_name].init_app(app)
    bootstrap.init_app(app)
    mail.init_app(app)
    moment.init_app(app)
    db.init_app(app)
    redis.init_app(app)
    login_manager.init_app(app)


    # 导入蓝本 main,建立联系,前者为包,后者为蓝本
    from .main import main
    app.register_blueprint(main)  

    from .auth import auth
    # 前缀url_prefix, 后缀url_suffix
    app.register_blueprint(auth, url_prefix='/auth')

    return app

3. 编写我们的管理文件或者说启动文件(manage.py)

from app import create_app,db
from flask_script import Manager,Shell
from flask_migrate import MigrateCommand,Migrate
from app.models import User

app = create_app('default')
manager= Manager(app)
migrate = Migrate(app,db)

def make_shell_context():
    return dict(app=app,db=db,User=User)

manager.add_command('db', MigrateCommand)
manager.add_command('shell', Shell(make_context=make_shell_context))


if __name__ == '__main__':
    manager.run()

# 在命令行terminal中,就可以使用
python manage.py db init 对数据库进行初始化,生成迁移目录
python manage.py db migrate 应用数据库迁移文件, 生成数据表
python manage.py db upgrade 当数据库模型中有改动时,使用此命令进行同步

4. 编写数据库模型(在app中的models.py中)

from . import db
from werkzeug.security import generate_password_hash,check_password_hash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
# 导入当前程序上下文
from flask import current_app
from flask_login import UserMixin
# 包含了一些用户方法的默认实现
from . import login_manager

class User(UserMixin,db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key = True)
    username = db.Column(db.String(64))
    email = db.Column(db.String(64))
    # password = db.Column(db.String(64))
    password_hash = db.Column(db.String(128))
    confirmed = db.Column(db.Boolean, default=False)
    comments = db.relationship('Comment', backref='user')  # 评论外键关系关联

    # 私有财产,将一个函数变成一个属性,后面不需要加括号
    @property
    def password(self):
        raise AttributeError('password is not a readable attribute!')

    # 可以写密码,但是不可以读
    @password.setter
    def password(self,pw):
        self.password_hash = generate_password_hash(pw)

    #  返回True Or False
    def verify_password(self,pw):
        return check_password_hash(self.password_hash, pw)

    def __repr__(self):
        return 'User: %r' % self.username

    # generate_confirmation_token() 方法生成一个令牌,有效期默认为一小时。
    def generate_confirmation_token(self, expiration=3600):
        # 生成token
        s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration)
        # 加密self.id
        return s.dumps({'confirm':self.id})

    def confirm(self,token):
        # 验证token,必须先生成token,和数据库中的token相同
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            # 解密token {'confirm':self.id}
            data = s.loads(token)
        except:
            return False
        # data就是之前生成的token,含有confirm.
        if data.get('confirm') != self.id:
            return False
        self.confirmed = True
        # 修改,把confirmed由默认的False改为True
        db.session.add(self)
        return True

    def generate_reset_token(self, expiration=3600):
        # 生成token
        s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration)
        # 加密self.id
        return s.dumps({'reset': self.id})

    def reset_pssword(self, token, newpassword):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('reset') != self.id:
            return False
        self.password = newpassword
        db.session.add(self)
        return True

    def generate_change_token(self,newemail,expiration=3600):
        s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration)
        return s.dumps({'change_email':self.id, 'new_email':newemail})

    def change_email(self,token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('change_email') != self.id:
            return False
        newemail = data.get("new_email")
        if newemail is None:
            return False
        self.email = newemail
        db.session.add(self)
        return True

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))
    # 因为Id是主键,所以可以直接使用get来获取对象







# 评论
class Comment(db.Model):
    __tablename__ = "comment"
    id = db.Column(db.Integer, primary_key=True)  # 编号
    content = db.Column(db.Text)  # 内容
    movie_id = db.Column(db.Integer, db.ForeignKey('movie.id'))  # 所属电影
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))  # 所属用户
    addtime = db.Column(db.DateTime, index=True, default=datetime.now())  # 添加时间

    def __repr__(self):
        return "" % self.id


# 电影
class Movie(db.Model):
    __tablename__ = "movie"
    id = db.Column(db.Integer, primary_key=True)  # 编号
    title = db.Column(db.String(255), unique=True)  # 标题
    url = db.Column(db.String(255), unique=True)  # 地址
    info = db.Column(db.Text)  # 简介
    logo = db.Column(db.String(255), unique=True)  # 封面
    star = db.Column(db.SmallInteger)  # 星级
    playnum = db.Column(db.BigInteger)  # 播放量
    commentnum = db.Column(db.BigInteger)  # 评论量
    
    area = db.Column(db.String(255))  # 上映地区
    release_time = db.Column(db.Date)  # 上映时间
    length = db.Column(db.String(100))  # 播放时间
    addtime = db.Column(db.DateTime, index=True, default=datetime.now())  # 添加时间
    comments = db.relationship("Comment", backref='movie')  # 评论外键关系关联
    

    def __repr__(self):
        return "" % self.title

5. 编写表单(蓝本下的forms.py中)

from flask_wtf import FlaskForm
from flask_sqlalchemy import SQLAlchemy

from wtforms import (
    StringField,
    PasswordField,
    SubmitField,
    FileField,
    TextField,
    TextAreaField,
    SelectField,
    SelectMultipleField
)

from wtforms.validators import (
    DataRequired,
    ValidationError,
    EqualTo,
)


class LoginForm(FlaskForm):
    """
    管理员登录表单
    """
    account = StringField(
        label="账号",
        validators=[
            DataRequired("请输入账号")
        ],
        description="账号",
        render_kw={
            "class":"form-contorl",
            "placeholder":"请输入账号!",
        }
    )
    pwd = PasswordField(
        label="密码",
        validators=[
            DataRequired("请输入密码!")
        ],
        description="密码",
        render_kw={
            "class":"form-control",
            "placeholder":"请输入密码!",
        }
    )
    submit = SubmitField(
        "登录",
        render_kw={
            "class": "btn btn-primary btn-block btn-flat",
        }
    )

    def validate_account(self, field):
        account = field.data
        admin = User.query.filter_by(name=account).count()
        if admin==0:
            raise ValidationError("账号不存在!")

# 其他字段:
url = FileField(
        label="文件",
        validators=[
            DataRequired("请上传文件!")
        ],
        description="文件",
    )

info = TextAreaField(
        label="简介",
        validators=[
            DataRequired("请输入简介!")
        ],
        description="简介",
        render_kw={
            "class":"form-control",
            "rows":10
        }
    )

star = SelectField(
        label="星级",
        validators=[
            DataRequired("请选择星级!")
        ],
        coerce=int, # 强制转化为int类型
        choices=[(1, '1星'),(2, '2星'),(3, '3星'),(4, '4星'),(5, '5星'),],
        description="星级",
        render_kw={
            "class":"form-control",
        }
    )

6. 创建蓝本(app下auth包中init.py文件)

from flask import Blueprint

admin = Blueprint("admin", __name__)

from . import views, errors

7. 编写视图views.py

# 1. 自己编写登录装饰器,退出函数
from functools import wraps
def admin_login_req(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if "admin" not in session:
            return redirect(url_for("admin.login", next=request.url))
        return f(*args, **kwargs)

    return decorated_function

# 退出
@admin.route("/logout/")
@admin_login_req
def logout():
    session.pop("admin", None)
    session.pop("admin_id", None)
    return redirect(url_for("admin.login"))    


# 2. 另一种方式就是使用系统自带的
from flask_login import login_required,logout_user,current_user,login_user


# 实际上为/auth/login/
@auth.route('/login/', methods=['POST', 'GET'])
def login():
    form = MyLogin()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user is not None and user.verify_password(form.password.data):
            # 常用的参数是两个,表示哪个用户登陆了
            login_user(user,form.remember.data)
            return redirect(request.args.get('next') or url_for('main.index'))
        flash('用户名或密码错误!')
        # 如果不清空form就是带着数据的form
        form.username.data = ''
    return render_template('auth/login.html', form=form)


@auth.route('/log_out/')
@login_required
def logout():
    # 注意,这个和login的区别是,没有参数
    logout_user()
    return redirect(url_for('main.index'))



@auth.route('/register/', methods=['GET','POST'])
def register():
    form = MyRegister()
    #  不断验证了字段内的验证条件,还验证了自定义的验证函数
    if form.validate_on_submit():
        # print(form.name.data, form.password.data, form.email.data)
        user = User(username=form.name.data,
            email=form.email.data,
            password=form.password.data)
        db.session.add(user)
        # 没有手动提交的话,数据库里面就没有该数据.就没有self.id.令牌是根据id生成的
        db.session.commit()
        token = user.generate_confirmation_token()
        sendMail(form.email.data, 'Confirm Your Acount', 'mail/confirm',user= current_user,token=token)
        flash('你可以登陆了!')
        return redirect(url_for('auth.login'))
    return render_template('auth/register.html', form=form)


# 让每一个没有激活的用户,让其激活,每一次请求都会执行此函数
@auth.before_app_request
def before_request():
    # 如果用户被激活
    # 用户已确认
    #  用户的断点为auth.
    if current_user.is_authenticated \
            and not current_user.confirmed\
            and request.endpoint\
            and request.endpoint[:5] != 'auth.':
        # 请求的端点(使用request.endpoint获取)不在认证蓝本中。
        # 实际上就是url_for函数的参数.
        print request.endpoint
        return redirect(url_for('auth.unconfirmed'))


@auth.route('/resetpassword/', methods=['POST','GET'])
def reset_password(token):
    # if current_user.is_anonymous:
    #     return redirect(url_for('main.index'))
    form = ResetPassword()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        # 使用该邮箱的用户不存在
        if user is None:
            flash('邮箱不存在,请重新输入!')
            return redirect(url_for('auth.reset_password_request'))
        # 密码重设,验证token和新的email
        if user.reset_pssword(token,form.newpassword.data):
            flash("你的密码已经重设成功!")
            return redirect(url_for('auth.login'))
        else:
            # token不正确或者是其他用户的/reset/乱输的
            return redirect(url_for('main.index'))
    return render_template('auth/resetpassword.html', form=form)


# 修改文件名称
from werkzeug.utils import secure_filename
import uuid
import datetime

def change_filename(filename):
    fileinfo = os.path.splitext(filename)
    filename = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + \
               str(uuid.uuid4().hex) + fileinfo[-1]
    return filename


@admin.route("/movie/add/", methods=["GET", "POST"])
@admin_login_req
def movie_add():
    form = MovieForm()
    if form.validate_on_submit():
        data = form.data
        file_url = secure_filename(form.url.data.filename)
        file_logo = secure_filename(form.logo.data.filename)
        if not os.path.exists(app.config["UP_DIR"]):
            os.makedirs(app.config["UP_DIR"])
            # os.chmod(app.config["UP_DIR"], "rw")
        url = change_filename(file_url)
        logo = change_filename(file_logo)
        form.url.data.save(app.config["UP_DIR"+url])
        form.logo.data.save(app.config["UP_DIR" + logo])
        movie = Movie(
            title = data["title"],
            url=url,
            info =data["info"],
            logo =logo,
            star = int(data["star"]),
            playnum = 0,
            commentnum = 0,
            tag_id = int(data["tag_id"]),
            area = data["area"],
            release_time = data["release_time"],
            length = data["length"]
        )
        db.session.add(movie)
        db.session.commit()
        flash("添加电影成功!", "ok")
        return redirect(url_for("admin.movieadd"))
    return render_template("admin/movie_add.html", form=form)


# 电影列表
@admin.route("/movie/list//", methods=["GET"])
@admin_login_req
def movie_list(page=None):
    if page is None:
        page = 1
    page_data = Movie.query.join(Tag).filter(
        Tag.id == Movie.tag_id
    ).order_by(
        Movie.addtime.desc()
    ).paginate(page=page, per_page=10)
    return render_template("admin/movie_list.html", page_data=page_data)

你可能感兴趣的:(Flask)