分类目录归档:计算机与编程

新发布的Flask1.0带来了哪些新变化?

就在五个小时前,Flask终于发布了8年来的第一个主版本——1.0,这个版本带来了很多新的变化,让我们拥抱变化吧,请使用下面的命令来更新:

$ pip install -U flask

主要的变化

  • 从0.11开始,Flask引入了命令行支持,建议使用flask run命令来取代app.run()方法。1.0版本支持将Flask_APP环境变量设为包名称。Flask会自动在app.pywsgi.py寻找名称为appapplication的程序实例,同时会在传入的模块或包中寻找名称为create_app()make_app()的0参数的工厂函数。
  • 新添加了一个FLASK_ENV环境变量来设置Flask运行的环境,默认为production。在开发时需要设为development,这会自动开启调试器和重载器(即调试模式),避免直接使用FLASK_DEBUG
  • 当安装了python-dotenv时,flask命令(比如flask runflask shell等)会自动从项目根目录下的.flaskenv.env文件中导入环境变量,这样可以避免每次都手动设置环境变量。前者用来存储公开的Flask相关的环境变量,比如FLASK_APPFLASK_ENV等,后者用来存储包含敏感信息的环境变量,比如邮箱密码,API密钥等等,需要将文件名添加到.gitignore中。
  • 开发服务器默认开启多线程支持。
  • 日志系统被极大的简化,日志器总是命名为flask.app,它只会在没有日志处理器注册的情况下才添加处理器,而且不会移除已经存在的处理器。
  • 测试客户端(test_client)支持使用json参数来传入JSON数据,你可以使用这个特性来测试Web API。返回的响应对象添加了get_json方法来获取JSON数据。
  • 添加了一个 test_cli_runner() 方法,它可以用来触发使用Click注册的flask命令,我们可以使用它来测试自定义的flask命令。
  • 添加了一个flask routes命令,用来输出程序所有注册的路由。
  • 移除了旧的扩展导入代码flask.ext,还在使用旧的导入方式的扩展将无法使用。
  • 带来了一个重写后的Flaskr教程,包含了大量重构和改进,其他部分的文档也包含大量更新。
  • 和大多数项目一样,1.0版本不再支持Python2.6和3.3。

你可以访问Flask Changelog – Flask 1.0 documentation查看完整的Changelog,并了解每一个变化所对应的PR或Issue。

顺便说一句

正在写的Flask书将在五月初结束写作,不过距离上市还需要一小段时间(按照正常的出版流程,大概是40天~三个月区间内)。这本书的内容完全采用1.0版本的Flask,其他库也都使用最新版,包括Bootstrap等前端库。另外,专栏之前发布的文章相关的示例程序已经过时,请不要参考,可以阅读Flask文档中更新的Flaskr教程。如果不着急的话,可以等一等,新书会带来几个更完善的示例程序。

Flask-CKEditor:为Flask项目集成富文本编辑器

这篇文章属于“Flask常用扩展介绍系列”,这个系列的文章目录索引可以在《Flask常用扩展介绍系列文章索引》看到。
 

富文本编辑器即WYSIWYG(What You See Is What You Get)编辑器(所见即所得编辑器)。在Web程序中可用的开源富文本编辑器中,CKEditor是一个流行的选择。Flask-CKEditor简化了将CKEditor集成到Flask项目中的过程,可以让你方便的在Flask项目中添加富文本编辑器。它包含下面这些特性:

  • 提供WTForms/Flask-WTF集成支持
  • 支持图片上传与插入
  • 通过Flask配置来设置编辑器的语言、高度等参数
  • 支持代码块语法高亮

《Flask Web开发实战》中的第2个示例程序(博客程序Bluelog)使用了这个扩展。

基本用法

安装

首先使用pip或Pipenv等工具安装:

$ pip install flask-ckeditor

初始化扩展

一般情况下,你只需要导入并实例化CKEditor类,并传入程序实例即可:

from flask_ckeditor import CKEditor

app = Flask(__name__)
ckeditor = CKEditor(app)

如果你使用了工厂函数,那么也可以调用init_app()方法来进行初始化:

from flask_ckeditor import CKEditor

ckeditor = CKEditor()

def create_app():
    app = Flask(__name__)
    ckeditor.init_app(app)
    return app

引入CKEditor资源

为了使用CKEditor,我们首先要在模板中引入CKEditor的JavaScript等资源文件。推荐的做法是自己编写资源引用语句,你可以在CKEditor提供的Online Builder构建一个自定义的资源包,下载解压后放到项目的static目录下, 并引入资源包内的ckeditor.js文件,比如(实际路径按需调整):

<script src="{{ url_for('static', filename='ckeditor/ckeditor.js') }}"></script>

如果你不需要自定义,那么也可以从CDN加载:

<script src="//cdn.ckeditor.com/4.9.2/standard/ckeditor.js"></script>

最后,作为替代选项,你也可以使用Flask-CKEditor提供的ckeditor.load()方法来生成引用语句:

{{ ckeditor.load() }}

它默认从CDN加载资源,将配置变量CKEDITOR_SERVE_LOCAL设为True会使用扩展内置的本地资源。另外,你也可以使用custom_url参数来使用自定义资源包:

{{ ckeditor.load(custom_url=url_for('static', filename='ckeditor/ckeditor.js')) }}

创建CKEditor文本区域

Flask-CKEditor提供了两种方式来CKEditor文本区域:

1. 与WTForms/Flask-WTF集成

Flask-CKEditor提供了一个CKEditorField字段类,和你平时从WTForms导入的StringField、SubmitField用法相同。事实上,它就是对WTForms提供的TextAreaField进行了包装。

作为示例,我们可以创建一个写文章的表单类。这个表单类包含一个标题字段(StringField),一个正文字段(CKEditorField)和一个提交字段(SubmitField)。你会看到,其中的正文字段使用了CKEditorField。

from flask_wtf import FlaskForm
from flask_ckeditor import CKEditorField
from wtforms import StringField, SubmitField

class PostForm(FlaskForm):
    title = StringField('Title')
    body = CKEditorField('Body')
    submit = SubmitField('Submit')

 

在渲染文本编辑区域的模板中,我们可以像往常一样渲染表单:

<form method="post">
    {{ form.title.label }}{{ form.title() }}
    {{ form.body.label }}{{ form.body() }}
    {{ form.submit() }}
</form>

{{ ckeditor.load() }}
{{ ckeditor.config(name='body') }}

唯一需要注意的是,我们需要在资源引用语句后调用ckeditor.config()方法来让对CKEditor进行配置和初始化,并将name参数的值设为CKEditor字段的属性名,这里即body。

当表单提交后,你可以像其他字段一样通过form.attr.data属性来获取数据,这里的文本区域数据即form.body.data。

2. 手动创建

如果你不使用WTForms/Flask-WTF,那么可以直接使用Flask-CKEditor提供的ckeditor.create()方法在模板中创建文本编辑区域:

<form method="post">
    {{ ckeditor.create() }}
    <input type="submit">
</form>

{{ ckeditor.load() }}
{{ ckeditor.config() }}  <!-- 这时不用设置name参数 -->

在表单被提交后,你可以使用ckeditor作为键从表单数据中获取对应的值,即request.form.get('ckeditor')。

提示 完整的示例程序在examples/basic/examples/without-flask-wtf目录下。

配置变量

Flask-CKEditor提供了下面这些配置变量:

配置 默认值 说明
CKEDITOR_SERVE_LOCAL False 使用内置的ckeditor.load()方法时,设置是否使用本地资源,默认从CDN加载
CKEDITOR_PKG_TYPE 'standard' CKEditor资源包的类型,basicstandardfull中的一个
CKEDITOR_LANGUAGE None 设置CKEditor文本编辑器的语言,默认会自动探测用户浏览器语言,所以一般不需要设置。你也可以设置ISO 639格式的语言码,比如zhenjp等
CKEDITOR_HEIGHT CKEditor默认 编辑器高度,单位为px
CKEDITOR_WIDTH CKEditor默认 编辑器宽度,单位为px
CKEDITOR_FILE_UPLOADER None 处理上传文件的URL或端点
CKEDITOR_FILE_BROWSER None 处理文件浏览的URL或端点
CKEDITOR_ENABLE_MARKDOWN False 设置是否开启markdown插件,需要安装对应插件
CKEDITOR_ENABLE_CODESNIPPET False 设置是否开启codesnippet插件(插入代码块),需要安装对应插件
CKEDITOR_CODE_THEME 'monokai_sublime' 当使用codesnippet插件时,设置语法高亮的主题
CKEDITOR_EXTRA_PLUGINS [] 在CKEditor中开启的额外扩展列表,对应的扩展需要被安装

图片上传

在使用文本编辑器写文章时,上传图片是一个很常见的需求。在CKEditor中,图片上传可以通过File Browser插件实现。在服务器端的Flask程序中,你需要做三件事:

  1. 创建一个视图函数来处理并保存上传文件
  2. 创建一个视图函数来获取图片文件,类似Flask内置的static端点
  3. 将配置变量CKEDITOR_FILE_UPLOADER设为这个视图函数的URL或端点值

完整的代码示例如下所示:

from flask_ckeditor import upload_success, upload_fail

app.config['CKEDITOR_FILE_UPLOADER'] = 'upload'

@app.route('/files/<path:filename>')
def uploaded_files(filename):
    path = '/the/uploaded/directory'
    return send_from_directory(path, filename)

@app.route('/upload', methods=['POST'])
def upload():
    f = request.files.get('upload')  # 获取上传图片文件对象
    # Add more validations here
    extension = f.filename.split('.')[1].lower()
    if extension not in ['jpg', 'gif', 'png', 'jpeg']:  # 验证文件类型示例
        return upload_fail(message='Image only!')  # 返回upload_fail调用
    f.save(os.path.join('/the/uploaded/directory', f.filename))
    url = url_for('uploaded_files', filename=f.filename)
    return upload_success(url=url) # 返回upload_success调用

注意 传入request.files.get()的键必须为'upload', 这是CKEditor定义的上传字段name值。

在处理上传文件的视图函数中,你必须返回upload_success()调用,每将url参数设置为获取上传文件的URL。通常情况下,除了保存文件,你还需要对上传的图片进行验证和处理(大小、格式、文件名处理等等,具体可以访问这篇《Flask文件上传(一):原生实现》了解),在验证未通过时,你需要返回upload_fail()调用,并使用message参数传入错误消息。

当设置了CKEDITOR_FILE_UPLOADER配置变量后,你可以在编辑区域点开图片按钮打开的弹窗中看到一个新的上传标签。另外,你也可以直接将图片文件拖拽到编辑区域进行上传,或复制文件并粘贴到文本区域进行上传(CKEditor >= 4.5)。

提示 对应的示例程序在examples/image-upload/目录下。

如果你使用的 CKEditor 版本小于 4.5,则使用下面的方式实现:

from flask import send_from_directory

app.config['CKEDITOR_FILE_UPLOADER'] = 'upload'  # this value can be endpoint or url

@app.route('/files/<filename>')
def uploaded_files(filename):
    path = '/the/uploaded/directory'
    return send_from_directory(path, filename)

@app.route('/upload', methods=['POST'])
@ckeditor.uploader
def upload():
    f = request.files.get('upload')
    f.save(os.path.join('/the/uploaded/directory', f.filename))
    url = url_for('uploaded_files', filename=f.filename)
    return url

为图片上传请求添加 CSRF 保护

如果你想为图片上传的请求添加 CSRF 保护,可以通过 CSRFProtect 实现(Flask-WTF 内置),首先安装 Flask-WTF:

$ pip install flask-wtf

然后初始化扩展:

from flask_wtf import CSRFProtect

csrf = CSRFProtect(app) 

Flask-CKEditor 0.4.3 版本内置了对 CSRFProtect 的支持,当使用 CSRFProtect 时,只需要把配置变量 `CKEDITOR_ENABLE_CSRF` 设为 `True` 即可开启 CSRF 保护:

app.config['CKEDITOR_ENABLE_CSRF'] = True

顺便说一句,在 Flask-CKEditor 内部需要把 CSRF 令牌放到上传图片的 AJAX 请求首部,这通过 CKEditor 4.9.0 版本新添加的一个配置选项 fileTools_requestHeaders 实现,这个配置可以用来想文件上传请求插入自定义的首部字段 。所以,如果想要实现 CSRF 保护,CKEditor 的版本需要大于或等于 4.9.0。

代码语法高亮

代码语法高亮可以通过Code Snippet插件实现(基于hightlight.js),你可以将配置变量CKEDITOR_ENABLE_CODESNIPPET设为Ture来开启。在此之前,你需要确保安装了这个插件(内置的资源包包含了这个插件)。

为了正确渲染代码块,你还需要引入对应的资源文件,最简单的方式是使用Flask-CKEditor提供的ckeditor.load_code_theme()方法:

<head>
 ...
 {{ ckeditor.load_code_theme() }}
</head>

你可以通过配置变量CKEDITOR_CODE_THEME来设置语法高亮的主题,默认为monokai_sublime,你可以在这个页面看到所有可用的主题对应的字符串。

提示 对应的示例程序在examples/codesnippet/目录下。

使用示例程序

项目仓库中提供了5个示例程序,分别展示基本用法、图片上传插入、代码语法高亮、Markdown模式和不使用Flask-WTF/WTForms。以基本示例程序为例,你可以通过下面的命令来获取并运行它:

$ git clone https://github.com/greyli/flask-ckeditor
$ cd flask-ckeditor/examples
$ pip install -r requirements.txt
$ python basic/app.py

然后在浏览器访问http://127.0.0.1:5000。

另外,helloflask仓库里在demos/form目录下的示例程序也包含一个Flask-CKEditor使用示例。

相关链接

 

我的第一个Chrome扩展:GhostPage(网页X光)

没有接触过Chrome扩展的时候,觉得它一定很复杂。crx的后缀也让我猜不出是用什么语言编写的,现在才发现不过就是HTML+CSS+JavaScript……

这个扩展本身很简单,实际只有一行代码。Google提供了一个非常好的入门指导,过一遍基本上就可以编写简单的扩展了。这个扩展作用也就是通过去掉页面样式来检查页面结构(参考这篇文章),可以用来解决下面的问题:

  • 元素超出边线,挤出的空白导致多余的平行滚动
  • 元素不对齐

使用这个扩展配合Chrome的检查工具可以快速的定位到出问题的元素。

Chrome商店的地址:
https://chrome.google.com/webstore/detail/ghostpage/hegpcollkgldlimbhkimijhhhoaicipp

crx文件下载(备用):https://chrome-extension-downloader.com/77577d1b5c39e945e597a9981f6e0ee6/hegpcollkgldlimbhkimijhhhoaicipp/GhostPage_v1.0.crx

附注:虽然扩展编写很简单,但为了交5美元的开发者认证费用却花了我一天时间。尝试了下面几种方式均失败了:

  • 全球付
  • Google Play礼品卡
  • 其他几个虚拟信用卡服务

最后花了两倍多的价格在淘宝上买了一个虚拟信用卡,第二天中午扣款成功了。

Flask-Dropzone:为你的Flask程序添加文件上传功能

这篇文章属于“Flask常用扩展介绍系列”,这个系列的文章目录索引可以在《Flask常用扩展介绍系列文章索引》看到。
 

某天在Stack Overflow上看到一个关于Dropzone.js的问题,就去研究了一下,写了一个回答,顺便写了一个小demo。我发现,如果有一个集成Dropzone.js到Flask,并且简化设置步骤的扩展,肯定要比其他上传方式简单的多——于是就有了Flask-Dropzone

Flask-Dropzone在模板中提供了一些方法来帮助你创建上传区域,引入相关资源。你只需要添加一些配置就可以实现上传类型的过滤,文件大小限制,上传后跳转等功能。当然,你还要自己编写视图函数来处理和保存文件,并进行服务器端的二次验证。

《Flask Web开发实战》中的第3个示例程序(图片社交程序Albumy)使用了这个扩展。

用法说明

安装

$ pip install flask-dropzone

初始化

和其他扩展类似,你可以通过实例化Dropzone类,并传入程序实例app进行初始化:

from flask_dropzone import Dropzone

app = Flask(__name__)
dropzone = Dropzone(app)

在使用工厂函数创建程序实例时,你也可以使用init_app()方法:

from flask_dropzone import Dropzone

dropzone = Dropzone()

def create_app():
    app = Flask(__name__)
    dropzone.init_app(app)
    return app

引入Dropzone.js资源

你需要自己手动编写引入Dropzone.js的CSS和JavaScript资源的语句。在开发时,或对于玩具项目,你可以可以使用Flask-Dropzone提供的两个快捷方法:

<head>
    ...
    {{ dropzone.load_css() }}
</head>
<body>
    ...
    {{ dropzone.load_js() }}
</body>

创建并美化上传区域

如果你不需要对上传区域的样式有太多控制,那么你只需要在想要渲染上传区域的地方使用dropzone.create()方法:

{{ dropzone.create(action='处理上传文件的路由URL') }}

记得把action的值更改成你要处理文件上传的的URL。你可以使用style()方法为上传区域添加简单的自定义样式:

<head>
...  <!-- 在引入Dropzone.js的CSS文件后调用style()方法 -->
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%') }}
</head>

上传区域的截图示例如下所示:

Flask-Dropzone上传截图

Flask-Dropzone上传截图

在服务器端处理并保存上传文件

当文件被拖拽到上传区域,或是点击上传区域选择上传文件后,这些文件会以AJAX请求的形式发送到你在dropzone.create()方法中使用action参数传入的URL。我们需要在服务器端创建对应的视图函数来处理这些请求,下面是一个最基本的示例:

import os

from flask import Flask, request
from flask_dropzone import Dropzone

app = Flask(__name__)

dropzone = Dropzone(app)

@app.route('/uploads', methods=['GET', 'POST'])
def upload():

    if request.method == 'POST':  # 如果请求类型为POST,说明是文件上传请求
        f = request.files.get('file')  # 获取文件对象
        f.save(os.path.join('the/path/to/save', f.filename))  # 保存文件

    return 'upload template'  # 渲染上传页面

 

上传完成后重定向

这里需要注意的是,因为Dropzone.js通过AJAX请求提交文件,所以你没法在保存文件后将页面重定向。对于这个问题,你可以使用配置变量DROPZONE_REDIRECT_VIEW设置上传完成后跳转到的目标端点,或是添加一个按钮让用户自己点击进行跳转。

服务器端验证

尽管Dropzone.js可以在前端对用户提交的文件进行验证,但为了安全考虑,我们仍然需要在服务器端进行二次验证。在服务器端验证时,如果验证出错,我们不能像往常那样使用flash()函数“闪现”错误消息,因为AJAX请求接受到响应后并不会重载页面,所以不会显示通过flash()函数发送的消息。正确的做法是返回400错误响应,使用错误消息作为响应的主体。下面是一个简单的进行服务器端验证并返回错误消息得示例:

@app.route('/', methods=['POST', 'GET'])
def upload():
    if request.method == 'POST':
        f = request.files.get('file')
        if f.filename.split('.')[1] != 'png':
            return 'PNG only!', 400  # return the error message, with a proper 4XX code
        f.save(os.path.join('the/path/to/save', f.filename))
    return render_template('index.html')

在上面的代码中,我们验证图片是不是png格式,如果不是就返回一个错误提示,在服务器端会在图片下面看到我们返回的错误消息:

blank

 

 

完整的配置列表

Flask-Dropzone提供了丰富的配置变量,你可以使用它们对Dropzone.js进行各类配置。

Name Default Value Info
DROPZONE_SERVE_LOCAL   False       使用Flask-Dropzone提供的load_css()和load_js()方法时,这个变量设置是否使用内置的本地资源,默认从CND加载
DROPZONE_MAX_FILE_SIZE 3 允许的文件最大值,单位为MB
DROPZONE_INPUT_NAME file 上传字段的name值
DROPZONE_ALLOWED_FILE_CUSTOM False 是否使用自定义文件类型允许规则,具体见后面
DROPZONE_ALLOWED_FILE_TYPE 'default' 允许的文件类型,具体见后面
DROPZONE_MAX_FILES ‘null’ 一次可以上传的文件数量最大值
DROPZONE_DEFAULT_MESSAGE “Drop files here to upload” 上传区域显示的提示文字
DROPZONE_INVALID_FILE_TYPE “You can’t upload files of this type.” 文件类型错误的错误消息
DROPZONE_FILE_TOO_BIG “File is too big {{filesize}}. Max filesize: {{maxFilesize}}MiB.” 文件太大时显示的错误消息
DROPZONE_SERVER_ERROR “Server error: {{statusCode}}” 服务器错误的错误消息
DROPZONE_BROWSER_UNSUPPORTED “Your browser does not support drag’n’drop file uploads.” 浏览器不支持的错误消息
DROPZONE_MAX_FILE_EXCEED “Your can’t upload any more files.” 超过最大文件数量限制的错误消息
DROPZONE_UPLOAD_MULTIPLE False 是否在单个请求中发送多个文件,默认一个请求发送一个文件
DROPZONE_PARALLEL_UPLOADS 2 当DROPZONE_UPLOAD_MULTIPLE设为True时,设置单个请求包含的文件数量
DROPZONE_REDIRECT_VIEW None 上传完成后重定向的模板端点
DROPZONE_ENABLE_CSRF False 是否开启CSRF保护

这些配置的用法你可以参考Flask-Drozone的文档或是示例程序了解,这里我们仅简单介绍一下对文件类型进行过滤的设置方法。

设置文件类型过滤

Flask-Dropzone内置了一些文件类型(通过MIME定义),可选的值和对应的文件类型如下所示:

  • default:默认值,运行所有类型
  • image:图片
  • audio:音频
  • video:视频
  • text:文本
  • app:程序

你需要为DROPZONE_ALLOWED_FILE_TYPE设置对应的值,比如下面设置仅允许上传图片:

app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image'

如果你想要自己定义允许的文件类型列表,那么你需要将DROPZONE_ALLOWED_FILE_CUSTOM设置True,然后传入一个包含允许的文件后缀名列表组成的字符串给DROPZONE_ALLOWED_FILE_TYPE变量,使用逗号分隔多个后缀名,比如:

app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*, .pdf, .txt'

对于平行上传、CSRF保护等内容的具体实现方法你可以参考文档了解。不过,这个项目目前还没有创建完善的文档,暂时只是写到README里,如果你发现了英文语法或拼写错误,欢迎指正,同时也欢迎为项目贡献代码。

示例程序

Flask-Dropzone的Git仓库中的examples目录下包含4个示例程序,分别演示了基本用法、CSRF保护、平行上传和上传完成后跳转四个功能。

源码可以在这里找到:https://github.com/greyli/flask-dropzone/tree/master/examples

另外,helloflask仓库里在demos/form目录下的示例程序也包含一个Flask-Dropzone使用示例。

相关链接

 

编程名词辨析(5×10’)

在和很多同学的交流中,发现有些编程词语被误用了。今天我们来辨析一下。

我简化了大部分解释,如果需要了解更多,请点链接。如果发现有错,请指正。

 

包(package)和模块(module)

:一个包含__init__.py文件的文件夹就是一个包,这个文件夹里可以放多个Python模块。比如:

my_package
    - __init__.py
    - hello.py

模块:一个Python文件(.py)就是一个模块。

What’s the difference between a Python module and a Python package?

 

插件(plugin)和扩展(extension)

在Flask里,插件和扩展没有具体的区别,都指代增强Flask功能的Python包,但通常称作扩展。

 

参数(parameter)和参数(argument)

参数(形参):函数所期望你传入的参数(即name

def say_hello(name):
    print 'hello,' + name

参数(实参):传给函数的实际的参数(即‘kitty’

say_hello('kitty')

What’s the difference between an argument and a parameter?

函数(function)和方法(method)

函数:为了便于进行管理和重用而包装起来的代码块。

def meow():
    print 'meow meow meow'

方法:在类中定义的函数是方法。

class Cat(object):
    
    def meow(self):
        print 'meow meow meow'

Difference between functions and methods in Python

 

表达式(expression)和语句(statement)

表达式:能被打印和赋值的是表达式。比如:

1 + 1
[x for x in y]

语句:任何一行(或多行)Python代码就是语句(包括表达式),比如:

print 42
name = 'john'

What is the difference between an expression and a statement in Python?

 

用Flask从零实现豆瓣相册

这是我在知乎专栏 – Hello, Flask!上连载的一个教程,这篇作为目录会持续更新。

被新年绑架,一个月忙忙碌碌,现在终于有时间继续写专栏。本来想先写其他的实践,比如便签本、个人博客,但这些都和《Flask Web开发》的Flasky差不多,没什么好写的。想着干脆先把这个相册实现了。

为什么是豆瓣相册

当然,最主要的原因是豆瓣相册比较简单,容易实现。而且用一个东西用久了,总想改造一下。

页面逻辑和豆瓣相册相同:用户相册主页——相册页——图片页——大图

主要的功能点:

  1. 相册与图片
  2. 添加喜欢
  3. 图片上传
  4. 批量编辑
  5. 拖拽排序
  6. 图片浏览
  7. 图片处理
  8. 隐私设置
  9. 自定义头像
  10. 相册增强插件

 

系列目录

  1. 相册与图片

 

相关项目

上面的功能实现在这个项目里都可以看到,后面的文章也会基于这个项目。因为这也是个实验场,有很多代码和实现方法需要重新考量。如果发现问题,欢迎提issue和贡献代码~

github.com/greyli/fanxiangce

部分截图

user page

用户主页

相册页面

相册页面

批量编辑页面

批量编辑页面

photo page

图片浏览页

做个新的相册网站

等这个系列结束,你就可以自己做个相册网站,找一些厉害的插件改造它,再起个好名字。然后到豆瓣里发篇日记,就叫《我做了个更好的豆瓣相册》。当然,那时它可能叫西红柿相册,或是袋鼠相册……

 

图片墙生成器

用Flask重写了图片墙生成器。

Github项目:https://github.com/greyli/image-wall
图片墙Demo:http://fanxiangce.com/demo1(旧Demo)

 

主要变化

和之前的Python版本相比的变动:

  • 实现了上传功能
  • 优化了CSS和JS代码
  • 简化了页面元素,去掉了没用的赞助页面
  • 把HTML分离出Python,代码结构更清晰

 

实现步骤

1、用户上传图片;

2、生成用户文件夹,重命名图片;

@app.route('/', methods=['GET', 'POST'])
def index():
    # 为每一个用户创建文件夹
    username = hashlib.md5('demo' + str(time.time())).hexdigest()[:7]
    if request.method == 'POST' and 'photo' in request.files:
        amount = len(request.files.getlist('photo'))
    if amount in range(10, 101):  # 控制图片数量在10~100之间
        for num, img in enumerate(request.files.getlist('photo')):
            filename = username + str(num)  # 使用用户名加序号命名图片
            photos.save(img, name=username + '/' + filename + '.')
        return redirect(url_for('image_wall', username=username))
    return render_template('index.html')

3、根据图片数量分配相应的行列图片数量,并为每一张图片生成三维坐标;

4、渲染模板,传入这些坐标;

{% for image in images %}
<div class="step center-text" data-x="{{ image[1] }}" data-y="{{ image[2] }}" data-z="{{ image[3] }}">
    <img class="image" src="{{ url_for('static', filename='photos/') }}{{ image[0] }}">
</div>
{% endfor %}

 

 

Google+社群——Hello, Flask!

为了方便讨论和交流,创建了一个Google+社群。现在可以在社群里分享信息,创建问题和讨论。欢迎加入!

关于提问

专栏关注人数增多以后,提问也越来越多。但是有些问题我完全不知道问的是什么……学会提问很重要!

遇到问题后应该有这么一个解决流程:

检查代码是否有语法错误——查看相应的源码和文档——Google搜索——如果到了这一步还没法解决,再到论坛上发帖(建议到StackOverflow上提问)。

问题应该尽量包括下面的内容:

  1. 期望效果
  2. 实际效果
  3. 你的操作步骤和尝试过的解决办法
  4. 相关的代码和错误输出
  5. 操作系统和语言、库等的版本

最后还要注意排版,内容尽量简洁,措辞礼貌一些。

因为Google+社群里不好放代码,可以在StackOverflow或知乎上提问,粘贴链接过来,或是在Github上创建Gist

 

Google+社群

你可以在这里分享关于Flask、Python以及Web开发的一切信息,说点儿想说的,不用拘束。

plus.google.com/u/0/com

Flask文件上传系列教程

文章目录

1、Flask文件上传(一):原生实现

    • 上传配置
    • 安全问题

2、Flask文件上传(二):使用扩展实现

    • 使用Flask-Uploads简化上传过程
    • 大型项目里Flask-Uploads的配置

3、Flask文件上传(三):完整实现

    • 使用Flask-WTF创建上传表单
    • 分离模板文件

4、Flask文件上传(四):文件管理与多文件上传

  • 文件管理
  • 文件名处理
  • 中文文件名问题
  • 多文件上传

5、Flask文件上传(五):拖拽上传和进度条

    • 使用插件集成进度条等功能
    • 使用Pillow生成图像缩略图

 

项目Demo

1、简单的文件管理系统:helloflask/cloud-drive

    • 文件上传
    • 文件删除
    • 图片预览

2、完善的文件管理系统:greyli/flask-file-uploader

    • 拖拽上传
    • 进度条
    • 文件管理
    • Bootstrap样式
    • 图片预览
    • 文件信息

 

 

JavaScript计算器

用JavaScript做了一个计算器,大部分时间都花在完善样式和交互上了,现在还想着再给它添加一个主题。我发现,我喜欢把一个东西从丑变美的过程,好看比好用更吸引我。这不是一个好习惯……calculator
Demo:https://greyli.github.io/calculator/
源码:https://github.com/greyli/calculator

 

优化交互、美化样式

在这个计算器里,用到了一些处理技巧,可以让它看起来更真实和漂亮。

计算器边缘阴影

这里使用box-shadow的inset方法,也就是把阴影内嵌到元素里,让计算器看起来是有厚度的:

box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), inset -1px -6px 12px 0.1px #89847e;

参考内容:https://css-tricks.com/snippets/css/css-box-shadow/

按钮按下效果

其实是设置按钮的box-shadow,按下时把box-shadow设为none,同时按钮向下移动

button {
  box-shadow: 1px 2px #666;
}

button:active {
  box-shadow: none;
  transform: translateY(4px);
}

按钮上的字不可选择

双击按钮或是拖动按钮选择会出现蓝色背景色,设置user-select去掉这个特性

.un-selectable {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

去掉按钮被选中后的蓝色边线

button:focus {outline:0;}  /* 设为none效果相同,或加上 !important */

 

换域名小记

起名字

起名字是个简单但让人头疼的事情。

你想要一个独特的名字,但又不想透露太多的个人信息。既不能太土,又不能太长,不能太难读,也不能太难记,而且最要命的是,如果你想在所有社交网站都有一个相同的id,这太难了——好名字全被注册完了。

连续换了两个域名后,我想这次终于遇到对我来说完美的域名了——greyli.com

 

换域名

更改完DNS设置后,在数据库里用新域名替换掉旧域名。

和上次同时换主机和域名不同,这次只需要在数据库里改动一些内容就完成了:

wp_options SET option_value = replace(option_value, 'www.mydomain.com','www.newdomain.com');
UPDATE wp_posts SET post_content = replace(post_content, 'www.mydomain.com','www.newdomain.com');
UPDATE wp_comments SET comment_content = replace(comment_content, 'www.mydomain.com', 'www.newdomain.com');
UPDATE wp_comments SET comment_author_url = replace(comment_author_url, 'www.mydomain.com', 'www.newdomain.com');
 
 
文章里有些地址是完整的,所以可能还需要加上下面这条语句:
UPDATE wp_posts SET post_content = replace(post_content, 'http://www.olddomain.com','http://www.newdomain.com');