python学习经历-2-flask

接着之前的python进行学习,决定还是从更加实用点的项目相关的知识去学习,这样也可以去更好的实践当中去锻炼自己的能力,所以就选择了flask这个python中的轻量模块去学习。

一、python中虚拟环境的使用

python是一个非常常见的编程语言,在python使用过程中,我们总是需要使用到很多需要自己安装下载的库,所以一般一个python编程人员的电脑上都有着许许多多的python库,而在我们去开发一些网站,程序相关的时候,我们并不需要去使用到我们所有的库,并且可能需要去使用某些特定版本的库,可这又应该怎么办?这个时候,虚拟环境也就因此诞生了。

在使用Python语言时,通过pip(pip3)来安装第三方包,但是由于pip的特性,系统中只能安装每个包的一个版本。但是在实际项目开发中,不同项目可能需要第三方包的不同版本,迫使我们需要根据实际需求不断进行更新或卸载相应的包,而如果我们直接使用本地的Python环境,会导致整体的开发环境相当混乱而不易管理,这时候我们就需要开辟一个独立干净的空间进行开发和部署,虚拟环境就孕育而生。

下面就直接去记录一下一些虚拟环境的常见使用方法,最流行的就是Virtualenv这个虚拟环境的配置工具

安装:

1
pip install virtualenv virtualenvwrapper-win  //windows环境下

查看虚拟环境有哪些

1
workon

创建一个新的虚拟环境

1
mkvirtualenv 虚拟环境名

去使用一个虚拟环境

1
workon 虚拟环境名

注意:虚拟环境文件默认在C盘中用户下中的env中

二、数据库中的数据迁移操作

数据库首先先初始化连接设置

1
2
3
db_uri = 'mysql+pymysql://root:password@localhost:3306/bookdb'
app.config['SQLALCHEMY_DATABASE_URI'] = db_uri
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

其次就是模块类的创建,也就相当于数据库中的列

1
2
3
4
5
6
7
class User(db.Model):
__tablename__ = 'tb_user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(30), unique=True, index=True)
age = db.Column(db.Integer, default=1)
sex = db.Column(db.Boolean, default=True)
salary = db.Column(db.Float, default=10000, nullable=False)

为什么要做数据迁移呢?在我的理解中这就相当于一个空白数据库的初始化,将创建的类中的列添加到数据库之中,其实就是相当于我们去创建各个字段的过程。

下面就是用到的命令

1
2
3
4
flask db init   //创建迁移文件夹migrates,只调用一次
flask db migrate //生成迁移文件
flask db upgrade //执行迁移文件中的升级
flask db downgrade //执行迁移文件中的降级

三、flask框架中的一些基本操作以及示例

1、最基础的单文件框架

文件结构:

image-20231119174414920

static: 静态文件文件夹,用于存放一些像是css,js,image,font等的文件。

templates:用于存放html网站文件

app.py:(主项目文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world(): # put application's code here
return 'Hello World!'

@app.route('/index/') #路由的注册
def index():
return 'hello index!' #页面显示的数据

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

app运行的时候一些可选的参数:

参数 说明 默认值
debug 代码更新是否自动重启 False
theaded 是否开启多线程 False
port 指定端口 5000
host 指定主机(设置0.0.0.0可以通过本地IP访问) 127.0.0.1

一般项目开发时都会开启debug模式

2、简单的模板渲染

1
render_template()  #可以返回一个渲染的html页面

示例:

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def hello_world():
return 'hello world'

@app.route('/index/')
def index():
return render_template('index.html', name="法外狂徒张三")


if __name__ == '__main__':
app.run(debug=True)

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>

<link rel="stylesheet" href="../static/index.css">
</head>
<body>
<h2>index</h2>
<hr>
<h3>name: {{ name }}</h3>
</body>
</html>

可以利用render_template去将后端的一些数据返回到前端,然后再使用去使用传递过来的参数

3、对文件结构进行进行拆分

文件结构:

image-20231119182749925

将除了主文件的其他文件都移入一个单独创建的App文件夹当中。

init.py:初始化的文件,在这里我们将app的创建函数放入其中,待需要使用时导入即可

models.py:模板文件,后边主要用于数据库方面的操作

views.py:视图函数的存放,也就是存放各种路由的地方

为了方便视图函数的使用,使用蓝图去绑定

1
app.register_blueprint(blueprint=blue)

将app绑定在blue上,这样之后路由就可以这样去使用了

1
2
@blue.route('/')
@blue.route('/home/')

4、简易登录界面的实现

①cookie的一些设置

1
2
3
response = redirect('/home/') #重定向,用于登录后界面的跳转

response.set_cookie('user', username, max_age=3600*24*7)

可以直接通过set__cookie去设置一个json格式的cookie

②session的一些设置

1
app.config['SECRET_KEY'] = 'admin'

将app的配置文件中的SECRET_KEY修改为不空的值,并且将这个作为密匙去加密

1
2
3
session['user'] = username

session.permanent = True #默认是false,关闭浏览器后就清空session

③实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import datetime

from flask import Blueprint, redirect, render_template, request, session
from .models import *

blue = Blueprint('user', __name__)


@blue.route('/')
@blue.route('/home/')
def index():
username = session.get('user')

return 'home.html', username


@blue.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
pass

username = request.form.get('username') #获取传递过来的参数
password = request.form.get('password')

if username == 'admin' and password == '123456':
response = redirect('/home/')

# response.set_cookie('user', username, max_age=3600*24*7)
session['user'] = username
session.permanent = True

return response

else:
return '用户名或密码错误!'


@blue.route('/logout/')
def logout():
response = redirect('/home/')

session.pop('user')

return response

# @blue.route('/string/<string:name>/', methods=['GET','POST'])
# def get_string(name):
# print(type(name))
# return name
#
# @blue.route('/redirect/')
# def make_redirect():
# pass
#
# return redirect('https://www.qq.com')

在自己设计一个前段登入即可

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>登录</h2>
<hr>

<form action="" method="post">
<p>
用户名:<input type="text" name="username">
</p>
<p>
密码:<input type="password" name="password">
</p>
<p>
<button>提交</button>
</p>
</form>

</body>
</html>

5、路由参数

string 接收如何没有斜杠(‘/‘)的字符串(默认)

int 接收整型

float 接收浮点型

path 接收路径,可接收斜线(‘/‘)

uuid 只接受uuid字符串,唯一码,一种生成规律(uuid是一段特定的字符串,基本无法伪造)

any 可以同时指定多种路径,进行限定

1
2
3
4
@blue.route('/any/<any(apple, orange, banana):fruit>/')
def get_any(fruit)
print(type(fruit))
return str(fruit)

any表示限定只能在给定的之中选择

6、路由请求方法的指定

1
@blue.route('/login/', methods=['GET', 'POST'])

设置该路由可以由GET和POST这两种请求方法区访问

一些最常见的请求方式

GET

POST

PUT

DELETE

四、flask中四个全局变量

**

变量 上下文 描述
current_app 应用上下文 相当与在主程序中激活的实例化app(app=Flask(__name__)
g 应用上下文 一次性函数,处理请求的临时变量。只在一个请求中被应用,下个请求开始时会自动重置
request 请求上下文 请求对象。存放了客户端发来的HTTP信息
session 请求上下文 记录用户和服务器之间的会话的。在服务器端记录需要记住的信息。(和cookie对应,cookies是记录在客户端的)

request中的一些用法:

url 完整请求地址

base_url 去掉GET参数的URL

host_url 只有主机和端口号的URL

path 路由中的路径

method 请求方式

remote_addr 请求的客户端地址

args GET请求参数

form POST请求参数

files 文件上传

headers 请求头

cookies 请求头中的cookie

五、钩子函数装饰器

函数 描述
before_first_request 处理第一次请求之前
before_request 在每次请求之前,通常利用这个处理一些变量,实现反爬
app.after_request 注册一个函数,如果没有未处理的异常抛出,每次请求结束后运行
app.teardown_request 有异常也会运行,每次请求结束后。当APP上下文被移除后执行的函数,可以进行数据库的提交和回滚

使用示例:

1
2
3
@blue.before_request  #每次请求之前访问
def before():
print('before request')

六、Flask的一些插件的使用

1、flask-caching

安装

1
pip install flask-caching

初始化

1
2
3
cache = Cache(config={
'CACHE_TYPE': 'simple' #缓存类型
})

添加缓存

1
2
3
4
5
6
7
8
@blue.route('/')
@cache.cached(timeout=20) #使缓存存在20秒
def index():
print('index2')
print(g.star)

time.sleep(5)
return 'index2'

对反爬的相关应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@blue.before_request  #每次请求之前访问
def before():
print('before request')

print(request.path)
print(request.method)
print(request.remote_addr)

#简单的反爬
print(request.user_agent)
if 'python' in request.user_agent.string:
return '您正在使用python爬虫,再见!'

#针对ip作反爬
ip = request.remote_addr
if cache.get(ip):
#做了个拦截,不会进入视图函数
return '小伙子,别爬了!'
else:
#对每个ip设置一个缓存,1秒内不让重复访问
cache.set(ip, 'value', timeout=1)

七、前后端分离模式

image-20231119204503992

将原本的views拆分为urls和apis。

urls.py

1
2
3
4
5
6
7
8
9
#  路由文件

from .exts import api
from .apis import *

api.add_resource(HelloResource, '/hello/')
api.add_resource(UserResource, '/user/', endpoint='id')
api.add_resource(User2Resource, '/user2/')
api.add_resource(User4Resource, '/user4/')

apis.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from flask import jsonify
from flask_restful import Resource, fields, marshal_with, reqparse
from .models import *


# 类视图 CBV
class HelloResource(Resource): #必须要继承Resource
def get(self):
return 'get请求'

def post(self):
return jsonify({'methods': 'post请求'})



# Flask-RESTful
ret_field = {
'status': fields.Integer,
'msg': fields.String,
'data': fields.String,
'like': fields.String(default='ball'),
'data2': fields.String(attribute='data') #类似于引用,将data的值给一个另外名字的变量
}


class UserResource(Resource):
@marshal_with(ret_field) #flask的一个拓展,用以规范后端返回数据的格式
def get(self):
return {
'status': 1,
'msg': 'ok',
'data': '千峰教育Python'
}


user_fields = {
'id': fields.Integer,
'name': fields.String,
'age': fields.Integer,
'url': fields.Url(endpoint='id', absolute=True)
}

ret_field2 = {
'status': fields.Integer,
'msg': fields.String,
'data': fields.Nested(user_fields) #获取一个列表数据
}


class User2Resource(Resource):
@marshal_with(ret_field2)
def get(self):
user = User.query.first() #从数据库中拿出数据
return {
'status': 1,
'msg': 'ok',
'data': user
}


# 参数解析 完成前端数据的返回
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, help='name是必须的')
parser.add_argument('age', type=int, action='append')

class User4Resource(Resource):
def get(self):
args = parser.parse_args()
name = args.get('name')
age = args.get('age')
return {'name': name, 'age': age}

八、数据表之间的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from .exts import db


#一对多
class Grade(db.Model):
__tablename__ = 'grade'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(30), unique=True, index=True)
#建立关联
#定义班级表的一对多关系,不是字段,Student为学生表模型,backref为反向查找名称
students = db.relationship('Student', backref='grade', lazy=True)


class Student(db.Model):
__tablename__ = 'student'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(30), unique=True)
age = db.Column(db.Integer)
#创建外键,关联到班级表的主键,实现一对多关系,班级表中也要有对应操作
gradeid = db.Column(db.Integer, db.ForeignKey(Grade.id))


#多对多
#中间表(不是类)
collect = db.Table(
'collects',
#user_id为表字段名称,user.id未外键表的id
db.Column('user_id', db.Integer, db.ForeignKey('usermodel.id'), primary_key=True),
db.Column('movie_id', db.Integer, db.ForeignKey('movie.id'), primary_key=True)
)


class UserModel(db.Model):
__tablename__ = 'usermodel'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(30))
age = db.Column(db.Integer)


class Movie(db.Model):
__tablename__ = 'movie'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(30))
age = db.Column(db.Integer)

#多对多 关联的学生摆个的模型 中间表的名称 反向查找
users = db.relationship('UserModel', backref='movies', lazy='dynamic', secondary=collect)

lazy属性

dynamic: 会返回一个query对象(查询集),可以继续使用其他查询方法,如all()

select: 首次访问到属性的时候,就会加载该属性的数据

joined: 在对关联的两个表进行join操作,从而获取到所有相关的对象

True: 返回一个可用的列表对象,同select

九、前端页面渲染的一些函数操作

函数 说明
capitalize 首字母大写
upper 全部大写
lower 全部小写
title 每个单词首字母大写
trim 去掉两边的空白
striptags 去掉所有的HTML标签
safe 即删除标签,又保留标签功能

我们就可以在html页面中去使用

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板</title>
</head>
<body>
<h1>hello {{ name|capitalize }} ! </h1>
</body>
</html>

这样就可以去便利我们对参数的使用

十、实践操作–个人简易博客的搭建

1、更加清晰分明的文件结构:

image-20231125193419163

由于需要有前端显示和后端管理两个层面,所以将视图文件,模板文件,以及静态文件分为两个,并且专门创建了一个文件夹去存放,这样就能去更清晰的去文件分类。

2、代码编写中的一些知识学习

1)装饰器的使用

对登录界面使用装饰器进行简化,并且解决flask中使用两次同个装饰器的报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
from functools import wraps
#装饰器 ---登入验证
def login_required(fn):
@wraps(fn)
def inner(*args, **kwargs):
user_id = request.cookies.get('user_id', None)
if user_id:
user = AdminUserModel.query.get(user_id)
request.user = user
return fn(*args, **kwargs)
else:
return redirect('/admin/login/')
return inner

2)前端js代码的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$(function () {
$("#main table tbody tr td a").click(function () {
var that = $(this);
var id = that.attr("aid"); //对应id
if (event.srcElement.outerText == "删除") {
if (window.confirm("此操作不可逆,是否确认?")) {

$.post('/admin/delarticle/', {'id': id}, function (data){
console.log(data.msg)
if(data.code == 200){
location.reload()
}
else{
alert(data.msg)
}
})
}
}
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@admin.route('/admin/delarticle/', methods=['GET', 'POST'])
@login_required
def admin_del_article():
if request.method == 'POST':
id = request.form.get('id')
article = ArticleModel.query.get(id)
try:
db.session.delete(article)
db.session.commit()
except Exception as e:
print('e:', e)
db.session.rollback()
return jsonify({'code': 500, 'msg': '删除失败!'})

return jsonify({'code': 200, 'msg': '删除成功!'})

return jsonify({'code': 400, 'msg': '请求方式错误!'})

进行前后端的数据交互,通过判断请求方法区决定不同的操作对于路由而言,从而去实现不同的功能,这里就是去利用了json数据与前端的交互,从而完成了页面相应和后台数据的关联。