日課書

编程100小时挑战

Jinja Cheat Sheet

在Web框架中,通过模板试了了业务逻辑和表现逻辑的分离。模板是一个包含响应文本的文件,其中包含占位变量表示动态的部分,其具体值只有在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称之为渲染。为了渲染模板,Flask使用Jinja2模板引擎。

基本用法

  • 渲染模板
1
2
3
4
5
from flask import render_template

@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
  • 变量
1
2
3
4
5
6
<p>A value from a dictionary: {{ mydict['key'] }}.</p>
<p>A value from a list: {{ mylist[3] }}.</p>
<p>A value from a list, with a variable index: {{ mylist[myintvar] }}.</p>
<p>A value from an object's method: {{ myobj.somemethod() }}.</p>

Hello, {{ name|capitalize }}

常见过滤器:safetrimstriptags

  • 控制结构
1
2
3
4
5
{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}
1
2
3
4
5
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
1
2
3
4
5
6
7
8
9
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}

<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
  • 导入、包含
1
2
3
4
5
6
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}
{% endfor %}
</ul>
1
{% include 'common.html' %}
  • 继承
1
2
3
4
5
6
7
8
9
10
11
12
<!-- base.html -->
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
1
2
3
4
5
6
7
8
9
10
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style>
</style>

{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}
  • 链接
1
2
3
4
5
url_for('index', _external=True)
url_for('user', name='john', _external=True)
url_for('index', page=2) # /?page=2

url_for('static', filename='css/style.css', _external=True)

Flask-Bootstrap

1
$ pip install Flask-Bootstrap
1
2
3
4
# app.py
from flask.ext.bootstrap import bootstrap
# ...
bootstrap = bootstrap(app)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{% extends "bootstrap/base.html" %}

{% block title %}Flasky{% endblock %}

{% block nav %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">

<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-brand" href="/">Flask</a>
<ul class="navbar-collapse collapse">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}

{% block content %}
<div class="container">
<div class="page-header">
<h1>Hello, {{ name }}!</h1>
</div>
</div>
{% endblock %}
1
2
3
4
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}

自定义错误页面

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

利用Flask创建一个mini微博(2)

利用Flask创建一个mini微博(1)

Step 3:创建视图函数

用户登录首页时会进行判断,如果是登录用户则显示,用户关注的时间线,如果没登录则显示公共的时间线。

在首页会有登录登出的选项,这个具体的选项会根据目前用户状态进行判断。如果是未登录则点击进入登录页面,登录成功后返回用户关注的时间线。如果用户已登录,则显示登出,登出后返回公共时间线。

如果是未登录状态,首页还会有注册选项。点击进入注册页面,注册成功后跳转至登录页面。

其次登录用户还可以进入特定的其他用户界面,以浏览某用户的全部微博。在用户的个人界面,可以选择关注或者取消关注某用户。并且可以查看该用户的关注列表。

  • 在处理每个请求前,判断用户是否已登录
    1
    2
    3
    4
    5
    6
    @app.before_request
    def before_request():
    g.user = None
    if 'user_id' in session:
    g.user = query_db('select * from users where user_id = ?',
    [session['user_id']], one=True)

通过分析,我们得知在整个网站中需要用到一下链接和对应的视图函数:

  • 首页。

当用户进入首页时判断用户是否登录,如果登录则显示个人的时间线,以显示自己和关注人的消息。否则重定向到公开时间线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import session, render_template, redirect, url_for

PER_PAGE = 20

@app.route('/')
def timeline():
if not g.user:
return redirect(url_for('public_timeline'))
query = '''
SELECT messages.*, users.* FROM messages, users
WHERE messages.user_id = users.user_id AND (
users.user_id = ? OR
users.user_id IN (SELECT followed_id FROM follows
WHERE follower_id = ?))
ORDER BY messages.pub_time DESC LIMIT ?'''

messages = query_db(query, [session['user_id'], session['user_id'], PER_PAGE])
return render_template('timeline.html', messages=messages)
  • 公共主页

公共时间线显示最近发布的若干条信息。

1
2
3
4
5
6
7
8
@app.route('/public')
def public_timeline():
query = '''
SELECT messages.*, users.* FROM messages, users
WHERE messages.user_id = users.user_id
ORDER BY messages.pub_time DESC LIMIT ?'''

messages = query_db(query, [PER_PAGE])
return render_template('timeline.html', messages=messages)
  • 用户页

用户页用以显示单个用户发布的所有消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from flask import abort

@app.route('/<username>')
def user_timeline(username):
profile_user = query_db('SELECT * FROM user WHERE user_name = ?',
[username], one=True)
if profile_user is None:
abort(404)
followed = False
if g.user:
followed = query_db('''SELECT 1 FROM follows WHERE
follows.follower_id = ? AND follows.followed_id = ?''',

[session['user_id'], profile_user['user_id']],
one=True) is not None
query = '''
SELECT messages.*, users.* FROM messages, users
WHERE users.user_id = message.user_id AND users.user_id = ?
ORDER BY messages.pub_time DESC LIMIT ?'''

messages = query_db(query, [profile_user[user_id], PER_PAGE])
return render_template('timeline.html', messages=messages,
followed=followed, profile_user=profile_user)
  • 关注

关注用户,就是在关注表中添加一条记录,关注人为当前登录用户,被关注者为浏览的用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import flash

@app.route('/<username>/follow')
def follow(username):
if not g.user:
abort(401)
followed_id = get_user_id(username)
if followed_id is None:
abort(404)
db = get_db()
db.execute('''INSERT INTO follows (follower_id, followed_id)
VALUES (?, ?)''', [session['user_id'], followed_id])

db.commit()
flash('Your are now following "%s".' % username)
return redirect(url_for('user_timeline', username=username))
  • 取消关注
1
2
3
4
5
6
7
8
9
10
11
12
13
@app.route('/<username>/unfollow')
def unfollow(username):
if not g.user:
abort(401)
followed_id = get_user_id(username)
if followed_id is None:
abort(404)
db = get_db()
db.execute('DELETE FROM follows WHERE follower_id=? AND followed_id=?',
[session['user_id'], followed_id])
db.commit()
flash('You are no longer following "%s".' % username)
return redirect(url_for('user_timeline', username=username))
  • 发布消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
from flask import request

@app.route('/add_message', methods=['POST'])
def add_message():
if not g.user:
abort(401)
if request.form['text']:
db = get_db()
db.execute('''INSERT INTO messages (user_id, text, pub_time)
VALUES (?, ?, ?)''', [session['user_id'], request.form['text'],

int(time.time())])
db.commit()
flash('Your message was recorded.')
return redirect(url_for('timeline'))
  • 注册

注册时,首先要检查用户的提交的信息是否完整,其次检查是否重名。为了安全起见,我们用werkzeug库提供的函数,对密码进行哈希处理,避免在数据库中明文存储密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from werkzeug import generate_password_hash

@app.route('/register', methods=['GET', 'POST'])
def register():
if g.user:
return redirect(url_for('timeline'))
error = None
if request.method == 'POST':
if not request.form['username']:
error = 'You have to enter a username'
elif not request.form['email'] or \
'@' not in request.form['email']:
error = 'You have to enter a valid emial address'
elif not request.form['password']:
error = 'You have to enter a password'
elif request.form['password'] != request.form['password2']:
error = 'The two password do not match'
elif get_user_id(request.form['username']) is not None:
error = 'The username is already taken'
else:
db = get_db()
db.execute('''INSERT INTO users (user_name, email, pw_hash) VALUES
(?, ?, ?)''', [request.form['username'], request.form['email'], generate_password_hash(request.form['password'])])

db.commit()
flash('You were successfully registered and can login now')
return redirect(url_for('login'))
if error:
flash(error)
return render_template('register.html')
  • 登录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from werkzeug import check_password_hash

@app.route('/login', methods=['GET', 'POST'])
def login():
if g.user:
return redirect(url_for('timeline'))
error = None
if request.method == 'POST':
user = query_db('''SELECT * FROM users WHERE user_name=?''',
[request.form['username']], one=True)
if user is None:
error = 'Invalid username'
elif not check_password_hash(user['pw_hash'], request.form['password']):
error = 'Invalid password'
else:
flash('You were logged in.')
session['user_id'] = user['user_id']
return redirect(url_for('timeline'))
if error:
flash(error)
return render_template('login.html')
  • 登出
1
2
3
4
5
@app.route('/logout')
def logout():
flash('You were logged out')
session.pop('user_id', None)
return redirect(url_for('public_timeline'))
  • 查看关注列表

通过关注列表,用户可以获取与自己有关的关注信息,包括自己关注的人,和关注自己的人。

1
2
3
4
5
6
7
8
9
@app.route('/follow_list')
def follow_list():
if not g.user:
abort(401)
followed = query_db('SELECT * FROM follows WHERE follower_id=?',
[session['user_id']])
follower = query_db('SELECT * FROM follows WHERE followed_id=?',
[session['user_id']])
return render_template('follow_list.html', followed=followed, follower=follower)

待续


Step 4:制作模板

Step 5:测试程序

Step 6: 部署程序

利用Flask创建一个mini微博

Mini-Weibo 简介

Flask官方文档提供了一个微型博客的教学案例:Flaskr。虽然这个小示例可以让我们掌握,一个基于Flask框架的Web程序的大致结构,但是功能过于简单、外形比较粗糙,并不是一个拿得出手的小项目。所以,我将参考Flask源码中的minitwit例子,制作一个功能稍复杂些、界面经过美化的「mini微博」—— Mini-Weibo。

这个mini微博可供使用者注册账户,发布消息,并且关注别的用户。实现了微博的骨干功能。同时我们将使用BootStrap来对交互界面进行美化。为了简单,数据库仅采用基本的SQLite。

Step 0:功能分析

考虑一下我们是如何使用微博的。

  • 如果我们没有成为注册用户,那么我们只能浏览部分最近发表的微博信息,即公开时间线
  • 如果想要发布微博,我们需要成为注册用户,那么网站就需要一个页面来实现注册功能
  • 注册的用户会用到登录登出功能。
  • 完成注册后,我们可以发布微博
  • 通常在微博上我们还会关注其他用户。有关注就会对应取消关注
  • 当关注的用户多了我们想要找到某个用户,就需要有一个关注列表,用来快速定位用户。
  • 因此,当一个注册用户登录时,他的界面显示的是他所关注用户发表的信息,我们称之为关注时间线
  • 有时我们只想看某个人的微博,那么会进入一个个人微博页面,仅浏览该用户发表的信息。

上面的每一个功能都会有对应的URL链接,如果有信息需要显示,和这个URL绑定的视图函数会返回给我们相应的页面。如果只是需要修改一些数据,那么URL绑定的视图函数在进行完相应的修改后,会重定向到后继显示的页面。

我们通过分析微博的功能,发现了在这个应用中存在以下几种数据:

  • 用户数据:只要注册过的用户都会有它的用户名和其他基本信息。
  • 微博数据:每条微博数据除了内容还会有其作者的名称。
  • 关注数据:每一次关注动作的发生,都涉及到两方面,关注者和被关注着。

有了上面的功能分析和数据类型分析,我们就可以设计出大概需要的各种页面,需要后台程序响应的数据,以及储存数据的模型。

Step 1:配置开发环境

在GitHub上创建项目

在GitHub上创建一个新的仓库,用于托管项目代码。添加项目描述、初始化文件、.gitignore和License。本教程基于Mac操作系统。

把GitHub上的远程仓库克隆回本地:

1
$ git clone git@github.com:wYibin/miniweibo.git

搭建开发虚拟环境

安装并激活虚拟环境

1
2
3
4
5
$ cd miniweibo

$ virtualenv venv

$ source venv/bin/activate

使用pip安装Python包

1
pip install flask

创建flask需要的文件夹

1
2
3
/flask
/static
/templates

测试开发环境

写一个最简单的Flask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# miniweibo.py
from flask import Flask

#configuration
DATABASE = 'miniweibo.db'
DEBUG = True
SECRET_KEY = 'development key'

# create application
app = Flask(__name__)
app.config.from_object(__name__)
app.config.from_envvar('MINIWEIBO_SETTING', silent=True)

@app.route('/')
def index():
return '<h1>Hello, MiniWeibo!</h1>'

测试开发环境是否正确设置

1
2
3
export FLASK_APP=miniweibo
export FLASK_DEBUG=1
flask run

访问127.0.0.1:5000,如果能正式显示信息则配置成功。

接着我们把它同步到GitHub上。

1
2
3
4
5
$ git add *

$ git commit -m "Set up the dev environment."

$ git push origin master

Step 2:数据库设计

定义数据模型

通过功能分析的部分,我们找出了微博程序中所需要的三种数据类型,分别表示:用户、微博、和关注关系。现在我们需要做的就是,利用数据库Schema具体定义出这三种抽象数据模型模型。下面我们队这三种模型进一步进行分析。

  • 用户。只有注册用户才能发表微博,所以用户模型是最基本的。每个用户会有用户名,注册时也会有邮箱和密码,密码必定不能明文存储在数据库中,一般以散列算法计算后存储,以免发生泄漏风险。用户名的检索不太方便也不利于排序,所以我们采用整数类型为用户分配id.
  • 微博。每条微博对着这内容和其作者。为了索引方便我们为每条微博分配id。同时作者也用id的方式进行存储。同时每条微博发布的时间也比较重要。
  • 关注。微博做为一种社交应用,必然会有人和人之间的关系。在微博中每一次关注,对应着一个关注者和被关注者。把所有的关注放在一起,我们就可以还原出整个关系网络。这里我们也用用户id来表示关注者和被关注者。

具体的数据库定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
drop table if exists users;
create table users (
user_id integer primary key autoincrement,
user_name text not null,
email text not null,
pw_hash not null
)

drop table if exists messages;
create table messages (
message_id integer primary key autoincrement,
user_id integer not null,
text text not null,
pub_time integer not null
)

drop table if exists follows;
create table follows (
follower_id integer not null,
followed_id integer not null
)

创建数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import sqlite3
from flask import g

DATABASE = 'database.db'

def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db

@app.teardown_appcontext
def close_db(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()

初始化数据库

利用schema来初始化数据库:

1
2
3
4
5
6
def init_db():
with app.app_context():
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()

在Python shell中完成初始化:

1
2
3
$ python
>>> from miniweibo import init_db
>>> init_db()

简化数据查询

对于常用的查询操作,我们定义专门的函数来简化它。

1
2
3
def query_db(query, args=(), one=False):
rv = get_db().execute(query, args).fetchall()
return (rv[0] if rv else None) if one else rv

由于用户输入的通常是用户名,而在数据库中微博和关注表中更多的是存储用户id。所以通过用户名检索用户id是个高频操作,因此我们特别定义一个函数:

1
2
3
def get_user_id(username):
rv = query_db('SELECT * FROM users WHERE user_name=?', [username], one=True)
return rv[0] if rv else None

待续

Step 3:创建视图函数

用户登录首页时会进行判断,如果是登录用户则显示,用户关注的时间线,如果没登录则显示公共的时间线。

在首页会有登录登出的选项,这个具体的选项会根据目前用户状态进行判断。如果是未登录则点击进入登录页面,登录成功后返回用户关注的时间线。如果用户已登录,则显示登出,登出后返回公共时间线。

如果是未登录状态,首页还会有注册选项。点击进入注册页面,注册成功后跳转至登录页面。

其次登录用户还可以进入特定的其他用户界面,以浏览某用户的全部微博。在用户的个人界面,可以选择关注或者取消关注某用户。并且可以查看该用户的关注列表。

Step 4:制作模板

Step 5:测试程序

Step 6: 部署程序

SQLite Cheatsheet

SQLite是一个轻量级的数据库,它以文件的形式存储在硬盘中,适合小型的简单应用。有些应用也会采用SQLite来做产品原型,然后把它迁移到其他功能更强大的数据库,比如:MySQL、PostgreSQL等。

Python标准库 —— sqlite3

参考:Python-Docs/sqlite3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()

# Create tables
c.execute('''CREATE TABLE stocks
(date text, trans text, symbol text, qty real, price real)''')


# Insert a row of database
c.execute("INSERT INTO stocks VALUES ('2016-06-27', 'BUY', 'APPL', 100, 100)")

# Save (commit) the changes
conn.commit()

conn.close()

# Read from database
conn = sqlite3.connect('example.db')
c = conn.cursor()

# Put ? as a placeholder and then provide a tuple of values
t = ('APPL',)
c.execute('SELECT * FROM stocks WHERE symbol=?', t)
print c.fetchone()

# Larger example that inserts many records at a timestamp
purchases = [('2016-06-01', 'BUY', 'IBM', 100, 45.00),
('2016-06-01', 'BUY', 'GOOG', 100, 450.00),
]
c.executemany('INSERT INTO stocks VALUES (?,?,?,?,?)', purchases)

# Treat the cursor as an iterator,or fetchall()
for row in c.execute('SELECT * FROM stocks ORDER BY price'):
print row

在Flask中使用SQLite

参考:Flask-Docs/sqlite3

  • Simple Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sqlite3
from flask import g

DATABASE = '/path/to/database.db'

def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db

@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()

@app.route('/')
def index():
cur = get_db().cursor()

with app.app_context():
# now you can use get_db()
  • Easy Querying
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def query_db(query, args=(), one=False):
cur = get_db().execute(query, args)
rv = cur.fetchall()
return (rv[0] if rv else None) if one else rv

for user in query_db('select * from users'):
print user['username'], 'has the id', user['user_id']

user = query_db('select * from users where username = ?',
[the_username], one=True)
if user is None:
print 'No such user'
else:
print the_username, 'has the id', user['user_id']
  • Initial Schemas
1
2
3
4
5
6
def init_db():
with app.app_context():
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
1
2
>>> from yourapplication import init_db
>>> init_db()
1
2
3
4
5
@app.cli.command('initdb')
def initdb_command():
"""Creates the database tables."""
init_db()
print('Initialized the database.')

SQL语句

参考:flask example

  • schema
1
2
3
4
5
6
7
drop table if exists message;
create table message (
message_id integer primary key autoincrement,
author_id integer not null,
text text not null,
pub_date integer
);
  • select
1
2
3
4
5
6
7
8
messages=query_db('''
select message.*, user.* from message, user
where message.author_id = user.user_id and (
user.user_id = ? or
user.user_id in (select whom_id from follower
where who_id = ?))
order by message.pub_date desc limit ?''',

[session['user_id'], session['user_id'], PER_PAGE])
1
2
3
4
followed = query_db('''select 1 from follower where
follower.who_id = ? and follower.whom_id = ?''',

[session['user_id'], profile_user['user_id']],
one=True)
  • insert
1
2
3
db.execute('insert into follower (who_id, whom_id) values (?, ?)',
[session['user_id'], whom_id])
db.commit()
  • delete
1
2
3
db.execute('delete from follower where who_id=? and whom_id=?',
[session['user_id'], whom_id])
db.commit()
  • update
1
2
3
db.execute('update message set text=? where message_id=?',
[new_text, message_id])
db.commit()

关于旅行的一些想法

为什么旅行

  • 人们在旅行时,普遍会有一个更开放的心态去接受事物。
  • 旅行增加不同的体验,包括自然、人文等各个方面。
  • 走出熟悉的环境,陌生感会提醒你思考自己和这个世界的关系。
  • 旅行给你体验未知的勇气,让你去做自己想做的事,成为想成为的人。

再过半个月就要启动自己下一次国外旅行了,一次二十多天的美国西部自驾游。如果没有外部愿意,但是我自己,可能一段时间内都没有旅行的想法了(包括即将启程的这次)。或者说说,没有让自己出发的冲动和理由。原因很难说,可能觉得目前自己的状体更加需要积累务实的技能,开创自己的实业,而非比较虚的旅行。

说旅行比较务虚,并不表示我反对或者讨厌旅行。相反我国内转过以一些地儿,欧洲也去过几个国家,但如今每次想到旅行,都不禁要反问自己,旅行究竟有什么意义?

记得去欧洲前,一度觉得人们已经没有必要旅行了,通过互联网上的游记各种视频图片,我们足以了解每个想要去的地方。有了网络可能就没有必要了旅行了。这是从观光的角度考量,但即使是观光,各人有个人不同的视角,同一片风景会有不同的感受和体悟,再就是经验还是一手的更加保真。暂且认为看看不同的风景,感受不同的人文风情,也是旅行的基本意义。

之前的欧洲之旅极度轻松,可能那段时间正好是自己放下前一段的工作生活经历,准备开启下一段里程的衔接点。因此没有什么特别的压力。旅行时只想着路上的美景美食,只为猎奇,心态平和,对不同的包容度极强。那种放松的心灵状态,无疑给眼前的风景自动加了滤镜,夜色巴黎变得更浪漫,雨中的阿姆斯特丹也别有情调。如果你真的能全身心投入到旅途中,这种心灵的放松和愉悦感,的确是你在平时工作生活的久居之地很难达到的状态。

灵感和思考都是需要素材的,这素材可以来自阅读,同样也可以出自旅行中的见闻。与阅读不同,旅行是获得一手鲜活素材的重要方式。你感受世界,发现不同,产生对比。感官刺激思考,这个过程无疑有助于创造新灵感、产生新想法,同时更可能去评估自己与这个世界的关系。

跳出习惯的生活环境,从另外一个角度省事原来的自己。旅行本身就是走没走过的路,见没见过的人,把这份习惯和勇气带入平时的生活,去做自己想做的事,成为你自己想成为的人,也是旅行最重要的意义。

我个人的一个原则是:收集更多的体验,对于实物,则保持基本需求即可。探求一件事情的意义总让人难以得到满意的答案,只愿每次旅行带给我更多的体验!

用LV的文案来为此行做序:

What is a journey?

A journey is not a trip.

It’s not a vacation

It’s a process. A Discovery.

It’s a process of self-discovery.

A journey brings us face to face with ourselves.

A journey shows us not only the world,

but how we fit in it.

Does the person create the journey

or does the journey create the person?

The journey is life itself.

Where will life take you?

如何计划

提早安排旅行。很多时候你并不会说走就走,打包行囊直接出发。旅行的念头可能很早就心里长草了,你应该提早为此规划。起码提前预定机票和住宿会让你剩下一大杯费用。适当对行程的熟悉程度,会让你的旅途更加靠谱充实。

不要把行程安排的太满,留有一定的随机性。旅途中总会出现一些特殊情况,如果你把行程安排的满满当当,意味着你的行程没有任何容错性,突发状况会让你措手不及。同时只是机械的按行程赶路,没有任何惊喜,又有什么意思呢。你可以留些时间,让自己在陌生的地方无目的的闲逛,没准哪就会让你眼前一亮。

避免跟风去热门景点,根据自己的兴趣安排特殊目的地。你可以去热门景点逛逛,但全部行程都是大众景点,那么你自己也就是一个普通的观光客而已,行前要对目的地有大概的认识,从而根据自己的兴趣爱好,寻找当地的参观体验点。如果你喜欢文物,就去当地的博物馆看看;喜欢各地特色,就去当地市场逛逛;喜欢咖啡,就深入小巷,在当地特色的咖啡馆奢侈的泡上半天。

想办法获得更多的体验,不只是买买买。现在的出国游难免都有些购物游的特点,我们会顺道去奥特莱斯和各种免税店买买买。除了实物上的收获,你应该把更多的钱花在之前从没有的体验上。如果你一直住普通酒店,那么下一次试试Airbnb和青旅。如果你总是跟团,不妨来一次自助游。又或者不在单调的城市观光,试试国家公园的野营。旅行无非是体验未知,如果总是重复自己的老套路,岂不是很无趣?

留下什么

如果你不带相机上路,那么你会为一次旅行留下什么呢?仅仅只是一些旅游纪念品吗。照片很廉价,你可以用手机或数码相机,随性来上成百上千张。但是你想过这里面有多少是来此一游式的记录,又有多少能给人美的冲击,或者关于目的地的思考。下次旅行前,可以学习一下拍照的技巧,拍照时留心下构图和想表达的状态。

有时记忆是不可靠的。如果你想多少年后,还能栩栩如生的回忆起某次旅行的每个细节,那么你很可能需要在旅行后,写一篇游记。游记可以供自己回忆,同时也可以看做是一个旅行攻略,分享到网上,为后来的人提供一些行程指引和参考。

其实每做一件事情,你都可以多花些心思,旅行除了走走看看,也可能产生很多有价值的副产品。我就在YouTube上看到很多旅行者录制了自己的旅行节目,用特别的角度观察世界,留下你的想法,对别人产生影响,这才价值所在。同样旅行中的见闻甚至会启发你新的灵感,为你将来的生活或者事业打开新的可能,关键是要用心体会。

Flask Web应用的一般模式

圣贤云:程序 = 数据结构 + 算法。

粗略看,Flask应用 = Models + View Functions + Templates。

其中Model即模型,是对Web应用的抽象表达,通常用关系数据库来实现。比如我们对一个博客应用进行模型抽象,即回答博客是什么。博客首先要有文章,每篇文章至少有标题和正文。如果开通评论,那么每个评论会对应:评论人,所属文章,和评论内容。还有些博客有几个人共同维护,每个人对应不同的管理权限,那么每个人都会有id、权限等属性。因此一个博客程序所抽象出来的Model就是,表示文章、评论和用户的三张表格。这些表格利用数据库schema来定义,生成相应的数据库,随着程序的运行会对里面的数据进行增删改查的操作。你通过网页看谋篇博文,其实是Web应用从表示文章的表格中读取了相应的一篇,返回给浏览器的。

所以在创建基于Flask的应用时,我们首先考虑要实现的项目目标,进而抽象出模型,利用关系模型来表示,最终在选取相应的数据库在程序中实现。对于练手的小型项目可以选择sqlite,大型直接选开源的MySQL。

在用户看来,使用Web应用,也就是浏览网站,无非是打开一个个网页。从开发者角度看,用户每输入一个网址或点击一个连接,都是期待获得一个新的页面。用技术的语言讲就是,Web编程就是,编写处理每个HTTP请求并返回响应内容的程序。而像Flask这样的Web框架,简化了我们处理请求的过程,它对请求和响应进行了抽象,做了大量底层的工作,让我们只要编写每个URL对应的响应函数就行了,一个请求对应一个View Function。

返回的内容基本上都是网页。对于一个网站而言不同的页面间有相同的布局和元素,只有部分内容不同。而且如果直接返回HTML代码会让程序很难维护。基于以上两点,Flask和大部分Web框架一样,都会采用模板机制。返回的内容添加到提前预设好的模板中,再把通过模板渲染后的网页响应给用户。在Flask中,采用Jinja模板。

所以开发Flask应用的一般流程和模式如下:

  1. 确定项目要实现的功能特性
  2. 设定开发环境和项目结构
  3. 抽象出数据模型
  4. 处理数据库的链接、创建等
  5. 根据不同的URL请求编写视图函数
  6. 根据视图函数的返回内容,编写不同的模板。
  7. 给模板添加样式,使用css。
  8. 对程序进行测试,利用unittest.
  9. 部署到服务器,比如Heroku云服务。

Flask简介

Flask是一个Python的小型Web程序框架,它基于Wekzeug和Jinja 2两个库,有很好的可扩展性。选择学习Flask就是基于他简洁和可扩展的特性。

Web应用的本质是,客户端向服务器发送请求,服务器读取请求内容,返回给客户端相应的数据。如果是静态网站,譬如Hexo博客,客户端输入地址,服务器直接返回静态的HTML页面即可,服务器程序并没有其他任务。但对于动态网站,服务器程序会根据请求的不同,对HTML页面进行相应的加工,返回不同内容的页面。

对应这种特质,一个Web程序,就是响应不同HTTP请求的函数。使用Web框架后,我们在编写Web应用,就是编写对应URL处理函数。

在Flask中URL与处理函数的对应,用@app.route()装饰器实现。route同时能识别不同的HTTP请求类型。

Flask返回页面,可以用render_template()来渲染Jinja模板。

同时HTTP请求中也可能包含客户端上传的数据,用request即可获得。同样利用request还可以获得Cookie数据。

通过redirect()我们可以重定向用户的请求,以达到权限控制的目的。同时用@app.errorhandler()来返回个性化的错误提醒页面。

利用make_response()可以设置响应的内容,状态码,头部信息等。

利用Session可以设置干安全的Cookie。

在网站的交互中,可以利用flash()发出反馈实时信息,模板中可以利用get_flashed_messages()来获得反馈信息。

服务器程序可以使用app.logger来记录日志内容。

参考内容:

进度记录: day19 | +5H | [91/100]

day19,2016-07-11,5H,第87-91小时

  • 87-91H,swift访问REST API读取json数据,建立TableView

day18,2016-07-10,6H,第81-86小时

  • 81-84H,iOS开发斯坦福公开课前两课
  • 85H,UITextView swift
  • 86H,部署博客到Heroku

day17,2016-07-9,+1H,第80小时

  • 80H,NSURLSession类,闭包特性

day16,2016-07-08,+8H,第72-79小时

  • 72-78H,iOS/swift示例

day15,2016-07-07,+7H,第65-71小时

  • 65-66H,博客API
  • 67-71H,swift官方教程案例

day14,2016-07-06,+8H,第57-64小时

  • 57-58H,博客分页功能
  • 59-61H,博客Markdown支持
  • 62H,博客评论功能
  • 63-64H,API设计

day13,2016-07-05,+3H,第54-56小时

  • 54H,登录和权限
  • 55-56H,文章发布

day12,2016-07-04,+3H,第51-53小时

  • 51H,半程总结
  • 52-53H,发送email、注册

day11,2016-07-03,+2H,第49-50小时

  • 49-50H,Flask-Login

day10,2016-07-02,+0H

day09,2016-07-01,+8H,第41-48小时

  • 41-43H,WTForms,SQLAlchemy
  • 44H,Flask-Script,Flask-Migrate
  • 45-47H,Flask大型程序结构
  • 48H,设计博客功能

day08,2016-06-30,+6H,第35-40小时

  • 35-37H,MiniWeibo教程:测试
  • 38-39H,MiniWeibo教程:部署到Heroku
  • 40H,Flask中的AJAX

day07,2016-06-29,+9H,第26-34小时

  • 26-27H,Bootstrap Cheat Sheet(1)
  • 28-31H,MiniWeibo教程 Templates
  • 32-33H,Bootstrap Cheat Sheet(2)
  • 34H,MiniWeibo教程 测试
    -

    day06, 2016-06-28, +4H, 第22-25小时

  • 22-24H,编写miniweibo 教程:视图函数
  • 25H,编写Jinja Cheat Sheet

day05, 2016-06-27, +8H, 第14-21小时

  • 14-15H,浏览Python文档及Flask文档中关于SQLite的部分
  • 16H,查看minitwit源码,编写SQLite-Cheatsheet
  • 17-19H,编写miniweibo tutorial前三章:简介、功能分析、开发环境设置
  • 20-21H,编写miniweibo tutorial第四章:数据库。

day04, 2016-06-26, +0H

day03, 2016-06-25, +2H, 第12-13小时

  • 12H, minitwit源码阅读
  • 13H, 浏览《Flask web开发》前5章

day02, 2016-06-24, +7H,第5-11小时

  • 5-7H,Flask文档:Tutorial,并实现Flaskr示例
  • 8H,Flask文档,学习Flaskr的测试方法
  • 9-10H,部署Flaskr到Heroku
  • 11H, 浏览Flask源码的minitwit实例,写一篇博客总结

day01,2016-06-23,+4H,第1-4小时

  • 1H,制定100小时编程挑战计划
  • 2H,创建GitHub项目,实现Flask-hello-world
  • 3-4H,浏览Flask文档:QuickStart部分

day02[计划], 2016-06-24

  • [X] 5-7H,Flask文档:Tutorial,并实现示例
  • [X] 8H, 部署实现示例到Heroku
  • [ ] 9-10H, 浏览其他几个代码范例
  • [ ] 11-12H,浏览书籍《Flask Web开发》第一部分

day03[计划], 2016-06-25

  • [ ] 12-13H,浏览书籍《Flask Web开发》第一部分
  • [ ] 14-16H,编写minitwit的tutorial

day04[计划],2016-06-26

  • [ ] 14H, 编写Flask cheatsheet: sqlite\

day05[计划],2016-06-27

  • [x] 14H, CheatSheet - SQLite for Flask
  • [ ] 15-16H, 阅读《Flask Web开发》第一部分,编写CheatSheet
  • [x] 17-19H, 编写minitwit tutorial教程
  • [ ] 20-21H, 完成我的博客0.1版,MVP最小可行产品。

day06[计划],2016-06-28

  • [x] 22-23H, miniweibo教程编写:视图函数。
  • [ ] 24H,Flask-cheatsheet: view function。
  • [ ] 25H,Flask-Bootstrap Cheatsheet。
  • [ ] 26-27H,miniweibo教程编写:模板。
  • [ ] 28-29H,测试

day07[计划],2016-06-29

  • [x] 26H,Bootstrap Cheatsheet。
  • [x] 27-28H,miniweibo教程编写:模板。
  • [ ] 29-31H,测试

day08[计划],2016-06-30

  • [x] 35-36H,MiniWeibo教程:测试。
  • [x] 37-38H,MiniWeibo教程:部署。
  • [ ] 39H,Flask表单扩展。
  • [ ] 40H,Flask邮件和认证扩展。

day09[计划],2016-07-01

  • [x] 41H,Flask表单扩展。
  • [ ] 42H,Flask邮件和认证扩展。
  • [ ] 43-44H,MySQL数据库、ORM
  • [ ] 45-46H,设计博客功能。

day10[计划],2016-07-02

day12[计划],2016-07-04

  • [x] 51H,半程总结
  • [x] 52H,用户认证
  • [x] 53H,用户角色
  • [ ] 54H,文章
  • [ ] 55-56H,廖雪峰教程案例

day13[计划],2016-07-05

  • [x] 54H,权限、注册
  • [ ] 55-56H,发表文章功能
  • [ ] 57H,廖雪峰教程案例

day14[计划],2016-07-06

  • [x] 57H,Markdown
  • [x] 55-56H,分页功能
  • [ ] 57H,界面设计,JavaScript时间转换

day15[计划],2016-07-07

  • [x] 65H,API完成
  • [ ] 66-67H,CSS和界面设计
  • [ ] 68-69H,iOS App

day16[计划],2016-07-08

  • [ ] 72-74H,swift教程示例
  • [ ] 75-76H,CSS和界面设计
  • [ ] 77-79H,博客iOS客户端

day19[计划],2016-7-11

  • [ ] 87-90H,完成博客iOS客户端

100小时编程挑战

缘由

习得一个技能的方法,无非是尝试着用它去做点东西出来,这就是践行。但既然是学习,就会遇到问题,有时学习曲线过于陡峭,或者没有计划、漫无目的,难免半途而废。

搞定编程也是一样的,你不可能一蹴而就,需要持续的学习、实践,真的做出有用的东西来,解决问题、用到生活中,才能保持学习的热情。这就是我进行#100小时编程挑战#的原因,利用100小时去学习相关知识,撸起袖子敲键盘,做出能上线的网站或者其他应用。

坦白的讲,我曾多次尝试学习编程,但是都没有坚持下来。前一阵看见笑来老师在朋友圈说,正常人在两个月内就能成为全栈工程师,并且准备搞个培训班。看后心潮澎湃,准备加入,结果发现和自己早已确定的美国旅行时间冲突,可能无法参加。跟着笑来老师博客里的文章一步步学习,竟也在一两天内学习了命令行、GitHub,并搭建了这个博客。人的潜力是无限的,既然无法加入培训,就自我学习,自我驱动,开启自己的挑战。

目标

100小时编程挑战的目标是,通过完成总计100小时的编程学习,创建一个个人博客网站,及另外一个可上线的项目或其他应用。在此期间做时间记录,通过博客来总结学习内容,把项目同步到GitHub上。通过计划-行动-总结的不断循环,来持续积累编程技能。

之前已经了解了Python的基本语法,100小时主要的学习内容为:Flask,Bootstrap, javascript,HTML/CSS及其他实践过程中发现的关键内容。前50小时,完成Flask制作的博客,并部署上线。后50小时,构思并完成另外一个可用项目。

由于7月中旬开始有一个月在美国旅行,中间不能有充裕时间编程。目前可支配时间比较多,从2016年6月23日开始,有将近20天时间空闲。除去有安排的那些天,大概有14天时间。也就意味着,要在去美国前完成挑战,每天我需要大概8小时的高强度学习。

计划

  • 可支配日期:每天8小时。上午3小时,下午3小时,晚上2小时。
  • 有特殊安排日期:晚上2小时。
  • 6月23日开始,在7月14日前完成100小时挑战。
  • 上线两个可访问项目。
  • 每日计划时间开支,每日撰写一篇学习博客进行总结,每日更新GitHub。

启动

时间对于每个人,它所代表的价值完全不同。有的人目标明确,日复一日花时间为实现目标,进行有效积累。他的时间的质量与密度就远远超过了其他人。耐心的花时间去做有意义的事情,就会产生复利效应,对于编程的学习同样如此。

无数次后悔自己浪费了时间,虚度人生一事无成;或者在刷知乎、微博、公众号后,发现自己求知成瘾,却无作品的尴尬。接受过去的现实,踏实的行动起来,最怕时间过去了,还是没有掌握一些技能。「种一棵树最好的时间是十年前,其次是现在」。

对于我这样的计划,有的人会觉得过于激进,强度太大,难以完成。但每个人可支配时间不同,学习能力也因人而异,我们走着看吧,也不是多大的事儿,耐心淡定就能完成。

做了再说,2016年7月14,看结果吧。