Flask精华总结,基于博客论坛

Flask精华总结,基于博客论坛

1.Flask简介

​ Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。另外,Flask还有很强的定制性,用户可以根据自己的需求来添加相应的功能,在保持核心功能简单的同时实现功能的丰富与扩展,其强大的插件库可以让用户实现个性化的网站定制,开发出功能强大的网站。

​ Flask是目前十分流行的web框架,采用Python编程语言来实现相关功能。它被称为微框架(microframework),“微”并不是意味着把整个Web应用放入到一个Python文件,微框架中的“微”是指Flask旨在保持代码简洁且易于扩展,Flask框架的主要特征是核心构成比较简单,但具有很强的扩展性和兼容性,程序员可以使用Python语言快速实现一个网站或Web服务。一般情况下,它不会指定数据库和模板引擎等对象,用户可以根据需要自己选择各种数据库。Flask自身不会提供表单验证功能,在项目实施过程中可以自由配置,从而为应用程序开发提供数据库抽象层基础组件,支持进行表单数据合法性验证、文件上传处理、用户身份认证和数据库集成等功能。Flask主要包括Werkzeug和Jinja2两个核心函数库,它们分别负责业务处理和安全方面的功能,这些基础函数为web项目开发过程提供了丰富的基础组件。Werkzeug库十分强大,功能比较完善,支持URL路由请求集成,一次可以响应多个用户的访问请求;支持Cookie和会话管理,通过身份缓存数据建立长久连接关系,并提高用户访问速度;支持交互式Javascript调试,提高用户体验;可以处理HTTP基本事务,快速响应客户端推送过来的访问请求。Jinja2库支持自动HTML转义功能,能够很好控制外部黑客的脚本攻击。系统运行速度很快,页面加载过程会将源码进行编译形成Python字节码,从而实现模板的高效运行;模板继承机制可以对模板内容进行修改和维护,为不同需求的用户提供相应的模板。

2.路由和请求响应对象

  • 装饰器其实就是将rule字符串跟视图函数进行了绑定,通过add_url_rule()实现的绑定。
@app.route('/index')
def index():
    return 'welcome everyone!'

等效

def index():
    return 'welcome everyone!'
app.add_url_rule('/index', view_func=index)

路由变量规则

string (缺省值) 接受任何不包含斜杠的文本 *
int 接受正整数 *
float 接受正浮点数
path 类似 string ,但可以包含斜杠
uuid 接受 UUID 字符串

@app.route('/index/<path:p>')
def get_path(p):
    print('******>', type(p))  # str类型
    print(p)
    return p

@app.route('/test/<uuid:uid>')  # 必须传递uuid的格式,uuid模块, uuid.uuid4() ---->UUID类型
def test(uid):
    print('#######>>>>>', type(uid))
    return '获取唯一的标识码'
# 所有的路由搜索规则都是自上而下搜索,在写路由的是有定义的路由是唯一的。
@app.route('/projects')  # 请求路由中如果添加了/:http://127.0.0.1:5000/about/  显示Not Found
def about():
    return 'The about page'

@app.route('/projects/')  # 路由中定义'/',无论请求的URL是否带有/,都可以执行视图函数。如果请求的是有/,浏览器做了一次重定向
def projects():
    return 'The project page'

视图

返回值:其实返回值返回的都是一个响应对象。

视图函数的返回值:
response响应:
1.str 自动转成response对象
2.dict json
3.response对象 response对象
4.make_response() response对象
5.redirect() 重定向 302状态码
6.render_template() 模板渲染 + 模板

response响应对象

print(response.content_type)
print(response.headers)
print(response.status_code)  # 200
print(response.status)  # 200 OK
response.set_cookie('name','wxy')

request请求对象:只需要导入,通过from flask import request
导入之后可以获取对象的属性和方法
属性:

 print(request.headers)  # request对象 对象访问属性,也可以调用方法
 print(request.path)
 print(request.full_path)
 print(request.base_url)
 print(request.url)
获取提交的数据

跟请求方法相关的

  • get:
    request.args 底层是字典的形式 主要获取get提交的请求参数
    如果是get请求格式是这个样子的:/register2?username=zhangsan&address=Beijing
    此时的username是form表单中表单元素的name值

    print(request.args.get('username'))   # 获取值
    print(request.args.get('address'))
    
  • post:
    request.form 底层是字典的形式 主要获取post提交的请求参数
    注意post提交必须在路由中进行设置,通过methods = [‘GET’,‘POST’]
    按照此种形式:
    @app.route(‘/register2’, methods=[‘GET’, ‘POST’])
    def register2(): # 获取页面提交的内容
    … 内容省略
    获取数据:
    print(request.form) # 如果请求方法是post则需要通过request.form取值

    print(request.form.get('username'))
    print(request.form.get('address'))
    

定制响应头

	response = make_response(content)  # 返回值就是一个response对象
    # 定制响应头
    response.headers['mytest'] = '123abc'
    response.headers['myhello'] = 'hellohello'
    # 将定制好的response返回
    return response

3.模板

模板的语法:

传参

在模板中获取view中传递的变量值:{{ 变量名key }}

render_template(‘模板名字’,key=value,key=value)

name = '沈凯'  # str
age = 18  # int
friends = ['建义', '陈璟', '小岳岳', '郭麒麟']  # list
dict1 = {'gift': '大手镯', 'gift1': '鲜花', 'gift2': '费列罗'}  # dict
# 创建对象
girlfriend = Girl('美美', '安徽阜阳')  # 自定义的类构建的类型:Girl对象

模板:
{{ list.0 }} 同 {{ list[0] }}
{{ dict.key }} 同 {{ dict.get(key) }}
{{ girl.name }} 同 {{ 对象.属性 }}

控制块

{% if 条件 %}

{% endif %}

{% if 条件 %}
条件为True
{% else %}
条件为False
{% endif %}

{% for 变量 in 可迭代的对象 %}
for循环要做的任务

{% endfor %}

  • 在for循环里面可以使用loop变量

    loop.index 序号从1开始
    loop.index0 序号从0开始

    loop.revindex reverse 序号是倒着的
    loop.revindex0

    loop.first 布尔类型 是否是第一行,是则返回TRUE
    loop.last 布尔类型 是否是第二行

{%for user in users%}
<tr {% if loop.last %}style="background-color: deeppink" {% endif %}>
<td> ff loop.index H</td>
<td>{{ user.username }}</td>
<td>{i user.password H</td>
<td>{{ user.addr H}</td>
<td>ii user.phone }</td>
</tr>
{%endfor%}

过滤器

过滤器的本质就是函数
模板语法中过滤器:
{{ 变量名 | 过滤器(*args) }}

{{ 变量名 | 过滤器 }}

常见的过滤器:
1。 safe : 禁用转译
msg = '<h1>520快乐!</h1>'
return render_template(‘show_2.html’, girls=girls, users=users, msg=msg)
不想让其转译:
{{ msg | safe }}
2。 capitalize:单词的首字母大写
{{ n1 | capitalize }}
3。lower和upper
大小写的转换
4。title 一句话中每个单词的首字母大写
msg = ‘She is a beautiful girl’
{{ msg | title}}
5。reverse 翻转
{{ n1 | reverse}}
6。format
{{ ‘%s is %d years old’ | format(‘lily’,18) }}
7.truncate 字符串截断

  • list的操作:
    {# 列表过滤器的使用 #}
    {{ girls | first }}

    {{ girls | last }}<br{{ girls | length }}

    {#{{ girls | sum }} 整型的计算 #}
    {{ [1,3,5,7,9] | sum }}

    {{ [1,8,5,7,3] | sort }}

  • dict:
    {% for v in users.0.values() %} ---->获取值

    {{ v }}


    {% endfor %}

    {% for k in users.0.keys() %} ----》获取键

    {{ k }}


    {% endfor %}

    {% for k,v in users.0.items() %} —》获取键值

​ {{ k }}—{{ v }}

​ {% endfor %}

自定义过滤器

​ 过滤器本质就是函数

  1. 通过flask模块中的add_template_filter方法
    a. 定义函数,带有参数和返回值
    b. 添加过滤器 app.add_template_filter(function,name=‘’)
    c. 在模板中使用: {{ 变量 | 自定义过滤器 }}

  2. 使用装饰器完成
    a. 定义函数,带有参数和返回值
    b. 通过装饰器完成,@app.template_filter(‘过滤器名字’)装饰步骤一的函数
    c. 在模板中使用: {{ 变量 | 自定义过滤器 }}

模板继承

需要模版继承的情况:
1。 多个模板具有完全相同的顶部和底部
2。 多个模板具有相同的模板内容,但是内容中部分不一样
3。 多个模板具有完全相同的模板内容

标签:
{% block 名字 %}

{% endblock %}

1.定义父模板
2.子模板继承父模板
步骤:
父模板:
1。 定义一个base.html的模板
2。 分析模板中哪些是变化的比如:{% block title %}父模板的title{% endblock %}
对变化的部分用block进行"预留位置"也称作:挖坑
3。注意:样式和脚本 需要提前预留
{% block mycss %}{% endblock %}
{% block myjs %}{% endblock %}

extends继承

1。 {% extends ‘父模板的名称’ %}将父模板继承过来
2。 找到对应的block(坑)填充,每一个block都是有名字的。

include包含

在A,B,C页面都共同的部分,但是其他页面没有这部分。
这个时候考虑使用include
步骤:
1。先定义一个公共的模板部分,xxx.html
2。谁使用则include过来, {% include ‘文件夹/xxx.html’ %}

宏macro

1。把它看作是jinja2的一个函数,这个函数可以返回一个HTML字符串
2。目的:代码可以复用,避免代码冗余

​ 定义两种方式:
​ 1。在模板中直接定义:
​ 类似: macro1.html 中定义方式
​ 2。将所有宏提取到一个模板中:macro.html
谁想使用谁导入:
{% import ‘macro.html’ as xxx %}
{{ xxx.宏名字(参数) }}

{% macro form(action,value='登录',method='post') %}
    <form action="{{ action }}" method="{{ method }}">
        <input type="text" placeholder="用户名" name="username">
        <br>
        <input type="password" placeholder="密码" name="password">
        <br>
        <input type="submit" value="{{ value }}">
    </form>
{% endmacro %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>宏的使用2</title>
</head>
<body>
{% import 'macro/macro.html' as func %}
{{ func.form('/welcome',value='注册') }}
</body>
</html>

在html定义变量

<!DOCTYPE html>
<body/>

全局变量
{% set username ='zhangsan' %}
{{ username }}

只能在with块里面使用的变量
{% with num=1000  %}
   {{ num }}
{% endwith %}

不显示1000
{{ num }}

</body>
</html>

4.蓝图

  1. 创建项目并设置settings.py文件

  2. 删除app.py中的部分内容

  3. 创建包apps,并在包中__init__.py中创建app的工厂函数

    def create_app():
        app = Flask(__name__)  # app是一个核心对象
        app.config.from_object(settings)  # 加载配置
        # 蓝图
        return app
    
  4. 在apps中创建项目的程序包比如user,goods,order等

  5. 以user为例,在user包中创建view.py,在view.py中定义蓝图

    user_bp = Blueprint('user', __name__)
    # 	其中第一个参数是蓝图的名字,第二个参数是import_name
    
  6. 使用蓝图定义路由和视图函数

    @user_bp.route('/')
    def user_center():
        return render_template('user/show.html', users=users)
    

    注意在蓝图中进行了模板的加载,而模板的文件夹默认是跟flask对象是同级的,如果不是同级的注意在Flask对象创建的时候初始化设置

  7. 注册蓝图到app对象上

    def create_app():
        app = Flask(__name__,template_folder='../templates',static_folder='../static')  # app是一个核心对象
        app.config.from_object(settings)  # 加载配置
        # 蓝图
        app.register_blueprint(user_bp)  # 将蓝图对象绑定到app上
    
        print(app.url_map)
    
        return app
    

补充数据删除操作

  • 点击删除按钮或者删除链接

  • 给链接添加单击事件,可以通过js或者jquery

     <a href="javascript:;" onclick="del('{{ user.username }}')">删除</a>
    
  • 在js中定义函数:

     function del(username){
                // console.log(username)
                // location 地址栏对象
                location.href = '/del?username='+username
            }
    
  • 触发/del的路由函数

    @user_bp.route('/del')
    def del_user():
        # 获取你传递的username
        username = request.args.get('username')
        # 根据username找到列表中的user对象
        for user in users:
            if user.username == username:
                # 删除user
                users.remove(user)
                return redirect('/')
        else:
            return '删除失败'
    

5.管理使用命令

flask-script
pip install flask-script

使用里面的Manager进行命令得到管理和使用:
manager = Manager(app=app)

manager.run() ---->启动

使用命令在终端:

python app.py runserver    ---->Runs the Flask development server
python app.py runserver -h 0.0.0.0 -p 5001
  • 自定义添加命令:

    * @manager.command
      def init():
          print('初始化')
    
    python3 app.py init
    

6.数据库

数据库配置

# 数据库配置信息
HOSTNAME = "127.0.0.1"
PORT = 3306
USERNAME = "root"
PASSWORD =''
DATABASE = "wenda"
DB_URI=f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
SQLALCHEMY_DATABASE_URI=DB_URI

或者一步到位

# mysql+pymysql://user:password@hostip:port/databasename
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@127.0.0.1:3306/flask'

flask-sqlalchemy的搭建

创建包exts

init.py中添加:

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()   ---->必须跟app联系

再去合适的__init__.py进行如下操作

from exts import db
def create_app():
   db.init_app(app)
  	return app

flask-migrate的配置

from flask-migrate import Migrate
migrate = Migrate(app=app, db=db)
manager.add_command('db', MigrateCommand)

创建模型

models.py
模型就是类,经常称作模型类

class User(db.Model):      ------> user表
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(15), nullable=False)
    password = db.Column(db.String(12), nullable=False)
    phone = db.Column(db.String(11), unique=True)
    rdatetime = db.Column(db.DateTime, default=datetime.now)

常见的数据类型:

Integer 整型
String(size) 字符串类型,务必指定大小
Text 长文本类型
DateTime 日期时间
Float 浮点类型
Boolean 布尔类型
PickleType 存储pickle类型 主要跟序列化有关
LargeBinary 存储大的二进制类型

可选的:

primary_key=True 主键
autoincrement=True 自增
nullable=False 不允许为空
unique=True 唯一
default=datetime.now 默认值 可以设置成当前系统时间或者其他的值

使用命令

a. ************敲黑板:没有这句话将会映射不起,创建不了表

在app.py 中导入模型:from apps.user.models import User

b. 在终端使用命令:db
python app.py db init -----》 产生一个文件夹migrations
python app.py db migrate -----> 自动产生了一个版本文件
项目
| —apps
| —ext
| —migrations python app.py db init 只需要init一次
|—versions 版本文件夹
|—71edde7ee937_.py —》 python app.py db migrate 迁移
|—cc0dca61130f_.py
python app.py db upgrade 同步
python app.py db downgrade 降级

添加数据

以注册为例:

#模板,视图与模型结合
#1. 找到模型类并创建对象
user = User(username=username,password=password,phone=phone)
#2. 给对象的属性赋值
#添加
#3.将user对象添加到session中(类似缓存)
db.session.add(user)
#4.提交数据
db.session.commit()

加密

new_password=hashlib.sha1(password.encode('utf-8')).hexgigest()
new_password=hashlib.sha256(password.encode('utf-8')).hexgigest()
new_password=hashlib.sha128(password.encode('utf-8')).hexgigest()

查询

查询所有: 模型类.query.all() ~ select * from user;
如果有条件的查询:
模型类.query.filter_by(字段名 = 值) ~ select * from user where 字段=值;
模型类.query.filter_by(字段名 = 值).first() ~ select * from user where 字段=值 limit…;

select * from user where age>17 and gender=‘男’;
select * from user where username like ‘zhang%’;
select * from user where rdatetime> xxx and rdatetime < xxx;

模型类.query.filter() 里面是布尔的条件,不只有等值 模型类.query.filter(模型名.字段名 == 值)
模型类.query.filter_by() 里面只能是等值 模型类.query.filter_by(字段名 = 值)

模型类.query.filter()

  1. 模型类.query.filter().all() -----> 列表
  2. 模型类.query.filter().first() ----->对象
  3.  User.query.filter(User.username.endswith('z')).all()   #select * from user where username like '%z';
     User.query.filter(User.username.startswith('z')).all()  # select * from user where username like 'z%';
     User.query.filter(User.username.contains('z')).all()  # select * from user where username like '%z%';
     User.query.filter(User.username.like('z%')).all()
    

#### 多条件

```python
from sqlalchemy import or_, and_,not_
并且: and_    获取: or_   非: not_
User.query.filter(or_(User.username.like('z%'), 			 User.username.contains('i'))).all()
类似: select * from user where username like 'z%' or username like '%i%';
User.query.filter(and_(User.username.contains('i'), User.rdatetime.__gt__('2020-05-25 10:30:00'))).all()
select * from user where username like '%i%' and rdatetime < 'xxxx'

补充:__gt__,__lt__,__ge__(gt equal),__le__(le equal)  ----》通常应用在范围(整型,日期)
也可以直接使用 >  <  >=  <=  !=

User.query.filter(not_(User.username.contains('i'))).all()
  
18 19 20 17 21 22 ....
User.username.in_(['','',''])
select * from user where age in [17,18,20,22];
排序

order_by

user_list = User.query.filter(User.username.contains(‘z’)).order_by(-User.rdatetime).all() # 先筛选再排序
user_list = User.query.order_by(-User.id).all() 对所有的进行排序
注意:order_by(参数):
1。 直接是字符串: ‘字段名’ 但是不能倒序
2。 填字段名: 模型.字段 order_by(-模型.字段) 倒序

限制

limit

limit的使用 + offset

user_list = User.query.limit(2).all() 默认获取前两条

user_list = User.query.offset(2).limit(2).all() 跳过2条记录再获取两条记录

总结
1. User.query.all()  所有

2. User.query.get(pk)  一个

3. User.query.filter()   *   ???????
    如果要检索的字段是字符串(varchar,db.String):
      User.username.startswith('')
      User.username.endswith('')
      User.username.contains('')
      User.username.like('')
      User.username.in_(['','',''])
      User.username == 'zzz'
    如果要检索的字段是整型或者日期类型:
       User.age.__lt__(18)
       User.rdatetime.__gt__('.....')
       User.age.__le__(18)
       User.age.__ge__(18)
       User.age.between(15,30)

     多个条件一起检索: and_, or_
     非的条件: not_

     排序:order_by()
     获取指定数量: limit() offset()

4. User.query.filter_by()

删除

两种删除:
1。逻辑删除(定义数据库中的表的时候,添加一个字段isdelete,通过此字段控制是否删除)
id = request.args.get(id)
user = User.query.get(id)
user.isdelete = True
db.session.commit()

2。物理删除(彻底从数据库中删掉)
id = request.args.get(id)
user = User.query.get(id)
db.session.delete(user)
db.session.commit()

更新

id = request.args.get(id)

user = User.query.get(id)

修改对象的属性

user.username= xxxx
user.phone =xxxx

提交更改

db.session.commit()

多表查询

flask框架为了简化多表的查询,制定了多表的关系:

  1. 1对多

    常见的比如:班级对学生,部门对员工,学校对班级,用户对文章,用户对订单

    可以说一个班级有多名同学或者一个部门有多名员工,但是不能说:

    一一个员工属于多个部门,一个班级属于多个学校…

    在flask的框架中如何体现1对多的模型关系?

    就是通过外键ForignKey和relationship体现。ForignKey是给映射关系说的,relationship是给模板使用的。

    class User(db.Model):
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        username = db.Column(db.String(15), nullable=False)
        password = db.Column(db.String(64), nullable=False)
        phone = db.Column(db.String(11), unique=True, nullable=False)
        email = db.Column(db.String(30))
        icon = db.Column(db.String(100))
        isdelete = db.Column(db.Boolean, default=False)
        rdatetime = db.Column(db.DateTime, default=datetime.now)
        # 增加一个字段
        articles = db.relationship('Article', backref='user')
        #
        def __str__(self):
            return self.username
    
    from datetime import datetime
    from exts import db
    class Article(db.Model):
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        title = db.Column(db.String(50), nullable=False)
        content = db.Column(db.Text, nullable=False)
        pdatetime = db.Column(db.DateTime, default=datetime.now)
        click_num = db.Column(db.Integer, default=0)
        save_num = db.Column(db.Integer, default=0)
        love_num = db.Column(db.Integer, default=0)
        # 外键 同步到数据库的外键关系
        user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    
  2. 多对多

    常见的多对多:用户对文章评论,用户对商品,学生对课程

    一个用户可以买多个商品,反过来这个个学生属于多个班级,商品的还可以让多个用户购买

    用户 1 -----》 n 商品
    n 《----- 1

    一个学生可以选择多门课程,反过来一门课程还可以让多个学生选择

    tags = db.Table('tags',
        db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
        db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
    )
    
    class Page(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        tags = db.relationship('Tag', secondary=tags,
        backref=db.backref('pages', lazy='dynamic'))
    
    class Tag(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(20),nullable=False)
    

或者

class Page(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    tags = db.relationship('Tag', secondary='Page_tag',
        backref='pages')

class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20),nullable=False)
    
class Page_tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
    db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
....

7.flask-bootstrap

使用flask-bootstrap:
步骤:

  1. pip install flask-bootstrap

  2. 进行配置:

     from flask-bootstrap import Bootstrap
     bootstrap = Bootstrap()
     # 在__init__.py中进行初始化:
    
     ###### 初始化bootstrap
    
     bootstrap.init_app(app=app)
    
  3. 内置的block:

    {% block title %}首页{% endblock %}
    {% block navbar %} {% endblock %}
    {% block content %} {% endblock %}
    {% block styles %} {% endblock %}
    {% block srcipts %} {% endblock %}
    {% block head %} {% endblock %}
    {% block body %} {% endblock %}
    
  4. 创建base.html

    {% extends "bootstrap/base.html" %}
    {% block title %}首页{% endblock %}
    {% block styles %}
        {{ super() }}
        <style>
           .....
        </style>
    {% endblock %}
    
    {% block navbar %}
    	....  
    {% endblock %}
    {% block content %}
        {% block newcontent %}
            <h1>Hello, Bootstrap</h1>
        {% endblock %}
        {% block footer %}
            <p id="myfoot">京ICP备11008000-6京公网安备11010802020853</p>
        {% endblock %}
    {% endblock %}
    
  5. 子模板继承父模板:

    {% extends 'base.html' %}
    {% block title %}
        博客首页
    {% endblock %}
    {% block styles %}
        {{ super() }}
        <style>
          .....
        </style>
    {% endblock %}
    {% block newcontent %}
        <div id="container">
            <div class="article"></div>
        </div>
    {% endblock %}
    
    

8.会话机制

  • cookie

在网站中,HTTP请求是无状态的。也就是说,即使第一次用户访问服务器并登录成功后,第二次请求服务器依然不知道当前发起请求的是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,浏览器将这些数据保存在本地。当用户发起第二次请求的时候,浏览器自动的将上次请求得到的cookie数据携带给服务器,服务器通过这些cookie数据就能分辨出当前发起请求的是哪个用户了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4K,因此使用cookie只能存储一些少量的数据。

  • session

session与cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,session存储在服务器。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源。

  • cookie和session结合

web开发发展至今,cookie和session的使用已经出现了一些非常成熟的方案。在如今的市场和企业里,一般有两种存储方式:

存储在服务器:通过cookie存储一个session_id,然后具体的数据则保存在session中。当用户已经登录时,会在浏览器的cookie中保存一个session_id,下次再次请求的时候,会把session_id携带上来,服务器根据session_id在session库中获取用户的session数据,从而能够辨别用户身份,以及得到之前保存的状态信息。这种专业术语叫做server side session

将session数据加密,然后存储在cookie中。这种专业术语叫做client side session,flask采用的就是这种方式,但是也可以替换成其它形式

cookie方式

  • 保存:

       通过response对象保存。
        response = redirect(xxx)
        response = render_template(xxx)
        response = Response()
        response = make_response()
        response = jsonify()
        通过对象调用方法
        response.set_cookie(key,value,max_age)
        其中max_age表示过期时间,单位是秒
        也可以使用expires设置过期时间,expires=datetime.now()+timedelta(hour=1)
    
  • 获取:

    通过request对象获取。
        request.form.get()
        request.args.get()
        cookie也在request对象中
        request.cookies.get(key) ----> value
    
  • 删除:

     通过response对象删除。 把浏览器中的key=value删除了
        response = redirect(xxx)
        response = render_template(xxx)
        response = Response()
        response = make_response()
        response = jsonify()
       通过对象调用方法
        response.delete_cookie(key)
    

session方式

session: 是在服务器端进行用户信息的保存。一个字典
注意:
使用session必须要设置配置文件,在配置文件中添加SECRET_KEY=‘xxxxx’,
添加SECRET_KEY的目的就是用于sessionid的加密。如果不设置会报错。

  • 设置:
    如果要使用session,需要直接导入:
    from flask import session

​ 把session当成字典使用,因此:session[key]=value
​ 就会将key=value保存到session的内存空间
​ 并会在响应的时候自动在response中自动添加有一个cookie:session=加密后的id

  • 获取
    用户请求页面的时候就会携带上次保存在客户端浏览器的cookie值,其中包含session=加密后的id
    获取session值的话通过session直接获取,因为session是一个字典,就可以采用字典的方式获取即可。
    value = session[key] 或者 value = session.get(key)

    ​ 这个时候大家可能会考虑携带的cookie怎么用的????
    ​ 其实是如果使用session获取内容,底层会自动获取cookie中的sessionid值,
    ​ 进行查找并找到对应的session空间

  • 删除
    session.clear() 删除session的内存空间和删除cookie
    del session[key] 只会删除session中的这个键值对,不会删除session空间和cookie

9.登录权限

只要走center路由,判断用户是否是登录状态,如果用户登录了,可以正常显示页面,如果用户没有登录
则自动跳转到登录页面进行登录,登录之后才可以进行查看。

钩子函数:
直接应用在app上:

before_first_request
before_request
after_request
teardown_request

应用到蓝图:

before_app_first_request
before_app_request
after_app_request
teardown_app_request

required_login_list = ['/user/center', '/user/change']
# ****重点*****
@user_bp.before_app_request
def before_request():
    if request.path in required_login_list:
        uid = session.get('uid')
        if not uid:
            return render_template('user/login.html')
        else:
            user = User.query.get(uid)
            # g对象,本次请求的对象,给g对象增加user属性
            g.user = user

10.文件上传

A. 本地上传
注意:
表单: enctype=“multipart/form-data”

view视图函数: photo = request.files.get('photo') ----》photo是FileStorage

属性和方法:FileStorage = 》fs

fs.filename

os.path.join(UPLOAD_DIR,filename)

fs.save(path) ----> path上传的路径
fs.read() ----> 将上传的内容转成二进制方式

B. 上传到云端(对象存储)
本地的资源有限或者是空间是有限的

https://developer.qiniu.com/kodo/sdk/1242/python —》参照python SDK

util.py:

import random
from qiniu import BucketManager
from qiniu import Auth, put_file, etag, put_data
import qiniu.config
def upload_qiniu(filestorage):
    # 需要填写你的 Access Key 和 Secret Key
    access_key = 'Access Key'
    secret_key = 'Secret Key'

    # 构建鉴权对象
    q = Auth(access_key, secret_key)

    # 要上传的空间
    bucket_name = 'uestcwxy-blog'

    # 上传后保存的文件名
    filename=filestorage.filename
    ran=random.randint(1,100000)
    suffix=filename.rsplit('.')[-1]
    key = filename.rsplit('.')[0]+'_'+str(ran)+suffix

    # 生成上传 Token,可以指定过期时间等
    token = q.upload_token(bucket_name, key, 3600)

    ret, info = put_data(token, key, filestorage.read())
    return ret,info

def delete_qiniu(filename):
    access_key = 'Access Key'
    secret_key = 'Secret Key'
    # 初始化Auth状态
    q = Auth(access_key, secret_key)
    # 初始化BucketManager
    bucket = BucketManager(q)

    # 你要测试的空间, 并且这个key在你空间中存在
    bucket_name = 'uestcwxy-blog'
    key = filename
    # 删除bucket_name 中的文件 key
    ret, info = bucket.delete(bucket_name, key)
    return info

评论:
文章的详情:必须携带aid,aid表示的是文章的主键id

通过主键id得到文章对象

如果还有其他内容的分页,就需要在路由携带page

例如:http://127.0.0.1:5000/article/detail?page=2&aid=1

分页:

print(pagination.items) # [<Article 4>, <Article 3>, <Article 2>]
print(pagination.page) # 当前的页码数
print(pagination.prev_num) # 当前页的前一个页码数
print(pagination.next_num) # 当前页的后一页的页码数
print(pagination.has_next) # True

print(pagination.has_prev) # True
print(pagination.pages) # 总共有几页
print(pagination.total) # 总的记录条数

生成上传 Token,可以指定过期时间等
token = q.upload_token(bucket_name, key, 3600)

ret, info = put_data(token, key, filestorage.read())
return ret,info

def delete_qiniu(filename):
access_key = ‘Access Key’
secret_key = ‘Secret Key’
# 初始化Auth状态
q = Auth(access_key, secret_key)
# 初始化BucketManager
bucket = BucketManager(q)

# 你要测试的空间, 并且这个key在你空间中存在
bucket_name = 'uestcwxy-blog'
key = filename
# 删除bucket_name 中的文件 key
ret, info = bucket.delete(bucket_name, key)
return info

评论:
文章的详情:必须携带aid,aid表示的是文章的主键id

通过主键id得到文章对象

如果还有其他内容的分页,就需要在路由携带page

例如:http://127.0.0.1:5000/article/detail?page=2&aid=1

分页:

print(pagination.items) # [<Article 4>, <Article 3>, <Article 2>]
print(pagination.page) # 当前的页码数
print(pagination.prev_num) # 当前页的前一个页码数
print(pagination.next_num) # 当前页的后一页的页码数
print(pagination.has_next) # True
print(pagination.has_prev) # True
print(pagination.pages) # 总共有几页
print(pagination.total) # 总的记录条数

11.flask-wtf

https://flask-wtf.readthedocs.io/en/stable/# https://wtforms.readthedocs.io/en/2.3.x/ flask-wtf:集成了wtform,csrf的保护和文件上传功能,图形验证码。

使用:

安装:

pip install Flask-WTF

全局使用csrf保护

csrf = CSRFProtect(app=app)

必须需要设置SECRET_KEY这个配置项 ​

app.config['SECRET_KEY'] = 'fgfhdf4564'
  1. 定义form.py: 在文件中中添加:
class UserForm(FlaskForm):
        name = StringField('name', validators=[DataRequired()])

各种:Field类型 ​ StringField ​ PasswordField ​ IntegerField ​ DecimalField ​ FloatField ​ BooleanField ​ RadioField ​ SelectField ​ DatetimeField

各种的验证:
DataRequired
EqualTo
IPAddress
Length
NumberRange
URL
Email
Regexp
  1. 使用:
    视图中: … form =UserForm() return render_template(‘user.html’,form=form)

    模板中:

 <form action='' method=''>
      {{form.csrf_token}}
      {{form.name}}
      {{form.password}}
      <input type='submit' value=''/>
    </form>
  1. 提交验证:

    @app.route('/',methods=['GET','POST'])
       def hello_world():
            uform = UserForm()
            if uform.validate_on_submit():   ------->主要通过validate_on_submit进行校验
                print(uform.name)
                print(uform.password)
                return '提交成功!'return render_template('user.html', uform=uform)
    

文件上传

  1. 定义form class UserForm(FlaskForm): 上传使用的就是FileField,如果需要指定上传文件的类型需要使用:FileAllowed icon = FileField(label=‘用户头像’, validators=[FileRequired(), FileAllowed([‘jpg’, ‘png’, ‘gif’], message=‘必须是图片文件格式’)])
  2. 模板中的使用同其他类型的字段,但是必须在form上面:enctype=“multipart/form-data”
  3. 视图函数中如果验证成功,通过: icon = uform.icon.data -----》icon是FileStorage类型 filename = secure_filename(icon.filename) icon.save(os.path.join(UPLOAD_DIR, filename))

12.图片验证码

步骤:

  1. 生成验证码
    pip install pillow
    from PIL import Image, ImageFont, ImageDraw, ImageFilter
  1. image对象 code验证码
  2. /login
GET:
   渲染login.html
   
   ```html
   <img src="{{url_for('blog.pic')}}" alt="ABC" style="height:34px;" id="pic">
   ```
   
   POST:
       获取数据
       验证码的匹配
       验证用户和密码(check_password_hash)
       session['uname'] =
       重定向到首页
   /pic
     image对象 code验证码
     将image对象转成二进制
     make_response(buf_bytes)  ---->response
     response.headers['Content-Type']= 'image/jpg'
   
     session['code']=code验证码
     return response

13.redis缓存

缓存中: pip install redis pip install flask-caching

启动redis ​ 进到redis目录: redis-server redis.windows.conf

使用缓存:

1. Cache对象
​
   from flask-caching import Cache
​
   cache = Cache()2.
config = {
    'CACHE_TYPE': 'redis',
    'CACHE_REDIS_HOST': '127.0.0.1',
    'CACHE_REDIS_PORT': 6379
}def create_app():
    .....
    cache.init_app(app,config)3. 设置缓存:
   cache.set(key,value,timeout=second)   ----> flask_cache_pic_abc
   cache.set_many([(key,value),(key,value),(key,value),...])
​
   获取缓存值:
   cache.get(key)  --->value
   cache.get_many(key1,key2,...)
​
   删除缓存:
   cache.delete(key)
   cache.delete_many(key1,key2,...)
   cache.clear()
​
视图函数缓存:
@app.route("/")
@cache.cached(timeout=50)
def index():
    return render_template('index.html')

14.SMS手机验证码

  1. 获取验证码:

    1. 输入手机号码

    2. 通过ajax发送请求

    3. 后端: 获取手机号码 使用requests向第三方的服务端(网易易盾)发送请求 URL

    https://api.netease.im/sms/sendcode.action method: POST header: headers={}
    headers[‘Content-Type’] = ‘application/x-www-form-urlencoded;charset=utf-8’ AppSecret = ‘ee8d51d1061e’ Nonce = ‘74093849032804’ CurTime = str(time.time())

    headers[‘AppKey’] = ‘cc735ffe22684cc4dab2dc943540777c’

    headers[‘Nonce’] = Nonce

    headers[‘CurTime’] = CurTime s = AppSecret + Nonce + CurTime

    headers[‘CheckSum’] = hashlib.sha1(s.encode(‘utf-8’)).hexdigest().lower() res =requests.post(url, data={‘mobile’: phone}, headers=headers) 获取响应对象: res.text 文本内容 res.content 二进制

    1. 转成json对象 r = json.loads(res.text)

      r.obj —> 验证码

      保存到缓存中: cache.set(phone,r.obj)

    2. 返回json结果给ajax

2.登录验证: 获取手机号码和验证码进行验证 phone = request.form.get(‘phone’) validate = request.form.get(‘valiadate’) code = cache.get(phone) if code == validate: user = User.query.filter(User.phone == phone).first() cache.set(‘uname’, user.username) session[‘uname’] = user.username return redirect(url_for(‘blog.index’)) else: flash(‘手机验证码错误’) return render_template(‘login_phonecode.html’)

15.nginx

安装可以参照的路径: http://nginx.org/en/linux_packages.html#Ubuntu

启动Nginx nginx [ -c configpath] 默认配置目录:/etc/nginx/nginx.conf

信息查看 nginx -v nginx -V

查看进程: ps -ef |grep nginx

控制Nginx nginx -s signal stop 快速关闭 quit 优雅的关闭 reload 重新加载配置

通过系统管理

systemctl status nginx 查看nginx状态

systemctl start nginx 启动nginx服务

systemctl stop nginx 关闭nginx服务

systemctl enable nginx 设置开机自启

systemctl disable nginx 禁止开机自启

16.闪现

Flask 的闪现系统提供了一个良好的反馈方式。

闪现系统的基本工作方式是:只在下一个请求中访问上一个请求结束时记录的消息。一般我们结合布局模板来使用闪现系统。

需求:有两个函数login 和index ,有一个人在向login页面发起请求,login生成一个错误,放到session,跳转到index显示错误,然后再把session移除,并且这个错去只能执行一次(也就是让你看一次)这个东西就可以用闪现是实现。

  1. 在一个请求结束的时候添加flash
        flash('恭喜!验证成功啦!','info')
        flash('哈哈哈','error')
        flash(username,'warning')
    
  2. 在当前请求中渲染获取或者仅仅下一个请求中可以获取。

添加闪现:(后面的类型是可选择的) ​ flash(‘恭喜!验证成功啦!’,‘info’) ​ flash(‘哈哈哈’,‘error’) ​ flash(username,‘warning’)

获取闪现内容: ​ get_flash_messages(with_categories=[True/False]) ​ get_flashed_messages(category_filter=[“error”]) 可选的 ​ 有针对性的获取对应类型的闪现消息

17.日志

uwsgi ----》 uwsgi.log

  1. 使用app自带, app.logger.info(‘’) app.logger.debug(‘A value for debugging’) app.logger.warning(‘A warning occurred (%d apples)’, 42) app.logger.error(‘An error occurred’)

  2. 通过logging进行创建:

    import logging
    logger = logging.getLogger('name')  # 默认flask的名字叫:app
    logger = logging.getLogger('app')
    
    

保存到文件:
3. python logging.basicConfig(filename='log.txt', filemode='a', level=logging.WARNING,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

  1. logger.setLevel(level=logging.INFO)
    handler = logging.FileHandler("log1.txt")
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    
  2. 使用logger.info(‘message’)

18.flask-restful

什么是RESTful架构: (1)每一个URI代表一种资源; (2)客户端和服务器之间,传递这种资源的某种表现层; (3)客户端通过四个HTTP动词(GET,POST,PUT,DELETE,[PATCH]),对服务器端资源进行操作,实现"表现层状态转化"。 Postman 前后端分离: 前端: app,小程序,pc页面 后端: 没有页面,mtv: 模型模板视图 去掉了t模板。 mv:模型 视图 模型的使用:跟原来的用法相同 视图: api构建视图 步骤:

 pip install flask-restful
  1. 创建api对象 api = Api(app=app) api = Api(app=蓝图对象)

  2. 定义类视图:

    from flask_restful import Resource
       class xxxApi(Resource):
            def get(self):
                pass
            def post(self):
                pass
            def put(self):
                pass
            def delete(self):
                pass
    
  3. 绑定

api.add_resource(xxxApi,'/user')

参照:http://www.pythondoc.com/Flask-RESTful/quickstart.htmlhttps://flask-restful.readthedocs.io/en/latest/

路由: @app.route(‘/user’) def user(): -------》视图函数 … return response对象

增加 修改 删除 查询 按钮动作

http://127.0.0.1:5000/user?id=1 http://127.0.0.1:5000/user/1

restful: ---->api ----> 接口 ---->资源 ----> url

class xxx(Resource): -------> 类视图 def get(self): pass …

http://127.0.0.1:5000/user get post put delete 增加 修改 删除 查询 是通过请求方式完成的

路径产生:

    api.add_resource(Resource的子类,'/user')
    api.add_resource(Resource的子类,'/goods')
    api.add_resource(Resource的子类,'/order')

endpoint: http://127.0.0.1:5000/user/1

http://127.0.0.1:5000/goods?type=xxx&page=1&sorted=price ----》get

输入

----------------进:请求参数传入-------------------

步骤:

  1. 创建RequestParser对象: # 参数解析 parser = reqparse.RequestParser(bundle_errors=True) # 解析对象

  2. 给解析器添加参数: 通过parser.add_argument(‘名字’,type=类型,required=是否必须填写,help=错误的提示信息,location=表明获取的位置form就是post表单提交) 注意在type的位置可以添加一些正则的验证等。 例如:

parser.add_argument('username', type=str, required=True, help='必须输入用户名', location=['form'])
parser.add_argument('password', type=inputs.regex(r'^\d{6,12}$'), required=True, help='必须输入6~12位数字密码',location=['form'])
parser.add_argument('phone',type=inputs.regex(r'^1[356789]\d{9}$'), location=['form'], help='手机号码格式错误')
parser.add_argument('hobby', action='append')  # ['篮球', '游戏', '旅游']
parser.add_argument('icon', type=FileStorage, location=['files'])

只要添加上面的内容,就可以控制客户端的提交,以及提交的格式。

  1. 在请求的函数中获取数据: 可以在get,post,put等中获取数据,通过parser对象.parse_args() # 获取数据 args = parser.parse_args() args是一个字典底层的结构中,因此我们获取具体的数据时可以通过get username = args.get(‘username’) password = args.get(‘password’)

输出

------------输出-----------------

需要定义字典,字典的格式就是给客户端看的格式

user_fields = {
        'id': fields.Integer,
        'username': fields.String(default='匿名'),
        'pwd': fields.String(attribute='password'),
        'udatetime': fields.DateTime(dt_format='rfc822')
    }

客户端能看到的是: id,username,pwd,udatetime这四个key 默认key的名字是跟model中的模型属性名一致,如果不想让前端看到命名,则可以修改 但是必须结合attribute=‘模型的字段名’

自定义fields
  1. 必须继承Raw
  2. 重写方法: def format(self): return 结果
class IsDelete(fields.Raw):
        def format(self, value):
            print('------------------>', value)
            return '删除' if value else '未删除'
    user_fields = {'isDelete1': IsDelete(attribute='isdelete'),
    }

URI:

xxxlist ----->点击具体的一个获取详情 ------> 详情

定义两个user_fields, 1.用于获取用户的列表信息结构的fields:

user_fields_1 = {
        'id': fields.Integer,
        'username': fields.String(default='匿名'),
        'uri': fields.Url('single_user', absolute=True)  ----》参数使用的就是endpoint的值
    }

2。具体用户信息展示的fields

user_fields = {
        'id': fields.Integer,
        'username': fields.String(default='匿名'),
        'pwd': fields.String(attribute='password'),
        'isDelete': fields.Boolean(attribute='isdelete'),
        'isDelete1': IsDelete(attribute='isdelete'),
        'udatetime': fields.DateTime(dt_format='rfc822')
    }

涉及endpoint的定义:

api.add_resource(UserSimpleResource, ‘/user/int:id’, endpoint=‘single_user’)

出: return data 注意:data必须是符合json格式 { ‘aa’:10, ‘bb’:[ { ‘id’:1, ‘xxxs’:[ {},{} ] }, {

} ] } 如果直接返回不能有自定义的对象User,Friend

如果有这种对象,需要:marchal(),marchal_with()帮助进行转换。

  1. marchal(对象,对象的fields格式) # 对象的fields格式是指字典的输出格式 marchal([对象,对象],对象的fields格式)
  2. marchal_with() 作为装饰器修饰请求方法

@marshal_with(user_friend_fields) ​ def get(self, id): ​ 。。。。 ​ return data

函数需要参数,参数就是最终数据输出的格式

参数: user_friend_fields,类型是:dict类型 例如:

user_friend_fields = {
        'username': fields.String,
        'nums': fields.Integer,
        'friends': fields.List(fields.Nested(user_fields))
    }

fields.Nested(fields.String) ----> [‘aaa’,‘bbb’,‘bbbc’] fields.Nested(user_fields) -----> user_fields是一个字典结构,将里面的每一个对象转成user_fields -----》[user,user,user]

参考面试题:https://www.cnblogs.com/Utopia-Clint/p/10824238.html

18.跨域问题

一、跨域问题来源于JavaScript的"同源策略",即只有 协议+主机名+端口号 (如存在)相同, 则允许相互访问。也就是说JavaScript只能访问和操作自己域下的资源, 不能访问和操作其他域下的资源。跨域问题是针对JS和ajax的,html本身没有跨域问题。 后端:

  1. 使用第三方扩展flask-cors

from flask_cors import CORS ​ cors= CORS()

与app进行绑定 ​ cors.init_app(app=app,supports_credentials=True)

  1. response = make_response() response.headers[‘Access-Control-Allow-Origin’]=‘*’ response.headers[‘Access-Control-Allow-Methods’]=‘GET,POST’ response.headers[‘Access-Control-Allow-Headers’]=‘x-request-with,Content-type’ return response

二、蓝图与api使用:

user_bp = Blueprint('user', __name__)
api = Api(user_bp)
class xxxxApi(Resource):
      pass
api.add_resource(xxxxApi,'/xxxx')

基于b站千锋博客论坛教程:https://www.bilibili.com/video/BV17z4y1X7UZ?share_source=copy_web

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王翊珩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值