视频地址:https://ke.qq.com/course/228864#term_id=100270059
为什么开启debug模式
原因1:看下面这段代码,很明显除数不能为0,会抛出异常。
@app.route('/')
def hello_world():
a = 1
b = 0
c = a / b
return '你好,世界'
如果不开启debug,则在网页中显示:(表示是程序内部出错)
Internal Server Error
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
网页中不显示是哪里出错,但在代码终端可以看到,但这样很不直观。虽然终端可以直接跳到错误行。
原因2:修改python代码后直接Ctrl + S保存后,对应的页面也会更改,不用重启服务器,更快更方便。
开启debug的方法
方法一
app.run(debug=True)
# 或者
app.debug = True
app.run()
方法二:配置参数
新建一个config.py,然后写DEBUG = True。然后在app.py导入import config并且app.config.from_object(config)
PS:如果开启不了,请检查editconfigurations中是否开启了flask debug,另外最好single instance
# 路由里的参数用<>括起来!
@app.route('/article/')
def article(id):
return '您请求的参数是:' + id
导入:from flask import Flask, url_for
@app.route('/')
def hello_world():
print(url_for('article', id='1'))
print(url_for('my_list'))
return '你好,世界'
# 路由里的参数用<>括起来!
@app.route('/article/')
def article(id):
return '您请求的参数是:' + id
@app.route('/list/')
def my_list():
return 'list'
导入:from flask import Flask, url_for, redirect
@app.route('/')
def hello_world():
print(url_for('article', id='1'))
print(url_for('my_list'))
return redirect('/list/')
return '你好,世界'
# 路由里的参数用<>括起来!
@app.route('/article/')
def article(id):
return '您请求的参数是:' + id
@app.route('/list/')
def my_list():
return 'list'
# 更优雅的做法如下:
list_url = url_for('my_list')
return redirect(list_url)
# 这样参数不管怎么变,只要视图函数名不变就可以了
导入:from flask import Flask, render_template
基本使用:
在模板中新建index.html文件
在视图函数中返回
@app.route('/')
def index():
return render_template('index.html')
模板传参:
@app.route('/')
def index():
return render_template('index.html', username='User1', gender='男')
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页title>
head>
<body>
我是主页
<p>用户:{{ username }}p>
<p>性别:{{ gender }}p>
body>
html>
注意:模板中参数用{{ 参数名 }}包括起来,参数名必须和视图函数中传递的参数名一致
数据结构传参:
字典:
@app.route('/')
def index():
context = {
'username': 'User1',
'gender': '男'
}
return render_template('index.html', context=context)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页title>
head>
<body>
我是主页
<p>用户:{{ context['username'] }}p>
<p>性别:{{ context['gender'] }}p>
body>
html>
或者将字典打散
@app.route('/')
def index():
context = {
'username': 'User1',
'gender': '男'
}
return render_template('index.html', **context)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页title>
head>
<body>
我是主页
<p>用户:{{ username }}p>
<p>性别:{{ gender }}p>
body>
html>
PS:注意字典打散加**号
实例
@app.route('/')
def index():
class Person:
name = 'Jay'
age = '20'
p = Person()
context = {
'username': 'User1',
'gender': '男',
'person': p
}
return render_template('index.html', **context)
<body>
我是主页
<p>用户:{{ username }}p>
<p>性别:{{ gender }}p>
<p>姓名:{{ person.name }}p>
<p>年龄:{{ person.age }}p>
body>
字典打散后的字典
@app.route('/')
def index():
class Person:
name = 'Jay'
age = '20'
p = Person()
context = {
'username': 'User1',
'gender': '男',
'person': p,
'website': {
'baidu': 'www.baidu.com',
'google': 'www.google.com'
}
}
return render_template('index.html', **context)
<body>
我是主页
<p>用户:{{ username }}p>
<p>性别:{{ gender }}p>
<hr/>
<p>姓名:{{ person.name }}p>
<p>年龄:{{ person.age }}p>
<hr/>
<p>百度:{{ website['baidu'] }}p>
<p>谷歌:{{ website['google'] }}p>
body>
PS:访问字典可以通过params.key的形式或者params[‘key’],推荐使用后者,和字典样式一致
if语句
@app.route('//')
def index(is_login):
if is_login == '1':
user = {
'username': 'Jay',
'age': 8
}
return render_template('index.html', user=user)
else:
return render_template('index.html')
<body>
{% if user %}
<a href="#">{{ user['username'] }}a>
<a href="#">注销a>
{% else %}
<a href="#">登录a>
<a href="#">注册a>
{% endif %}
body>
PS:注意,return render_template(‘index.html’, user=user)传参给模板不能直接打散,否则得不到该变量
for语句
字典的遍历
@app.route('/')
def index():
user = {
'username': 'Jay',
'age': 20
}
for k, v in user.items():
print(k)
print(v)
return render_template('index.html', user=user)
<body>
{% for k, v in user.items() %}
<p>{{ k }}:{{ v }}p>
{% endfor %}
body>
列表的遍历
@app.route('/')
def index():
context = {
'user': {
'username': 'Jay',
'age': 20,
},
'website': [
'www.baidu.com',
'www.google.com'
]
}
for website in context['website']:
print(website)
return render_template('index.html', **context)
<body>
{% for k, v in user.items() %}
<p>{{ k }}:{{ v }}p>
{% endfor %}
<hr>
{% for website in website %}
<p>{{ website }}p>
{% endfor %}
body>
过滤器
default过滤器:
def index():
context = {
'user': {
'username': 'Jay',
'age': 20,
},
'website': [
'www.baidu.com',
'www.google.com',
'www.souhu.com'
],
'avatar': 'https://avatar.csdn.net/B/A/9/3_towtotow.jpg'
}
for website in context['website']:
print(website)
return render_template('index.html', **context)
<body>
{% for k, v in user.items() %}
<p>{{ k }}:{{ v }}p>
{% endfor %}
<hr>
{% for website in website %}
<p>{{ website }}p>
{% endfor %}
<hr>
<img src="{{ avatar|default('https://avatar.csdn.net/D/B/C/3_core___java.jpg') }}">
body>
PS:default过滤器只是其中一个过滤器而已,作用是后台传的变量不存在,就用default展示。但是如果变量存在,值为空,那么过滤器不会用默认值来展示,这样展示的是后台传过来的空值
length过滤器:求字符串、列表、元祖、字典的长度
@app.route('/')
def index():
website = [
'www.baidu.com',
'www.google.com',
'www.souhu.com'
]
return render_template('index.html', website=website)
<body>
网站数:{{ website|length }}
body>
PS:如果后台没传数据,则过滤为0,传了就是数据的长度,这个不用担心数据为空,为空就是0。
继承和block
首先创建一个base.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<style>
.nav {
background: #3a3a3a;
height: 65px;
}
ul {
overflow: hidden;
}
ul li {
float: left;
list-style: none;
padding: 0 10px;
line-height: 65px;
}
ul li a {
color: #fff;
}
style>
head>
<body>
<div class="nav">
<ul>
<li>
<a href="#">首页a>
li>
<li>
<a href="#">发布评论a>
li>
ul>
div>
{% block main %}{% endblock %}
body>
html>
python
@app.route('/')
def index():
return render_template('index.html')
@app.route('/login/')
def login():
return render_template('login.html')
index.html
<body>
{% extends 'base.html' %}
{% block main %}
<h1>这是主页面h1>
{% endblock %}
body>
login.html
<body>
{% extends 'base.html' %}
{% block main %}
<h1>这是登录页面h1>
{% endblock %}
body>
url_for链接和静态文件使用,重构上述代码:
在static下新建 css 文件夹,在css文件夹下新建base.css文件,将base.html的head中设置的style复制到base.css中去,让base.html的head标签中引入base.css属性。
base.css
.nav {
background: #3a3a3a;
height: 65px;
}
ul {
overflow: hidden;
}
ul li {
float: left;
list-style: none;
padding: 0 10px;
line-height: 65px;
}
ul li a {
color: #fff;
}
base.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}"/>
head>
<body>
<div class="nav">
<ul>
<li>
<a href="{{ url_for('index') }}">首页a>
li>
<li>
<a href="{{ url_for('login') }}">登录a>
li>
ul>
div>
{% block main %}{% endblock %}
body>
html>
python代码和index.html以及login.html不变
PS:加载图片,请求项目URI都可以用url_for来加载
python连接数据库的安装与配置
代码中连接mysql
配置文件
# 配置参数
DEBUG = True
DIALECT = 'mysql'
DRIVER = 'mysqldb'
USERNAME = 'root'
PASSWORD = '1234'
HOST = '127.0.0.1'
PORT = '3306'
DATABASE = 'test'
# SQLALCHEMY标志URI
SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME,
PASSWORD, HOST, PORT,
DATABASE)
# 忽略警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
主代码:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config
app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)
class Student(db.Model):
__tablename__ = 'student'
number = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
db.create_all()
@app.route('/')
def index():
return 'index'
if __name__ == '__main__':
app.run()
注意:
增:
# 创建一个映射实例
student1 = Student(name='Jay')
# 增加
db.session.add(student1)
# 提交
db.session.commit()
查:
返回数组:
# 按条件返回一个数组
students = Student.query.filter(Student.name == 'Jay').all()
# 取到数组的第一个元素
student = students[0]
#复杂查询
# 查询或
search_questions = Question.query.filter(
or_(Question.title.contains(q), Question.content.contains(q))).order_by(
'-create_time').all()
# 查询与
Question.query.filter(Question.title.contains(q), Question.content.contains(q))
返回查到的第一个元素:
# 只查找匹配的第一条
student = Student.query.filter(Student.name == 'Jay').first()
改
# 修改
# 1.先把要修改的数据查出来
student = Student.query.filter(Student.name == 'Jay').first()
# 2.修改
student.name = 'new name'
# 3.提交
db.session.commit()
删
# 删除
# 1.把需要删除的数据查出来
student = Student.query.filter(Student.name == 'Jay').first()
# 2.删除
db.session.delete(student)
# 3.提交
db.session.commit()
高级用法之外键,反向引用
from datetime import datetime
from exts import db
class User(db.Model):
__table_name__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
phone = db.Column(db.String(11), nullable=False)
username = db.Column(db.String(50), nullable=False)
password = db.Column(db.String(100), nullable=False)
class Question(db.Model):
__table_name__ = 'question'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
# now()表示服务器第一次运行的时间
# now表示当前时间
create_time = db.Column(db.DateTime, default=datetime.now)
# 外键为user表中的id字段
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
# 按外键反转查找author信息,关系到User模型。然后反向引用得到该author的所有questions
author = db.relationship('User', backref=db.backref('questions'))
一对多关系,引入中间表
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from exts import db
# 用户
class User(db.Model):
__table_name__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(50), nullable=False)
email = db.Column(db.String(50))
# 文章tag的中间表
article_tag_table = db.Table('article_tag',
db.Column('article_id', db.Integer, db.ForeignKey('article.id'),
primary_key=True),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'),
primary_key=True))
# 文章
class Article(db.Model):
__table_name__ = 'article'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100))
content = db.Column(db.Text)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
author = db.relationship('User', backref='articles')
tags = db.relationship('Tag', secondary=article_tag_table, backref='tags')
# 文章标签
class Tag(db.Model):
__table_name__ = 'tag'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50))
article_id = db.Column(db.Integer, db.ForeignKey('article.id'))
@app.route('/')
def hello_world():
user = User(username='lzj', email='[email protected]')
article = Article(title='title123', content='content123')
article.author = user
tag1 = Tag(name='前端')
tag2 = Tag(name='python')
article.tags.append(tag1)
article.tags.append(tag2)
# 这里数据库操作增加一个article,那么与该article相关的表会自动映射更新
db.session.add(article)
db.session.commit()
return 'Hello World!'
flask-script
在虚拟环境中安装:pip install flask-script
用法:请看视频
分开modules解决循环引用
新建一个exts.py文件
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
新建model.py文件
from exts import db
class Student(db.Model):
__tablename__ = 'student'
number = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
app.py引用如下
from flask import Flask
from exts import db
from models import Student
import config
app = Flask(__name__)
app.config.from_object(config)
# 数据库迁移后,不要忘记初始化
db.init_app(app)
# 否则只能在视图函数中执行,不能在全局执行
with app.app_context():
db.create_all()
@app.route('/')
def index():
return 'index'
if __name__ == '__main__':
app.run()
flask-migrate
在虚拟环境中安装:pip install flask-migrate
作用:数据库的迁移,假如对数据库的字段属性进行了更改,数据库是不会映射的。比如增加一个字段,这时可以直接将表drop掉,但是用户的数据就没了,显然很不好。于是可以采用migrate。
用法:
python manage.py db init
初始化migrate
python manage.py db migrate
生成一个迁移文件
python manage.py db upgrade
数据库迁移后不要忘记在app.py中初始化:db.init_app(app)
flask中session机制:
session的使用:
在config.py中设置SECRET_KEY:24位的随机字符码
# session
SECRET_KEY = os.urandom(24)
导入session:from flask import Flask, session
# session设置一个key-value
session['username'] = 'jay'
session['password'] = '1399'
# 访问字典元素可以用session['key']也可以session.get('key')
# 区别是前者没有查找到会出异常KeyError,后者返回None
print(session['username'])
# 推荐使用第二种方式,即使不存在也不会抛出异常
print(session.get('password'))
if not session.get('user'):
print('user为None')
# 如果key不存在会抛出异常KeyError
# del session['user_id']
# session.pop('user')
# 设置第二个参数后,即使user_id不存在也不会出现Key_Error
session.pop('username', None)
# 清空本项目所有session数据
session.clear()
设置session的过期时间:
默认的session的过期时间为:关闭会话(浏览器关闭)
设置permanent后过期时间为一个月(31天)
session.permanent = True
PERMANENT_SESSION_LIFETIME = timedelta(days=25)
使用场景和传参方式
get请求
使用场景:如果只对服务器获取数据,并没有对服务器产生影响,那么这个时候用get请求。
传参方式:get请求的参数放在url中,通过’?’形式来指定key-value
post请求
使用场景:如果对服务器产生影响,如上传数据,那么用post请求。
传参方式:post请求参数不是放在url中,是通过’form data’的形式发送给服务器的
get请求基本用法
index.html中定义一个链接
<body>
<a href="{{ url_for('search', q='hello') }}">跳转到搜索页面a>
body>
搜索页面的视图函数
@app.route('/search/')
def search():
# 得到一个key-value传参字典
arguments = request.args
print(arguments)
print(arguments.get('q'))
return 'search'
post请求基本用法
login.html中定义一个表单
<body>
{#如果不写method="post"的话,表单默认使用get请求#}
{#action表示跳转到指定页面(不写就是本页面)如果指定的页面没有定义post请求方法会报Method Not Allowed#}
<form action="{{ url_for('login') }}" method="post">
<table>
<tbody>
<tr>
<td>用户名:td>
{#name可理解为post请求的key,输入框输入的内容未value#}
<td><input type="text" placeholder="请输入用户名" name="username">td>
tr>
<tr>
<td>密码:td>
<td><input type="text" placeholder="请输入密码" name="password">td>
tr>
<tr>
<td>td>
<td><input type="submit" value="登录">td>
tr>
tbody>
table>
form>
body>
login视图函数:
# 默认的视图函数只能使用get请求
# 如果用post请求需要定义
@app.route('/login/', methods=['GET', 'POST'])
def login():
# 判断用的是get方法还是post方法
if request.method == 'GET':
return render_template('login.html')
else:
form = request.form
print('账号:' + form.get('username'))
print('密码:' + form.get('password'))
return '得到参数:' + str(request.form)
注意:这里login在浏览器中回车访问是get请求,返回一个视图。而表单提交后是post请求,返回一个字符串视图
g对象的使用:
login.html
<body>
{#如果不写method="post"的话,表单默认使用get请求#}
{#action表示跳转到指定页面,不写就是本页面如果指定页面没有定义post请求方法会报Method Not Allowed#}
<form action="{{ url_for('login') }}" method="post">
<table>
<tbody>
<tr>
<td>用户名:td>
{#name可理解为post请求的key,输入框输入的内容未value#}
<td><input type="text" placeholder="请输入用户名" name="username">td>
tr>
<tr>
<td>密码:td>
<td><input type="text" placeholder="请输入密码" name="password">td>
tr>
<tr>
<td>td>
<td><input type="submit" value="登录">td>
tr>
tbody>
table>
form>
body>
login视图函数:
# 默认的视图函数只能使用get请求
# 如果用post请求需要定义
@app.route('/login/', methods=['GET', 'POST'])
def login():
# 判断用的是get方法还是post方法
if request.method == 'GET':
return render_template('login.html')
else:
form = request.form
username = form.get('username')
password = form.get('password')
if '1' == username and '1' == password:
# 记录登录信息
g.username = username
login_log()
return '登录成功'
else:
return '登陆失败'
before_request
在请求之前执行(所有请求),即在视图函数之前执行
这个函数用一个装饰器修饰 @app.before_request
应用场景:
可以和g对象一起使用,即每一次请求都调用一次这个before_request钩子函数,然后将session的数据保存到g对象中,由于每一次请求之前调用,所以g对象包含的session信息在每次请求前都存在,不会在下一次请求后就销毁。比如:
用户登录后一般在session中存一个user_id,然后用这个user_id来在数据库中查找User信息,如果每一次都查找数据库会很慢,这样可以在before_request钩子函数中将session中的user_id取出来,然后在数据库中查找User信息,然后将这个user信息保存到g对象中去,这样每次请求都有这个user信息了,不用每次都访问数据库。
(但是有个疑问,before_request是在每次请求之前都调用,那么每次都访问数据库将user信息存到g对象中不是比需要user信息的时候在视图函数中访问数据库的次数不是多多了?这个疑问后来懂了再补上)
视图函数:虽然g对象在一次请求过后就会被销毁,但是因为每次请求前(before_request)都访问了session然后将需要的信息存到g对象中,所以g对象被销毁了又在钩子函数中创建,所以每个视图函数都可以使用g对象。
@app.route('/')
def index():
print('index, g.username == ' + g.username)
return render_template('index.html')
# 默认的视图函数只能使用get请求
# 如果用post请求需要定义
@app.route('/login/', methods=['GET', 'POST'])
def login():
# 判断用的是get方法还是post方法
if request.method == 'GET':
return render_template('login.html')
else:
form = request.form
username = form.get('username')
password = form.get('password')
if '1' == username and '1' == password:
# 记录登录信息
session['username'] = username
return redirect(url_for('index'))
else:
return '登陆失败'
@app.before_request
def my_before_request():
print('before_request钩子函数执行了')
if session.get('username'):
g.username = session.get('username')
@app.route('/edit/')
def edit():
if hasattr(g, 'username'):
return 'edit user information'
else:
return redirect(url_for('login'))
login.html和上面的一样就不复制了
context_processor
使用场景:渲染的模板的多个页面需要相同的数据
注意:必须有返回值,即使返回空字典{}也要返回,否则报错
用法如下:
# 钩子函数返回一个字典,这个字典在所有模板可用
# 从打印结果来看,这个钩子函数应该是在视图函数末尾执行
# 不能没有返回值,任何一种情况都必须返回一个字典,否则报错
@app.context_processor
def my_context_processor():
print('my_context_processor钩子函数执行了')
username = session.get('username')
if username:
return {'username': '111'}
else:
return {}
一个少处理返回情况的异常:TypeError: ‘NoneType’ object is not iterable
@app.context_processor
def my_context_processor():
user_id = session.get('user_id')
# 这是两种情况,其中一种没处理返回就报:TypeError: 'NoneType' object is not iterable
if user_id:
user = User.query.filter(User.id == user_id).first()
if user:
return {'user': user}
return {}
配置文件
debug调试,数据库,session等等
import os
from datetime import timedelta
# debug模式
DEBUG = True
# 配置数据库
DIALECT = 'mysql'
DRIVER = 'mysqldb'
USERNAME = 'root'
PASSWORD = '1234'
HOST = '127.0.0.1'
PORT = '3306'
DATABASE = 'test'
# SQLALCHEMY标志URI
SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8" \
.format(DIALECT, DRIVER, USERNAME,
PASSWORD, HOST, PORT,
DATABASE)
# 忽略SQLALCHEMY警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
# session
# 设置session'盐':SECRET_KEY
SECRET_KEY = os.urandom(24)
# 设置session过期时间,25天,设置permanent默认一个月,没设置关闭浏览器就销毁
PERMANENT_SESSION_LIFETIME = timedelta(days=25)
导包,各种包
from flask import Flask, url_for, render_template, request, session, g, redirect
文件夹管理等等
具体看问答平台小案例
flask debug模式怎么开都开不了的坑
点run左边的project->EditConfiguration,勾选FLASK_DEBUG,另外如果只想启动一个服务,不用在控制台弹出多个,点击右上角的Single instance only
配置虚拟环境
需要的各种包都在虚拟环境中安装,创建项目的时候注意选择虚拟环境即可