《Flask Web开发》-基于python的web应用开发实战-笔记

《Flask Web开发》这本书中的一些坑

1. 需要安装的包

  • pip install flask
  • pip install flask_script
  • pip install flask_bootstrap
  • pip install flask_moment
  • pip install flask_wtf
  • pip install flask_sqlalchemy
  • pip install flask_mail
  • pip install flask_login
  • pip install pymysql
  • pip install flask_migrate
  • pip install hashlib
  • pip install faker #生成虚拟数据的
  • pip install flask_pagedown
  • pip install markdown
  • pip install bleach
  • pip install flask_httpauth
  • pip install httpie
  • pip install selenium

2. bootstrap

Bootstrap

3. 响应abort函数

一种特殊的响应由abort函数生成,用于处理错误

4. @app.errorhandler

用来处理状态码错误的

@app.errorhandler(400)
def page_not_found(e):
      return render_template('404.html'),404

5.{% import "bootstrap/wtf.html" as wtf %}

在模板中使用{% import "bootstrap/wtf.html" as wtf %}.
import指令的使用方法和普通Python代码一样,允许导入模板中的元素并用在多个模板中。

6. 数据库

(1).

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('User', backref='role')

    def __repr__(self):
        return '<Role %r>' % self.name

Flask-SQLAlchemy创建的数据库实例为模型提供了一个基类以及一系列辅助类和辅助函数,可用于定义模型结构。

类变量__tablename__定义在数据库中使用的表名。如果没有定义__tablename__,Flask-SQLAlchemy会使用一个默认名字,但默认的表名没有遵守使用复数形式进行命名的约定。其余的类变量都是该模型的属性,被定义db.Column类的实例。

(2).要让Flask-SQLAlchemy根据模型类创建数据库。方法是使用db.create_all()函数。如果数据库表已经存在与数据中,那么db.create_all()不会重新创建或者更新这个表。如果修改模型后要把改动应用到现有的数据库中,这一特性会带来不便。更新现有的数据库表的粗暴方式是先删除旧表再重新创建:

>>>db.drop_all()
>>>db.create_all()

数据库的操作是先创建模型的实例,然后将数据加入到数据库会话中db.session。要将对象写入数据库,最后要使用db.commit()提交会话。

数据库会话db.session和Flask session对象没有关系。数据库会话也称为事务。

数据库会话可以回滚,调用db.session.rollback()后,添加到数据库会话中的所有对象都会还原到它们在数据库时的状态。

(3). python hello.py shell使用与运行
要想使用python hello.py shell但没有没有进入 shell 环境。原因为没有调用manager.run(),应该要使用如下代码,才能进入shell环境。

from flask_script import Manager
if __name__ == '__main__':
    manager.run()

(4).数据库插入行admin_role = Role(name = 'Admin')

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(64),unique = True)
    users = db.relationship('User',backref='role')
    def __repr__(self):
        return '<Role %r>'%self.name

admin_role = Role(name = 'Admin')

模型的构造函数接受的参数是使用关键字参数指定的模型属性初始值。

7. 使用Flask_mail发送电子邮件

from flask_mail import Mail, Message

app.config['MAIL_SERVER'] = 'smtp.126.com'
app.config['MAIL_PORT'] = 25
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'MAIL_USERNAME'
app.config['MAIL_PASSWORD'] = 'MAIL_PASSWORD'

成功用上述代码在python shell中发送邮件

8. WTForm表单的field的取值form.name.data

官方文档的解答

9.蓝本Blueprint的使用

(1)创建蓝本

# app/main/__init__.py
from flask import Blueprint

main = Blueprint('main',__name__)
from . import views,errors

错误与路由处理的程序在app/main/init.py脚本的末尾导入,这是为了避免循环导入依赖,因为在views.py和errors.py中还要导入蓝本main。

--Python是脚本语言,只会由上往下执行
(2)蓝本中的错误处理程序

from flask import render_template
from .import main

@main.app_errorhandler(404)
def page_not_found(e):
    return render_template('404.html'),404

@main.app_errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'),500

只有蓝本中的错误才能触发处理程序,要想注册程序全局的错误处理程序,必须使用app_errorhandler
(3)蓝本中定义路由

from flask import render_template, session, redirect, url_for, current_app
from .. import db
from ..models import User
from ..email import send_email
from . import main
from .forms import NameForm


@main.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            db.session.commit()
            session['known'] = False
            if current_app.config['FLASKY_ADMIN']:
                send_email(current_app.config['FLASKY_ADMIN'], 'New User',
                           'mail/new_user', user=user)
        else:
            session['known'] = True
        session['name'] = form.name.data
        return redirect(url_for('.index'))
    return render_template('index.html',
                           form=form, name=session.get('name'),
                           known=session.get('known', False))

蓝本中编写的视图函数主要有两点不同:

  • 1 路由修饰器是由蓝本提供的
  • 2 url_for()函数的用法不同。url_for()函数的第一个参数是路由的端点名,在程序的路由中,默认为视图函数的名字。例如,在单脚本程序中,index()视图函数的URL可使用url_for('index')获取

而在蓝本中就不一样,Flask会为蓝本中的全部端点加上一个命名空间,这样就可以在不同的蓝本中使用相同的端点名定义视图函数,而不会产生冲突。命名空间就是蓝本的名字(Blueprint构造函数的第一个参数),所以视图函数index()注册的端点名是main.index,其URL使用`url_for('main.index')获得

9. 认证蓝本

render_template()指定的模板文件保存在auth文件夹中,这个文件夹必须在app/templates中创建,因为Flask认为模板的路径是相对鱼程序模板文件夹而言的。render_template()函数会首先搜索程序配置的模板文件夹,然后在搜索蓝本的配置的模板文件夹

10. 数据库migrations时遭遇错误(Can't locate revision identified by ‘xxx’)

Can't locate revision identified by ‘xxx’

这种错误最简单的解决办法就是把数据库中的alembic_version表删掉,然后就可以继续后面的操作了


image.png

参考:Flask框架+mySQL数据库:误删migrations文件夹后再次创建时遭遇错误(Can't locate revision identified by ‘xxx’)

11.添加登录表单

登录页面使用的模板保存在auth/login.html文件中。这个模板只需要是使用Flask-Bootstrap提供的wtf.quick_form()宏渲染表单即可

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flasky - Login{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Login</h1>
</div>
<div class="col-md-4">
    {{ wtf.quick_form(form) }}
</div>
{% endblock %}

12.保护路由

Flask-login提供了一个login_required修饰器。

from flask_login import login_required
@app.route('/secret')
@login_required
def secret():
      return 'Only authenticated users are allowed!'

13.表单类

  1. 表单的创建,可以通过继承从 Flask-WTF 导入的FlaskForm父类实现
from flask_wtf import FlaskForm # 表单类,从第三方扩展的命名空间 导入
  1. 表单类中需要定义 属性/字段,值是字段类型类,就是将要在 HTML 中显示的表单各个字段,其实就是对 HTML 表单各种标签的包装
from wtforms import StringField,PasswordField,BooleanField,SubmitField
  1. 字段类型类(说明文本,验证器列表)
    验证器列表,检查用户填写表单时输入的内容是否符合我们的期望,有多个验证器时,需要同时通过验证
from wtforms.validators import DataRequired,Length,Email,Regexp,EqualTo
# 普通用户的资料编辑表单
class EditProfileForm(Form):
    name = StringField('Real name', validators=[Length(0, 64)]) # 因为是可选,允许长度为0
    location = StringField('Location', validators=[Length(0, 64)])
    about_me = TextAreaField('About me') # 文本区域,可以多行,可以拉动
    submit = SubmitField('Submit')

在表单类中定义了以validate_开头并且后面跟着字段名的方法,这个方法就和常规的验证函数一起调用。自定义的验证函数要想表示验证失败,可以抛出ValidationError异常,其参数就是错误信息。

class RegistrationForm(FlaskForm):
    email = StringField('Email',validators=[DataRequired(),Length(1,64),Email()])
    username = StringField('Username',validators=[DataRequired(),Length(1,64),Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0,
               'Usernames must have only letters, numbers, dots or '
               'underscores')])
    password = PasswordField('Password',validators=[DataRequired(),EqualTo('password2',message='Passwords must match.')])
    password2 = PasswordField('Confirm password',validators=[DataRequired()])
    submit=SubmitField('Register')

    def validate_email(self,field):
        if User.query.filter_by(email = field.data).first():
            raise ValidationError('Email already registered.')
    
    def validate_username(self,field):
        if User.query.filter_by(username = field.data).first():
            raise ValidationError('Username already in use.')

14.没有继承的类class

class Permission:
    FOLLOW = 1
    COMMENT = 2
    WRITE = 4
    MODERATE = 8
    ADMIN = 16

在写Flask的用户权限中看到上述的类定义。在Python中定义类class的时候,加上()与不加上()有没有区别?

Python创建类的时候,加()和不加有什么区别、联系?
python的class(类)中继承object 与不继承的区别
通过查看上述的文章,可以得知的是事实上是没有区别的,以下三种写法是等价的

class A:
    pass
    
class A():
    pass
    
class A(object):
    pass

15.类的方法

方法 init()
类中的函数称为方法;你前面学到的有关函数的一切都适用于方法,就目前而言,唯一重要
的差别是调用方法的方式。 处的方法 init() 是一个特殊的方法,每当你根据 Dog 类创建新实
例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约
定,旨在避免Python默认方法与普通方法发生名称冲突。

16.Jinja2 宏方法

https://blog.csdn.net/hello_albee/article/details/51638358/
https://blog.csdn.net/whtqsq/article/details/76278374

17. 用户资料编辑器(Edit Profile)

@main.route('/edit-profile',methods=['GET','POST'])
@login_required
def edit_profile():
    form = EditProfileForm()
    if form.validate_on_submit():
        current_user.name = form.name.data
        current_user.location = form.location.data
        current_user.about_me = form.about_me.data
        db.session.add(current_user._get_current_object())
        db.session.commit()
        flash('Your profile has been updated.')
        return redirect(url_for('.user',username = current_user.username))
    form.name.data = current_user.name
    form.location.data = current_user.location
    form.about_me.data = current_user.about_me
    return render_template('edit_profile.html',form = form)

刚写代码的时候一直不是太懂为什么在显示表单之前,这个视图函数要为所有字段设定了初始值。后来运行了代码,在网页界面尝试修改用户profile才明白,初始值就是让你在修改前,能够看到以前的用户信息。
Edit Profile

在这里还有一个问题,当用户第一次访问程序时,服务器会收到一个没有表单数据的GET请求,所以validate_on_submit()将返回False。if语句的内容将被跳过,通过渲染模板处理请求!
当用户提交表单后,服务器收到一个包含数据的POST请求。通过所有验证之后,validate_on_submit()返回True,然后执行后续表单的数据的处理。这里当时自己有一个问题,就是执行完return redirect(url_for('.user',username = current_user.username))之后,后面的form.<field>.name数据还会不会执行赋值?是不会的,因为每一个函数只会执行返回一个值。当执行到return语句并返回了值,就会跳出函数。所以后续的语句是不会再执行了。

18. Flask 'endpoint'端点的理解

Flask中'endpoint'(端点)的理解1
Flask中endpoint的理解2

回到flask接受用户请求地址并查询函数的问题。实际上,当请求传来一个url的时候,会先通过rule找到endpoint(url_map),然后再根据endpoint再找到对应的view_func(view_functions)。通常,endpoint的名字都和视图函数名一样。
这时候,这个endpoint也就好理解了:
实际上这个endpoint就是一个Identifier,每个视图函数都有一个endpoint,
当有请求来到的时候,用它来知道到底使用哪一个视图函数

19. Flask Request

flask框架的请求上下文request中的args获取请求参数方式

request.form 和request.args.get的区别

用url_for()来给指定的函数构造URL。它接受函数名作为第一个参数,也接受对应URL规则的变量部分的命名参数。未知变量部分会添加到URL末尾作为查询参数。

20.Flask中flash不显示问题

flask中有个flash功能用来提示用户一些信息,比如用户退出登陆或者登陆用户名密码不匹配等。。。
在开发过程中,后台里边写入消息提示flash,但是触发了flash条件但是页面并没有显示需要提示的信息。
原因:flash消息需要在模板中进行渲染。

例子:用方法get_flashed_messages()来获取后台堆积的flash消息,然后用一个for循环来显示需要提示的信息。

应用代码:

@auth.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user is not None and user.verify_password(form.password.data):
            login_user(user, form.remember_me.data)
            return redirect(request.args.get('next') or url_for('main.index'))
        flash('无效用户名或密码.')
    return render_template('auth/login.html', form=form)

模板渲染:

<div class="container">
    {% for message in get_flashed_messages() %}
    <div class="alert alert-warning">
        <button type="button" class="close" data-dismiss="alert">&times;</button>
        {{ message }}
    </div>
    {% endfor %}
    {% block page_content %}{% endblock %}
</div>

21. Flask_script

flask_script的作用是可以通过命令行的形式来操作flask例如通过一个命令跑一个开发版本的服务器,设置数据库,定时任务等.安装flask_script比较简单,直接通过pip install flask_script安装就可以了。
具体可以参考下面有关Flask_script的使用方法:
Flask之flask-script模块使用

然后就是
从Flask-Script迁移到Flask-Cli

Flask 中的click模块与Flask中的Flask_cli不知道是不是一样的

还有遇到上面的这个错误:

Error: Could not locate Flask application. You did not provide the FLASK_APP environment variable.

参考如何的文档:
indows下Flask run报错'Could not locate flask application' 原因?

https://blog.csdn.net/AuserBB/article/details/79524470

像这样设置环境变量:

(venv) $ export FLASK_APP=manage.py

windows就比较坑爹了,官方文档中说这么写:

(venv) $ set FLASK_APP=manage.py

但是实际也许不会成功,会提示以下错误:

    Usage: flask run [OPTIONS]
     
    Error: Could not locate Flask application. You did not provide the FLASK_APP environment variable.
     
    For more information see http://flask.pocoo.org/docs/latest/quickstart/

我用的是PowerShell,网上找了一些方法,都不成功,后来按照Stack Overflow上这个方法来就成功了:

(venv) $env:FLASK_APP = "manage.py"

Click模块
Flask_cli是Click模块的一个backport(补丁)

Flask-CLI is a backport of Flask 1.0 new click integration to v0.10.
Do not install this package if you use Flask 1.0+.

shell_context_processor
这个的作用是避免了每次启动shell会话时都要导入数据库和模型

使用Flask的内置click命令行启动Flask时总提示找不到'flask_bootstrap'
参考一下的解决方法
https://segmentfault.com/q/1010000015568164/a-1020000015595092

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,423评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,339评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,241评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,503评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,824评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,262评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,615评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,337评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,989评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,300评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,829评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,193评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,753评论 3 230
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,970评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,708评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,295评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,207评论 2 258

推荐阅读更多精彩内容