Flask学习11:阿里云新手Flask + nginx + uwsgi + ubuntu的完整项目部署教程

Flask项目部署

web工作原理

客户端 < = > 服务器(nginx) < = > uWSGI < = > Python(Flask) < = > 数据库

nginx安装

源码安装、apt-get install …

sudo /usr/local/nginx/sbin/nginx -s reload

添加虚拟主机

1.在nginx的主配置文件最后一个大括号的上面添加:

include vhost/*.conf;

2.在conf/下新建文件夹,用于保存所有的虚拟主机配置文件

mkdir vhost

3.vhost目录下新建一个虚拟主机的配置文件(sudo vim idandan.vip.conf)

server{
    listen 80;
  server_name idandan.vip;
  location / {
      root html/blog;
      index index.html;
  }
}

# sudo vim idandan.vip.conf

4.在html目录下创建blog文件夹,在blog下新建index.html

Hello World

5.重启nginx(sudo /usr/local/nginx/sbin/nginx -s reload)

6.添加本地的域名解析,修改文件:(C:WindowsSystem32driversetchosts)

最后一行添加:139.196.81.238 idandan.vip

7.测试,在浏览器地址栏输入:idandan.vip

uWSGI

1.安装:pip3 install uwsgi

2.配置:

http:    # 采用http协议
socket:      # 采用socket协议
wsgi-file    # 将数据交给哪个模块
callable # 具体可调用的对象
chdir        # 指定uwsgi启动后的当前目录
daemonize    # 后台运行,需要指定一个日志文件


# blog.py文件

from flask import Flask
app = Flask(__name__)

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

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

3.简单实例

1.在blog目录下创建blog.py文件,里面写上flask启动代码用于测试
2.启动:
    sudo uwsgi --http host:port --wsgi-file blog.py --callable app

4.socket方式启动

1)nginx转发请求:

python 
server{ 
listen 80; 
server_name idandan.vip; 
location / { 
# root html/blog; 
# index index.html; 
include uwsgi_params; 
uwsgi_pass 127.0.0.1:5000; 
} 
} 

2)以socket方式启动

sudo uwsgi --socket 127.0.0.1:5000 --wsgi-file blog.py --callable app

3)将启动参数配置写进配置文件

[uwsgi]
socket  = 127.0.0.1:5000
wsgi-file = blog.py
callable = app

(blog/vim uwsgi.ini)
启动:sudo uwsgi wsgi.ini

静态文件处理

1.准备静态资源

python
1.在项目根目录下(blog)创建static目录
2.将图片拷贝到static下

2.配置nginx转发

# 添加一个location

location /static{
 # root html/blog;

# 或

alias html/blog/static;  # 两种方式都可以
}

Flask学习10:Flask项目集成富文本编辑器CKEditor 上传图片

CKEditor下载地址:https://ckeditor.com/ckeditor-4/download/
访问CKeditor官方网站,进入下载页面,选择Standard Package(一般情况下功能足够用了),如果你想尝试更多的功能,可以选择下载Full Package。
下载好CKeditor之后,解压到Flask项目static/ckeditor目录即可。

在Flask项目中集成CKEditor:

  1. <scripts>标签中导入CKEditor
  2. 使用CKEDITOR.replace()把现存的<textarea>替换成CKEditor

代码如下:

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

{#    引入CKEditor#}
    <script type="text/javascript" src="../static/js/ckeditor/ckeditor.js"></script>
</head>
<body>
    <form method="post" action="{{ url_for('ckupload') }}" enctype="multipart/form-data">
        <input type="text" name="subject" placeholder="主题">
        <textarea name="content" id="content" placeholder="内容"></textarea>
        <input type="submit" value="立即发布">

{#        替换textarea#}
        <script type="text/javascript">
            CKEDITOR.replace('content', {
{#        开启上传功能,配置上传路径#}
                filebrowserUploadUrl: '/ckupload/',
            });
        </script>
    </form>
</body>
</html>

Flask处理上传请求:

CKEditor上传功能是统一的接口,即一个接口可以处理图片上传、文件上传、Flash上传。先来看看代码:

from flask import Flask, render_template, request, make_response, url_for
from flask_script import Manager
import datetime
import os
import random

app = Flask(__name__)
manager = Manager(app)

def gen_rnd_filename():
    filename_prefix = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    return '%s%s' % (filename_prefix, str(random.randrange(1000, 10000)))

@app.route('/ckupload/', methods=['POST'])
def ckupload():
    """CKEditor file upload"""
    error = ''
    url = ''
    callback = request.args.get("CKEditorFuncNum")
    if request.method == 'POST' and 'upload' in request.files:
        fileobj = request.files['upload']
        fname, fext = os.path.splitext(fileobj.filename)
        rnd_name = '%s%s' % (gen_rnd_filename(), fext)
        filepath = os.path.join(app.static_folder, 'upload', rnd_name)
        # 检查路径是否存在,不存在则创建
        dirname = os.path.dirname(filepath)
        if not os.path.exists(dirname):
            try:
                os.makedirs(dirname)
            except:
                error = 'ERROR_CREATE_DIR'
        elif not os.access(dirname, os.W_OK):
            error = 'ERROR_DIR_NOT_WRITEABLE'
        if not error:
            fileobj.save(filepath)
            url = url_for('static', filename='%s/%s' % ('upload', rnd_name))
    else:
        error = 'post error'
    res = """

<script type="text/javascript">
  window.parent.CKEDITOR.tools.callFunction(%s, '%s', '%s');
</script>

""" % (callback, url, error)
    response = make_response(res)
    response.headers["Content-Type"] = "text/html"
    return response

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

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

再去看自己项目的目录,就可以找到/upload底下已经有自己上传的图片了。

原文:http://flask123.sinaapp.com/article/49/

Flask学习9:在服务器上处理富文本

继上一篇博客(Flask学习8),提交表单后,POST 请求只会发送纯 Markdown 文本,页面中显示的 HTML 预览会被丢掉。和表单一起发送生成的 HTML 预览有安全隐患,因为攻击者轻易就能修改 HTML 代码,让其和 Markdown 源不匹配,然后再提交表单。安全起见,只提交 Markdown 源文本,在服务器上使用 Markdown(使用 Python 编写的 Markdown 到 HTML 转换程序)将其转换成 HTML。得到 HTML 后,再使用 Bleach 进行清理,确保其中只包含几个允许使用的HTML 标签。

把 Markdown 格式的博客文章转换成 HTML 的过程可以在 _posts.html 模板中完成,但这么做效率不高,因为每次渲染页面时都要转换一次。为了避免重复工作,我们可在创建博客文章时做一次性转换。转换后的博客文章 HTML 代码缓存在 Post 模型的一个新字段中,在模板中可以直接调用。文章的 Markdown 源文本还要保存在数据库中,以防需要编辑,修改后的模型如下:

...
from markdown import markdown
import bleach

class Posts(db.Model):
    ...
    content_html = db.Column(db.Text)

    @staticmethod
    def on_changed_content(target, value, oldvalue, initiator):
        allowed_tags = [
                        'a', 'abbr', 'acronym', 'b', 'blockquote', 'code',
                        'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul',
                        'h1', 'h2', 'h3', 'p'
                        ]
        target.content_html = bleach.linkify(bleach.clean(markdown(value, output_format='html'), tags=allowed_tags, strip=True))
db.event.listen(Posts.content, 'set', Posts.on_changed_content)

on_changed_body 函数注册在 body 字段上,是 SQLAlchemy“set”事件的监听程序,这意味着只要这个类实例的 body 字段设了新值,函数就会自动被调用。on_changed_body 函数把 body 字段中的文本渲染成 HTML 格式,结果保存在 body_html 中,自动且高效地完成Markdown 文本到 HTML 的转换。真正的转换过程分三步完成。首先,markdown() 函数初步把 Markdown 文本转换成 HTML。
然后,把得到的结果和允许使用的 HTML 标签列表传给 clean() 函数。clean() 函数删除所有不在白名单中的标签。转换的最后一步由 linkify() 函数完成,这个函数由 Bleach 提供,把纯文本中的 URL 转换成适当的链接。最后一步是很有必要的,因为 Markdown规范没有为自动生成链接提供官方支持。PageDown 以扩展的形式实现了这个功能,因此在服务器上要调用 linkify() 函数。

最后,如果 post.body_html 字段存在,还要把 post.body 换成 post.body_html:

# 文章内容部分显示
<a href="#">
    {% if post.content_html %}
          {{ post.content_html }}
   {% else %}
          {{ post.content }}
   {% endif %}
</a>

Flask学习8:使用markdown

对于发布短消息和状态更新来说,纯文本足够用了,但如果用户想发布长文章,就会觉得在格式上受到了限制,因此可以使用markdown。
依赖:

  • PageDown:使用 JavaScript 实现的客户端 Markdown 到 HTML 的转换程序。
  • Flask-PageDown:为 Flask 包装的 PageDown,把 PageDown 集成到 Flask-WTF 表单中。
  • Markdown:使用 Python 实现的服务器端 Markdown 到 HTML 的转换程序。
  • Bleach:使用 Python 实现的 HTML 清理器。
这些包都可以用pip安装: 
pip install flask-pagedown markdown bleach

Flask-PageDown 扩展定义了一个 PageDownField 类,这个类和 WTForms 中的 TextAreaField接口一致。使用 PageDownField 字段之前,先要初始化扩展,

...
from flask_pagedown import PageDown

# 创建对象
...
pagedown = PageDown()

# 初始化对象
def config_extensions(app):
    ...
    pagedown.init_app(app)

若想把首页中的多行文本控件转换成 Markdown 富文本编辑器,表单中的字段要进行修改,

# 发布文章表单
class PublishForm(FlaskForm):
    title = StringField('',render_kw={'placeholder':'主题'}, validators=[DataRequired()])
    # markdown
    content = PageDownField('',render_kw={'placeholder':'内容'}, validators=[DataRequired(), Length(1, 9999, message='文章字数超出限制')])

    submit = SubmitField('立即发布')

Markdown 预览使用 PageDown 库生成,因此要在模板中修改。Flask-PageDown 简化了这个过程,提供了一个模板宏,从 CDN 中加载所需文件:

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

{% block title %}
    记录
{% endblock %}

{% block scripts %}
    {{ super() }}
{#    markdown#}
    {{ pagedown.include_pagedown() }}
{% endblock %}

{% block page_content %}
    <div style="width: 800px; height: 800px; background-color: white; margin: 0 auto">
        {{ wtf.quick_form(form) }}

    </div>
{% endblock %}

做了上述修改后,在多行文本字段中输入 Markdown 格式的文本会被立即渲染成 HTML 并显示在输入框下方。

未分类

Flask学习7:完整项目(blog)

Flask完整项目:Blog

# manage.py代码
import os
from flask_script import Manager
from flask_migrate import MigrateCommand
from app import create_app

app = create_app(os.environ.get('FLASK_CONFIG') or 'default')

manager = Manager(app)
manager.add_command('db', MigrateCommand)

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

由于代码较多,可下载查看。
http://download.csdn.net/download/qq_25046261/10176467

Flask学习6:博客项目基本构思

Flask项目

项目需求

  1. 用户注册登陆
  2. 用户信息管理
  3. 博客发表、评论
  4. 博客展示(分页)
  5. 收藏(点赞)
  6. 搜索、统计、排序、…

项目结构

blog/                           # 项目根目录
    app/                        # 程序包目录
        static/                 # 静态文件目录
            js/                 # js文件目录
            css/                # css文件目录
            img/                # 图片文件目录
        templates/              # 模板文件目录
        views/                  # 视图函数(蓝本)
        models/                 # 所有的数据模型文件
        forms/                  # 所有的表单文件
        config.py               # 配置文件
        email.py                # 邮件发送
        extensions.py           # 所有扩展
        __init__.py             # 作为一个包必须有
    migrations/                 # 数据库迁移目录
    tests/                      # 测试文件目录
    venv/                       # 虚拟环境
    requirements.txt            # 项目依赖包列表我呢见
    manage.py                   # 启动控制文件

开发环境

1.新建一个项目,按照需求创建需要的目录及文件

2.创建虚拟环境

virtualenv venv  # 创建虚拟环境
venvScriptsactivate  # 启动虚拟环境
venvScriptsactivate  # 退出虚拟环境

3.依赖包管理

生成依赖环境:pip freeze > requirements.txt

下载依赖包:pip install -r requirements.txt

书写步骤

1.配置文件的书写与使用

1.在config.py文件中书写项目配置
2.在app/__init__.py中封装create_app函数
3.在manage.py文件中调用create_app函数并启动实例

2.添加各种扩展

1.在app/extensions.py中,创建扩展对象,封装初始化函数config_extensions
2.在create_app函数中调用配置函数即可

3.添加蓝本

1.在view目录下创建文件,在新建的文件中创建蓝本,添加视图函数等
2.在views目录下创建__init__.py文件中,封装一个config_blueprint函数,完成蓝本注册
3.为了简化蓝本注册,多写一个蓝本配置的元组,然后遍历执行注册
4.自行添加新的蓝本时,只需要导入,然后再配置中增加一项即可

4.项目基础模板定制

1.基础模板定制
2.为了测试,顺便定制了错误显示页面(config_errorhandler)

5.邮件的异步发送

1.http://blog.csdn.net/qq_25046261/article/details/78914370#t5异步发送邮件的两个函数
2.导入对应的依赖

Flask学习5:模型

数据模型

数据库回顾

1.分类

​关系型数据库:MySQL、Oracle、SQLite、…

非关系型数据库:MongoDB、Redis、…

2.选择

​数据库没有好坏,要根据项目需求进行选择:盲目的评价或跟风只能证明不够

flask-sqlalchemy

1.说明

​提供了大多数关系型数据库的支持,而且提供了ORM(对象关系映射)

2.安装

pip install flask-sqlalchemy

3.连接配置

​指定数据库地址

MySQL:mysql://username:password@host/database

SQLite:
        windows:sqlite:///c:/path/to/database
         linux:sqlite:////c:/path/to/database

配置选项:
        SQLACHEMY_DATABASE_URI

4.使用:

from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)
manager = Manager(app)

# 配置数据库连接地址
import os
# 当前路径
base_dir = os.path.abspath(os.path.dirname(__file__))
# 连接地址
database_uri = 'sqlite:///' + os.path.join(base_dir, 'data.sqlite')
app.config['SQLALCHEMY_DATABASE_URL'] = database_uri

# 创建对象
db = SQLAlchemy(app)

5.添加数据模型

# 继承自特定的基类
# 定义模型类
class User(db.Model):
    # 不指定表明,默认会将大驼峰转换为小写+下划线风格
    # 如:类名为UserModel =》user_model

    # 指定表名
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(32), unique=True)
    email = db.Column(db.String(64), unique=True)

6.创建表、删除表

# 创建表,若是sqlite没有数据库时会自动创建,若是MySQL会报错
@app.route('/')
def index():
    # 删除表
    db.drop_all()
    # 创建表
    db.create_all()

    return 'Nice day!'

7.添加命令行删除表和创建表

from flask_script import prompt_bool

# 创建表python model.py createall
@manager.command
def createall():
    db.drop_all()
    db.create_all()
    return '数据表已创建'

# 删除表python model.py dropall
@manager.command
def dropall():
    if prompt_bool('确定要删库跑路吗?'):
        db.drop_all()
        return '数据表已删除'
    return '还是在考虑一下吧'

创建表:python model.py createall
删除表:python model.py dropall

8.自定义终端shell命令

原因是:系统默认有一个shell命令,启动后可以进行终端测试,但是没有导入任何数据,因此需要自己定制。

# 定制shell
def shell_context():
    # 返回在shell环境中需要的数据,以字典的形式返回
    return dict(db=db, User=User)
manager.add_command('shell',Shell(make_context=shell_context))

数据的CURD操作

1.增加数据

# 在请求结束后,自动提交数据库操作(执行commit),否则每次都要手动commit
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

# 添加数据
@app.route('/insert/')
def insert():
    # 创建数据模型
    # zhangsan = User(username='zhangsan', password='12345', sex=False, email='[email protected]')
    # lisi = User(username='lisi', password='12345', sex=False, email='[email protected]')
    # mazi = User(username='mazi', password='12345', sex=False, email='[email protected]')
    xjt = User(username='xjt', password='12345', sex=False, email='[email protected]')

    # 添加到会话中
    db.session.add(xjt)
    # 添加多个
    # db.session.add_all([zhangsan, lisi, mazi])

    # 提交
    db.session.commit()  # 如果设置了SQLALCHEMY_COMMIT_ON_TEARDOWN就不用手动commit了
    return '已经添加'

2.查询数据

# 根据主键查询数据
@app.route('/select/<uid>')
def select(uid):
    # 根据主键进行查询,如果有,返回User对象,没有None
    u = User.query.get(uid)
    if u:
        return u.username
    return '查无此人'

3.更新数据

# 修改(更新)数据
@app.route('/update/<uid>')
def update(uid):
    u = User.query.get(uid)
    if u:
        u.username = u.username + 'Copy'
        # 更新没有单独的函数,当添加的对象有id时被认为时更新操作
        db.session.add(u)
        # db.session.commit()
        return '修改完成'
    return '查无此人'

4.删除数据

# 删除数据
@app.route('/delete/<uid>')
def delete(uid):
    u = User.query.get(uid)
    if u:
        db.session.delete(u)
        return '删除成功'
    return '查无此人'

真是的项目很少用到物理删除(彻底从磁盘删掉),大多数都是使用逻辑删除(打标记)

5.各种查询

# 各种查询
@app.route('/selectby/')
def select_by():
    # 根据主键进行查询
    # u = User.query.get(2)
    # return u.username

    # 查询所有满足条件的
    # users = User.query.all()
    # return ','.join([u.username for u in users])

    # 指定过滤条件(只能是等值条件)
    # u = User.query.filter_by(username='Dandan').first()
    # return u.username

    # 指定过滤条件(可以是等值条件)
    # u = User.query.filter(User.id > 2).first()
    # u = User.query.filter(User.id == 2).first()
    # return u.username

    # 有就返回,没有报404
    # u = User.query.get_or_404(8)
    # u = User.query.filter(User.id > 8).first_or_404()
    # return u.username

    # 数据统计
    total = User.query.count()
    return str(total)

自行测试:limit, offset, order_by, paginate

模型设计参考

1.常见的字段类型

未分类

2.常见选项

未分类

3.总结

1.插入数据时,可以不传值的情况:自增的主键、有默认值、可以为空
2.flask-sqlalchemy:要求每个模型都有一个主键,通常为id

数据库的迁移

1.说明

​当数据模型更改时,需要将更改应用到数据库,这个过程叫数据库迁移。

​直接删除,然后再创建太过于简单粗暴,副作用大(原来的数据全部丢失)

​更好的解决方案是:既能将修改应用到数据库,又不删除原来的数据,若自己不会,可以采用flask-migrate扩展库.

2.安装

pip install flask-migrate

3.配置

from flask_migrate import Migrate, MigrateCommand

# 创建对象
migrate = Migrate(app, db)

# 添加终端命令
manager.add_command('db', MigrateCommand)

4.使用

1.初始化数据库迁移的仓库,执行一次就行了
    python manage.py db init
2.创建迁移脚本,会根据数据模型与数据库的表的差异生成sql语句
    python manage.py db migrate
3.执行迁移,就是执行上面生成的sql语句
    python manage.py upgrade
4.以后再迁移,只需2、3两步即可

Flask学习4:文件上传与邮件发送

文件上传与邮件发送

原生上传文件

1.添加一个模板文件html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
    {% if img_url %}
        <img src="{{ img_url }}">
    {% endif %}
    <form method="post" enctype="multipart/form-data">
        <input type="file" name="photo">
        <input type="submit" value="上传">
    </form>
</body>
</html>

2.添加视图函数

@app.route('/upload/', methods=['GET', 'POST'])
def upload():
    img_url = None
    if request.method == 'POST':
        photo = request.files.get('photo')
        if photo and allowed_file(photo.filename):
            # 获取文件后缀
            suffix = os.path.splitext(photo.filename)[-1]

            # 随机生成文件名
            photoname = random_string() + suffix
            # 保存上传的文件
            photo.save(os.path.join(app.config['UPLOAD_FOLDER'], photoname))

            img_url = url_for('uploaded', filename=photoname)
    return render_template('upload.html', img_url = img_url)

3.相关配置及函数

# 允许上传的文件后缀
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])

# 配置上传文件的保存位置
app.config['UPLOAD_FOLDER'] = os.getcwd()

# 限制上传文件大小
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 8

# 判断是否是允许的文件后缀
def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

# 获取上传文件的url
@app.route('/uploaded/<filename>')
def uploaded(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

# 生成随机字符串
def random_string(length = 32):
    import random
    base_str = 'abcdefghijklmnopqrstuvwxyz1234567890'
    return ''.join([random.choice(base_str) for i in range(length)])

4.注意事项,文件上传失败时,应从哪些方面着手

1.表单的提交方法必须是POST
2.表单的enctype属性必须设置(multipart/form-data)
3.上传的字段类型必须为file, 并且必须有name属性
4.是否超过了允许的最大尺寸
5.文件的保存位置是否还有空间,是否有权限

5.生成缩略图(需要安装Pillow库)

# 保存上传的文件
photo.save(pathname)

# 生成缩略图
# 1.打开文件
img = Image.open(pathname)
# 2.重新设置尺寸
img.thumbnail((128, 128))  # 参数为元组,指定宽高
# 3.保存修改
img.save(pathname)

扩展

环境变量:好处是可以避免隐私信息公布于众(设置时注意等号两边不要加空格)。

windows配置

设置:set NAME=dandan
获取:set NAME

linux配置

导出:export NAME=dandan
获取:echo $NAME

代码获取

@app.route('/env/')
def env():
   return os.environ.get('NAME', 'Ahh')

flask-uploads

1.说明

​在文件上传时,提供了很大的方便,比如文件类型的过滤、校验等

2.安装

pip install flask-upload

3.使用

from flask_uploads import UploadSet, IMAGES, configure_uploads, patch_request_class


# 上传文件的大小

app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 8

# 上传文件的保存位置

import os
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd()


# 创建上传对象, 需要指定过滤的文件后缀

photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)


# 配置上传文件大小, 默认64M,设置为None时使用MAX_CONTENT_LENGTH的大小

patch_request_class(app, size=None)


@app.route('/upload/', methods=['GET', 'POST'])
def upload():
   img_url = None
   if request.method == 'POST' and 'photo' in request.files:
       # 保存文件
       filename = photos.save(request.files['photo'])

       # 获取保存文件的url
       img_url = photos.url(filename)
   return render_template('upload.html', img_url = img_url)





# html

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>文件上传</title>
</head>
<body>
   {% if img_url %}
       <img src="{{ img_url }}">
   {% endif %}
   <form method="post" enctype="multipart/form-data">
       <input type="file" name="photo">
       <input type="submit" value="上传">
   </form>
</body>
</html>

完整的文件上传

1.flask-uploads配置同上

2.flask-wtf配置

# 导入表单基类

from flask_wtf import FlaskForm

# 导入文件上传字段及验证器

from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms import SubmitField


# 上传文件表单类

class UploadForm(FlaskForm):
   photo = FileField('头像上传', validators=[FileRequired('文件未选择'), FileAllowed(photos, message='文件类型错误')])
   submit = SubmitField('上传')

3.视图函数

# 随机生成文件名

def random_string(length = 32):
   import random
   base_str = '1234567890qwertyuiopasdfghjklmnbvcxz'
   return ''.join([random.choice(base_str) for i in range(length)])

@app.route('/upload/', methods=['GET', 'POST'])
def upload():
   img_url = None
   form = UploadForm()
   if form.validate_on_submit():
       # 获取文件后缀
       suffix = os.path.splitext(form.photo.data.filename)[1]
       filename = random_string() + suffix

       # 保存上传文件
       photos.save(form.photo.data, name=filename)

       # 生成缩略图
       pathname = os.path.join(app.config['UPLOADED_PHOTOS_DEST'], filename)
       # 打开文件
       img = Image.open(pathname)

       # 设置尺寸
       img.thumbnail((128, 128))

       img.save(pathname)

       img_url = photos.url(filename)
   return render_template('upload2.html', form=form, img_url=img_url)

4.模板文件

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

{% block content %}
   <div class="container">
       {% if img_url %}
           <img src="{{ img_url }}">
       {% endif %}

       {{ wtf.quick_form(form) }}
   </div>
{% endblock %}

flask-mail

1.说明

​是一个邮件发送的扩展库,使用非常方便

2.安装

pip install flask-mail

3.配置

from flask_mail import Mail, Message


# 配置一定要写在创建Mail对象之前,否则不起作用


# 邮箱服务器

import os
app.config['MAIL_SERVER'] = os.environ.get('MAIL_SERVER', 'smtp.163.com')

# 用户名

app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME', '[email protected]')

# 密码,密码有时是授权码

app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD', 'xxxxxx')


# 创建对象

mail = Mail(app)

4.发送邮件

@app.route('/')
def hello_world():
   # 创建邮件对象
   msg = Message(subject='丹丹的测试邮件', recipients=['[email protected]'], sender=app.config['MAIL_USERNAME'])
   # 浏览器打开显示这个
   msg.html = '<h1>Nice day</h1>'
   # 终端打开显示这个
   msg.body = 'Body'
   mail.send(msg)
   return '邮件已发送!'

5.封装函数,发送邮件

# 封装函数,发送邮件

def send_mail(to, subject, template, **kwargs):
   # 创建邮件对象
   msg = Message(subject=subject, recipients=to, sender=app.config['MAIL_USERNAME'])
   # 浏览器打开显示这个
   msg.html = '<b>Nice day</b>'
   # 终端打开显示这个
   msg.body = 'Nice day'
   mail.send(msg)

6.异步发送邮件

# 异步发送邮件

def async_send_mail(app, msg):
   # 发送邮件需要程序上下文,新的线程中没有上下文,需要手动创建
   with app.app_context():
       # 发送邮件
       mail.send(msg)



# 封装函数,发送邮件

def send_mail(to, subject, template, **kwargs):
   # 根据current_app获取当前实例
   app = current_app._get_current_object()
   # 创建邮件对象
   msg = Message(subject=subject, recipients=to, sender=app.config['MAIL_USERNAME'])
   # 浏览器打开显示这个
   msg.html = render_template(template + '.html', **kwargs)
   # 终端打开显示这个
   msg.body = render_template(template + '.txt', **kwargs)
   # mail.send(msg)

   # 创建线程
   thr = Thread(target=async_send_mail, args=[current_app, msg])
   # 启动线程
   thr.start()
   return thr

Flask学习3:表单

flask-表单

原生表单

1.添加模板文件login.html

<form method="post" action="{{ url_for('check') }}">
    用户名:<input type="text" name="username">
    <input type="submit" value="登陆">
</form>

2.视图函数

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

# 校验
@app.route('/check/', methods=['GET', 'POST'])
def check():
    return '登陆成功, %s~' % request.form['username']

3.将登陆页面和校验的路由合并

@app.route('/login/', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    return '登陆成功, %s~' % request.form['username']

一个路由处理时,表单的action属性不用书写

flask-wtf

1.说明

​是一个用于表单处理的扩展库,提供了CSRF、校验等功能,使用非常方便。

2.安装

pip install flask-wtf

3.使用

# 原生渲染
# 导入表单基类
from flask_wtf import FlaskForm
# 导入相关类型
from wtforms import StringField, SubmitField
# 导入验证器
from wtforms.validators import DataRequired

# CSRF需要
app.config['SECRET_KEY'] = 'idandan'

# 定义表单类
class NameForm(FlaskForm):
    name = StringField('账号:', validators=[DataRequired()])
    submit = SubmitField('登陆')


@app.route('/formTest/')
def formTest():
    # 创建表单对象
    form = NameForm()
    return render_template('formTest.html', form = form)


# html
<form>
    {{ form.hidden_tag() }}
    {{ form.name .label()}} {{ form.name(id = 'username', class  = 'username') }}
    {{ form.submit }}
</form>



# bootstrap渲染
# 设置bootstrap本地库中的文件
app.config['BOOTSTRAP_SERVER_LOCAL'] = True

# html
{% extends 'bootstrap/base.html' %}
{# 导入 #}
{% import 'bootstrap/wtf.html' as wtf %}

{% block title %}
    表单渲染
{% endblock %}

{% block content %}
    <div class="container">
        {{ wtf.quick_form(form) }}
    </div>
{% endblock %}

4.表单校验

@app.route('/formTest/', methods=['GET', 'POST'])
def formTest():
    # 创建表单对象
    form = NameForm()
    # return render_template('formTest.html', form = form)
    # return render_template('bootForm.html', form = form)

    # 表单校验
    name = None
    if form.validate_on_submit():
        name = form.name.data
        form.name.data = ''
    return render_template('bootForm.html', form = form, name = name)


# html
{% extends 'bootstrap/base.html' %}

{# 导入 #}
{% import 'bootstrap/wtf.html' as wtf %}

{% block title %}
    表单渲染
{% endblock %}

{% block content %}
    <h1>
        Hi,
        {% if name %}
            {{ name }}!
        {% else %}
            Stranger!
        {% endif %}
    </h1>

    <div class="container">
        {{ wtf.quick_form(form) }}
    </div>
{% endblock %}

5.POST重定向GET

@app.route('/formTest/', methods=['GET', 'POST'])
def formTest():
    # 创建表单对象
    form = NameForm()

    # 表单校验
    name = None
    if form.validate_on_submit():
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('formTest'))
    name = session.get('name')
    return render_template('bootForm.html', form = form, name = name)

6.常见的字段类型

未分类

7.常见验证器类

未分类

8.自定义字段验证

# 定义表单类
class NameForm(FlaskForm):
    name = StringField('账号:', validators=[DataRequired()])
    submit = SubmitField('登陆')

    # 自定义字段验证函数,格式是写一个‘valudate_字段名’函数
    def validate_name(self, field):
        if len(field.data) < 6:
            raise ValidationError('账号长度不能小于6个字符')

flash消息

1.说明

​ 当用户请求发出后,用户状态发生了改变,需要给出提示、警告等信息时,通常可以通过弹窗的形式给出指示,用户可以跟据提示进行下一步操作,也可手动取消显示。

2.使用

​在合适的时候书写flash消息,调用flash函数

@app.route('/formTest/', methods=['GET', 'POST'])
def formTest():
    # 创建表单对象
    form = NameForm()
    # return render_template('formTest.html', form = form)
    # return render_template('bootForm.html', form = form)

    # 表单校验
    name = None
    if form.validate_on_submit():
        oldName = session.get('name')
        if oldName is not None and oldName != form.name.data:
            flash('Change your name!')
        session['name'] = form.name.data
        return redirect(url_for('formTest'))
    name = session.get('name')
    return render_template('bootForm.html', form = form, name = name)


# html
{% for message in get_flashed_messages() %}
    <h3>{{ message }}</h3>
{% endfor %}

若好多页面都有弹出消息,可以将flash消息放在基础模板中展示。

flask-moment

1.说明

​专门负责时间本地化显示的扩展库,使用非常方便 。

2.安装

pip install flask-moment

3.使用

# 导入类库
from flask_moment import Moment
# 创建对象
moment = Moment(app)

from datetime import datetime, timedelta
@app.route('/moment/')
def moment():
    current_time = datetime.utcnow() + timedelta(seconds=-3600)  # 伪造时间
    return render_template('moment.html', current_time=current_time)

# HTML
{#{% extends 'bootstrap/base.html' %}#}
{% block content %}
    <div class="time">
        <h1>时间本地化显示</h1>
        <h3>距离:{{ moment(current_time).fromNow() }}</h3>
        <h3>时间:{{ moment(current_time).format('LLLL') }}</h3>
    </div>
{% endblock %}

{#加载jquery和moment,因为moment.js需要依赖,使用bootstrap时就不需要再加载jquery#}
{{ moment.include_jquery() }}
{{ moment.include_moment() }}

Flask学习2:模板引擎

模板引擎

模板引擎

说明:

模板文件就是按照特定规则书写的一个负责展示效果的html文件;模板引擎就是提供了特定规则的解释和替换的功能。

Jinja2:

在Flask中使用的就是该模板引擎,由Flask核心开发组人员开发。

jinja2使用

1.准备工作,目录结构

project/
manage.py  # 项目的启动控制文件
    templates/  # 所有的模板文件

2.渲染模板文件

在templates下创建一个模板文件(hello.html),内容如下:

<h1>渲染字符串</h1>

​模板渲染

@app.route('/')
def index():
    # return '模板引擎测试'

    # 渲染模板文件
    # return render_template('hello.html')

    # 渲染字符串
    return render_template_string('<h1>渲染字符串</h1>')

3.使用变量

​在templates下创建一个模板文件(hello.html),内容如下:

<h1>Nice,{{ g.name }}</h1>


模板渲染

@app.route('/var/')
def va():
    # g对象,在模板使用中不需要分配
    g.name = 'Nian'
    # return render_template('hello.html', name='dandan')
    # return render_template_string('<h1>{{ name }}</h1>', name='dandan')
    return render_template_string('<h1>{{ g.name }}</h1>')

4.使用函数

​在模板文件中可以使用特定的函数,对指定的变量处理后再显示,用法如下:

<h1>Nice,{{ g.name | upper }}</h1>

​常用函数

未分类

5.流程控制

​分支语句(if-else)

    {% if name %}
        <h1>{{ name }}</h1>
    {% else %}
        <h1>Stranger</h1>
    {% endif %}


@app.route('/control/')
def control():
    # return render_template('if-else.html', name = 'Nian')
    return render_template('if-else.html')

​循环语句

    <ol>
        {% for i in range(1, 5) %}
            <li>{{ i }}</li>
        {% endfor %}
    </ol>

6.文件包含

​include1.html

# include.html
<h1>Include 1</h1>

{% include 'include2.html' %}


# include2.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>被包含</title>
</head>
<body>
    include 2
</body>
</html>

7.宏的使用

    {# 定义宏 #}
    {% macro show_name(name) %}
        <h1>Hello, {{ name }}</h1>
    {% endmacro %}


    {# 调用宏 #}
    {{ show_name(name) }}


    # 宏的使用
    @app.route('/macro/')
    def macro():
        # return render_template('macro.html', name = 'Nian')
        return render_template('macro2.html', name = 'dandan')

  说明:
      采用类似Python中的函数的形式进行定义和调用,可以把特定功能的内容定义为一个宏,哪里使用哪里调用即可。

8.模板继承

# parents.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}父级标题{% endblock %}</title>
</head>
<body>
    {% block body %}
        默认内容
    {% endblock %}
</body>
</html>


# children.html
{% extends 'parents.html ' %}

{% block title %}
    丹丹测试
{% endblock %}

{% block body %}
    这是模板继承   这是=》{{ super() }}
{% endblock %}

flask-bootstrap

1.安装

pip install flask-bootstrap

2.使用:

from flask_bootstrap import Bootstrap

# 创建实例
app = Flask(__name__)
bootstrap = Bootstrap(app)

3.测试模板文件boot.html

{# 继承自bootstrap模板 #}
{% extends 'bootstrap/base.html' %}

{% block title %}
    用户注册
{% endblock %}

{% block content %}
    <div class="container">
        欢迎丹丹来注册
    </div>
{% endblock %}

4.bootstrap继承模板base.html中定义的block

未分类

使用bootstrap时,发现重写block后原来的显示效果消失,可能是忘了调用super

定义项目基础模板

1.说明:

一个项目中,很多页面都很相似,只有细微的差别,如果每个页面都定制,势必会有大量的重复代码。为了简化这种重复工作,通常我们会为项目定制一个基础模板,让它继承自bootstrap,其他页面继承自该基础模板,只需稍微定制即可。

2.步骤

1.从bootcss.com复制一个导航条
2.将container-fluid改为container
3.根据需要,定制显示内容

3.基础模板文件base.html

{% extends 'bootstrap/base.html' %}

{% block title %}
    丹丹的标题
{% endblock %}

{% block navbar %}
    <nav class="navbar navbar-inverse">
  <div class="container">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">首页</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></li>
          </ul>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
{% endblock %}


{# 定制内容 #}
{% block content %}
    <div class="container">
        {# 这里可以为弹出内容显示预留位置 #}
        {% block page_content %}
            默认内容
        {% endblock %}
    </div>
{% endblock %}

错误页面定制

1.添加视图函数

# 错误页面定制
@app.errorhandler(404)
def error(e):
    return render_template('404.html')

2.定制404.html

{% extends 'base.html' %}

{% block title %}
    出错了
{% endblock %}

{% block content %}
    <h1>找不见了吧@_@</h1>
{% endblock %}

回顾url_for函数

1.根据视图函数名反向构造路由地址,路由需要的直接构造,多出来的参数以GET方式 传递

2.若需要构造完整的外部链接,需要添加_extenal=True参数

3.通常网站中的点击链接都是url_for构造的

<li><a href="{{ url_for('register') }}">注册</a></li>

加载静态资源

1.flask框架中静态资源的默认目录为static,项目目录如下:

project/
    manage.py  # 启动控制文件
    static/  # 存放静态资源
    templates/  # 存放模板文件

2.加载网站图标

{# 加载收藏夹的图标 #}
{% block head %}
    {{ super }}
    <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename = 'bg.png') }}">
{% endblock %}

3.静态资源加载

# manage.py
# 加载静态文件
@app.route('/source/')
def source():
    return render_template('static.html')

# static.html
{% block styles %}
    {{ super() }}
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename = 'css.css') }}">
{% endblock %}

4.设置本地日期和时间

# base.html
{#引入moment.js类#}
{% block scripts %}
    {{ super() }}
    {{ moment.include_moment() }}
{% endblock %}



# manage.py
# 使用Flask-Monment本地化日期和时间
from datetime import datetime
@app.route('/testtime/')
def testtime():
    return render_template('testtime.html', current_time = datetime.utcnow())


# testtime.html
  {% block content %}
    <p>The local date and time is {{ moment(current_time).format('LLL') }}</p>
{% endblock %}

5.表单

# maange.py
from flask import Flask, render_template, session, redirect, url_for
from flask_script import Manager
from flask_bootstrap import Bootstrap
from wtforms import SubmitField, StringField, IntegerField
from wtforms.validators import Required
from flask_wtf import Form

# 实例化对象
app = Flask(__name__)
bootstrap = Bootstrap(app)
manager = Manager(app)
app.config['SECRET_KEY'] = 'idandan'

class PersonInfo(Form):
    name = StringField('Name: ', validators=[Required()])
    age = IntegerField('age: ', validators=[Required()])
    submit = SubmitField('Submit')

@app.route('/', methods=['GET', 'POST'])
def index():
    name = None
    age = None
    form = PersonInfo()

    if form.validate_on_submit():
        session['name'] = form.name.data
        session['age'] = form.age.data
        return redirect(url_for('index'))
        # name = form.name.data
        # age = form.age.data
        # form.name.data = ''
        # form.age.data = ''  # ? 不能清空数据???
    # return render_template('personInfo.html', form = form, name = session.get('name'), age = session.get('age'))
    return render_template('personInfo.html', form = form)

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



# personInfo.html
{% import 'bootstrap/wtf.html' as wtf %}

{% block title %}
    Form
{% endblock %}

{% block content %}
{#    <h1>#}
{#        Nice,#}
{#        {% if name %}#}
{#            {{ name }}, your age is {{ age }}?#}
{#        {% else %}#}
{#            Stranger!#}
{#        {% endif %}#}
{#    </h1>#}

    <h1>
        Nice,
        {% if session.get('name') %}
            {{ session.get('name') }}, your age is {{ session.get('age') }}?
        {% else %}
            Stranger!
        {% endif %}
    </h1>

    {{ wtf.quick_form(form) }}

{% endblock %}