Flask 学习笔记

环境安装与搭建(Windows7 up)

1.python3.6或者2.7的安装请参考百度资料
2.通过在cmd中键入下行命令安装虚拟环境 pip install virtualenv
3.激活虚拟环境
 cmd下键入
  mkdir Virtualenv (创建一个名为Virtualenv的文件夹)
  cd Virtualenv (进入该文件夹)
  virtualenv flask-env (创建一个虚拟环境,安装一个名为flask-env的软件)
  cd flask-env
  cd Scripts
  activate
然后出现
(flask-env) C:\Users\UserName\Virtualenv\flask-env\Scripts>
则激活成功
4.在 cmd 下键入
 pip install flask==0.12.2(安装特定版本(0.12.2)的flask)
 python
 import flask
 print(flask._version_)
出现版本号则说明安装成功

编程(编译器——PyCharm专业版)

新建项目

打开PyCharm ——> 新建项目,位置随意,名称最好不要出现中文
左侧选择Flask(PyCharm专业版,社区版无此功能,其他版本我不太了解)
解释器(interpreter)设置有两种情况
 1.Project interpreter New Virtualenv environment
  ·展开Project interpreter New Virtualenv environment
  ·在Base interpreter中点击...(右侧省略号)
  ·选择C:\Users\UserName\Virtualenv\flask-env\Scripts\python.exe
 2.Interpreter
  ·在interpreter右侧点击...(右侧省略号)
  ·选择C:\Users\UserName\Virtualenv\flask-env\Scripts\python.exe
点击create按钮

Hellow World

新建项目过后,如果你使用的是python2.x,请在代码段中的第一行添加

#encoding utf-8

因为 python2.x 使用的是ASKII编码,需要手动改成utf-8
如果你使用的是python3.x,则无需担心

运行该项目,运行结果如下图
hello_world
它表示当前的这个项目运行在 http://127.0.0.1:5000/ 这个网站上
其中 127.0.0.1 表示本机地址,5000代表端口
打开后会发现,网页出现了"Hello World"字样
表明 return 之后的字符串表示的就是网页中显示的字样
在运行后,修改完程序,点击右上角红色按钮停止运行,之后重新运行代码方可使修改后的代码产生效果
代码解释

# 从 flask 框架中导入 Flask 这个类
from flask import Flask
# 初始化一个Flask对象
# 书写这个类的原因
# 需要传一个参数
# 1. 方便flask框架去寻找资源
# 2. 方便flask插件,例如Flask-Sqlalchemy 出现错误的时候,好去寻找问题所在的位置
app = Flask(__name__)

# @app.route是一个装饰器
# @开头为装饰器
# 装饰器的作用是做一个url与视图函数的映射
# 这句话的意思是以后你如果访问 127.0.0.1:5000/
# 到斜杠的时候,他就会去请求执行 hello_world()这个函数 让后将结果返回给浏览器
@app.route('/')    # url
def hello_world(): # 视图函数
    return 'Hello'


# 如果当前这个文件是作为入口程序运行
# 那么就执行app.run()
if __name__ == '__main__':
    # app.run()
    # 启动一个应用服务器接受用户的请求
    # 它相当于是一个 while True
    # 会一直监听用户的请求,对于用户的请求进行处理
    app.run()

debug模式(教程中为Flask0.12.2版本,Flaks不支持)

需要注意,开启调试模式会成为一个巨大的安全隐患,因此他绝对不能用于生产环境中。
Debug 模式有两个功能
1.
重新回到我们的代码段
如果你这样书写代码

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    a = 1
    b = 0
    c = a / b
    return 'Hello'

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

运行且打开网页,会出现如下图所示的错误
Error
如图,浏览器并不会告诉你是除法出了错误,只有编译器下方的窗口中有该错误信息
我们将代码中的 app.run() 修改为

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

之后停止原先运行的程序,重新运行。
如果运行时未出现下图提示信息(多出现于 PyCharm 2018 2018.5.8写)
Debug
则点击编译器右上侧运行符号旁边的项目名称处,选择Edit Configuration
勾选下图选项
Debug_Wrong
重新打开网站便可以看到错误信息了
2.
在修改你的代码后,保存文件的同时,编译器会在下方提示你你的信息被改变了,同时网页也会重新加载变更后的代码信息。
提示信息如下图
Debug_Mode

使用配置文件

在项目中新建python file,添加下行代码

DEBUG = True

回到原先的py文件中,删除app.run(debug = True)括号中的debug = True
修改为

from flask import Flask
# 被添加的代码
import config    
app = Flask(__name__)
# 被添加的代码
app.config.from_object(config)
@app.route('/')
def hello_world():
    return 'wol'

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

这同样可以为该文件设置debug模式

url传参

新建项目url_params
在项目中添加一行代码,重载视图函数

@app.route('/article/<id>')
def article(id):
    return '您请求的参数是:%s' % id

Tips:
 1. 在python2.x中,需要这样书写

    return u'您请求的参数是:%s' % id

 在字符串之前加 u 表示将字符串进行Unicode编码
 2. 参数需要放进两个尖括号之间
 3. 视图函数中需要放置和url中参数同名的参数

然后运行项目,打开链接,在 http://127.0.0.1:5000/ 后面添加article/aabbcc
点击回车,页面会显示您请求的参数是:aabbcc

PS:
如果只能通过勾选debug项来改变debug模式的启动或关闭,那么不论是在run()中更改debug
模式,还是通过配置文件更改,都是无效的

反转URL

正转URL的概念:通过URL取得视图函数的内容
反转则是反过来的意思,就是知道视图函数的名称,可以反转得到视图函数当前的url
新建项目url_reverse,在项目中添加下列代码

from flask import Flask, url_for

app = Flask(__name__)

@app.route('/')
def index():
    print(url_for('my_list'))
    print(url_for('article', id = 'abc'))
    return 'Hello World!'

@app.route('/list/')
def my_list():
    return 'list'

@app.route('/article/<id>')
def article(id):
    return '您请求的参数为:%s' % id

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

在执行后,控制台会打印如下图所示信息
reverse
反转URL的用处:
 ·在页面重定向的时候会用到
 ·在模板中也会使用到

页内跳转和重定向

重定向例子:
例如在某论坛中,用户在未登录的情况下,点击论坛中的评论,当然此时是没有办法评论的,因为用户
没有登陆,此时,页面应当跳转至登陆界面;如果你登陆过了,那么能够跳转至评论页面。
这个过程称之为重定向

新建项目redirect,添加代码

from flask import Flask, redirect, url_for

app = Flask(__name__)


@app.route('\')
def index():
    return '这是首页'

@app.route('\login\')
def login():
    return '这是登陆页面'

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

执行程序后打开链接会发现,页面中出现“这是首页”的字样
然后修改代码如下

from flask import Flask, redirect, url_for

app = Flask(__name__)


@app.route('/')
def index():
    return redirect('/login/')
    return '这是首页'

@app.route('/login/')
def login():
    return '这是登陆页'

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

运行后刷新页面,发现页面直接跳入http://127.0.0.1:5000/login/

当然比较正确的写法为

from flask import Flask, redirect, url_for

app = Flask(__name__)

@app.route('\')
def index():
    # 博主本人理解为通过url_for找到名称为login的函数
    # 然后找到这个函数装饰器中的url参数
    # 将这个url参数传递给login_url,达到了url反转的功能
    login_url = url_for('login') 
    return redirect(login_url)
    return '这是首页'

@app.route('\login\')
def login():
    return '这是登陆页'

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

这样书写的好处是无论你的装饰器@app.route('/login/')中的url参数如何改变,
都不会导致login_url在传递参数给redirect()时出现问题。

下面我们实现一个小案例:未登录用户点击评论进入登陆页面,已登录用户进入评论页面
用“1”表示已登陆,其他表示未登录

from flask import Flask, redirect, url_for

app = Flask(__name__)


@app.route('/')
def index():
    login_url = url_for('login')
    return redirect(login_url)
    return '这是首页'

@app.route('/login/')
def login():
    return '这是登陆界面'

@app.route('/commitment/<is_login>/')
def commitment(is_login):
    if is_login == '1':
        return '这是评论页面'
    else:
        return redirect(url_for('login'))



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

执行程序后,首先跳转到登陆页面,删除login/,添加commitment/1,点击回车,显示这是评论页面,输入其他数字则显示登陆页面

模板渲染和参数

新建项目template01,添加代码

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return 'index'


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

在下图位置可以看见
templete01
static和templates文件夹
static:用于存放一些静态资源,例如css, gs, img文件
templates:专门用于存放html文件
所以,我们在templates上右键->New file->HTML file,取名为index.html
在<//body>之间添加一段文字(博主不知道怎么处理Hexo下的转移QAQ),达到如下效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    这是HTML文件中出现的文字
</body>
</html>

然后回到app.py(或者你建立的主文件)
由于你添加了一个HTML文件,所以这时你就不能只渲染'index',除此之外还要渲染HTML
这时候需要import render_template

from flask import Flask, render_template

接着删除return 'index',添加 return render_template('index.html'),括号内为刚刚新建的HTML文件
代码如下

from flask import Flask, render_templale
app =  Flask(__name__)

@app.route('\')
def index():
    return render_template('index.html')

if __name__ == __main__:
    app.run(debug = True)   
'''
    在render_template()中,是不需要传文件夹templates的名称的当调用render_template()时,
编译器会自动在templates文件夹下寻找与传入参数(文件名)匹配的文件,如找不到则报错

    但是如果你在templates下创建了一个文件夹another,且将index.html移动进这个文件夹内,
那么这时需要你这样使用这个文件
    render_template('another/index.html')
'''

运行后打开链接会发现在<//body>中书写的文字显示在了网页上

接下来,我们尝试使用HTML在页面的右上角显示用户的用户名
因为显示用户名需要与数据库通信,所以不能将其写死了,不可以像下面这样书写代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    这是HTML文件中出现的文字
    <p>用户名:Outro</p> <!--这样书写是错误的!-->
</body>
</html>

所以我们应该试着数据库模拟传参的行为
回到app.py,在index()函数中修改render_template()

@app.route('\')
def index():
    return render_template('index.html', username = Outro)

在index.html中的<//body>添加

</head>
<body>
    这是HTML文件中出现的文字
    <p>用户名:{{ username }}</p> <!-- 被添加的内容 -->
</body>
</html>
<!-- Tip:两个嵌套花括号{{被引用的变量}}是Flask的特有语法,记住就好 -->

这样我们就完成了参数的传递(模拟数据库的通信)
重新运行一下程序(注意,这里无论你是否开启了flask的debug模式,都要重新运行一下,因为
修改html文件并不会引起服务器重启)
接着读者们可以尝试自己修改一下render_template('index.html', username = '123')
中的 username 内容。观察页面的变化

当然,网页中可添加的信息完全不止这些,比如客户还想向网页中添加年龄、性别、身高或者电话等等信息
下面的实现方式就显得有些愚笨了
app.py中的部分代码

@app.route('\')
def index():
    return render_template('index.html', username = 'xxx', gender = 'xxx', height = 'xxx')    

index.html中的部分代码

</head>
<body>
    这是HTML文件中出现的文字
    <p>用户名:{{ username }}</p>
    <p>身高:{{ height }}</p>
    <p>性别:{{ gender }}</p>
</body>
</html>

而且这样的实现方式同时并不利于后期维护
因此我们可以用下面这种方式实现

@app.route('\')
def index():
    # 以字典的方式存储你需要放到网站上的信息
    context = {
        'username' : 'Outro',
        'height' : '180',
        'gender' : 'male'
    }  
    # 这里使用 **congtext 说明是将传入的参数作为字典进行处理
    return render_template('index.html', **context)

模板中访问属性和字典

在原有的项目template01中的app.py文件中添加下列代码

from flask import Flask, url_for,render_template

app = Flask(__name__)


@app.route('/')
def index():
    # 被添加的代码
    class Person(object):
        name = 'Outro'
        age = 18
    p = Person()
    # 被添加的代码
    context = {
        'username':'Outro',
        'height':'180',
        'gender':'male',
        'age':'18',
        # 被添加的代码
        'person':p,
        'websites':{
            'baidu':'www.baidu.com',
            'google':'www.google.com'
        }
        # 被添加的代码

    }
    return render_template('index.html', **context)

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

在index.html文件中添加代码至如下代码所示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>用户名:{{ username }}</p>
    <p>身高:{{ height }}</p>
    <p>性别:{{ gender }}</p>

    <hr>
    <p>名字:{{ person.name }}</p>
    <p>年龄:{{ person.age }}</p>
    <hr>
    <p>百度:{{ websites.baidu }}</p>
    <p>谷歌:{{ websites.google }}</p>
    <!-- 上两行代码可以这样写:
        <p>百度:{{ websites.[baidu] }}</p>
        <p>谷歌:{{ websites.['google'] }}</p>
    -->
    

</body>
</html>

以上就是在Flask中使用字典序和类对象的方法

if判断

新建项目if_statement,项目中新建文件if_statement.py,代码如下

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html',)


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

在templates文件夹下新建文件index.html,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    这里模板
</body>
</html>

现在做一个需求,实现是否判断已登陆的功能
更改上方代码为:

if_statement.py

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/<is_login>/')
def index(is_login):
    if is_login == '1':
        user = {
            'username':'Outro',
            'age':18
        }
        return render_template('index.html',user = user)
    else:
        return render_template('index.html')

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

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {% if user %} <!-- 判断 user 是否存在 -->
        <a href="#">{{ user.username }}</a>
        <a href="#">注销</a>
    {% else %}
        <a href="#">登陆</a>
        <a href="#">注册</a>
    {% endif %}

</body>
</html>

这时,保存两个文件并运行if_statement,会显示网页错误
因为此时的路径需要你输入一个login的状态,于是在端口号后面添加
/1得到已经登陆的状态,/其他数字得到未登录的状态

接着我们看一下下面这段HTML代码(修改index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!-- 使用 and 做了另一个对年龄的判断 -->
    {% if user and user.age > 18 %}
        <a href="#">{{ user.username }}</a>
        <a href="#">注销</a>
    {% else %}
        <a href="#">登陆</a>
        <a href="#">注册</a>
    {% endif %}

</body>
</html>

如注释所写,HTML的if使用and关键字做与逻辑运算

for循环

新建项目for_statement,新建文件for_statement.py 和 index.html

字典遍历
for_statement.py

from flask import Flask,render_template
app = Flask(__name__)

@app.route('/')

def index():
    user = {
        'username':'Outro',
        'age':'18'
    }
    return render_template('index.html',user = user)


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

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!-- 对字典的遍历 -->
    {% for v,k in user.items() %}
        <p>{{ v }}:{{ k }}</p>
    {% endfor %}
</body>
</html>

列表遍历
修改for_statement.py 和 index.html 为下列代码

from flask import Flask,render_template

app = Flask(__name__)

# for遍历字典

@app.route('/')

def index():
    user = {
        'username':'Outro',
        'age':'18'
    }
    # 添加 websites 列表变量
    websites = ['baidu.com','google.com']
    return render_template('index.html',user = user, websites = websites)

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {% for v,k in user.items() %}
        <p>{{ v }}:{{ k }}</p>
    {% endfor %}
    <!-- 新增 for 循环输出列表值 -->
    {% for website in websites %}
        <p>{{ website }}</p>
    {% endfor %}
    
</body>
</html>

小案例:做一个四大名著的网络表(书名,作者,价格)
修改for_statement.py 和 index.html 如下

from flask import Flask,render_template

app = Flask(__name__)

@app.route('/')
def index():
    books = [
        {
        'name' : '西游记',
        'author' : '吴承恩',
        'price' : '109'
        },
        {
            'name' : '红楼梦',
            'author': '曹雪芹',
            'price': '200'
        },
        {
            'name': '三国演义',
            'author': '罗贯中',
            'price': '120'
        },
        {
            'name': '水浒传',
            'author': '施耐庵',
            'price': '130'
        }
    ]
    return render_template('index.html',books = books)

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<!-- 表格 -->
<table>
    <!-- 表头 -->
    <thead>
        <th>书名</th>
        <th>作者</th>
        <th>价格</th>
    </thead>
    <tbody>
    {% for book in books %}
        <tr>
            <td>{{ book.name }}</td>
            <td>{{ book.author }}</td>
            <td>{{ book.price }}</td>
        </tr>
    {% endfor %}

    </tbody>
</table>
</body>
</html>

过滤器

创建一个项目名称为 filter_demo 创建文件 filter_demo.py, index.html代码如下:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


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

案例1:如果用户上传了头像,则显示,如果没有则显示默认头像

在该论坛中https://bbs.csdn.net/topics/392280790 (2018/5/17)
赋值其中一个非默认头像的图片地址,然后修改 filter_demo.py 中的代码为

from flask import Flask, render_template

app = Flask(__name__)

> 引用块内容

@app.route('/')
def index():
    return render_template('index.html', avatar = "https://avatar.csdn.net/F/4/9/2_zhao4zhong1.jpg")


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

接着修改 index.html 文件中的代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>过滤器</title>
</head>
<body>
<img src="{{ avatar|default('https://avatar.csdn.net/D/1/D/2_keer_zu.jpg') }}" alt="">
</body>
</html>

其中 | 符号为管道符号,作用是过滤。在这里的意思是如果找不到 avatar ,则将其过滤为default
之后我们可以将 filter_demo.py 文件中的 avatar 变量(删去),然后运行一下文件。

案例2:在你的页面中添加评论
修改 filter_demo.py 和 index.html 文件如下
fliter_demo.py

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    comment = [
        {
            'user':'Outro',
            'content':'空白页面啊这个!'
        },
        {
            'user':'游客',
            'content':'破站!'
        }
    ]
    return render_template('index.html', avatar = "https://avatar.csdn.net/F/4/9/2_zhao4zhong1.jpg",comments = comment)


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

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>过滤器</title>
</head>
<body>
<img src="{{ avatar|default('https://avatar.csdn.net/D/1/D/2_keer_zu.jpg') }}" alt="">

<hr>

<p>评论数:({{ comments | length }})</p>
<ul>
    {% for comment in comments %}
        <li>
            <a href="#">{{ comment.user }}</a>
            <p>{{ comment.content }}</p>
        </li>
    {% endfor %}

</ul>
</body>
</html>

在这里的 length 是用来取comments 的长度的,也就是计算评论数

连接数据库注意事项

由于MySQLdb不支持python3.x,因此需要使用pymysql来代替它。
而使用 SQLAlchemy 时其URI格式如下

DB_URI = 'mysql+pymysql://{}:{}@{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, DATABASE)

此外,第一次配置环境后运行时会报警告

C:\Users\Outro\Virtualenv\flask-env\lib\site-packages\pymysql\cursors.py:170:Warning (1366, "Incorret string value: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 518") result = self._query(query)

可以无视,数据库可以照常连接,另外,我在重启之后再无报错,读者可以试试配置环境后重启试试

数据的增删改查操作通过session完成

增加:
config.py

USERNAME = 'root'
PASSWORD = 'root'
HOSTNAME = '127.0.0.1'
DATABASE = 'db_demo2'
DB_URI = 'mysql+pymysql://{}:{}@{}/{}?charset=utf8'.format(USERNAME,PASSWORD,
                                                           HOSTNAME,DATABASE)

SQLALCHEMY_DATABASE_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = True

app.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True,autoincrement=True)
    title = db.Column(db.String(100),nullable=Flask)
    content = db.Column(db.Text,nullable=False)

db.create_all()

@app.route('/')
def hello_world():
    article1 = Article(title='aaa',content='bbb')
    # 增加一条数据
    db.session.add(article1)
    # 执行事务
    db.session.commit()
    return 'Hello!'


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

查找:

@app.route('/'):
    result = Article.query.filter(Article.title == 'aaa').all()
    article1 = result[0]
    print(article1.title)
    print(article1.content)
    return 'Hello!'

第二种方式:

@app.route('/'):
    # result = Article.query.filter(Article.title == 'aaa') 
    # 因为 result 的数据类型此时是 Query,它可以进行类似于 list 的操作
    # 如果想要对第一条数据进行访问的话,可以用如下方式
    result = Article.query.filter(Article.title == 'aaa').first()
    # 使用first()方法的话,如果没有数据,那么它返回null
    # 如果是用 [] 访问数据,如果没有数据,它就会抛出异常

    article1 = result[0]
    print(article1.title)
    print(article1.content)
    return 'Hello!'

修改:

@app.route('/')
def hello_world():
    # 先查找需要被修改的表项
    result = Article.query.filter(Article.title == 'aaa').first()
    # 修改该表项的某个字段
    result.title = 'new title'
    # 呈递事务请求
    db.session.commit()
    return 'Hello'

删除:

@app.route('/')
def hello_world():
    # 先找到需要被删除的表项
    result = Article.query.filter(Article.content == 'bbb').first()
    # 删除该项目
    db.session.delete(result)
    # 呈递该请求
    db.session.commit()
    return 'Hello'

外键以及 backref 的使用

1 为一个表添加外键:

# 用户表
class User(db.Model):
    id = db.Column(db.Integer,primery_key=True,autoincrement=True)
    username = db.Column(db.String(100),nullable=False)

# 文章表
class Article(db.Model):
    id = db.Column(db.Integet,primery_key=True,autoincrement=True)
    title = db.Column(db.String(100),nullable=False)
    content = db.Column(db.Text, nullable=False)
    author_id = db.Column(db.Integer,db.ForeignKey('user.id'))

使用外键查找作者

article = Article.query.filter(Article.title == 'aaa').first()
author_id = article.author_id
user = User.query.filter(User.id == author_id).first()
print('username : %s' % user.username)

2 为一个表使用 refback

class Article(db.Model):
    id = db.Column(db.Integer,primery_key=True,autoincrement=True)
    title = db.Column(db.String(100),nullable=False)
    content = db.Column(db.Text,nullable=False)
    author_id = db.Column(db.Integer,db.ForeignKey='user.id')

    author = db.relationship('User',backref=db.backref('articles'))

使用 refback 查询某个已知作者的所有文章

user = User.query.filter(User.username == 'Outro')
result = user.articles
for article in result:
    print('-'*10)
    print(article.title)

使用 refback 查询已知文章的作者

article = Article.query.filter(Article.title == 'xxx').first()
print('username : %s' % article.author.username)

多对多关系

多对多的关系,要通过一个中间表进行关联
中间表的创建方式如下:

article_tag = db.Table('article_tag',
        db.Column('Column_name1',db.Integer,db.ForeignKey('article.id')),
        db.Column('Column_name2',db.Integer,db.ForeignKey('tag.id')))

设置关联:

class Article(db.Model):
    __tablename__ = 'table'
    id = db.Column(db.Integer,primer_key=True,autoincrement=True)
    title = db.Column(db.String(100),nullable=False)

    tag = db.relationship('Tag',secongdary=article_tag,backref=db.backref('articles'))

Flask-Script

1.Flask-Script的作用:
 可以通过 cmd 形式操作Flask,包括跑开发版本的服务器,设置数据库,定时任务等
2.安装:
 进入虚拟环境,键入"pip install flask-script" 进行安装。
3.运行:
 在cmd中激活虚拟环境,使用python file_name.py运行
示例代码:
创建一个项目,两个文件 app.py 以及 manager.py
flask_scripy_demo.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

manager.py

from flask_script import Manager
# 也可以写成 from file_name import app
# from flask_scripy_demo import app
import app
# app.app:引用 app.py 中的app变量
manager = Manager(app.app)

@manager.command
def runserver():
    print('Server is running!')

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

cmd下键入

python manager.py runserver

按下回车,运行成功会出现

Server is running!

添加命令,并通过命令调用功能
flask_script_demo.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

新建文件db_script.py
db_script.py

from flask_script import Manager

DBManager = Manager()

@DBManager.command
def init():
    print('数据库初始化完成')

@DBManager.command
def migrate():
    print('数据表迁移成功')

manager.py

from flask_script import Manager
# 也可以写成 from file_name import app
# from flask_scripy_demo import app
import app
from db_script.py import DBManager

# app.app:引用 app.py 中的app变量
manager = Manager(app.app)

@manager.command
def runserver():
    print('Server is running!')

manager.add_command('db',DBManager)

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

cmd 中分别键入1,2两条命令

python manager.py db init
回车
python manager.py db imgrate
回车

运行成功后分别显示

数据库初始化完成
数据表迁移成功

model分离主文件

 如果将所有的 class 均放在主文件中,会使各个文件分工不明确,因此需要将 model (class)集中放在一起。这样的话就有两个文件 app.py 和 models.py。app.py 是一定会引用models.py 中的class, 那么如果 models.py 需要引用 app.py 中的变量时,这时候不能够在models.py 中书写

from app.py import xxx

这样会导致递归引用,编译器也会报错,所以可以用以下思路解决该问题
Graphic
config.py

USERNAME = 'root'
PASSWORD = 'root'
HOST = '127.0.0.1'
DATABASE = 'db_demo4'
DATABASE_URI = 'mysql+pymysql://{}:{}@{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOST,DATABASE)

SQLALCHEMY_DATABASE_URI = DATABASE_URI
SQLALCHEMY_TRACK_MODIFICATIONS = True

exts.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

model.py

from exts import db

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(100),nullable=False)

app.py

from flask import Flask
from models import Article
from exts import db
import config

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)

db.create_all()

@app.route('/')
def hello_world():
    return 'Hello World!'

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

如果不方便在定义的时候将db初始化,可以在后期调用db.init()函数初始化db。
运行app.py,会报错,错误信息如下:

RuntimeError: No application found. Either work inside a view function or push an application context. 

这是因为,虽然代码中使用了db.init_app(app)注册了 app ,但是并未将其添加进 app 栈中,情况如下图:
Stock
因此,程序在 app栈 中无法访问到 app。所以需要手动将 app 加入到栈中,修改 app.run 代码如下:

from flask import Flask
import config
from exts import db
# 一定要引用模块,否则在运行后程序会因为找不到模型,创建表失败
from models import Article

app = Flask(__name__)
app.config.from_object(config)

db.init_app(app)
# 增加下行代码,修改应用程序级别的上下文请求
# app_context()将会生成 AppContext 对象
# with 块将会将其(db.create_all())入栈,并且使得 current_app 指针指向该应用程序(db.create_all())
'''
app_context()
Create an AppContext. Use as a with block to push the context, which will make current_app point at this application.
'''
###########################
with app.app_context():
    db.create_all()
###########################

@app.route('/')
def hello_world():
    return 'Hello World!'


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

Flask-Migrate

  当你的数据库中已上线且已有多条用户数据时,想要添加数据库中的属性,使用db.create_all是不能够将属性映射到数据库中的,通过先 drop 再 create 的方式则会删除已有的用户数据。这个时候就需要使用 Flask-Migrate。
在 cmd 中打开虚拟环境,使用pip install flask-migrate进行安装。

代码示例基于下述四个基本文件,文件名及代码如下述
config.py

USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'migrate_demo'
HOST = '127.0.0.1'

DATABASE_URI = 'mysql+pymysql://{}:{}@{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOST,DATABASE)

SQLALCHEMY_DATABASE_URI = DATABASE_URI
SQLALCHEMY_TRACK_MODIFICATIONS = True

exts.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

models.py

from exts import db

class Article(db.Model):
    __tablename__ = 'article'
    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)    

migrate_demo.py

from flask import Flask
import config
from exts import db
from models import Article

app = Flask(__name__)
app.config.from_object(config)

db.init_app(app)


@app.route('/')
def hello_world():
    return 'Hello World!'


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

创建文件 manage.py 内容如下
manage.py

from migrate_demo import app
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from exts import db
# db 要获取模型
from models import Article

# 创建思想:模型 -> 迁移文件 -> 生成表
manager = Manager(app)

# 1. 要使用flask_migrate,必须绑定 app 和 db
migrate = Migrate(app, db)

# 2.把MigrateCommand命令添加到manager中
manager.add_command('db', MigrateCommand)

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

在激活虚拟环境的 cmd 下键入以下命令

python manage.py db init
python manage.py db migrate
python manage.py db upgrade

第一条命令可以理解为建立数据表模型
第二条命令可以理解为建立迁移文件
第三条命令可以理解为更具迁移文件更新数据库

在执行完第一条命令后,会发现 PyCharm 中的项目下多了一个名为 migrations 的文件夹
migrations
此时的 versions 文件夹是没有文件的
执行完第二条命令之后,会发现 migrations 文件夹下的 versions 文件夹多出了一个文件
version
此文件即为迁移文件
执行完第三条命令之后,在 mysql 中查看,发现数据库已经更新。且数据表中有一个名为 alembic_version 的数据表,它是用于记录迁移文件的版本号,暂且不管

根据思路

模型 -> 迁移文件 -> 生成表

可以推断出,如果你更新了数据表模型(本例中为 models.py 中的 Article 类),那么在更新数据库的时候,第一条指令时不需要执行的,需要分别执行第二和第三条指令。
修改 models.py 文件如下

from exts import db

class Article(db.Model):
    __tablename__ = 'article'
    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)
    # 添加 tags 属性
    tags = db.Column(db.String(100),nullable=False)

在 cmd 中键入第二和第三条命令之后
查看 mysql 中的表,会发现 tags 属性已经被添加进数据表中
此时如果查看 versions 文件夹,会发现多出一个文件
version1
这就是更新后的迁移文件

Chrome 查看 cookie 的方法:
点击右上角竖排三点的按钮,点击 设置,打开 高级选项,点击 内容设置,点击 Cookie 项,查看所有 Cookie 和网站数据,找到 127.0.0.1,点击即可浏览 session 信息。

  • 在 Flask 中使用 session,需要将 session 导入项目,使用下行命令

    from flask import session
    
  • 使用 session 时,需要设置 SECRET_KEY 对 session 中的进行数据加密,设置方式有两种,这两种都可以基于随机生成的数串
    第一种:

    import os
    app.config['SECRET_KEY'] = os.urandom(24)
    

    第二种:
    新建 config.py 文件,添加以下代码

    import os
    SECRET_KEY = os.urandom
    

    在 app.py 中添加下行代码

    import config
    app.config.from_object(config)
    
  • 使用 session 时,就如同使用字典一样。比如下列代码实现了向 session 中添加用户

    session['username'] = 'Outro'
    

    当你想要获取 session 中的某个数据时,和字典的操作方式相似

    session.get('username')
    

    当你想要删除 session 中的某个数据时,代码如下

    session.pop('username')    
    

    或者

    del session['username']
    

    当你想要清除 session 中的所有数据时,代码如下

    session.clear()
    
  • 过期时间
    过期时间可以使用以下代码设置

    session.permanent = True
    

    默认31天后关闭
    如果不设置,则默认关闭浏览器关闭

    如果要设置特定的时间,可以使用以下代码

    from datetime import timedelta
    app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
    
    @app.route('/')
    def hello_world():
    session['username'] = 'Outro'
    # session 默认过期时间是浏览器关闭后
    # permanent 为True时,过期时间自动设置为31天
    session.permanent = True
    return 'Hello World!'
    

测试完整代码如下:

from flask import Flask,session
import os
from datetime import timedelta

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)

# app.config.from_object(config)

# 添加上数据到session中
# 操作session和操作字典是相同的
# SECRET_KEY

@app.route('/')
def hello_world():
    session['username'] = 'Outro'
    # session 默认过期时间是浏览器关闭后
    # permanent 为True时,过期时间自动设置为31天
    session.permanent = True
    return 'Hello World!'

@app.route('/get/')
def get():
    # session['username'] 未找到抛出异常
    # session.get('username') 未找到返回 null
    return session.get('username')

@app.route('/delete/')
def delete():
    print(session.get('username'))
    print(session.pop('username'))
    print(session.get('username'))
    return 'success'

@app.route('/clear/')
def clear():
    print('clear')
    print(session.get('username'))
    # 删除session中的所有数据
    session.clear()
    print(session.get('username'))
    return 'success'



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

get 和 post

  1. get 是用户获取服务器端数据的请求,使用场景一般为获取或查询数据,它通常跟在url的?号后面,不会修改服务器上的数据。
  2. post 使用户修该服务器数据的请求,使用场景一般为论坛评论或者更新博客等等。当然它也可以只用于获取数据。
  3. get 和 post 均包含在 flask 下的 request 库中,操作方式与字典类似。
  • 获取 get 请求
    创建 html 文件 index.html,内容如下
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>

<a href="{{ url_for('search',q = 'hello') }}">跳转到搜索页面</a>
</body>
</html>

在 app.py 中添加下列代码:

from flask import request


@app.route('/search/')
def search():
    # 下行为获取 get 请求的关键行
    q = request.args.get('q')

对于 index.html 中的

<a href="{{ url_for('search',q = 'hello') }}">跳转到搜索页面</a>

此行代码,点击链接后的网址为
127.0.0.1/search/?q=hello
将该行代码修改至如下内容后

<a href="{{ url_for('search',p = 'hello') }}">跳转到搜索页面</a>

此行代码,点击链接后的网址为
127.0.0.1/search/?p=hello

  • 获取 post 请求
    创建 html 文件 login.html,内容如下
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="{{ url_for('login') }}" method = "post">
    <table>
        <tbody>
            <tr>
                <td>用户名: </td>
                <td><input type="text" placeholder="请输入用户名" name = "username"></td>
            </tr>
            <tr>
                <td>密码:</td>
                <!--type 后的字段为该内容的字段类型-->
                <td><input type="password" placeholder="请输入密码" name = "password"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="登陆"></td>
            </tr>
        </tbody>
    </table>
</form>
</body>
</html>

在 app.py 文件中添加下行代码:

# methods 是 add_url_rule 文件中 rule 关键字需要获取到的一个参数
# rule 被初始化为 rule = self.url_rule_class(rule, methods=methods, **options)
# GET 和 POST 则是固定两种模式的关键字
@app.route('/login/',methods=['GET','POST'])
def login():
    # request.method 方法能够获取到当前的模式
    if request.method == 'GET':
        return render_template('login.html')
    else:
        username = request.form.get('username')
        password = request.form.get('password')
        print("username: ", username)
        print("password: ", password)
        return "login success"

线程隔离的 g 对象

g 的意思是 global
它是专门用于保存用户数据的,在不同请求中,g 并不是同一个 g,换言之,当前请求完全结束时,当前的 g 也就销毁了。简而言之,g 的作用范围,只在一个请求(即一个线程)中。
示例代码以及文件如下:
app.py

from flask import Flask, g, request, render_template
from utils import login_log
app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'

@app.route('/login/',methods=['GET','POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        username = request.form.get('username')
        password = request.form.get('password')
        if username == 'Outro' and password == '123':
            # login_log(username)
            g.username = username
            login_log()
            return '登陆成功'
        else:
            return '您的用户名或者密码输入错误'

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

utils.py

from flask import g

def login_log():
    print("当前登陆的用户是: %s" % g.username)

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    <table>
        <tbody>
            <tr>
                <td>用户名:</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value = "登陆"></td>
            </tr>
        </tbody>
    </table>
</form>
</body>
</html>

钩子函数

如果有三个函数 A 、B 和 C,其中 A 和 B 的执行顺序为 A -> B,如果 C 函数可以插入其中,将顺序改变为 A -> B -> C,那么这种函数叫做钩子函数

  1. before_request:
     用处:在请求之前那执行的函数
  2. context_processor:
     用处:上下文处理器,当你使用上下文处理器返回一个字典时,项目中所有模板中的相同变量名都会使用该字典下的值
    示例项目:
    edit.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {{ username }}
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name = "username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name = "password"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="登陆"></td>
        </tr>
    </table>
</form>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {{ username }}
</body>
</html>

app.py

from flask import Flask, render_template, request, session, redirect, url_for, g
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)

@app.route('/')
def hello_world():
    return render_template('index.html')

@app.route('/login/', methods=['GET','POST'])
def login():
    return render_template('login.html')

@app.route('/edit/')
def edit():
    return render_template('edit.html')

# before_request: 在请求之前执行的函数,在视图函数执行之前执行
# before_request:只是一个装饰器,把需要作为钩子函数执行的代码放在试图函数之前执行


@app.before_first_request
def my_before_request():
    if session.get('username'):
        g.username = session.get('username')

@app.context_processor
def my_context_processor():

    return {'username':'Outro'}



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

登陆限制的装饰器

博客有两种基本功能必须要能够实现

  1. 未登录时点击发布博客自动跳转到登陆界面
  2. 未登录时访问首页会自动跳转至登陆界面
    于是创建 decorators.py 添加下列代码
from functools import wraps
from flask import session, redirect, url_for

def login_require(func):
    @wraps(func)

创建博文模型并添加时间类型字段

模型代码如下

class Question():
    __tablename__ = '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)
    
    create_time = db.Column(db.DateTime,default=datetime.now(),nullable=False)
    author_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    author = db.relationship('User', backref = db.backref('question'))

Tip:
 上述代码段中的 datetime.now() 获取每次创建一个模型时的当前时间

创建好评论之后,需要使用数据类型存储以便前端调用,修改 app.py 文件
app.py

@app.route('/')
@login_required
def hello_world():
    context = {
        'question' : Question.query.order_by('-create_time').all()    
    }
    return render_template('index.html', **context)

上述将 Question 表中查询到的数据按照 create_time 字段数据从大到小的顺序赋值给 context ,并将其传递给 index.html 文件
order_by("xxxx")函数表示根据xxx字段按照什么方式排序查询到的数据,默认按照从小到大排序,在字段前面添加负号表示从大到小排序。

在模型中定义属性排序

class Answer(db.Model):
    __tablename__ = 'answer'
    id = db.Column(db.Integer, primary_key=True,autoincrement=True)
    content = db.Column(db.Text,nullable=False)
    question_id = db.Column(db.Integer,db.ForeignKey('question.id'))
    author_id = db.Column(db.Integer,db.ForeignKey('user.id'))

    question = db.relationship('Question',backref = db.backref('answers',order_by=id.desc()))
    author = db.relationship("User", backref = db.backref("answer"))

在模型中是不能够使用

    order_by('-create_time')

进行倒序排序的,需要使用以下方式

    question = db.relationship('Question',backref = db.backref('answers',order_by=id.desc()))

调用 desc() 方法

关于 PyCharm2018 后续版本中修改 host port 以及 Debug

本人在使用 PyCharm 时,普通教程中给出的修改 host,port 以及 debug 方法都不能在我这里体现出来,搜索“flask 无法修改服务器端口”,“flask 无法修改 host 和 port ” 以及 “flask cannot change port or host ” 这样的字段半天之后,找到了解决方案
修改 Debug 模式需要点击右上角运行按钮左侧,选择 Edit-Configuration 找到 FLASK_DEBUG 打上勾即可
修改 host 以及 port 有两种方式

  1. 修改app.run()代码如下
if __name__ == '__main__'
    app.run(host='x.x.x.x',port=8000)

然后进入cmd,找到 app.py 文件所在的位置,使用

python app.py

运行改文件即可
2. 在虚拟环境开启的状况下,进入 app.py 所在文件目录,在 cmd 中键入一下代码

set FLASK_APP=app.py
flask run -h x.x.x.x -p 8000
  1. 修改 Debug 模式需要点击右上角运行按钮左侧,选择 Edit-Configuration 找到 Additinal options 栏,手动写入:--host=x.x.x.x --port=xxxx

出现无法更改 Debug port 以及 host 的原因是使用 PyCharm 运行该文件时,并不会运行到

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

因此,上述参数也就不会得到更改
参考资料:
Pycharm2018设置debug模式与host,port的坑
flask无法修改访问ip和端口
Why can't I change the host and port that my Flask app runs on?
以及群友的帮助!感谢你们!

posted @ 2018-07-29 21:08  Intro1997  阅读(228)  评论(0编辑  收藏  举报