Flask 源码解读(一)

其中Flask用于处理Web应用的路由,request,response等模块的核心是werkzeug.

werkzeug 提供了 python web WSGI 开发相关的功能:

  • 路由处理:怎么根据请求中的 url 找到它的处理函数
  • request 和 response 封装:可以更好地读取 request 的数据,也容易生成响应
  • 一个自带的 WSGI server,可以用来测试环境运行自己的应用

比如,我们可以使用 werkzeug 编写一个简单的 hello world 的 WSGI app:

from werkzeug.wrappers import Request,Response

def application(environ,start_response):
    request = Request(environ)
    text = 'Hello %s' % request.args.get('name','World')
    response = Response(text,mimetype='text/plain')
    return response(environ,start_response)

除了和 web WSGI 相关的功能,werkzeug 还实现了很多非常有用的数据结构和函数。比如用来处理一个 key 对应多个值的 MultiDict,不支持修改的字典 ImmutableDict ,可以缓存类属性的 cache_property 等等。

第一个开源到Github上的Web App cm-Todo

cm-Todo

这是一款基于-Flask-Canvas-Mysql-Python3-Bootstrap-的TODO/记事本/交流 的Web App应用

Github地址: 点击=> https://github.com/834930269/cm-Todo

其中记事本框的CSS和部分JS来自于 点击=> youknowznm-vue-memo

本地使用方法:

先在config.py中的:

DATABASE_USERNAME=
DATABASE_PASSWORD=

中输入你Mysql数据库的用户名和密码,并自行创建一个名为notebook的数据库.
(这里数据库自行选择,不同数据库的接口配置在config.py中进行)

然后在当前目录下的命令行中执行以下命令:

python manage.py db init
python manage.py db migrate -m ‘first’
python manage.py db upgrade

最后在命令行中执行manage.py并通过localhost:5000进行访问即可.
python manage.py runserver

注意,是python3.*,如果您是Linux用户,可能会需要修改以下Mysql数据库的所有编码方式为utf-8.

该App可以轻松集成到到您的Flask Web App中,至于如何集成,这里不再赘述.

Demo: 点击=> 地平线上的一匹狼-日志

关于使用ajax(JavaScript)+Flask(Python)+Canvas 上传画布(Canvas)图片并存储

因为想要做一个todo list的记事本app.其中有一个模块是使用Canvas做涂鸦.
其中有两个操作:

1.然后上传canvas的画布图片并保存到本地.

2.将对应画布的url传给画布,并绘制出来.

首先,Canvas画板的css和js是在Github上淘的一个,链接如下:

https://github.com/youknowznm/paint<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>paint</title> <link rel="stylesheet" href="./paint.css"> </head> <body> <div class="canvas-wrapper"> <ul class="doodle-colors"> <li data-color="black"></li> <li data-color="green"></li> <li data-color="yellow"></li> <li data-color="red"></li> <li data-color="white"></li> </ul> <ul class="doodle-controllers"> <li class="undo"></li> <li class="redo"></li> <li class="clear"></li> </ul> <canvas id="cv" class="doodle-content" width='500' height='500'></canvas> </div> <script src="https://cdn.jsdelivr.net/jquery/3.1.1/jquery.min.js"></script> <script src="./paint.js"></script> <script> window.onload = function() { let canvasEle = ('.doodle-content')[0]; let colorsEle = ('.doodle-colors')[0]; let controllersEle = $('.doodle-controllers')[0]; initCanvas(canvasEle, colorsEle, controllersEle, null); } function oc(){ let cav=document.getElementById('cv'); console.log(cav); let data=cav.toDataURL(); console.log(data); let img = document.createElement('img'); img.src = data; document.body.appendChild(img); let image=cav.toDataURL('image/jpeg'); window.location=image; } </script> </body> </html>

注意,我在代码中添加了一个JavaScript函数oc():

        function oc(){
            let cav=document.getElementById('cv');
            console.log(cav);
            let data=cav.toDataURL();
            console.log(data);
            let img = document.createElement('img');
            img.src = data;
            document.body.appendChild(img);
            let image=cav.toDataURL('image/jpeg');
            window.location=image;
        }

其中cav.toDataURL('type')即可以实现提取当前Canvas中的图片流信息.
然后我们尝试向后台发送Canvas图片的用base64加密的数据.
使用一个Button来发送post信息.
添加<button id="btn" >123</button>
button添加click事件:

(function(){     ('#btn').click(function(){
        let cav=document.getElementById('cv');
        let image=cav.toDataURL('image/jpeg');
        .post('/upload',{'data':image,'idx':223},function(result){             let canvasEle = ('.doodle-content')[0];
            let colorsEle = ('.doodle-colors')[0];             let controllersEle = ('.doodle-controllers')[0];
            initCanvas(canvasEle, colorsEle, controllersEle, result);
        })
    })
})

其中表单中data为图片加密信息,idx为假想当前记事本的id.
实践.发送post内容如下:

注意这里只是介绍了前台代码,让大家看一下发送的表单数据,如果没编写后台接受Post的Route的话是会返回404的.

然后在后台的View视图中添加相应的Route:

from . import main
from flask import Flask,render_template
from flask import make_response,render_template, session, redirect, url_for, current_app,abort,flash,request
import json,os,base64  

@main.route('/upload',methods=['POST'])
def upLoad():
    """
    disturbing by jq
    this method cannot work
    """
    files=request.values.get('data')
    f=request.form['data'][23:]
    ind=request.form['idx']
    imgdata=base64.b64decode(f)
    file=open('app/static/'+ind+'.jpg','wb')
    file.write(imgdata)
    file.close()
    return '/static/'+ind+'.jpg'

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

其中用request.values.get('key')来获取对应表单的数据.然后用base64算法对获取到的数据进行解码.之后创建一个空文件idx.jpg,将解码后的数据写进去.得到的即为图片.

我们可以看到获取到的data的头有一段非编码部分:

data:image/jpeg;base64,/9j/4A

所以我们需要将前面不属于编码的部分去掉.
后台处理代码中:

    f=request.form['data'][23:]

即完成了这一任务.

注意在JavaScript用这种方式获取Canvas的时候,开启管道时背景如果是透明,它会在转换成base64时自动补成#fff(纯黑色).
所以我们需要在创建Canvas时将背景染成纯白色#000.
我们在Paint.js中初始化context中加入下面两句:

    // 初始化 context
    var ctx = canvasEle.getContext('2d');
    ctx.fillStyle="#fff";
    ctx.fillRect(0, 0, 500, 500);

OK,我们获取图片后即为原图了!

至于如何在Canvas中将图片画出来.直接用paint.js中的 initCanvas(canvasEle, colorsEle, controllersEle, result);
即可.result为对应图片的地址一般是发布网站的相对地址.
上传结果:

最后一个问题:
当我们试图读取图片的url时,浏览器多半会返回对应url的缓存图片文件,如何解决这个问题呢?
我们只需要在URL中随便加入一个参数即可,保证每次的url不相同即可,比如:

URL=URL+'?t='+Math.random();

OK,日志本的第一个难题攻关!

Python Flask 富文本添加支持格式的以及Attributes的方法

通过设置attributestags如:

    #将Markdown转换为HTML
    @staticmethod
    def on_change_body(target,value,oldvalue,initiator):
        attrs = {
            '*': ['class','style','width','height'],
            'a': ['href', 'rel'],
             'img': ['src', 'alt'],
        }
        #允许的标签类型
        allowed_tags = ['a','abbr','acronym','b','blockquote','code',
                        'em','i','li','ol','pre','strong','ul',
                        'h1','h2','h3','p','img']
        target.body_html = bleach.linkify(bleach.clean(
            markdown(value,output_format='html'),
            tags=allowed_tags, attributes=attrs,strip=True))

最近先把这个博客做好…
之后再看情况写部分重点= =.
最近眼有点难受.

Python Flask 番外 1 可以随鼠标移动的背景图片

通过和大佬的交流学会了一点黑科技…
1.因为body的高度和内容挂钩的,所以鼠标可以到达的body区域只有body的高度而已,如果内容高度没填满整个屏幕的话,就会出现下面有一部分无法响应鼠标移动事件,自然无法更新背景图片的position.
然后一个老司机告诉我:想要简单粗暴一点.可以用(css3):

height:100vh

卧槽…
2.响应函数:

// 一个小小的视觉效果
('body').on('mousemove', (evt) => {   ('body')
    .css('background-position-x', Math.ceil(evt.pageX / 40))
    .css('background-position-y', Math.ceil(evt.pageY / 40));
});
window.ondeviceorientation = (evt) => {
  $('body')
    .css('background-position-x', evt.gamma)
    .css('background-position-y', evt.beta);
};

Python Flask 10 用户上传头像

借助上节的基础==>

2.

3.

4.

开始!

1:

因为我们需要对每个用户存下他的头像信息.
这里我们选择直接存储图片url的形式进行存储.
所以我们需要对model层User添加一个gravatar列,存储头像url.
主要代码如下:

class User(UserMixin,db.Model):
    __tablename__ = 'users'
    gravatar = db.Column(db.String(200),default='/_uploads/photos/default.jpg')

其中gravatar的默认值是默认头像相对url.

2:

首先我们依然需要先码出上传头像界面的表单(form).

先配置所要上传的目标地址变量/config.py:

import os
UPLOADED_PHOTOS_DEST = os.path.abspath(os.path.join(os.getcwd(),"app/static/Gravatar"))

我们需要对上传文件进行约束为图片格式,这里我们用一个UploadSet模块,编辑代码/app/__init__.py:

from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class

photos = UploadSet('photos',IMAGES)

然后将创建好的photo对象加入已经创建好的app中:

configure_uploads(app,photos)
patch_request_class(app)  

然后编写form表单:

from flask_wtf.file import FileField, FileRequired, FileAllowed
from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class
from .. import photos
from flask_wtf import FlaskForm

class UploadForm(FlaskForm):
    photo = FileField(validators=[
        FileAllowed(photos, u'只能上传图片!'), 
        FileRequired(u'请选择一个头像吧!')])
    submit = SubmitField(u'确认上传',render_kw={"class":"btn btn-primary"})

其中render_kw是一个dict类型,作用是渲染对应表单的css.
FileAllowed用来检测上传文件类型,不对的的话返回第二个参数.
FileRequiredRequired作用近似,即非空判断.

3:

接下来我们就要开始编写view层了.
这里我们将编辑头像页面的url设为/edit-gravatar
所以我们需要码一个有GETPOST的视图函数.

from flask_login import  current_user
from flask_login import login_user,login_required,logout_user
from .forms import UploadForm
from flask import render_template, session, redirect, url_for, current_app,abort,flash
from .. import db,photos

@main.route('/edit-gravatar',methods=['GET','POST'])
@login_required
def edit_gravatar():
    form = UploadForm()
    if form.validate_on_submit():
        filename = photos.save(form.photo.data)
        file_url = photos.url(filename)
        current_user.gravatar = file_url
        db.session.add(current_user)
        flash('修改成功!')
        return redirect(url_for('.user',username=current_user.username,file_url=file_url))

    return render_template('edit_gravatar.html',form=form,file_url=current_user.gravatar)

注意我们所上传的头像文件即使名字相同也不会覆盖掉之前的头像文件的..(所以还是以二进制存到数据库中更好一点么).

4:

接下来我们需要编写前台界面了:
首先,由于如果我们直接用quick_form创建表单的话,表单的css就不好改了.所以我们把每个表单分开放入form type=file中,并且通过传参或者重叠覆盖修改表单控件的样式.

其中,修改input type=file样式的通用方法是将opacity设为0,然后用一个a标签将input和a组合到一起,这样修改a的样式便相当于修改input的样式.
对应的几部分代码如下:

/*CSS*/
.upload{
    padding: 5px 10px;
    height: 40px;
    line-height: 30px;
    position: relative;
    border: 1px solid #999;
    text-decoration: none;
    text-align:center;
    color: #fff;
}
.change{
    position: absolute;
    overflow: hidden;
    right: 0;
    top: 0;
    opacity: 0;
}
<a class="btn btn-success upload" placeholder="上传头像" >
    <p id="show_gt">选择您要上传的头像~</p>
    {{ form.photo(class="change",placeholder="上传头像",onchange="c()") }}
</a>

然后在css中修改upload样式即可.
其中btn btn-xxxbootstrap样式.

但是到这里还需要考虑一个问题,就是当选中图片以后,并不会显示到页面上,必选点击提交以后才会修改,下次访问这个页面才会变成新图片.
所以我们需要一种方法使选中图片的同时将图片显示在页面上,并和之前的头像对比.
这里我们使用直接通过JavaScript在本地显示:
对应JS代码如下:

<script type="text/javascript">
    function c () {
        var gt=document.getElementById('show_gt');
        gt.innerText = '已选中图片~'
        var r= new FileReader();
        f=document.getElementById('photo').files[0];
        r.readAsDataURL(f);
        r.onload=function  (e) {
            document.getElementById('show').src=this.result;
        };
    }
</script>

因为FlaskForm生成的表单的id和类中对应表单实例的名字是一样的,所以这里的id是photo.
获取图片对象(object) f.
把这个File对象传给FileReader对象的读取方法,就能读取文件了。

连接: <!-- edit_gravatar.html --> {% extends "base.html" %} {% import "bootstrap/wtf.html" as wtf %} {% block title %}编辑个人头像{% endblock %} {% block page_content %} <form method="POST" enctype="multipart/form-data"> {{ form.hidden_tag() }} <div class="page-header"><h2>展览板~</h2></div> <a class="btn btn-success upload" placeholder="上传头像" ><p id="show_gt">选择您要上传的头像~</p> {{ form.photo(class="change",placeholder="上传头像",onchange="c()") }} </a><br/> {% for error in form.photo.errors %} <span style="color: red;">{{ error }}</span> {% endfor %} {% if file_url %} <br><img src="{{ file_url }}" width="256" height="256"> {% endif %} <a class="btn btn-success"><-之前 之后-></a> <img src="{{ file_url }}" id='show' width="256" height="256"><br/><br/> {{ form.submit }} </form> <script type="text/javascript"> function c () { var gt=document.getElementById('show_gt'); gt.innerText = '已选中图片~' var r= new FileReader(); f=document.getElementById('photo').files[0]; r.readAsDataURL(f); r.onload=function (e) { document.getElementById('show').src=this.result; }; } </script> {% endblock %}

Python Flask 10.0.1上传文件

1. os模块:

#获取指定文件目录
os.path.abspath(os.path.join(os.getcwd(),"static/Gravatar"))

2.``Flask_wtf.file包:里面放着各种处理文件上传的处理表单类.

3.获取当前文件的url:

if filename is None:
    fn='default.jpg'
else:
    fn=filename
fn = os.path.abspath(os.path.join(app.config['UPLOADED_PHOTOS_DEST'],fn))

代码:

#app.py
# -*- coding: utf-8 -*-
from flask import Flask, render_template
from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms import SubmitField
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = 'I have a dream'
app.config['UPLOADED_PHOTOS_DEST'] = os.path.abspath(os.path.join(os.getcwd(),"static/Gravatar"))

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

#创建Form
class UploadForm(FlaskForm):
    photo = FileField(validators=[FileAllowed(photos,u'只能上传图片哦!'),
                                  FileRequired(u'文件为选择!')])
    submit = SubmitField(u'上传')

@app.route('/',methods=['GET','POST'])
def upload_file():
    form = UploadForm()
    if form.validate_on_submit():
        filename = photos.save(form.photo.data)
        file_url = photos.url(filename)
    else:
        file_url = None
        filename = None
    if filename is None:
        fn='default.jpg'
    else:
        fn=filename
    fn = os.path.abspath(os.path.join(app.config['UPLOADED_PHOTOS_DEST'],fn))
    return render_template('index.html',form=form,file_url=file_url,filename=fn)

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

前台-app/templates:

<!DOCTYPE html>
<title>Upload File</title>
<h1>图片上传</h1>
<form method="POST" enctype="multipart/form-data">
     {{ form.hidden_tag() }}
     {{ form.photo }}
     {% for error in form.photo.errors %}
         <span style="color: red;">{{ error }}</span>
     {% endfor %}
     {{ form.submit }}
</form>
{{ filename }}
{% if file_url %}
<br><img src="{{ file_url }}">
{% endif %}

Python Flask 7 博客架构一: 登录注册模块

1.flask_login模块中,views层判断是否登录用current_user.is_authenticated,但是,flask-login2.x是current_user.is_authenticated(),flask-login3.x是current_user.is_authenticated
即:

<ul class="nav navbar-nav navbar-right">
    {% if current_user.is_authenticated %}
        <li><a href="{{ url_for('auth.logout') }}">Sign Out</a></li>
    {% else %}
        <li><a href="{{ url_for('auth.login') }}">Sign In</a></li>
    {% endif %}
</ul>

2.SQLAlchemy 中, add() 操作之后数据成为pending 状态,此时数据不会立即写入到数据库中。
当你执行 query() 的时候,它会先把之前状态为 pending 的数据写入到数据库,并且更新当前 session 中存储的数据,然后再执行 query()

3.如果出现Can't locate revision identified by 'XXX',要删除一下数据库中的alembic_version表,保证版本不会互相影响以完成更新.

项目地址(Github):

Click Here: flask-大型网站架构2-注册和邮箱验证

4.看到了一个之前没太注意的地方:

这里是Flask-Login需要的自己实现的四个函数.可以用一个UserMixin解决.但需要自己实现一个回调函数


这就是代码里

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

这段代码的意义.

..有点累..躺尸一会,先不更新了.._(:3」∠)_我的床需要我