Flask

1. 搭建项目

  • 简单demo搭建

    from flask import Flask
    
    app = Flask(__name__)  # 更多参数查看源码
    
    # 加载与获取配置
    app.config.from_object(Config)
    # app.config.from_pyfile('config.py')
    # app.config.from_envar('ENVCONFIG')
    debug_config = app.config['DEBUG']
    
    @app.route('/haha')
    def hello_world():
        return "helloworld!"
    
    
    if __name__ == '__main__':
        app.run(host=127.0.0.1 ,port=8888, debug=True)  # 三个均为缺省参数
    
  • 大型项目目录结构

    • /.gitignore
    • /.config.py
    • /manage.py
    • /requirements.txt
    • /logs/xxx
    • /app
      • libs
      • modules
        • admin
          • __init__.py
          • views.py
      • static
      • templates
      • utils
      • models.py
      • constants.py
      • __init__.py
  • 大型项目搭建

    /.gitignore

    .idea/
    *.py[cod]
    logs/log*
    migrations
    .qiniu*
    

    /.manage.py

    from flask_script import Manager
    from flask_migrate import Migrate, MigrateCommand
    from info import create_app, db, models
    
    # manage.py是程序启动的入口,只关心启动的相关参数以及内容,不关心具体该
    # 如果创建app或者相关业务逻辑
    
    # 通过指定的配置名字创建对应配置的app
    # create_app 就类似于工厂方法
    from info.models import User
    
    app = create_app('development')
    
    manager = Manager(app)
    # 将 app 与 db 关联
    Migrate(app, db)
    # 将迁移命令添加到manager中
    manager.add_command('db', MigrateCommand)
    
    
    @manager.option('-n', '-name', dest="name")
    @manager.option('-p', '-password', dest="password")
    def createsuperuser(name, password):
    
        if not all([name, password]):
            print("参数不足")
    
        user = User()
        user.nick_name = name
        user.mobile = name
        user.password = password
        user.is_admin = True
    
        try:
            db.session.add(user)
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            print(e)
    
        print("添加成功")
    
    
    if __name__ == '__main__':
        manager.run()
    

    /config.py

    import logging
    from redis import StrictRedis
    
    
    class Config(object):
        """项目的配置"""
    
        SECRET_KEY = "iECgbYWReMNxkRprrzMo5KAQYnb2UeZ3bwvReTSt+VSESW0OB8zbglT+6rEcDW9X"
    
        # 为数据库添加配置
        SQLALCHEMY_DATABASE_URI = "mysql://root:mysql@127.0.0.1:3306/information27"
        SQLALCHEMY_TRACK_MODIFICATIONS = False
        # 在请求结束时候,如果指定此配置为 True ,那么 SQLAlchemy 会自动执行一次 db.session.commit()操作
        SQLALCHEMY_COMMIT_ON_TEARDOWN = True
    
        # Redis的配置
        REDIS_HOST = "127.0.0.1"
        REDIS_PORT = 6379
    
        # Session保存配置
        SESSION_TYPE = "redis"
        # 开启session签名
        SESSION_USE_SIGNER = True
        # 指定 Session 保存的 redis
        SESSION_REDIS = StrictRedis(host=REDIS_HOST, port=REDIS_PORT)
        # 设置需要过期
        SESSION_PERMANENT = False
        # 设置过期时间
        PERMANENT_SESSION_LIFETIME = 86400 * 2
    
        # 设置日志等级
        LOG_LEVEL = logging.DEBUG
    
    
    class DevelopmentConfig(Config):
        """开发环境下的配置"""
        DEBUG = True
    
    
    class ProductionConfig(Config):
        """生产环境下的配置"""
        DEBUG = False
        LOG_LEVEL = logging.WARNING
    
    
    class TestingConfig(Config):
        """单元测试环境下的配置"""
        DEBUG = True
        TESTING = True
    
    
    config = {
        "development": DevelopmentConfig,
        "production": ProductionConfig,
        "testing": TestingConfig
    }
    

    requirements.txt

    alembic==0.9.9
    blinker==1.4
    certifi==2018.4.16
    chardet==3.0.4
    Flask==0.10.1
    Flask-Migrate==2.1.1
    Flask-MySQLdb==0.2.0
    Flask-Script==2.0.6
    Flask-Session==0.3.1
    Flask-SQLAlchemy==2.3.2
    Flask-WTF==0.14.2
    idna==2.6
    itsdangerous==0.24
    Jinja2==2.10
    Mako==1.0.7
    MarkupSafe==1.0
    mysqlclient==1.3.12
    Pillow==5.1.0
    python-dateutil==2.7.2
    python-editor==1.0.3
    qiniu==7.2.0
    redis==2.10.6
    redis-py-cluster==1.3.4
    requests==2.18.4
    six==1.11.0
    SQLAlchemy==1.2.6
    urllib3==1.22
    Werkzeug==0.14.1
    WTForms==2.1
    

    app/__init__.py

    import logging
    from logging.handlers import RotatingFileHandler
    
    from flask import Flask
    # 可以用来指定 session 保存的位置
    from flask import g
    from flask import render_template
    from flask.ext.session import Session
    from flask.ext.sqlalchemy import SQLAlchemy
    from flask.ext.wtf import CSRFProtect
    from flask.ext.wtf.csrf import generate_csrf
    from redis import StrictRedis
    
    from config import config
    
    db = SQLAlchemy()
    
    # https://www.cnblogs.com/xieqiankun/p/type_hints_in_python3.html
    redis_store = None  # type: StrictRedis
    
    
    # redis_store: StrictRedis = None
    
    
    def setup_log(config_name):
        # 设置日志的记录等级
        logging.basicConfig(level=config[config_name].LOG_LEVEL)  # 调试debug级
        # 创建日志记录器,指明日志保存的路径、每个日志文件的最大大小、保存的日志文件个数上限
        file_log_handler = RotatingFileHandler("logs/log", maxBytes=1024 * 1024 * 100, backupCount=10)
        # 创建日志记录的格式 日志等级 输入日志信息的文件名 行数 日志信息
        formatter = logging.Formatter('%(levelname)s %(filename)s:%(lineno)d %(message)s')
        # 为刚创建的日志记录器设置日志记录格式
        file_log_handler.setFormatter(formatter)
        # 为全局的日志工具对象(flask app使用的)添加日志记录器
        logging.getLogger().addHandler(file_log_handler)
    
    
    def create_app(config_name):
        # 配置日志,并且传入配置名字,以便能获取到指定配置所对应的日志等级
        setup_log(config_name)
        # 创建Flask对象
        app = Flask(__name__)
        # 加载配置
        app.config.from_object(config[config_name])
        # 通过app初始化
        db.init_app(app)
        # 初始化 redis 存储对象
        global redis_store
        redis_store = StrictRedis(host=config[config_name].REDIS_HOST, port=config[config_name].REDIS_PORT,
                                  decode_responses=True)
        # 开启当前项目 CSRF 保护,只做服务器验证功能
        # 帮我们做了:从cookie中取出随机值,从表单中取出随机,然后进行校验,并且响应校验结果
        # 我们需要做:1. 在返回响应的时候,往cookie中添加一个csrf_token,2. 并且在表单中添加一个隐藏的csrf_token
        # 而我们现在登录或者注册不是使用的表单,而是使用 ajax 请求,所以我们需要在 ajax 请求的时候带上 csrf_token 这个随机值就可以了
        CSRFProtect(app)
        # 设置session保存指定位置
        Session(app)
    
        # 初始化数据库
        #  在Flask很多扩展里面都可以先初始化扩展的对象,然后再去调用 init_app 方法去初始化
        from info.utils.common import do_index_class
        # 添加自定义过滤器
        app.add_template_filter(do_index_class, "index_class")
    
        from info.utils.common import user_login_data
    
        @app.errorhandler(404)
        @user_login_data
        def page_not_fount(e):
            user = g.user
            data = {"user": user.to_dict() if user else None}
            return render_template('news/404.html', data=data)
    
        @app.after_request
        def after_request(response):
            # 生成随机的csrf_token的值
            csrf_token = generate_csrf()
            # 设置一个cookie
            response.set_cookie("csrf_token", csrf_token)
            return response
    
        # 注册蓝图
        from info.modules.index import index_blu
        app.register_blueprint(index_blu)
    
        from info.modules.passport import passport_blu
        app.register_blueprint(passport_blu)
    
        from info.modules.news import news_blu
        app.register_blueprint(news_blu)
    
        from info.modules.profile import profile_blu
        app.register_blueprint(profile_blu)
    
        from info.modules.admin import admin_blu
        app.register_blueprint(admin_blu, url_prefix="/admin")
    
        return app
    

    /app/models.py

    from datetime import datetime
    from werkzeug.security import generate_password_hash, check_password_hash
    
    from info import constants
    from . import db
    
    
    class BaseModel(object):
        """模型基类,为每个模型补充创建时间与更新时间"""
        create_time = db.Column(db.DateTime, default=datetime.now)  # 记录的创建时间
        update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)  # 记录的更新时间
    
    
    # 用户收藏表,建立用户与其收藏新闻多对多的关系
    tb_user_collection = db.Table(
        "info_user_collection",
        db.Column("user_id", db.Integer, db.ForeignKey("info_user.id"), primary_key=True),  # 新闻编号
        db.Column("news_id", db.Integer, db.ForeignKey("info_news.id"), primary_key=True),  # 分类编号
        db.Column("create_time", db.DateTime, default=datetime.now)  # 收藏创建时间
    )
    
    tb_user_follows = db.Table(
        "info_user_fans",
        db.Column('follower_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True),  # 粉丝id
        db.Column('followed_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True)  # 被关注人的id
    )
    
    
    class User(BaseModel, db.Model):
        """用户"""
        __tablename__ = "info_user"
    
        id = db.Column(db.Integer, primary_key=True)  # 用户编号
        nick_name = db.Column(db.String(32), unique=True, nullable=False)  # 用户昵称
        password_hash = db.Column(db.String(128), nullable=False)  # 加密的密码
        mobile = db.Column(db.String(11), unique=True, nullable=False)  # 手机号
        avatar_url = db.Column(db.String(256))  # 用户头像路径
        last_login = db.Column(db.DateTime, default=datetime.now)  # 最后一次登录时间
        is_admin = db.Column(db.Boolean, default=False)
        signature = db.Column(db.String(512))  # 用户签名
        gender = db.Column(  # 订单的状态
            db.Enum(
                "MAN",  # 男
                "WOMAN"  # 女
            ),
            default="MAN")
    
        # 当前用户收藏的所有新闻lazy="dynamic"
        collection_news = db.relationship("News", secondary=tb_user_collection, lazy="dynamic")  # 用户收藏的新闻
        # 用户所有的粉丝,添加了反向引用followed,代表用户都关注了哪些人
        followers = db.relationship('User',
                                    secondary=tb_user_follows,
                                    primaryjoin=id == tb_user_follows.c.followed_id,
                                    secondaryjoin=id == tb_user_follows.c.follower_id,
                                    backref=db.backref('followed', lazy='dynamic'),
                                    lazy='dynamic')
    
        @property
        def password(self):
            raise AttributeError("当前属性不允许读取")
    
        @password.setter
        def password(self, value):
            # self.password_hash = 对value加密
            self.password_hash = generate_password_hash(value)
    
        def check_password(self, password):
            """校验密码"""
            return check_password_hash(self.password_hash, password)
    
        # 当前用户所发布的新闻
        news_list = db.relationship('News', backref='user', lazy='dynamic')
    
        def to_dict(self):
            resp_dict = {
                "id": self.id,
                "nick_name": self.nick_name,
                "avatar_url": constants.QINIU_DOMIN_PREFIX + self.avatar_url if self.avatar_url else "",
                "mobile": self.mobile,
                "gender": self.gender if self.gender else "MAN",
                "signature": self.signature if self.signature else "",
                "followers_count": self.followers.count(),
                "news_count": self.news_list.count()
            }
            return resp_dict
    
        def to_admin_dict(self):
            resp_dict = {
                "id": self.id,
                "nick_name": self.nick_name,
                "mobile": self.mobile,
                "register": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "last_login": self.last_login.strftime("%Y-%m-%d %H:%M:%S"),
            }
            return resp_dict
    
    
    class News(BaseModel, db.Model):
        """新闻"""
        __tablename__ = "info_news"
    
        id = db.Column(db.Integer, primary_key=True)  # 新闻编号
        title = db.Column(db.String(256), nullable=False)  # 新闻标题
        source = db.Column(db.String(64), nullable=False)  # 新闻来源
        digest = db.Column(db.String(512), nullable=False)  # 新闻摘要
        content = db.Column(db.Text, nullable=False)  # 新闻内容
        clicks = db.Column(db.Integer, default=0)  # 浏览量
        index_image_url = db.Column(db.String(256))  # 新闻列表图片路径
        category_id = db.Column(db.Integer, db.ForeignKey("info_category.id"))
        user_id = db.Column(db.Integer, db.ForeignKey("info_user.id"))  # 当前新闻的作者id
        status = db.Column(db.Integer, default=0)  # 当前新闻状态 如果为0代表审核通过,1代表审核中,-1代表审核不通过
        reason = db.Column(db.String(256))  # 未通过原因,status = -1 的时候使用
        # 当前新闻的所有评论
        comments = db.relationship("Comment", lazy="dynamic")
    
        def to_review_dict(self):
            resp_dict = {
                "id": self.id,
                "title": self.title,
                "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "status": self.status,
                "reason": self.reason if self.reason else ""
            }
            return resp_dict
    
        def to_basic_dict(self):
            resp_dict = {
                "id": self.id,
                "title": self.title,
                "source": self.source,
                "digest": self.digest,
                "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "index_image_url": self.index_image_url,
                "clicks": self.clicks,
            }
            return resp_dict
    
        def to_dict(self):
            resp_dict = {
                "id": self.id,
                "title": self.title,
                "source": self.source,
                "digest": self.digest,
                "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "content": self.content,
                "comments_count": self.comments.count(),
                "clicks": self.clicks,
                "category": self.category.to_dict(),
                "index_image_url": self.index_image_url,
                "author": self.user.to_dict() if self.user else None
            }
            return resp_dict
    
    
    class Comment(BaseModel, db.Model):
        """评论"""
        __tablename__ = "info_comment"
    
        id = db.Column(db.Integer, primary_key=True)  # 评论编号
        user_id = db.Column(db.Integer, db.ForeignKey("info_user.id"), nullable=False)  # 用户id
        news_id = db.Column(db.Integer, db.ForeignKey("info_news.id"), nullable=False)  # 新闻id
        content = db.Column(db.Text, nullable=False)  # 评论内容
        parent_id = db.Column(db.Integer, db.ForeignKey("info_comment.id"))  # 父评论id
        parent = db.relationship("Comment", remote_side=[id])  # 自关联
        like_count = db.Column(db.Integer, default=0)  # 点赞条数
    
        def to_dict(self):
            resp_dict = {
                "id": self.id,
                "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "content": self.content,
                "parent": self.parent.to_dict() if self.parent else None,
                "user": User.query.get(self.user_id).to_dict(),
                "news_id": self.news_id,
                "like_count": self.like_count
            }
            return resp_dict
    
    
    class CommentLike(BaseModel, db.Model):
        """评论点赞"""
        __tablename__ = "info_comment_like"
        comment_id = db.Column("comment_id", db.Integer, db.ForeignKey("info_comment.id"), primary_key=True)  # 评论编号
        user_id = db.Column("user_id", db.Integer, db.ForeignKey("info_user.id"), primary_key=True)  # 用户编号
    
    
    class Category(BaseModel, db.Model):
        """新闻分类"""
        __tablename__ = "info_category"
    
        id = db.Column(db.Integer, primary_key=True)  # 分类编号
        name = db.Column(db.String(64), nullable=False)  # 分类名
        news_list = db.relationship('News', backref='category', lazy='dynamic')
    
        def to_dict(self):
            resp_dict = {
                "id": self.id,
                "name": self.name
            }
            return resp_dict
    
    

    constants.py

    # 图片验证码Redis有效期, 单位:秒
    IMAGE_CODE_REDIS_EXPIRES = 300
    
    # 短信验证码Redis有效期,单位:秒
    SMS_CODE_REDIS_EXPIRES = 300
    
    # 七牛空间域名
    QINIU_DOMIN_PREFIX = "http://oyucyko3w.bkt.clouddn.com/"
    
    # 首页展示最多的新闻数量
    HOME_PAGE_MAX_NEWS = 10
    
    # 用户的关注每一页最多数量
    USER_FOLLOWED_MAX_COUNT = 4
    
    # 用户收藏显示最多新闻数量
    USER_COLLECTION_MAX_NEWS = 10
    
    # 其他用户每一页最多新闻数量
    OTHER_NEWS_PAGE_MAX_COUNT = 10
    
    # 点击排行展示的最多新闻数据
    CLICK_RANK_MAX_NEWS = 6
    
    # 管理员页面用户每页多最数据条数
    ADMIN_USER_PAGE_MAX_COUNT = 10
    
    # 管理员页面新闻每页多最数据条数
    ADMIN_NEWS_PAGE_MAX_COUNT = 10
    

2. 路由分发

  • Get

    # 用装饰器定义定义
    @app.route('/')
    def index():
        return 'index'
    
    
  • Post

    @app.route('/', methods=['POST'])
    def index():
        return 'index'
    
    

3. 获取请求参数

3.1 请求

  • request

    在视图函数中直接使用代表当前本次请求 from flask import request

  • request可获得的属性

    data, form, args, cookies, headers, method, url, files

3.1 路径参数

  • 路由定义 (int为转换器)

    @app.route('/user/<int:user_id>')
    
    
  • 视图中获取

    @app.route('/user/<int:user_id>')
    def user(user_id):
        pass
    
    

3.2 路径参数过滤 ==> 转换器

  • 视图

    from werkzeug.routing import BaseConverter
    
    # 定义转换器
    class RegexConverter(BaseConverter):
        def __init__(self, url_map, *args):
            super(RegexConverter, self).__init__(url_map)
            # 获取正则
            self.regex = args[0]
            
        def to_python(self, value):
        	# 对获取的参数进行处理,并返回
    		return value
        
        def to_url(self, value):
            # 使用url_for时,对传入的参数先处理,再匹配路由
            return value
    
    
    # 在app中注册转换器
    app.url_map.converters['re'] = RegexConverter
    
    # 对路径参数使用转换器
    def index(/user/<re('[0-9]{6}'):user_id>)
    
    

3.3 获取表单参数

  • 视图

    def index():
        username = request.form.get('username')
    
    

3.4 获取查询参数

  • 视图

    image_code_id = request.args.get('imageCodeId', None)
    
    

3.5 获取文件参数

  • 视图

    def index():
        file = request.forms.get('pic')
        file.save()
        return success
    
    

3.6 获取请求体json数据

  • 视图

    param_dict = request.json
    mobile = param_dict.get("mobile")
    password = param_dict.get("password")
    
    

4. 返回响应

4.1 返回固定状态码响应

  • 视图

    def index():
        return 'index', 666  # 返回固定状态码
    
    

4.1 返回JSON响应

  • 视图

    def index():
    	json_dict = {
            'name': 'zs'
        }
        
    	return jsonify(json_dict)
    	# 底层: json_str = json.dumps(json_dict)  # json.loads json ==> dict
    
    

4.2 重定向

  • 视图

    def index():
        return redirect(url_for('user', user_id=123))  # 重定向到user视图
    
    

4.3 返回模板

  • 视图

    def index():
    	return render_template()
    
    

4.4 返回响应并设置响应头

  • 视图

    def index():
        response = make_response("Content-Type") = 'image/jpg'
        return response
    
    

4.5 返回静态资源

  • 视图

    def index():
    	return current_app.send_static_file('new/favicon.ico')
    
    

4.6 加载favicon.ico图标

  • 视图

    current_app.send_static_file() 查找指定的静态文件

    @index_blue.route('/favicon.ico')
    def favicon():
        return current_app.send_static_file('news/favicon.ico')
    
    

5. 视图常用逻辑

5.1 异常响应

  • 主动抛出异常

    def index():
        abort(404)
    
    
  • 异常处理

    # 捕获全局错误状态码 500, 400
    @app.errorhandler(404)
    def page_not_found(error):
        # error中包含错误信息
        return 'page not found'
    
    # 捕获错误类型
    @app.errorhander(Exception):
    def exception_error(error)
        return 'Exception'
    
    

5.2 请求勾子

  • 视图

    @before_first_request
    def before_f_r():
        """第一次请求前执行"""
        pass
    
    @before_request
    def bofore_r():
        """每次请求前执行"""
        # 如果return不会执行视图,直接返回return响应
        return
    
    @after_request
    def after_r(value):
        """每次请求结束后对响应数据处理,并返回"""
        return value
    
    @teardown_request
    def teardown_r(error):
        """异常发生时,抛出错误"""
        pass
    
    

5.3 Cookie&Session

  • 设置Cookie与删除

    def index():
        response = make_response('success')
        response.set_cookie('user_id', '1', max_age=3600)  # 更多参数查看源码
        response.delete_cookie('user_id')
        return response
    
    
  • 获取Cookie

    def index():
        user_id = request.cookies.get('user_id')
        return 'success'
    
    
  • 设置Session与删除

    app.config['SECRET_KEY'] = 'lfjweioano'
    
    def index():
        session['user_id'] = '1'
        session.pop('user_id', None)
    
    
  • 获取Session

    user_id = session['user_id']
    
    

5.4 上下文

  • 请求上下文 — 一个视图中有效
    • request
    • session
  • 应用上下文 — 整个应用中都有效
    • current_app
    • g变量

5.5 CSRF

跨站请求伪造 ==> 简单理解: 第三方网站通过用户浏览器访问主网站, 携带主网点的cookie, 导致第三方访问主网站请求成功, 通过在cookie和表单中设置一个相同的值在后台进行等值校验以解决问题

  • 生成随机字符串

    bytes.decode(base64.b64encode(os.uramdom(48)))
    
    
  • 防止CSRF攻击

    通过在cookie和表单中设置一个相同的值在后台进行等值校验以解决问题

  • Flask中解决CSRF

    app.secret_key = "#此处可以写随机字符串#"
    
    # 开启csrf
    from flask.ext.wtf import CSRFProtect
    CSRFProtect(app)
    
    
    # 在cookie中设置csrf
    @app.after_response
    def csrf_protect(response):
        response.set_cookie('csrf_token', generate_csrf())
        return response
    
    
    # 在表单中设置csrf
    <form method="post" action="/">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
    </form>
    # 或ajax中设置csrf
    $.ajax({
        headers: {
            "X-CSRFToken": getCookie('csrf_token')
        },
    })
    
    
    

5.6 蓝图

  • 蓝图的使用

    # 定义蓝图对象
    admin=Blueprint('admin',__name__)  # 更多参数查看源码
    # 注册蓝图
    app.register_blueprint(admin, url_prefix='/admin')
    # 使用蓝图
    @admin.route('/')
    def admin_home():
        return 'admin_home'
    
    

5.7 操作数据库

  • 操作redis

    try:
        redis_store.set("sms", "save content", 3600)
        sms = redis_store.get('sms')
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=123, errmsg='数据库查询失败')
    
    
  • 操作mysql

    try:
        db.session.xxx
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=456, errmsg="数据库操作失败")
    
    

5.8 记录日志

  • 视图

    error, debug…

    current_app.logger.error(e)
    
    

5.9 装饰器和g变量的使用

  • 视图

    def user_login_data(f):
        @functools.wraps(f)  # 保证被装饰的函数名称不变化
        def wrapper(*args, **kwargs):
            g.user = None
            return f(*args, **kwargs)
        return wrapper
    
    @user_login_data
    def login():
        user = g.user
    
    

6. 模板

6.1 Flask数据展示

  • 视图

    @app.route('/')
    def index():
        # 往模板中传入的数据
        my_str = 'Hello 黑马程序员'
        my_int = 10
        my_array = [3, 4, 2, 1, 7, 9]
        my_dict = {
            'name': 'xiaoming',
            'age': 18
        }
        return render_template('temp_demo1.html',
                               my_str=my_str,
                               my_int=my_int,
                               my_array=my_array,
                               my_dict=my_dict
                               )
    
    
  • html

    我的模板html内容
    <br/>{{ my_str }}
    <br/>{{ my_int }}
    <br/>{{ my_array }}
    <br/>{{ my_dict }}
    
    相关运算和取值
    {{ my_int + 10 }}
    {{ my_array[0] }}
    <br/>{{ my_array.1 }}
    {{ my_dict['name'] }}
    {{ my_dict.age }}
    
    

6.2 过滤器

  • 内建过滤器

    名称作用
    capitalize首字母大写,其余字母小写
    lower转成小写
    upper转成大写
    title每个单词的首字母都转成大写
    reverse字符串反转
    format格式化输出{{ ‘%s is %d’ | format(‘name’, 17)
    striptags渲染之前把值中所有的HTML标签都删掉
    first列表第一个元素
    last列表最后一个元素
    length获取列表长度
    sum列表求和
    sort列表排序
    {{ "hello world" | reverse | upper }}  {# 支持链式调用 #}
    
    
    {% filter upper %}
        #对语句块过滤#
    {% endfilter %}
    
    
  • 自定义过滤器

    @app.template_filter('lireverse')
    # 或使用注册方式:  app.add_template_filter(do_listreverse,'lireverse')
    def do_listreverse(li):
        # 通过原列表创建一个新列表
        temp_li = list(li)
        # 将新列表进行返转
        temp_li.reverse()
        return temp_li
    
    

6.3 控制代码块

  • if

    {% if comments | length > 0 %}
        There are {{ comments | length }} comments
    {% else %}
        There are no comments
    {% endif %}
    
    
  • for

    {% for post in posts if post.text %}
        <div>
            <h1>{{ post.title }}</h1>
            <p>{{ post.text | safe }}</p>
        </div>
    {% endfor %}
    
    

    for中使用循环计数

    变量描述
    loop.index当前循环迭代的次数(从 1 开始)
    loop.index0当前循环迭代的次数(从 0 开始)
    loop.revindex到循环结束需要迭代的次数(从 1 开始)
    loop.revindex0到循环结束需要迭代的次数(从 0 开始)
    loop.first如果是第一次迭代,为 True 。
    loop.last如果是最后一次迭代,为 True 。
    loop.length序列中的项目数。
    loop.cycle在一串序列间期取值的辅助函数。见下面示例程序。
    {% for post in posts%}
    {{loop.index}}, {{post.title}}
    {% endfor %}
    
    

6.4 代码复用

  • 宏 (相当于函数)

    使用时, 单独使用文件保存宏 macro.html

    {# 定义宏 macro.html #}
    {% macro input(label='', type='text', value='') %}
    <label>{{ label }}</label><input type="{{ type }}" value="{{ value }}"></input>
    {% endmacro %}
    
    {# 导入宏 #}
    {% import 'macro.html' as macro %}
     
    {# 使用宏 #}
    {{ macro.input('用户名', type='passowrd') }}
    
    
  • 继承

    {% extends 'base.html' %}
    
    {# 重写父类中被继承的内容 #}
    {% block xxxx %}
    	{{ super() }}
    
    {% endblock %}
    
    
  • 包含

    {% include 'base.html' ignore missing %}
    
    

6.5 模板中特有的变量和函数

  • 示例

    变量或函数作用示例
    configFlask当前的config对象{{config.SQLALCHEMY_DATABASE_URI}}
    request当前请求的request对象{{request.url}}
    sessionFlask的session对象{{session.new}}
    g变量取出g变量的属性值{{ g.name }}
    url_for()根据视图函数名,返回路由对应URL{{ url_for('home') }}
    {{ url_for('post', post_id=1)}}
    get_flashed_messages()闪现视图中flask(‘xxx’)传入的消息的列表的内容{%for message in get_flashed_messages()%}
    {{message}}
    {%endfor%}

7. 扩展

7.1 flask_script

运行脚本扩展, 用于启动服务器时,通过命令传入参数

安装

pip install flask_script

使用

manage.py

from flask_script import Manager

manager = Manager(app)  # app为项目应用

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

bash

python manage.py runserver -h xxxx -p xxxx -d

7.2 flask_wtf

生成表单, 并自动完成校验 表单字段与校验参照books

安装

pip install flask_wtf

使用

  • 视图处理

    from flask_wtf import FlaskForm
    from wtforms import StringField, PasswordField, SubmitField
    from wtforms.validators import InputRequired
    
    # 测试关闭csrf
    app.config['WTF_CSRF_ENABLED'] = False
    
    
    class RegisterForm(FlaskForm):
        username = StringField('用户名:', 
                               render_kw={'placeholder':'占位文字'}
                               validators=[InputRequired('必输')]
                              )
        password = PasswordField('密码:')
        submit = SubmitField('注册')
        
    @app.route('/register', methods=['POST'])
    def register():
        register_form = RegisterForm()
        # wtf_forms验证表单
        if register_form.validate_on_submit():
            return "success"
        else:
            flash("参数有误或者不完整")
    
    
  • 模板

    <form method="post">
    	{{ form.csrf_token() }}  # 使用wtf表单,调用此函数直接自动完成csrf校验
        {{ form.username.label }} {{ form.username }}<br/>
        {{ form.password.label }} {{ form.password }}<br/>
        {{ form.password2.label }} {{ form.password2 }}<br/>
        {{ form.submit }}
    </form>
    
    

7.3 Flask-SQLAlchemy

安装配置

  • 安装

    pip install flask-sqlalchemy
    # 连接到mysql时需要安装mysqldb
    pip install flask-mysqldb
    
    
  • 配置

    更多配置参照books

    # 数据库使用URL指定
    app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'
    # 动态追踪修改设置,如未设置只会提示警告
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
    #查询时会显示原始SQL语句
    app.config['SQLALCHEMY_ECHO'] = True
    
    

数据库模型类

字段类型,列选项,关系选项 参照books

  • 模型类

    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    
    
    class Role(db.Model):
        # 定义表名
        __tablename__ = 'roles'
        # 定义列对象
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        # 定义关联关系
        # backref为类User申明新属性的方法
        # lazy='dynamic'动态查询
        us = db.relationship('User', backref='role', lazy='dynamic')
    
        #repr()方法显示类的描述信息
        def __repr__(self):
            return 'Role:%s'% self.name
    
    class User(db.Model):
        __tablename__ = 'users'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True, index=True)
        email = db.Column(db.String(64),unique=True)
        password = db.Column(db.String(64))
        # 定义关联关系
        role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    
        def __repr__(self):
            return 'User:%s'%self.name
    
    
  • 一对多

    class Role(db.Model):
        ...
        #关键代码
        us = db.relationship('User', backref='role', lazy='dynamic')
        ...
    
    class User(db.Model):
        ...
        role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    
    
  • 多对多

    registrations = db.Table('registrations',  
        db.Column('student_id', db.Integer, db.ForeignKey('students.id')),  
        db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))  
    )  
    class Course(db.Model):
        ...
    class Student(db.Model):
        ...
        courses = db.relationship('Course',secondary=registrations,  
                                        backref='students',  
                                        lazy='dynamic')
    
    

数据库CRUD

  • 新增数据

    # 插入一条数据
    ro1 = Role(name='admin')
    db.session.add(ro1)
    db.session.commit()
    
    # 一次插入多条数据
    ro2 = Role(name='user')
    ro3 = Role(name='student')
    db.session.add_all([ro2, ro3])
    db.session.commit()
    
    
  • 查询

    • filter_by精确查询

      User.query.filter_by(name='wang').all()
      
      
    • first()返回查询第一个对象

      User.query.first()
      
      
    • all()返回查询的所有对象

      User.query.all()
      
      
    • filter模糊查询

      User.query.filter(User.name.endswith('xxx')).all()
      User.query.filter(User.name.startswith('xxx')).all()
      User.query.filter(User.name.contains('xxx')).all()
      # 查询用户在[1,3,5,7,9]的用户
      User.query.filter(User.id.in_([1,3,5,7,9])).all()
      
      
    • get(id)参数为主键

      User.query.get(1)
      
      
    • 排序查询

      User.query.order_by(User.email.asc).all()
      User.query.order_by(User.email.desc).all()
      
      
    • 分页查询

      paginate = User.query.paginate(2, 3)  # 查询第2页,每页3个
      paginate.items  # 当前页数据
      paginate.pages  # 总页数
      paginate.page  # 当前页
      
      
    • 聚合查询

      User.query.count()
      
      
    • 逻辑与或非

      # 逻辑非
      User.query.filter(User.name!='wang').all()
      User.query.filter(not_(User.name=='wang')).all()
      # 逻辑与
      User.query.filter(and_(User.name!='wang', User.email.endwith('163.com'))).all()
      User.query.filter(User.name!='wang', User.email.endwith('163.com')).all()
      # 逻辑或
      User.query.filter(or_(User.name!='wang', User.email.endwith('163.com'))).all()
      
      
    • 关联查询

      # 查询角色的所有用户  一对多
      ro1 = Role.query.get(1)
      ro1.us.all()
      
      # 查询用户的角色  多对一
      us1 = User.query.get(3)
      us1.role
      
      
  • 删除数据

    user = User.query.first()
    db.session.delete(user)
    db.session.commit()
    
    
  • 更新数据

    user = User.query.first()
    user.name = 'dong'
    db.session.commit()
    
    

7.4 flask-migrate

修改数据库模型时, 在不丢失数据的情况下,配合flask_migrate进行修改

安装配置

  • 安装

    pip install flask-migrate
    
    
  • 配置

    #第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例
    migrate = Migrate(app, db) 
    
    #manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
    manager.add_command('db', MigrateCommand)
    
    

迁移数据库

  • 迁移

    python manage.py db init  # 创建迁移仓库
    python manage.py db migrate -m 'initial migration'  # 创建迁移脚本
    python manage.py db upgrade  # 更新数据库
    python app.py db history  # 返回以前的版本
    python app.py db downgrade 版本号
    
    

3.2 用户相关

3.2.1 注册
3.2.1.1 图片验证码
3.2.1.1.1 逻辑分析
  1. Client发起获取验证码图片的请求
    1. 请求方式: GET
    2. 请求头: Request URL: /image_code?imageCodeId=xxx
  2. Server生成图片验证码发送给Client
    1. 生成图片验证码
    2. 保存图片验证码内容(K-V)到redis中
3.2.1.1.2 生成UUID
  • 示例

    function generateUUID() {
        var d = new Date().getTime();
        if(window.performance && typeof window.performance.now === "function"){
            d += performance.now(); //use high-precision timer if available
        }
        var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = (d + Math.random()*16)%16 | 0;
            d = Math.floor(d/16);
            return (c=='x' ? r : (r&0x3|0x8)).toString(16);
        });
        return uuid;
    }
    
3.2.1.1.3 前端实现
  • js

    var imageCodeId = ""
    
    // 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性
    function generateImageCode() {
        imageCodeId = generateUUID();
        var imageCodeUrl = "/image_code?imageCodeId="+imageCodeId
        $(".get_pic_code").attr("src", imageCodeUrl)
    }
    
3.2.1.1.4 后端逻辑实现
  • views

    @passport_blue.route('/image_code')
    def image_code():
        """
        生成图片验证码并返回
        :return: 图片验证码
        """
        # 如果没有id, 直接返回403
        image_code_id = request.args.get('imageCodeId', None)
        if not image_code_id:
            return abort(403)
        # 生成验证码
        name, text, image = captcha.generate_captcha()
        # 保存到redis
        try:
            redis_store.set("image_code_" + image_code_id, text, IMAGE_CODE_REDIS_EXPIRES)
        except Exception as e:
            current_app.logger.error(e)
            return abort(500)
        # 设置数据类型, 以便Client更加容易识别数据类型
        response = make_response(image)
        response.headers["Content-Type"] = "image/jpg"
        return image
    
3.2.1.2 短信验证码
3.2.1.2.1逻辑分析
  1. Client发起获取短信验证码的请求
    1. 请求方式: POST
    2. 请求头: /passport/send_sms_code
    3. 请求体: JSON格式数据: imageCodeId=xxx mobile=xxx
  2. Server向用户发送短信验证码并发送给Client
    1. 验证验证码是否有效
    2. 生成短信验证码保存在redis中
    3. 返回响应 errno, errmsg
3.2.1.2.2 后端逻辑实现
  • views

    @passport_blue.route('/image_code')
    def image_code():
        """
        生成图片验证码并返回
        :return: 图片验证码
        """
        # 如果没有id, 直接返回403
        image_code_id = request.args.get('imageCodeId', None)
        if not image_code_id:
            return abort(403)
        # 生成验证码
        name, text, image = captcha.generate_captcha()
        current_app.logger.debug("图片验证码内容为%s" % text)
        # 保存到redis
        try:
            redis_store.set("image_code_" + image_code_id, text, constants.IMAGE_CODE_REDIS_EXPIRES)
        except Exception as e:
            current_app.logger.error(e)
            return abort(500)
        # 设置数据类型, 以便Client更加容易识别数据类型
        response = make_response(image)
        response.headers["Content-Type"] = "image/jpg"
        return response
    
3.2.1.2.3 前端实现
  • main.js

        params = {
            "mobile": mobile,
            "image_code": imageCode,
            "image_code_id": imageCodeId
        };
        $.ajax({
            url: "/passport/send_sms_code",
            type: "post",
            data: JSON.stringify(params),
            contentType: "application/json",
            success: function (response) {
                if (response.errno == 0) {
                    //发送成功
                    var num = 60;
                    var t = setInterval(function () {
                        if (num == 1) {
                            clearInterval(t);
                            $(".get_code").html("点击获取验证码");
                            $(".get_code").attr("onclick", "sendSMSCode();");
                        } else {
                            num -= 1;
                            $(".get_code").html(num+"秒")
                        }
                    }, 1000)
                } else {
                    //发送失败
                    alert(response.errmsg)
                    $(".get_code").attr("onclick", "sendSMSCode();");
                }
            }
        })
    
3.2.1.3 注册
3.2.1.3.1 注册逻辑分析
  1. 请求
    1. 请求方式: POST
    2. 请求参数: sms_code, mobile, password
  2. 响应
    1. 保存用户信息
    2. 返回JSON响应 errno, errmsg
3.2.1.3.2 注册后端实现
  • views

    @passport_blue.route('/register', methods=['POST'])
    def register():
        """
        注册逻辑实现
        :return:
        """
        # 获取参数
        params_dict = request.json
        mobile = params_dict.get('mobile')
        sms_code = params_dict.get('smscode')
        password = params_dict.get('password')
    
        # 校验参数
        if not all([mobile, sms_code, password]):
            return jsonify(errno=RET.PARAMERR, errmsg="参数有误")
        if not re.match(r"^1[3578]\d{9}", mobile):
            return jsonify(errno=RET.PARAMERR, errmsg="手机号不正确")
        try:
            real_sms_code = redis_store.get(mobile+"_sms_code")
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg="数据库查询错误")
        if real_sms_code != sms_code:
            return jsonify(errno=RET.DATAERR, errmsg="短信验证码错误")
    
        # 保存数据
        user = User()
        user.mobile = mobile
        user.nick_name = mobile
        user.last_login = datetime.now()
        user.password = password
        try:
            db.session.add(user)
            db.session.commit()
        except Exception as e:
            current_app.logger.error(e)
            db.session.rollback()
            return jsonify(errno=RET.DBERR, errmsg="数据库保存错误")
    
        # 处理用户登录
        session['user_id'] = user.id
        session['mobile'] = user.mobile
        session['nick_name'] = user.nick_name
    
        return jsonify(errno=RET.OK, errmsg="注册成功")
    
3.2.1.3.3 注册前端实现
  • main.js

        $(".register_form_con").submit(function (e) {
            // 阻止默认提交操作
            e.preventDefault()
    
            // 取到用户输入的内容
            var mobile = $("#register_mobile").val()
            var smscode = $("#smscode").val()
            var password = $("#register_password").val()
    
            if (!mobile) {
                $("#register-mobile-err").show();
                return;
            }
            if (!smscode) {
                $("#register-sms-code-err").show();
                return;
            }
            if (!password) {
                $("#register-password-err").html("请填写密码!");
                $("#register-password-err").show();
                return;
            }
    
            if (password.length < 6) {
                $("#register-password-err").html("密码长度不能少于6位");
                $("#register-password-err").show();
                return;
            }
    
            // 发起注册请求
            params = {
                "mobile": mobile,
                'smscode': smscode,
                'password': password
            };
            $.ajax({
                url: 'passport/register',
                type: 'post',
                data: JSON.stringify(params),
                contentType: 'application/json',
                success: function (response) {
                    if (response.errno == 0) {
                        // 注册成功
                        window.location.reload()
                    } else {
                        // 注册失败
                        alert(response.errmsg)
                        $("#register-password-err").html(response.errmsg);
                        $("#register_password-err").show()
                    }
                }
            })
        });
    
3.2.2 登录登出
3.2.2.1 登录逻辑分析
  1. 请求
    1. 请求方式: POST
    2. 请求参数: mobile, password
  2. 响应
    1. 保存用户登录信息(session)
    2. 返回JSON响应 errno, errmsg
3.2.2.2 后端逻辑实现
  • views

    @passport_blue.route('/login', methods=['GET', 'POST'])
    def login():
        """
        用户登录
        :return:
        """
        if request.method == "POST":
            # 获取参数
            params_dict = request.json
            mobile = params_dict.get('mobile')
            password = params_dict.get('password')
    
            # 校验参数
            if not all([mobile, password]):
                return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
            if not re.match(r"^1[3578]\d{9}", mobile):
                return jsonify(errno=RET.PARAMERR, errmsg="手机号不正确")
            try:
                user = User.query.filter(User.mobile == mobile).first()
            except Exception as e:
                current_app.logger.error(e)
                return jsonify(errno=RET.DBERR, errmsg="数据库查询失败")
            if not user:
                return jsonify(errno=RET.NODATA, errmsg="用户不存在")
            if not user.check_passowrd(password):
                return jsonify(errno=RET.PWDERR, errmsg="用户名或密码错误")
    
            # 保存session 处理用户登录
            session['user_id'] = user.id
            session['mobile'] = user.mobile
            session['nick_name'] = user.nick_name
    
            # 返回响应
            return jsonify(errno=RET.OK, errmsg="登录成功")
    
3.2.2.3 前端逻辑实现
  • main.js

    	$(".login_form_con").submit(function (e) {
            e.preventDefault()
            var mobile = $(".login_form #mobile").val()
            var password = $(".login_form #password").val()
    
            if (!mobile) {
                $("#login-mobile-err").show();
                return;
            }
    
            if (!password) {
                $("#login-password-err").show();
                return;
            }
    
            // 发起登录请求
            params = {
                'mobile': mobile,
                'password': password,
            }
            $.ajax({
                url: "/passport/login",
                type: "post",
                data: JSON.stringify(params),
                contentType: 'application/json',
                success: function (response) {
                    if (response.errno == 0) {
                        location.reload();
                    } else {
                        alert(response.errmsg);
                        $("#login-password-err").html(response.errmsg);
                        $("#login-password-err").show();
                    }
                }
            })
        })
    
    
3.2.2.4 登录主页右上角显示
  • views

    @index_blue.route('/')
    def index():
        user_id = session.get("user_id", None)
        user = None
        try:
            user = User.query.get(user_id)
        except Exception as e:
            current_app.logger.error(e)
    
        data = {
            "user": user.to_dict() if user else None
        }
        return render_template('index.html', data=data)
    
    
  • index.html

                {% if not data.user %}
                    <div class="user_btns fr">
                        <a href="javascript:;" class="login_btn">登录</a> / 
                		<a href="javascript:;" class="register_btn">注册</a>
                    </div>
                {% else %}
                    <!-- 用户登录后显示下面,隐藏上面 -->
                    <div class="user_login fr">
                        <img src="../static/news/images/person01.png"
                             class="lgin_pic">
                        <a href="#">Hello {{ data.user.nick_name }}</a>
                        <a href="javascript:;" class="logout_btn">退出</a>
                    </div>
                {% endif %}
    
    
3.2.2.4 登出
  • views

    @passport_blue.route('/logout', methods=['GET'])
    def logout():
        """
        用户登出
        :return:
        """
        session.pop("user_id", None)
        session.pop("nick_name", None)
        session.pop('mobile', None)
    
        return jsonify(errno=RET.OK, errmsg="登出成功")
    
    
  • main.js

    function logout() {
        $.ajax({
            url: '/passport/logout',
            type: 'get',
            success: function (response) {
                alert(response.errmsg);
                location.reload()
            }
        })
    }
    
    
3.2.3 CSRF实现
  • create_app

        # CSRF配置
        # 需要在表单和cookie中设置csrf_token, 校验自动处理了,返回校验结果
        CSRFProtect(app)
    
        @app.after_request
        def after_request(response):
            csrf_token = generate_csrf()
            response.set_cookie('csrf_token', csrf_token)
            return response
    
    
  • ajax

        $.ajax({
            url: '/passport/logout',
            headers: {
                "X-CSRFToken": getCookie("csrf_token")
            },
        })
    
    

3.3 数据展示

3.3.1 首页点击排行
  • view.py

    @index_blue.route('/')
    def index():
        user_id = session.get("user_id", None)
        user = None
        try:
            user = User.query.get(user_id)
        except Exception as e:
            current_app.logger.error(e)
    
        # 右侧新闻数据
        news_list = []
        news_list = News.query.order_by(News.clicks.desc()).limit(6)
        news_dict_list = []
        for news in news_list:
            news_dict_list.append(news.to_basic_dict())
    
        data = {
            "user": user.to_dict() if user else None,
            "news_dict_list": news_dict_list
        }
        return render_template('index.html', data=data)
    
    
  • index.html

    <ul class="rank_list">
        {% for news in data.news_dict_list %}
        <li><span class="{{ loop.index | index_class }}">{{ loop.index }}</span><a href="#">{{ news.title }}</a></li>
        {% endfor %}
    </ul>
    
    
3.3.1.1 过滤器实现class
  • utils/common.py

    def do_index_class(index):
        """
        返回指定索引对应的类名
        :param index:
        :return:
        """
        if index == 1:
            return "first"
        elif index == 2:
            return "second"
        elif index == 3:
            return "third"
        return ""
    
    
  • 注册过滤器

        app.add_template_filter(do_index_class, "index_class")
    
    
3.3.2 首页新闻数据展示
3.3.2.1 后端逻辑实现
  • views

    @index_blue.route('/news_list')
    def news_list():
        """
        获取首页数据
        :return:
        """
        # 获取参数
        cid = request.args.get("cid", "1")
        page = request.args.get("page", "1")
        per_page = request.args.get("per_page", "10")
    
        # 校验参数
        try:
            cid = int(cid)
            page = int(page)
            per_page = int(per_page)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.PARAMERR, errmsg="传入参数有误")
    
        # 查询数据
        filters = []
        if cid != 1:
            filters.append(News.category_id == cid)
        try:
            paginate = News.query.filter(*filters).order_by(News.create_time.desc()).paginate(page, per_page, False)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg="数据库查询错误")
    
        # 取到当前页数据
        news_list_items = paginate.items  # 返回模型对象列表
        total_page = paginate.pages
        current_page = paginate.page
    
        news_dict_list = []
        for news in news_list_items:
            news_dict_list.append(news.to_basic_dict())
    
        # 返回响应
        data = {
            "total_page": total_page,
            "current_page": current_page,
            "news_dict_list": news_dict_list
        }
        return jsonify(errno=RET.OK, errmsg='请求成功', data=data)
    
    
3.3.2.2 前端展示
  • js请求数据, 加载到页面上

    $(function () {
        // 界面加载完成加载新闻数据
        updateNewsData();
    });
    
    function updateNewsData() {
        // 更新新闻数据
        var params = {
            "cid": currentCid,
            'page': cur_page,
        };
        $.get("/news_list", params, function (resp) {
            if (resp.errno == 0) {
                // 请求成功
                total_page = resp.data.total_page;
                data_querying = true;
                if (cur_page == 1) {
                    $(".list_con").html("");
                }
                for (var i = 0; i < resp.data.news_dict_list.length; i++) {
                    var news = resp.data.news_dict_list[i]
                    var content = '<li>'
                    content += '<a href="#" class="news_pic fl"><img src="' + news.index_image_url + '?imageView2/1/w/170/h/170"></a>'
                    content += '<a href="#" class="news_title fl">' + news.title + '</a>'
                    content += '<a href="#" class="news_detail fl">' + news.digest + '</a>'
                    content += '<div class="author_info fl">'
                    content += '<div class="source fl">来源:' + news.source + '</div>'
                    content += '<div class="time fl">' + news.create_time + '</div>'
                    content += '</div>'
                    content += '</li>'
                    $(".list_con").append(content)
                }
    
            } else {
                // 请求失败
                alert(resp.errmsg)
            }
    
        })
    }
    
    
3.3.2.3 滚动到底部加载更多
  • 页面滚动到底部时加载更多

    var currentCid = 1; // 当前分类 id
    var cur_page = 1; // 当前页
    var total_page = 1;  // 总页数
    var data_querying = true;   // 是否正在向后台获取数据
    
    
    $(function () {
        //页面滚动加载相关
        $(window).scroll(function () {
            // 浏览器窗口高度
            var showHeight = $(window).height();
            // 整个网页的高度
            var pageHeight = $(document).height();
            // 页面可以滚动的距离
            var canScrollHeight = pageHeight - showHeight;
            // 页面滚动了多少,这个是随着页面滚动实时变化的
            var nowScroll = $(document).scrollTop();
    
            if ((canScrollHeight - nowScroll) < 100) {
                // TODO 判断页数,去更新新闻数据
                // console.log("页面到底部了")
                if (data_querying) {
                    data_querying = false;
                    // 加载数据
                    if (cur_page < total_page) {
                        cur_page += 1;
                        updateNewsData()
                    }
                }
            }
        })
    });
    
    
3.3.3 数据分类展示
  • views

        # 上方分类数据展示
        try:
            categories = Category.query.all()
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg="数据库查询错误")
    
        category_list = []
        for category in categories:
            category_list.append(category.to_dict())
    
        data = {
            "user": user.to_dict() if user else None,
            "news_dict_list": news_dict_list,
            "category_list": category_list
        }
    
    
  • js

        $('.menu li').click(function () {
            var clickCid = $(this).attr('data-cid')
            $('.menu li').each(function () {
                $(this).removeClass('active')
            })
            $(this).addClass('active')
    
            if (clickCid != currentCid) {
                // 记录当前分类id
                currentCid = clickCid
    
                // 重置分页参数
                cur_page = 1
                total_page = 1
                updateNewsData()
            }
        })
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值