Flask笔记


title: Flask笔记

课程基于Python 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按钮

Hello World

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

#encoding utf-8

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

运行该项目,运行结果如下图
Flask笔记_第1张图片

它表示当前的这个项目运行在 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写) ]
Flask笔记_第2张图片
则点击编译器右上侧运行符号旁边的项目名称处,选择Edit Configuration
勾选下图选项
Flask笔记_第3张图片

重新打开网站便可以看到错误信息了
2.
在修改你的代码后,保存文件的同时,编译器会在下方提示你你的信息被改变了,同时网页也会重新加载变更后的代码信息。
提示信息如下图
Flask笔记_第4张图片

使用配置文件

在项目中新建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/')
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/')
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//')
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()

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




    
    Title


    这是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')
'''

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

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




    
    Title


    这是HTML文件中出现的文字
    

用户名:Outro

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

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

在index.html中的添加



    这是HTML文件中出现的文字
    

用户名:{{ username }}

这样我们就完成了参数的传递(模拟数据库的通信)
重新运行一下程序(注意,这里无论你是否开启了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中的部分代码



    这是HTML文件中出现的文字
    

用户名:{{ username }}

身高:{{ height }}

性别:{{ gender }}

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

@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文件中添加代码至如下代码所示




    
    Title


    

用户名:{{ username }}

身高:{{ height }}

性别:{{ gender }}


名字:{{ person.name }}

年龄:{{ person.age }}


百度:{{ websites.baidu }}

谷歌:{{ websites.google }}

以上就是在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,代码如下:




    
    Title


    这里模板


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

if_statement.py

from flask import Flask,render_template

app = Flask(__name__)


@app.route('//')
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




    
    Title


    {% if user %} 
        {{ user.username }}
        注销
    {% else %}
        登陆
        注册
    {% endif %}



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

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




    
    Title


    
    {% if user and user.age > 18 %}
        {{ user.username }}
        注销
    {% else %}
        登陆
        注册
    {% endif %}



如注释所写,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




    
    Title


    
    {% for v,k in user.items() %}
        

{{ v }}:{{ k }}

{% endfor %}

列表遍历
修改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()




    
    Title


    {% for v,k in user.items() %}
        

{{ v }}:{{ k }}

{% endfor %} {% for website in websites %}

{{ website }}

{% endfor %}

小案例:做一个四大名著的网络表(书名,作者,价格)
修改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()




    
    Title





    {% for book in books %}
        
    {% endfor %}

    
书名 作者 价格
{{ book.name }} {{ book.author }} {{ book.price }}

过滤器

创建一个项目名称为 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 文件中的代码




    
    过滤器





其中 | 符号为管道符号,作用是过滤。在这里的意思是如果找不到 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




    
    过滤器





评论数:({{ comments | length }})

在这里的 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 migrate
回车

运行成功后分别显示

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

model分离主文件

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

from app.py import xxx

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

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 栈中,情况如下图:

因此,程序在 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 的文件夹
Flask笔记_第6张图片
此时的 versions 文件夹是没有文件的
执行完第二条命令之后,会发现 migrations 文件夹下的 versions 文件夹多出了一个文件
Flask笔记_第7张图片
此文件即为迁移文件
执行完第三条命令之后,在 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 文件夹,会发现多出一个文件
Flask笔记_第8张图片
这就是更新后的迁移文件。

session 和 cookie

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,内容如下

<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,内容如下

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<table> <tbody> <tr> <td>用户名: td> <td>td> tr> <tr> <td>密码:td> <td>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


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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>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

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    {{ username }}
body>
html>

login.html


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<form action="" method="post">
    <table>
        <tr>
            <td>用户名:td>
            <td>td>
        tr>
        <tr>
            <td>密码:td>
            <td>td>
        tr>
        <tr>
            <td>td>
            <td><input type="submit" value="登陆">td>
        tr>
    table>
form>
body>
html>

index.html


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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?
以及群友的帮助!感谢你们!

你可能感兴趣的:(Python,Learning)