python笔记
Flask
路由配置篇
基本使用
综合案例——音乐网站
https://github.com/xishandong/Music_Web
在我们创建好的项目中,我们有一个基础的路由,如下所示:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world(): # put application's code here
...
if __name__ == '__main__':
app.run()
其中的@app.route(‘/’)就是我们需要的路由,我们运行这个就可以打开对应的网页。
返回数据(html,json,重定向)
-
返回html数据
- 纯粹的网页数据
我们可以使用模板进行返回数据,我们需要从flask中导入render_template,然后将我们需要的网页模板放在templates目录中即可。
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def hello_world(): # put application's code here return render_template('index.html') if __name__ == '__main__': app.run()
这样我们就可以在对应的页面看见index.html中的内容了。
-
后端给前端传数据
我们只需要在返回数据的时候传入对应的值即可。
@app.route('/') def hello_world(): # put application's code here something = [], {}, '', 1 return render_template('index.html', something=something)
我们在前端用{{ something }}即可接受到传递的数据
-
返回json数据
我们需要从flask导入jsonify,像这样就可以传递json信息了。
from flask import jsonify
@app.route('user/', methods=['POST'])
def user_json():
return jsonify({'success': 200, 'message': '请登陆后进行操作'})
-
重定向
使用这个功能我们需要从flask导入redirect
from flask import redirect, url_for
@app.route('/refer', method=['GET', 'POST'])
def refer():
return redirect('/') # 表示重定向到/路由下
# 这个是重定向到蓝图中的user的login函数
# return redirect(url_for("user.login"))
路由配置高级篇
蓝图
我们在实际开发时,由于代码量会很大,如果我们把所有内容都放到app.py一个文件中去编写,那么无疑是很痛苦的一件事情,这个时候就可以用到Flask的蓝图函数。
我们需要在项目中创建一个文件夹,可以叫blueprints,我们在里面就可以编写蓝图了。
- 在app中
from blueprints.user import bp as user_bp
# 注册蓝图
app.register_blueprint(user_bp)
- 在蓝图文件中
from flask import Blueprint
bp = Blueprint("user", __name__, url_prefix='/')
然后就可以正常写路由了。
路由传参
我们可以在url中自定义参数进行获取,如下所示
# 可以定义类型 @app.route('/blog/<int: blog_id>')
@app.route('/blog/<blog_id>')
def blog_detail(blog_id):
return render_template("blog_detail.html", blog_id=blog_id)
Flask获取前端传入的数据
包含在url中的数据
@app.route('/book/list')
def book_list():
# argument: 参数
# request.args: 字典类型
page = request.args.get("page", default=1, type=int)
return f"你获取的是{page}"
json格式
- 前端指定了contentType: ‘application/json;charset=UTF-8’
@bp.route('user/search_add', methods=['POST'])
def search_add():
query = request.json.get('query')
offset = request.json.get('offset')
return "成功"
- 前端没有指定json,而是表单传输
@bp.route('user/addPlaylist', methods=['POST'])
def add_playlist():
name = request.form.get('playlistName')
desc = request.form.get('playlistDesc')
return "成功"
文件格式
@bp.route('user/upload_cover/<id>', methods=['POST'])
def upload_cover(id):
file = request.files['file']
filename = file.filename
file.save(save_path)
return jsonify({'success': 200, 'file': save_path})
在js中:
var avatar = $("#avatar")
avatar.click(function () {
$("#fileInput").click();
});
$("#fileInput").change(function () {
var fileInput = document.getElementById("fileInput");
var file = fileInput.files[0];
var formData = new FormData();
formData.append("file", file);
$.ajax({
url: "/upload",
type: "POST",
data: formData,
processData: false,
contentType: false,
success: function (response) {
if (response.file) {
$("#avatar").attr("src", response.file);
}
},
error: function (error) {
console.log(error);
}
});
});
在html中:
<!-- 上传头像 -->
<input type="file" id="fileInput" style="display: none;">
Flask的jinjia2模板语言
模板中的数据展示
在flask中,开发者为我们提供了一种jinjia2的模板语言,可以配合flask后端使用。
<div>
{{ message }}
</div>
这样就可以展示后端传递给html的信息了,如何传递信息见上文Flask-路由配置篇-返回数据下。
模板的循环以及条件语句
-
循环语句
在jinjia2中,我们用下述方式进行循环,注意循环会完全遍历,不能中途break掉。假设后端给模板传递了一个items的可迭代数据:
{% for item in items %}
<div>
{{ item }}
</div>
{% endfor %}
-
条件语句
类似于上述写法,jinjia2也可以进行条件语句的编写
{% if something %}
<div>
条件语句为真
</div>
{% else %}
<div>
条件语句为假
</div>
{% endif %}
模板的继承
如果我们的网页有很多重复的内容,例如导航栏以及页脚等内容,我们可以用模板继承的写法来编写,然后再父模板非公共部分划分出block即可让子模版在非公共地方编写。
-
在父模板中
假设父模板的名字叫base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>title</title>
<link>
{% block head %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
{% block tail %}{% endblock %}
</html>
- 在子模版中
{%extends "base.html" %}
{% block head %}
<link>
{% endblock %}
{% block body %}
<div>
我是子模版
</div>
{% endblock %}
{% block tail %}
<script></script>
{% endblock %}
在模板中引用静态文件
-
引用网络静态文件
这个和普通的html文件一样。
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.0.3/css/font-awesome.css">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
-
引用本地静态文件
我们在模板中使用本地的静态文件,可以使用url_for来实现,例如:
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/base.js') }}"></script>
<img id="avatar" src="{{ url_for('static', filename='img/profile.webp') }}"
-
页面跳转
我们在一个页面想要跳转到其他页面时,也需要使用url_for,下面将展示两种不同的情况:
<!-- 这个hello_world时app里面的函数 -->
<a href="{{ url_for('hello_world') }}" class="item-link" id="pageLink">
<!-- playlists是user蓝图中的函数 -->
<a href="{{ url_for('user.playlists') }}" class="item-link icon-fill" id="pageLink">
模板中的 | 语法(过滤器)
总所周知,模板我们可以使用{{ something }}来展示后端给前端的数据,但是我们如何对这个数据进行一些简单的处理呢?这就要用到模板语言中的 | 语法。
首先,我们来看一个相对复杂的案例:
<li>推荐指数<span>{{ (10 - ((score - song.score | float) | abs | round(2) )) | round(2) }}</span></li>
这个是什么意思呢?首先第一个是将分数转化为浮点数,然后用另外一个分数去做差,然后去绝对值,同时保留两位小数,然后用10去做差,再保留两位小数。当然,jinjia2还支持更多的运算,下面将列出常见的用法:
过滤器名称 | 说明 | 例子 |
---|---|---|
safe | 渲染时值不转义 | {{ ‘name’ |safe }} |
escape | HTML转义,即使autoescape关了也转义,可以缩写为e | {% autoescape false %} {{ ‘name’ |escape }} {% endautoescape %} |
capitialize | 把值的首字母转换成大写,其他子母转换为小写 | {{ ‘hello world’ |capitalize }} |
lower | 把值转换成小写形式 | {{ ‘XML’ |lower }} |
upper | 把值转换成大写形式 | {{ ‘hello world’ |upper}} |
title | 把值中每个单词的首字母都转换成大写 | {{ ‘hello world’ |title}} |
trim | 把值的首尾空格去掉 | {{ ’ hello ’ |trim }} |
striptags | 渲染之前把值中所有的HTML标签都删掉 | |
join | 拼接多个值为字符串 | |
replace | 替换字符串的值 | |
round | 默认对数字进行四舍五入,也可以用参数进行控制 | {{ 12.8888 |round(2, ‘floor’) }} |
int | 把值转换成整型 | {{ 12.8888 |int }} |
float | 把值转化为小数 | {{ song.score | float }} |
abs | 把值转化为正数 | {{ -12 |abs }} |
reverse | 字符串反转 | {{ ‘hello’ |reverse }} |
default | 增加默认值 | {{ name |default(‘No name’) }} |
format | 格式化显示 | {{ ‘%s is %d’ |format(“Number”, 99) }} |
first | 取第一个元素 | {{ [1,2,3] |first }} |
last | 取最后一个元素 | {{ [1,2,3] |last }} |
length | 返回列表长度,可以写为count | {{ [1,2,3,4,5] |length }} |
sum | 列表求和 | {{ [1,2,3,4,5] |sum }} |
sort(列表) | 列表排序,默认为升序 | {{ [3,2,1,5,4] |sort }} |
join | 合并为字符串 | {{ [1,2,3,4,5] |join(’ |') }} |
sort(字典) | 按指定字段排序,这里设reverse为true使其按降序排 |
|
groupby | 列表分组,每组是一个子列表,组名就是分组项的值 |
|
map | 取字典中的某一项组成列表,再将其连接起来 | {{ users |map(attribute=‘name’) |join(', ') }} |
见到了常见的过滤器之后,我们还可以在python内部进行自定义过滤器
# 第一种方式
def get_even_list(l):
return l[::2]
# 函数的第一个参数是过滤器函数,第二个参数是过滤器名称
app.jinja_env.filters['even_filter'] =get_even_list
# 第二种方式
@app.template_filter() # 过滤器函数
def is_even(num):
if num % 2 == 0:
return "even number"
else:
return "odd number"
<p>{{ [1,2,3,4,5] | even_filter }}</p>
<p>{{ 2 | is_even }}</p>
测试器
测试器总是返回一个布尔值,它可以用来测试一个变量或者表达式,使用”is”关键字来进行测试。
{% set name='ab' %}
{% if name is lower %}
<h2>"{{ name }}" are all lower case.</h2>
{% endif %}
测试器本质上也是一个函数,它的第一个参数就是待测试的变量,在模板中使用时可以省略去。如果它有第二个参数,模板中就必须传进去。测试器函数返回的必须是一个布尔值,这样才可以用来给if语句作判断。
- jinjia2内置测试器
{# 检查变量是否被定义,也可以用undefined检查是否未被定义 #}
{% if name is defined %}
<p>Name is: {{ name }}</p>
{% endif %}
{# 检查是否所有字符都是大写 #}
{% if name is upper %}
<h2>"{{ name }}" are all upper case.</h2>
{% endif %}
{# 检查变量是否为空 #}
{% if name is none %}
<h2>Variable is none.</h2>
{% endif %}
{# 检查变量是否为字符串,也可以用number检查是否为数值 #}
{% if name is string %}
<h2>{{ name }} is a string.</h2>
{% endif %}
{# 检查数值是否是偶数,也可以用odd检查是否为奇数 #}
{% if 2 is even %}
<h2>Variable is an even number.</h2>
{% endif %}
{# 检查变量是否可被迭代循环,也可以用sequence检查是否是序列 #}
{% if [1,2,3] is iterable %}
<h2>Variable is iterable.</h2>
{% endif %}
{# 检查变量是否是字典 #}
{% if {'name':'test'} is mapping %}
<h2>Variable is dict.</h2>
{% endif %}
- 自定义测试器
# 自定义测试器
# 第一种方式
import re
def test_tel(tel_num):
tel_re = r'\d{11}'
return re.match(tel_re,tel_num)
app.jinja_env.tests['is_tel'] = test_tel
# 第二种方式
@app.template_test('start_with')
def start_with(str, suffix):
return str.lower().startswith(suffix.lower())
{% set tel = '18910171111' %}
{% if tel is is_tel %}
<h2>{{ tel }} is mobile phone</h2>
{% endif %}
{% set name = 'Hello world' %}
{% if name is start_with 'hello' %}
<h2>"{{ name }}" start_with "hello"</h2>
{% endif %}
数据库篇
数据库配置以及模型建立
-
首先安装好下述依赖
pip install pymysql pip install sqlalchemy pip install flsk-sqlalchemy # 这个是进行提交的 pip install flask-migrate
-
然后编写config.py文件
# MySQL的主机名 HOSTNAME = "127.0.0.1" # MySQL监听的端口, 默认3306 PORT = 3306 # 连接MySQL的用户名 USERNAME = "root" # 连接MySQL的密码 PASSWORD = "" # MySQL上连接的数据库名称 DATABASE = "test" # 配置 app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8'
-
编写exts.py文件
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
这个文件的作用是解决了文件引用死锁
-
在app.py中注册
from flask_migrate import Migrate from exts import db import config # 添加配置文件 app.config.from_object(config) db.init_app(app) migrate = Migrate(app, db)
-
在models.py中编写数据库的表
from exts import db class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(100), nullable=False) password = db.Column(db.String(500), nullable=False) email = db.Column(db.String(100), nullable=False, unique=True) interval = db.Column(db.Float(precision=1), nullable=False, default=2) avatar = db.Column(db.String(500), nullable=False, default='') songs = db.relationship('Song', secondary='user_song', backref=db.backref('users', lazy='dynamic')) playlists = db.relationship('Playlist', backref='users', cascade="all, delete-orphan") class UserSong(db.Model): __tablename__ = 'user_song' user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), primary_key=True) song_id = db.Column(db.String(100), db.ForeignKey('song.id', ondelete='CASCADE'), primary_key=True) class Song(db.Model): __tablename__ = "song" id = db.Column(db.String(100), primary_key=True) name = db.Column(db.String(100), nullable=False) singer = db.Column(db.String(100), nullable=False) cover_url = db.Column(db.String(500), nullable=False) score = db.Column(db.String(100), nullable=False) class Playlist(db.Model): __tablename__ = 'playlists' id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(100), nullable=False) description = db.Column(db.String(200), nullable=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) cover = db.Column(db.String(500), nullable=False, default='img/default_cover.png') songs = db.relationship('Song', secondary='playlist_song', backref=db.backref('playlists', lazy='dynamic', cascade="save-update, merge")) class PlaylistSong(db.Model): __tablename__ = 'playlist_song' playlist_id = db.Column(db.Integer, db.ForeignKey('playlists.id', ondelete='CASCADE'), primary_key=True) song_id = db.Column(db.String(100), db.ForeignKey('song.id', ondelete='CASCADE'), primary_key=True)
上述表结构展现了常见的一对多以及多对多和外键约束
提交表结构
-
在terminal输入以下命令
flask db init flask db migrate flask db upgrade
-
创建完成
使用ORM模型进行增删改查
使用这些模型就像是使用对象一样简洁
from models import User...
@app.route('/user/add')
def add_user():
user = User(username='张三', password='123456')
db.session.add(user)
db.session.commit()
return "用户创建成功!"
@app.route('/user/query')
def query_user():
# 1. get: 根据主键
# user = User.query.get(1)
# print(user.id, user.username, user.password)
# 2.filter, 返回的是Query, list
users = User.query.filter_by(username='张三')
for user in users:
print(user)
return "数据查找成功"
@app.route('/user/update')
def update_user():
user = User.query.filter_by(username='张三').first()
user.password = '2222'
db.session.commit()
return "数据修改成功"
@app.route('/user/delete')
def delete():
user = User.query.filter_by(username='张三').first()
db.session.remove(user)
db.session.commit()
return "用户删除成功"
@bp.route('user/deletePlaylistSong', methods=['POST'])
def delete_PlaylistSong():
pid = request.form.get('pid')
mid = request.form.get('mid')
playlist = Playlist.query.filter_by(id=pid).first()
song = Song.query.filter_by(id=mid).first()
if playlist.user_id == g.user.id:
if song in playlist.songs:
playlist.songs.remove(song)
try:
db.session.commit()
return jsonify({'success': 200, 'message': '歌曲从歌单中移除'})
except Exception as e:
current_app.logger.error('user/deletePlaylistSong: %s', str(e))
return jsonify({'success': 200, 'message': '数据库错误'})
else:
return jsonify({'success': 500, 'message': '错误操作!'})
表单验证
定义验证表格
在我们将用户提交的信息存入数据库之前一定要进行表单验证,这里我们使用wtforms来进行表单验证
pip install wtforms
注: 我们在使用Email进行表单校验的时候还需要安装一个邮箱验证的第三方库,到时候根据报错信息自行安装即可。
- 常规验证
import wtforms
from wtforms.validators import Email, Length, EqualTo
class RegisterForm(wtforms.Form):
email = wtforms.StringField(validators=[Email(message="邮箱格式错误"), EmailVerify()])
username = wtforms.StringField(validators=[Length(min=3, max=20, message="用户名格式错误, 最少3位,至多20位!")])
password = wtforms.StringField(validators=[Length(min=6, max=20, message="密码格式错误! 最少6位, 最多20位!")])
password_confirm = wtforms.StringField(
validators=[EqualTo("password", message="两次输入密码不一致! 请检查密码输入!")])
class LoginForm(wtforms.Form):
email = wtforms.StringField(validators=[Email(message="邮箱格式错误")])
password = wtforms.StringField(validators=[Length(min=6, max=20, message="密码格式错误! 最少6位, 最多20位!")])
在上述代码中,我们对注册以及登录接口的数据进行了验证,但是在email我们加入了自定义验证,自定义验证见下文。
- 自定义验证
class EmailVerify(wtforms.Form):
def __call__(self, form, field):
email = field.data
user = User.query.filter_by(email=email).first()
if user:
raise wtforms.ValidationError(message="该邮箱已经被注册!")
我们定义自定义验证时,利用python的魔术方法__call__来定义类在调用时的方法,这里我们进行了数据库筛查,判断邮箱是否被注册,如果被注册就返回一个验证错误的错误信息。
下面我们来看一个另一种自定义的一个验证:
class RegisterForm(wtforms.Form):
email = wtforms.StringField(validators=[Email(message="邮箱格式错误"), EmailVerify()])
captcha = wtforms.StringField(validators=[Length(min=4, max=4, message="验证码格式错误!")])
username = wtforms.StringField(validators=[Length(min=3, max=20, message="用户名格式错误, 最少3位,至多20位!")])
password = wtforms.StringField(validators=[Length(min=6, max=20, message="密码格式错误! 最少6位, 最多20位!")])
password_confirm = wtforms.StringField(
validators=[EqualTo("password", message="两次输入密码不一致! 请检查密码输入!")])
def validate_captcha(self, field):
captcha = field.data
email = self.email.data
confirm = EmailCaptcha.query.filter_by(email=email, captcha=captcha).first()
if not confirm:
raise wtforms.ValidationError(message="邮箱或验证码错误!")
我们在想要验证的字段前加入__validate___这个字段,就可以自定义验证这个字段。
使用验证表格
我们需要先导入我们自定义的验证表单
from .forms import RegisterForm, LoginForm
之后我们只需要将前端传入的表单数据直接传递给这个Form就可以了
form = LoginForm(request.form)
if form.validate():
...
else:
# return '表单验证失败'
return redirect(url_for("user.login"))
配置全局变量
Flask中我们可以为整个后端配置一个全局变量,这个需要引入g,然后配置属性即可
from flask import g
setattr(g, "user", user)
配置登录信息
我们想要给用户配置登录信息,需要用到一个session函数
在user的蓝图中,我们在登录的路由登录成功后添加一个session信息
import ...
@bp.route('user/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template("login.html")
elif request.method == 'POST':
form = LoginForm(request.form)
if form.validate():
email = form.email.data
password = form.password.data
user = User.query.filter_by(email=email).first()
if not user:
return redirect(url_for("user.login"))
if check_password_hash(user.password, password):
# cookie
session['user_id'] = user.id
return redirect("/")
else:
return redirect(url_for("user.login"))
else:
return redirect(url_for("user.login"))
之后在app中配置上下文以及请求
import ...
# before_request/ before_first_request/ after_request
@app.before_request
def get_session():
# 在登录成功后设置cookie过期时间为一年
session.permanent = True
app.permanent_session_lifetime = timedelta(days=365)
user_id = session.get("user_id")
if user_id:
user = User.query.get(user_id)
setattr(g, "user", user)
else:
setattr(g, "user", None)
@app.context_processor
def my_context_processor():
return {"user": g.user}
第一个是在每次发送请求的时候都在cookie中获取用户信息,如果有用户信息,就把他设置为全局变量的一个属性,更多的用法还有注释中的两种。
第二个是在上下文管理器中定义了user这个字段,这个字段就是可以在前端模板中,以{{ user }}访问到用户的信息。
注:在登录成功后设置cookie过期时间为一年,若不设置,默认浏览器关闭即清除登录状态。
Django
数据库篇
连接数据库以及配置模型
-
首先在settings中配置好数据库的相关信息, 注:需要安装好mysqlclient
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 数据库的连接类型 'NAME': 'app01', # 数据库的名字 'USER': 'root', # 数据库的登录账号 'PASSWORD': '', # 密码 'HOST': '127.0.0.1', # ip 'PORT': '3306', # 端口 } }
-
在对应app的models.py文件中编写对应的表结构
下面展示简单的表结构以及外键约束
class Department(models.Model): """ 部门表 """ title = models.CharField(max_length=32, verbose_name='部门名称') class Employee(models.Model): """ 员工表 """ name = models.CharField(max_length=16, verbose_name='姓名') password = models.CharField(max_length=64, verbose_name='密码') age = models.IntegerField(verbose_name='年龄') account = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='账户余额') created_at = models.DateTimeField(verbose_name='入职事件') # 外键约束 # 设置级联删除 department = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE) # 设置置空 # department = models.ForeignKey(to="Department", to_field="id", null=True, blank=True, on_delete=models.SET_NULL) # 设置选择,即确立简单的对应关系,由Django自动匹配 gender_choices = ( (1, "男"), (2, "女"), ) gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
将编写好的表结构注册进数据库
- 首先将数据库在MySQL中创建好
create database your_database_name
- 执行下列命令
python manage.py makemigrations python manage.py migrate
- 创建完成