We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.
j3n5en / blog_articles Goto Github PK
View Code? Open in Web Editor NEWMy blog articles
My blog articles
作为一名Pyer,我非常python的简洁语法。也希望在写前端JS是能用到类似他的语法。。。。于是碰到了coffeescript。coffeescript能让我们编写更少的代码,使得语言特性更强。其实最爽的是写js让我感觉我在写python,不用再管那些麻烦的符号。。哇哈哈哈。。。。。
那么壮士,让我们干了这杯热 ~~ 翔 ~~ 咖啡吧。。
我们先来看看官方给的demo
左边是 CoffeeScript, 右边是编译后输出的 JavaScript.
代码量骤减啊,有木有!!其实他也大概讲到了一些常用的要点了。赋值,条件语句,函数,数组,对象,等。。。下面我就分开来仔细说说把。
CoffeeScript的单行注释是#,多行注释是###,
#编译前
array = [1, 2, 3, 4, 5, 6]
obj =
name: 'xxx'
age: 10
//编译后
var array, obj;
array = [1, 2, 3, 4, 5, 6];
obj = {
name: 'xxx',
age: 10
};
CoffeeScript里面的代码块都不需要花括号{}来包裹,而是通过换行和缩进来控制的。很优雅,right?
函数声明
用一组可选的圆括号包裹的参数, 一个箭头, 一个函数体来定义函数
#编译前
square = (x) ->
x * x
//编译后
var square;
square = function(x) {
return x * x;
};
#没参数 包括参数的括号可要可不要
square = -> # or square =() ->
x * x
##多参数
square = (x, y) ->
x * y
# 默认参数值 如果有多个参数的话,必填参数在前,默认参数在后!
square = (x, y = 2) ->
x * y
#编译前
if a
a()
else if b
b()
else
c()
//编译后
if (a) {
a();
} else if (b) {
b();
} else {
c();
}
什么?不够简洁??那么来试试if/unless后置写法吧.
#编译前
a() if a
b() unless a
//编译后
if (a) {
a();
}
if (!a) {
b();
}
不够后置写法只能单操作呢,,,,也就是不能判断后运行几条操作.
还有三目运算呢....
#编译前
date = if a then b else c
//编译后
date = a ? b : c;
可读性好强把,
对象的遍历
对象的遍历只要拿到key和value就可以做爱做的事了。嘿嘿
先看操作前置写法:
#编译前
obj =
name: 'xxx'
age: 10
console.log key + ':' + value for key,value of obj
#编译后
var key, obj, value;
obj = {
name: 'xxx',
age: 10
};
for (key in obj) {
value = obj[key];
console.log(key + ':' + value);
}
多操作还是得这样写,注意缩进
#编译前
obj =
name: 'xxx'
age: 10
for key,value of obj
console.log key + ':' + value
alert key + ':' + value
#编译后
var key, obj, value;
obj = {
name: 'xxx',
age: 10
};
for (key in obj) {
value = obj[key];
console.log(key + ':' + value);
alert(key + ':' + value);
}
如果你希望仅迭代在当前对象中定义的属性,通过hasOwnProperty检查并避免属性是继承来的,可以这样来写:
#编译前
obj =
name: 'xxx'
age: 10
for own key,value of obj
console.log key + ':' + value
#编译后
var key, obj, value,
__hasProp = {}.hasOwnProperty;
obj = {
name: 'xxx',
age: 10
};
for (key in obj) {
if (!__hasProp.call(obj, key)) continue;
value = obj[key];
console.log(key + ':' + value);
}
for item in array
for key of obj
所谓的推导式其实就是在遍历数组进行操作的同时,将操作后的结果生成一个新的数组。注意啊,这里仅仅是操作数组,对象可不行。
看例子:将每个数组的每个元素进行+1
#编译前
array = [1, 2, 3, 4]
addOne = (item)->
return item + 1
newArray = (addOne item for item in array)
#编译后
var addOne, array, item, newArray;
array = [1, 2, 3, 4];
addOne = function(item) {
return item + 1;
};
newArray = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = array.length; _i < _len; _i++) {
item = array[_i];
_results.push(addOne(item));
}
return _results;
})();
推导式的代码就一行,但是编译到JavaScript,大家可以看到节省了大量的代码,而且从CoffeeScript代码上,我们一眼就看出了代码功能。
当然了,实际上推导式不可能就这样简单:遍历所有元素进行操作。有时候得进行一些过滤。CoffeeScript里面也提供了这些功能,看例子:
#编译前
array = [1, 2, 3, 4]
addOne = (item)->
return item + 1
newArray = (addOne item for item,i in array)
newArray1 = (addOne item for item,i in array when i isnt 0) #过滤掉第一个元素
newArray2 = (addOne item for item,i in array when item > 3) #过滤掉小于4的元素
newArray3 = (addOne item for item,i in array by 2) #迭代的跨度
#编译后
var addOne, array, i, item, newArray, newArray1, newArray2, newArray3;
array = [1, 2, 3, 4];
addOne = function(item) {
return item + 1;
};
newArray = (function() {
var _i, _len, _results;
_results = [];
for (i = _i = 0, _len = array.length; _i < _len; i = ++_i) {
item = array[i];
_results.push(addOne(item));
}
return _results;
})();
newArray1 = (function() {
var _i, _len, _results;
_results = [];
for (i = _i = 0, _len = array.length; _i < _len; i = ++_i) {
item = array[i];
if (i !== 0) {
_results.push(addOne(item));
}
}
return _results;
})();
newArray2 = (function() {
var _i, _len, _results;
_results = [];
for (i = _i = 0, _len = array.length; _i < _len; i = ++_i) {
item = array[i];
if (item > 3) {
_results.push(addOne(item));
}
}
return _results;
})();
newArray3 = (function() {
var _i, _len, _results;
_results = [];
for (i = _i = 0, _len = array.length; _i < _len; i = _i += 2) {
item = array[i];
_results.push(addOne(item));
}
return _results;
})();
CoffeeScript构建的**,借鉴了很多Python和Ruby的。比如现在所说的切片功能。
切片其实就是对数组的截断,插入和删除操作。说白了就是用JavaScript的数组slice和splice函数操作数组。还是先简单说明一下这两函数吧。注意啊,这里讲的JavaScript。
slice(start,end)
var array = [1, 2, 3, 4, 5];
var newArray = array.slice(1); //newArray: [2,3,4,5]
var newArray1 = array.slice(0, 3); //newArray1: [1,2,3]
var newArray2 = array.slice(0, -1); //newArray2: [1,2,3,4]
var newArray3 = array.slice(-3, -2); //newArray3: [3]
var array = [1, 2, 3, 4, 5]
var newArray = array.splice(1);//array=[1] newArray=[2,3,4,5]
var newArray1 = array.splice(0, 2);//array=[3,4,5] newArray1=[1,2]
var newArray2 = array.splice(0, 1, 6, 7);//array=[6,7,2,3,4,5] newArray2=[1]
好了,回到CoffeeScript,看看所谓的切片。
#编译前
array = [1, 2, 3, 4]
newArray = array[0...2] #newArray=[1,2]
newArray1 = array[0..2] #newArray1=[1,2,3]
newArray2 = array[..] #newArray2=[1,2,3,4]
newArray3 = array[-3...-1] #newArray3=[2,3]
#编译后
var array, newArray, newArray1, newArray2, newArray3;
array = [1, 2, 3, 4];
newArray = array.slice(0, 2);
newArray1 = array.slice(0, 3);
newArray2 = array.slice(0);
newArray3 = array.slice(-3, -1);
CoffeeScript里面是这样操作数组的。
#编译前
array = [1, 2, 3, 4]
array[0...1] = [5] #array=[5,2,3,4]
array[0..1] = [5] #array=[5,3,4]
#编译后
var array, _ref, _ref1;
array = [1, 2, 3, 4];
[].splice.apply(array, [0, 1].concat(_ref = [5])), _ref;
[].splice.apply(array, [0, 2].concat(_ref1 = [5])), _ref1;
其实就是把两个下标之间的元素替换成新的数据。同样的,... 不包括结束位置的元素,.. 包括结束位置的元素
好了~
coffeescript的知识点大概就这些了....漏了再补吧..哈哈哈....
happy hacking!
Let's Encrypt在2015/12/3开放了公测,先来介绍一下Let's Encrypt,它是Mozilla、Facebook、Cisco等很多巨头为了推动Web加密而新建的一个项目。more : https://letsencrypt.org/ 来看看我的成果图
先来签证吧。
由于nginx 默认还不支持nginx自动配置。so。手动呗。。
git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
#letsencrypt-auto certonly --webroot -w /var/www/example/ -d www.j3n5en.com -d j3n5en.com -d blog.j3n5en.com
-w 后面跟的是到时letsencrypt验证的web临时目录。
再打开一个终端,进入/var/www/example 运行python的SimpleHTTPServer
python -m SimpleHTTPServer 80 #监听80端口
然后再运行
letsencrypt-auto certonly --webroot -w /var/www/example/ -d www.j3n5en.com -d j3n5en.com -d blog.j3n5en.com
OK~不出错的话 证书和私钥都在/etc/letsencrypt/live/$domain
里面.
接着我们配置nginx (确保nginx为最新版我现在是nginx/1.9.7)
server {
listen 80;
location / {
return 301 https://$host$request_uri;
}
}
#301 重定向 -> 强制 https
server {
listen 443 ssl http2;
#这里开启了http2
server_name j3n5en.com;
ssl on;
ssl_certificate /etc/letsencrypt/live/www.j3n5en.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.j3n5en.com/privkey.pem;
#这里指向刚刚生成的证书和私钥
#下面省略其他配置.
ok ~
http2 + https 装逼指南到此结束
我叫J3n5en,一名来自广东的计算机爱好者,沉迷于前端技术、Pyhon开发。
是一个经常笑,却不是一个经常开心的怪咖。
凡事3分钟热度的怪兽。
喜欢萝莉的怪蜀黍。
我们都是赌徒,用宝贵的青春去赌未知的未来。
Email: [email protected]
Blog: http://blog.j3n5en,com
今天是计划的第三天。ok~继续!
这里主要讲得是Flask-WTF扩展的使用。
安装: pip install flask-wtf
为了CSRF保护,Flask-WTF需要程序设置一个密钥。
#hello.py
app = Flask(__name__)
app.config['SECRET_KEY'] = 'Some string,By J3n5n'
app.config字典可以用来存储框架、扩展、程序的配置变量。
在Flask-WTF里,每个表单都由一个继承自Form的类表示,这个类定义表单中的一组字段。每个字段都用对象表示。字段对象可以附属一个或多个验证函数。验证函数用来验证用户提交的输入是否符合要求。
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtfforms.validators import Required
class NameForm(Form):
name = StringField(u'请输入您的用户名', validators=[Required()]) # 文本字段 相当于<input>中的 type='text' 。
submit = SubmitField(u'提交') # 提交按钮 相当于<input>中的type='submit'
StringField
构造函数中的可选参数validators指定一个有验证函数组成的列表,在接受用户提交的数据之前验证数据。验证函数Required()确保提交的字段不为空。
WTForms支持的HTML标准字段
字段类型 | 说明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
DateField | 文本字段,datetime.date |
DateTimeField | 文本字段,datetime.datetime |
IntegerField | 文本字段,整数 |
DecimalField | 文本字段,decimal.Decimal |
FloatField | 文本字段,浮点 |
BooleanField | 复选框,True/False |
RadioField | 一组单选框 |
SelectMultipleField | 下拉列表,可多选 |
SelectField | 下拉列表 |
FileField | 文件上传字段 |
submitField | 提交按钮 |
FormField | 把表单作为一个表单,嵌入另一个表单 |
FieldList | 一组指定类型的字段 |
WTForms内建的验证函数
验证函数 | 说明 |
---|---|
验证Email | |
EqualTo | 比较两个字段的值 |
IPAddress | 验证IPv4网络地址 |
Length | 验证长度 |
NumberRange | 数字范围 |
Optional | 无输入值时跳过其他函数 |
Required | 确保不为空 |
Regexp | 使用正则验证输入值 |
URL | 验证URL |
AnyOf | 确保输入值在可选的列表中 |
NoneOf | 确保输入值不在可选的列表中 |
假设视图函数把一个NameForm实例通过参数form传入模板,在模板中可以生成一个简单的表单
<form method='POST'>
{{ form.hidden_tag() }}
{{ form.name.label }}{{ form.name() }}
{{ form.submit() }}
</form>
那我们怎么为字段指定id和class呢?
<form method='POST'>
{{ form.name.label }}{{ form.name(id='username') }}
{{ form.submit() }}
</form>
#hello.py
@app.route('/',method=['GET', 'POSt'])
def index():
name = None
form = NameForm()
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template("index.html", form=form, name=name)
validate_on_submit()
会验证所有用户提交的数据。符合这返回True
.
@app.route('/',method=['GET','POST'])
def index():
form = NameForm()
if form.validate_on_submit():
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html',form=form,name=session.get('name'))
session.get('name')
会从会话中读取name参数的值。当值不存在时返回None而不是返回异常。
一般请求完成后,需要用户知道状态发生了变化,就像是确认信息,错误信息,警告,提示,之类的。这种功能就是Flah消息的核心特性。
from flask import Flask,render_template, session, redirect, url_for, flash
@app.route('/')
def index():
form = NameForm()
if form.validate_on_submit():
old_name = session.get('name')
if old_name is not None and old_name != form.name.date:
flash(u'修改了用户名')
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html',form=form, name=session.get('name'))
仅仅调用Flash并不能把消息显示出来。还需要把get_flashed_messages()
函数开放给模板,用来获取渲染消息。
{% block content %}
<div class='container'>
{% for msg in get_flashed_messages() %}
{{msg}}
....
</div>
{% endblock %}
Happy Hacking !
今天是计划的第二天。ok~继续!
Flask中使用的是Jinja2,当初我在使用Django的时候也是喜欢用这款模版引擎。这里就当是巩固学习把。
默认情况下,Flask程序会在根目录下的templates子文件夹寻找模版。我们把之前的hello.py中的模板保存到templates文件夹中、分别命名为index.html 和 user.html
# hello.py
from flask import Flask , render_template
# ...
@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
renturn render_template('user.html' , name=name)
Flask中的render_template把Jinja2模板引擎集成到程序中。render_template函数的第一个参数是模板的文件名。随后的参数都是键值对。
在模板中使用 {{ name }}结构表示一个变量。Jinja2 能识别所有类型的变量(甚至列表,字典,对象)。
<p> 一个来自字典的值 {{ onedict['key'] }} .</p>
<p> 一个来自列表的值 {{ onelist[2] }} .</p>
<p> 一个来自对象的值 {{ oneobj.somemethod() }} .</p>
Jinja2 中还内置了一些常用的过滤器。过滤器名称加在变量名后面,中间用竖线( "|" )分隔。例如
Hello, {{ name | capitalize }}
除了这个首字母转换大写以外,Jinja2还内置了其他过滤器如下表。
过滤器名 | 说明 |
---|---|
safe | 渲染值的时候不进行转义 |
capitalize | 把值转换成首字母大写其他小写 |
lower | 把值转换成小写 |
upper | 把值转换成大写 |
title | 把值中的每个单词的首字母都转换成大写 |
trim | 值的首尾去空格 |
striptags | 渲染前把值中的所有HTML标签都去掉 |
{% if user %}
Hello , {{ user }} !
{% else %}
Hello , Stranger !
{% endif %}
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_template(comment) }}
{% endfor %}
</ul>
为了重复使用宏,可以将宏保存到一个单独的文件中,需要时在进行导入
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ render_template(comment) }}
{% endfor %}
</ul>
需要多处利用的代码片段可以使用
{% include 'comment.html' %}
或着使用强大的模板继承。
jinja2中的模板继承有点像Python中的类继承。首先创建一个名为base.html
的基模板
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - J3n5en's blog </title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
再创建这个基模板的衍生模板:
{% extends 'base.html' %}
{% block title %}Home{% endblock %}
{% block head %}
{{ super() }}
<style>
*{margin:0;padding:0}
</style>
{% endblock %}
{% block body %}
<h1> Hello , World !</h1>
{% endblock %}
如果用户输入了不可用的路由,那么将会显示一个状态码为404的错误页面。但是默认的404页面奇丑无比。那我们就只能让其丑下去?当然不是。我们可以自定义错误页面。例如:
@app.errorhandler(404)
def page_not_found(e):
retuan render_template('404.html'), 404
在jinja2中链接到其他路由。可以使用url_for()
在
@app.route('/')
def index():
pass
中,在模板中使用url_for('index')
得到的是 /
,使用url_for('index' , _external=True)
的话得到的则是绝对地址,http://localhost:5000/
生成动态的地址时,可以将动态部分作为关键字传入。例如 url_for('user', name='J3n5en',_external=True)
还能将任何额外参数添加到查询字符串中,例如 url_for('index', page=2)
得到的地址是/?page=2
为了处理静态文件,Flask中有一个特殊的路由即 /static/<filename>
例如调用 url_for('static', filename='css/style.css' , _external=True)
返回的地址是 http://localhost:5000/static/css/style.css
。
#EOF#
今天是计划的第五天。今晚顾着看我是歌手,竟然拖到这么晚。。。
很多时候我们都希望我们的程序能通过Email与用户进行交流。在这里我们利用Flask-Mail来实现
安装 pip install flask-mail
Flask-Mail SMTP服务器的配置
配置 | 默认值 | 说明 |
---|---|---|
MAIL_SERVER | localhost | Email服务器的ip地址或者主机名 |
MAIL_PORT | 25 | Email服务器端口 |
MAIL_USE_TLS | False | 启用传输层安全协议 |
MAIL_USE_SSL | False | 启用安全套接曾协议 |
MAIL_USERNAME | None | Email用户名 |
MAIL_PASSWORD | None | Email密码 |
from flask.ext.mail import Mail
# email setting
app.config['MAIL_SERVER'] = 'mail.xxx.com'
app.config['MAIL_USERNAME'] = 'username'
app.config['MAIL_PASSWORD'] = 'pwd'
mail = Mail(app) # 这个要在email setting后面,当初这个坑了我
注:在实际生产中我们应该把账号信息等重要信息保存到环境变量中,而不是程序里。
python mail.py shell
>>> from flask.ext.mail import Message
>>> from mail import mail
>>> msg = Message('qweq',sender='[email protected]',recipients=['[email protected]'])
>>> msg.body = 'text body'
>>> msg.html = '<b>HTML</b> body'
>>> with app.app_context():
mail.send(msg)
为了更方便,更灵活地发送右键,我们可以把程序发送Email的通用部分抽出来定义成一个函数,那么以后我们就可以用Jinja2模板渲染邮件正文。
#mail.py
from flask.ext.mail import Message
app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <[email protected]>'
def send_email(to, subject, template, **kwargs):
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
mail.send(msg)
这样我们就能够很简单地调用这个函数进行Email的发送,例如:
app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')
#...
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
if user is None:
user = User(username=form.name.data)
db.session.add(user)
session['known'] = False
if app.config['FLASKY_ADMIN']:
send_email(app.config['FLASKY_ADMIN'], 'New User',
'mail/new_user', user=user)
else:
session['known'] = True
session['name'] = form.name.data
form.name.data = ''
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'), known=session.get('known', False))
在运行上面的例子时,我们可以明显发现发送邮件时,程序被堵塞了,浏览器就像失去了响应一样。为了避免这种情况,我们可以把发送Email的函数移到后台的线程里。
from threading import Thread
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def send_email(to, subject, template, **kwargs):
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
thr = Thread(target=send_async_email, args=[app, msg])
thr.start()
return thr
Happy Hacking !
嘤嘤嘤.....双十一剁手剁了一个显示屏,想右边打代码左边即时显示....
这时可能会想到livereoad ~ ~......的确,配合浏览器插件来使用,livereload是一个很好用的自动刷新软件.
但是这篇博文的主角是 "browsersync" 一个更新,更方便的开发工具... 这里给出中文网 http://www.browsersync.cn
Browsersync可以在不同的浏览器,不同设备下自动刷新修改的页面,更神器的是在其中一个浏览器中的动作可以同步到其他浏览器,其他设备中.
而且修改css后,browsersync不是刷新整个页面,而是重新请求该css,并将修改更新到页面中....
-安装Node.js
由于Browsersync是基于node.js的,,,,,,so....先安装好node.js和npm吧.
-安装BrowserSync
#全局npm install -g browser-sync#当然,结合用gulp或者grunt的npm install --save-dev browser-sync
-使用
browser-sync start --server --files "css/*.css"
这个命令用于纯静态站点,也就是仅一些.html
文件的情况。后面的--files "css/*.css"
,是指监听css
目录中的后缀名为.css
的文件。请注意这个命令里的start --server
,这其实是BrowserSync自己启动了一个小型服务器。
如果是动态站点,则使用代理模式。例如PHP站点,已经建立了一个本地服务器如http://localhost:8080,此时会是这样的命令:
browser-sync start --proxy "localhost:8080" --files "css/*.css"
BrowserSync会提供一个新地址用于访问。
从BrowserSync的命令来看,很重要的一点就是通过--files
指定需要监听的文件。有关这里的文件匹配模式(称为glob)的详情,请参考isaacs's minimatch。
经过尝试,如果简单只是想要监听整个项目,可以写成这样:
browser-sync start --server --files "**"
下面是一个BrowserSync的控制台输出示例:
可以看到还有一个叫做UI的一个地址,它是BrowserSync提供的一个简易控制面板。BrowserSync最常用的几个配置选项,都可以在这个面板里调整。
在面板里面你还会发现那个经典的远程调试工具weinre也在这:
你可能会发现browsersync不像livereload需要安装浏览器插件,
这是因为browsersync会新建一个服务器对你的文件进行预览,并在页面中插入JS,
而这段JS用来新建web socket连接,一旦有监听的文件发生变化,BrowserSync会通知浏览器.
更多的玩法?读文档去吧!!!
happy hacking ~ ~ ~
Time waits for no one.
有花堪折直须折,莫待无花空折枝。
女主角绀野因偶然的机会而获得了能够穿梭时空的作弊技能,而且还拥有“怎么跑、怎么跳都不会走光的反重力裙”神级装备
作为一个17岁的少女,尚未沾染上社会的险恶与世故。
为了吃到布丁 , 跑! 跳!施展技能回去吃!
为了唱k唱久一点,跑!跳!施展技能回去唱!
为了吃到铁板烧,跑!跳!施展技能回去吃!
.......
为了逃避被告白的尴尬场景,跑!跳!施展技能回去逃避!
挺喜欢这样的设定的。
正值青春的我们不也是这么莽撞率真吗?想到就做的直爽,还有一往直前的意气奋发,而这个世界是我的,只需要不断向前奔跑,一定就能到达想去的地方。
未卜先知的力量乍看之下虽为绀野带来不少便利,让她跳脱了现世规则之外。
暂时得以操弄超然力量来掌控生活周遭瞬息万变的人际关系。
但人生毕竟不是照著公式来运作,绀野每每遇到自己处理不来的窘境便试图重新回到原点强加以扭转改变,反而却将局势弄得越来越僵。
显见即使身负绝世超能力,真琴还是逃脱不出青春的因果定律,反倒是更加深陷其中。
最喜欢的那一段是绀野发现自己还有一次穿梭时空的机会,她奋力地向前跑,面朝一个城市的灯火辉煌,为着心中的少年而无畏向前。
在时空穿梭间,无数的画面显现,与他相识的过程不断在眼前闪现。他刚刚来到学校的样子,与功介和自己的相识,三个人一起玩耍嬉闹、无忧无虑的时光。
那些记忆,都在闪闪发光,明明看似那么普普通通微不足道,可又那么令人怀念。
最后,是三个人一起撑伞的画面,伴着一句曾令自己脸红心跳的话,响彻心头。
试想,倘若我也用拥有了能够穿梭时空的能力。我会去做什么呢?
是像女主那样穿梭回去吃布丁?还是像其他人说的那样,穿梭回去买彩票?
是像女主那样穿梭回去尽兴地唱一次K?还是像穿越小说一样,把未来的技术带回过去,开挂的人生,开辟自己的天地?
是像女主那样穿梭回去吃铁板烧?还是像别人的作文一样,穿梭回去,把自己过去的错误一一纠正。
。。。
我想,我会哪里都不去。因为我认为“现在就是最好的时光”。
过去的错,就让他错了吧,或许正因为这些错,才有更正确的未来。
能穿梭时光,那便拥有了未卜先知、预知未来的能力,
但我并不希望我的未来是已知的,我希望他是神秘的,是变幻莫测的。
我并不对我能到达哪个“位置”感兴趣,我只对在到达那个“位置”途径的风景感兴趣。
我就是我,我不会去改变,现在的我,就是最好的我。
那么,*年,珍惜当下吧。
珍惜现在的时光,珍惜现在所拥有的,珍惜身边的人(嗯,也就是在你身边的我)。
#EOF#
之前我的博客一直用的是DjangoUeditor作为我的富文本编辑器,然而由于种种原因 djangoueditor 的代码高亮失效了,
后来发现了simditor,UI 好有feel,就懒得改 djangoueditor 了,,直接换 simeditor,,,在这里记录一些过程。。。。
来来来,,,,我们先看看她那撩人的外表
开始结合吧!!
#admin.py
#引入simditor文件
from django.contrib.staticfiles.storage import staticfiles_storage
def _wrap(*args, **kwargs):
return tuple(staticfiles_storage.url(path) for path in args)
class SimditorMixin(object):
class Media:
js = _wrap(*('scripts/jquery.min.js',
'scripts/module.js',
'scripts/hotkeys.js',
'scripts/uploader.js',
'scripts/simditor.js'))
css = {
'all': _wrap(*('styles/simditor.css',
))
}
class BlogAdmin(SimditorMixin , admin.ModelAdmin):
`其他内容`
admin.site.register(Blog,BlogAdmin)
#admin/templates/change_form.html
#用了 bootstrap_admin 的自己换成对应目录咯
#启用 simditor
<script>
var $editor = $('#editor')#注意这里
if(!$editor.length){}
else{
(function() {
$(function() {
var editor, mobileToolbar, toolbar;
toolbar = ['title', 'bold', 'italic', 'underline', 'strikethrough', 'color', '|', 'ol', 'ul', 'blockquote', 'code', 'table', '|', 'link', 'image', 'hr', '|', 'indent', 'outdent'];
mobileToolbar = ["bold", "underline", "strikethrough", "color", "ul", "ol"];
if (mobilecheck()) {
toolbar = mobileToolbar;
}
return editor = new Simditor({
textarea: $editor,
toolbar: toolbar,
pasteImage: true,
defaultImage: "{% static 'img/image.png' %}",
});
});
}).call(this);
}
</script>
对于代码高亮的问题,我使用了highlightjs,,,so 在 base.html引入highlightjs的内容,然后加上
#这里是highlightjs的内容
<script>
$(document).ready(function(){
hljs.configure({useBR: true});
$("pre[class^='lang']").each(function(i, block){
hljs.highlightBlock(block);
});
hljs.initHighlightingOnLoad();
});
</script>
好了,,,快去看看成果吧!!!!
今天是大年初一,我将在这几天里,每天抽出至少100分钟来学习Flask Web开发,并记录过程。
所读教材:《Flask Web 开发》
使用编辑器:Pycharm。
编程环境:win10 + Python2.7
安装Flask和安装其他Python包一样可以使用pip进行快速的安装pip install flask
所有的Flask程序都必须创建一个程序实例。Web服务器使用WSGI (Web Server Gateway Interface)协议,把所有的请求都转给Flask程序实例这个对象进行处理。一般使用下列代码创建:
from flask import Flask
app = Flask( __name__ )
在Flask中定义路由的最简便方式,是使用实例提供的 @app.route
装饰器。把装饰的函数注册为路由。
@app.route('/')
def index():
return '<h1>hello World </h1>''
这里的 index()
用来返回响应,这样的函数被称为视图函数。
除了这种简单的URL,Flask当然也支持动态的URL。
@app.route('/user/<username>')
def user(username):
return '<h1> Hello %s ! </h1>' % username
实例用 run
方法启动Flask集成的开发Web服务器
if __name__ == '__main__':
app.run(debug=True)
#hello.py
# coding:utf-8
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1> hello word ! </h1>'
@app.route('/user/<name>')
def user(name):
return '<h1>hello %s </h1>' % name
if __name__ == '__main__':
app.run(debug=True)
然后使用 python hello.py
启动程序
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<h1>Your broswer is %s </h1>' % user_agent
Flask上下文全局变量
变量名 | 上下文 | 说 明 |
---|---|---|
current_app | 程序上下文 | 当前激活的程序的程序实例 |
g | 程序上下文 | 处理请求时用作临时储存的对象,每次请求都会重设 |
request | 请求上下文 | 请求对象,封装了客户端发出的HTTP请求中的内容 |
session | 请求上下文 | 用户回话,用于存储请求之间需要记住的值的词典 |
@app.before_request
def before_request():
pass
除了像之前一样返回字符串以外,我们还可以返回状态码,
``` python
@app.route('/')
def index():
return '<h1> Bad Request </h1>' , 400
```
还可以设置响应的headers (一般不这样做)。
``` python
from falsk import make_response
@app.route('/')
def index():
response = make_response("<h1>set cookies</h1>")
response.set_cookie('man' , 'j3n5en')
return response
```
重定向响应 -> redirect()
``` python
from flask import redirect
@app.route('/')
def index():
return redirect("http://j3n5en.com")
```
处理错误的响应:
``` python
from flask import abort
@app.route('/user/<name>')
def get_username(name):
user = load_user(name)
if not user:
abort(404)
return '<h1>hello %s !</h1>' % name
```
这样的话当用户不存在就会抛出404异常。
Flask-Script是一个为Flask程序添加命令行解析器的插件。
首先用`pip install flask-script` 进行安装。
``` python
# coding:utf-8
from flask import Flask
from flask.ext.script import Manager
app = Flask(__name__)
manager = Manager(app)
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<h1>Your broswer is %s </h1>' % user_agent
if __name__ == '__main__':
manager.run()
```
#EOF#
SAE上是没有目录读写权限的,所以要在SAE使用Ueditor的图片上传功能需要借助SAE的Storage服务。并对DjangoUeditor 进行修改,,,
1、开通Sae的Storage功能:
在SAE控制台开通Storage服务,并新增一个domain,按需求填写即可。
2、修改 DjangoUeditor 的代码:
# DjangoUeditor/Views.py
# 保存上传图片的部分
# 原因:将图片上传至Storage并返回图片地址
# 注意修改 domain
#coding:utf-8
def save_upload_file(PostFile,FilePath):
try:
f = open(FilePath, 'wb')
for chunk in PostFile.chunks():
f.write(chunk)
except Exception,E:
f.close()
return u"写入文件错误:"+ E.message
f.close()
return u"SUCCESS"
# 改为:
def save_upload_file(PostFile,FilePath):
try:
import sae.const
access_key = sae.const.ACCESS_KEY
secret_key = sae.const.SECRET_KEY
appname = sae.const.APP_NAME
domain_name = "注册的domain"
import sae.storage
s = sae.storage.Client()
ob = sae.storage.Object(PostFile)
#此处返回的url是文件在Storage上的url
url = s.put(domain_name, FilePath, ob)
return u"SUCCESS",url
except Exception,e:
return u'FAIL'
# 注释以下两行代码
# DjangoUeditor/Views.py
# 原因:sae没有读写权限,不能makedirs
#
# if not os.path.exists(OutputPath):
# os.makedirs(OutputPath)
#所有检测完成后写入文件
if state==u"SUCCESS":
#改为
if state!=u"FAIL":
# 原因:由于上传成功还会返回url所以不能通过SUCCESS来判断是否上传成功
state=save_upload_file(file,os.path.join(OutputPath,OutputFile))
# 改为:
state,url=save_upload_file(file,os.path.join(OutputPath,OutputFile))
# 作用:
# 提取上传状态和图片地址
'url': urllib.basejoin(USettings.gSettings.MEDIA_URL , OutputPathFormat) , # 保存后的文件名称
# 修改为:
'url': url, # 保存后的文件地址
# 作用:原来的是返回文件名,修改后返回文件地址
这样你就可以在SAE上通过Ueditor将图片上传到SAE Storage上去了。
enjoy it!~~
在双十一剁了一本《Python Web开发测试驱动方法》。从11.16看了1、2个星期。感觉吸收的不多。效率太低了。前几天看了个文章叫“写作驱动学习”。感觉有点道理。so,决定重头再看,一边看一边写笔记。希望能有不错的效果。
环境:debian + python3 + django1.9 + selenium
我的情况:懂python(在写这篇文章之前一直用python2),略懂django
TDD :测试驱动开发(Test-driven development)是极限编程中倡导的程序开发方法,以其倡导先写测试程序,然后编码实现其功能。
在TDD过程中第一步始终是:编写测试。
也就是说,我们首先要先编写测试程序,and run,看看是否和预期一样失败,只有失败了才能去编写程序 to fix the error。
ok~ 介绍TDD就介绍到这里,开始安装django,here we go!
from selenium import webdriver
browser = webdriver.Firefox()
browser.get("http://localhost:8000")
assert 'Django' in browser.title
yo man what the hell are you doing?? 不是说安装django吗?
哈哈..看来你又忘了 在TDD过程中第一步始终是:编写测试。恩,跑起来报错了.of course~ 我们还没安装'占狗'嘛~
那就安装django :
pip install django # 现在是1.9版本
为了fix 前一个test的坑,这里我们需要新建一个项目并跑起django
django-admin startproject superlists
cd sperlists
manage.py runserver
跑起来后我们再运行测试程序看看。
Cool~ ~测试通过了。
为了纪念第一次测试成功,我们是不是应该把他保存下来呢?
https://coding.net/u/jensen/p/Python-web-TDD
Cool ~~
ok~ 我们继续完善我们的function_test.py
from selenium import webdriver
browser = webdriver.Firefox()
# j3n5en 进入首页
browser.get("http://localhost:8000")
# 标题有"TO-Do"字样
assert 'To-Do' in browser.title
# 在一个文本框输入 "购买显示屏"
# 点击回车,完成输入
# 表格中出现"1、购买显示屏"
# J再次输入“安装显示屏”
# 页面再次更新
# 页面中显示两条事项
# 滚去睡觉,关闭浏览器
browser.quit()
先做到显示标题有“To-Do”吧。不用说跑起来肯定是失败的(所谓的“预期失败”),然后我们通过自己的努力去修复这个错误。。。哇。。听着就爽。
➜ superlists :python3 function_test.py
Traceback (most recent call last):
File "function_test.py", line 7, in <module>
assert 'To-Do' in browser.title
AssertionError
不过我们先看看这个错误,我们只看到“AssertionError“这个没什么用的报错,也不知道哪里错,而且跑完了浏览器还是在打开状态。不爽。
处理方法有很多,但我们这里用unittest模块的现成方法试试去解决他把。
from selenium import webdriver
import unittest
class NewVisitorTest(unittest.TestCase):
def setUp(self): # 特殊方法,在测试前运行
self.browser = webdriver.Firefox()
def tearDown(self): # 特殊方法,在测试完成后运行。出错了也会运行
self.browser.quit()
def test_can_start_a_list_and_retrieve_it_later(self):
self.browser.get("http://localhost:8000") # j3n5en 进入首页
self.assertIn("To-Do",self.browser.title) # 标题有"TO-Do"字样
self.fail("finish the test!") # 不管是否测试成功都会输出
if __name__ == "__main__":
unittest.main(warnings='ignore')
恩,跑起来的确完成了想要的效果了。(哪里出错,结束时浏览器关闭)
万一机子慢,页面没加载完就进行判断了那怎么办? 可以加入
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3) # 等待3秒
这样的话就让selenium等待3秒,让页面出现.
创建django应用
python3 manage.py startapp lists
为此app编写一个单元测试
#lists/tests.py
from django.core.urlresolvers import resolve
from lists.views import home_page
from django.test import TestCase
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
我们测试下解析根目录时,是否能找到home_page函数.明显是不行的,因为我们的url和view都还没写,哇哈哈....
#superlists/urls/py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', 'lists.views.home_page', name="home"),
]
# lists/views.py
def home_page():
pass
好了,终于有写一点django的代码了....现在我们再来运行一下单元测试吧.
cool ~ 测试通过了.
# lists/test.py
from django.core.urlresolvers import resolve
from django.http import HttpRequest
from lists.views import home_page
from django.test import TestCase
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.func, home_page)
def test_home_page_returns_correct_html(self):
request = HttpRequest() # 创建HttpRequest对象(请求后django看到的就是HttpRequest对象)
response = home_page(request) # 把HttpRequest对象丢给home_page处理
self.assertTrue(response.content.startswith(b'<html>'))
self.assertIn(b'<title>To-Do lists</title>', response.content)
self.assertTrue(response.content.endswith(b'</html>'))
# 注意b'' 因为response.content是原始字符串,而不是python字符串,因此对比时要用b''
运行看看效果,Nope~ ~ TypeError: home_page() takes 0 positional arguments but 1 was given,看到错误了,我们就去编写代码,去修复他..这就是TDD的一个体现: 遇红 --> 变绿 --> 重构
现在我们遇到了 home_page() takes 0 positional arguments but 1 was given这个红,我们就去编写最少量的代码让他变绿。
# lists/views.py
def home_page(request):
pass
刚刚说不需要参数,却提供了一个参数,那么我们就使他需要参数.再运行测试看看
AttributeError: 'NoneType' object has no attribute 'content'
yes~ 刚刚的红变绿了,,但是又有一个红了。再写
# lists/views.py
from django.http import HttpResponse
def home_page(request):
return HttpResponse()
还有红,
self.assertTrue(response.content.startswith(b'<html>'))
AssertionError: False is not true
再来一发~ 哈哈,,终于全绿了,,,哈哈哈。再跑function_test.py。也是毫无错误。。。
哈哈,好刺激。成功编写一段HTML。。。。醉了。。
这一篇文就到这里。。
happy hacking~ ~
今天是计划的第四天。ok~继续!
这本书里面使用的数据库框架是Flask-SQLAlchmy。
依旧使用
pip
进行安装,pip install flask-sqlalchemy
在Flask-SQLAlchmy中,数据库使用URL指定。
数据库引擎 | URL |
---|---|
MySQL | mysql://usernam:password@hostname/database |
Postgres | posrpresql://usernam:password@hostname/database |
SQLite(Unix) | sqlite:////数据库的绝对路径 |
SQLite(Win) | sqlite:///c:/绝对路径 |
使用数据库时,必须把数据库的URL保存到Flask配置对象的SQLALCHEMY_DATABASE_URL
中。还要注意的一个配置选项是SQLALCHEMY_COMMIT_ON_TEARDOWN
,当设定为True时,每次请求结束后,都会自动提交数据库的变动。
from flask.ext.sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir,'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
db对象就是SQLAlchemy类的实例,表示程序使用的数据库,同时还获得了Flask-SQLAlchemy的所有功能。
#hello.py
#定义Role和User的例子
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(64),unique=True)
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
def __repr__(self):
return '<Role %r>' % self.username
其中__tablename__
定义在数据库中使用的表明,没有定义的话Flask将会使用一些默认的表名。
最常用的SQLAlchemy列类型
类型名 | Python类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般32位 |
SmallInteger | int | 取值范围小的整数,一般16位 |
BigInteger | int或long | 不限制精度的整数 |
Float | float | 浮点型 |
Numeric | decimal.Decimal | 定点数 |
String | str | 变长字符串 |
Text | str | 变长字符串,对长字符串坐了优化 |
Unicode | unicode | 变长unicode字符串 |
Unicode | unicode | 变长unicode字符串,对长做了优化 |
Boolean | bool | 布尔值 |
Date | datetime.date | 日期 |
Time | datetime.time | 时间 |
DateTime | datetime.datetime | 日期和时间 |
Interval | datetime.timedelta | 时间间隔 |
Enum | str | 一组字符串 |
PickleType | 任何的Python对象 | 自动使用Pickle序列化 |
LargeBinary | str | 二进制文件 |
最常用的SQLAchemy列选项
选项名 | 说明 |
---|---|
primary_key | 主键 |
unique | 唯一 |
index | 创建引索,提升查询效率 |
nullable | 能否为空 |
default | 定义默认值 |
注:Flask-SQLAchemy要求每个模型都要定义主键,这一列经常命名为id。
一对多。
class Role(db.Model):
# ...
users = db.relationship('User', backref='role')
class User(db.Model):
# ...
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
在Flask-SQLAlchemy中使用db.create_all()
创建表。
python hello.py shell
>>from hello import db
>>db.create_all()
如果数据库已经存在的话则不会重新创建或更新,那么粗暴的解决方法就是先删除,再重建。
db.drop_all()
db.create_all()
如下例子尝试插入users和roles的一些数据行:
>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=user_role)
models的构造函数接收属性值作为参数,注意虽然role属性被使用了,但它不是真正的数据库列,它只是一个高层次的one-to-many的relationship的展示。这些role的id都还没有被设置:因为它们是由Flask-SQLAlchemy来维护的,到目前为止它们只是一些Python对象:
>>> print(admin_role.id)
None
>>> print(mod_role.id)
None
>>> print(user_role.id)
None
所有数据库改动都被记录到了数据库提供的session中,这里你可以通过Flask-SQLAlchemy的db.session获取到它,为了把对象写到数据库它们必须先保存到session中:
>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)
或者更简单地:
>>> db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
然后你要把所有的数据库改动提交:
>>> db.session.commit()
这时可以检查id属性值:
>>> print(admin_role.id)
1
>>> print(mod_role.id)
2
>>> print(user_role.id)
3
数据库也能回滚操作,如果
db.session.rollback()
被调用,所有数据库session中的对象都会恢复到数据库中的状态。
数据库session中的add()方法同样也能被用于更新模型,如下的例子把role从“Admin”重命名为“Administrator”:
>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
可以使用session中的delete()方法来删除数据,更其他操作一样,删除也要通过session.commit()才能生效:
>>> db.session.delete(mod_role)
>>> db.session.commit()
最基本的model的查询操作是返回整个表格的数据:
>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
>>> User.query.all()
[<User u'john'>, <User u'susan'>, <User u'david'>]
你还可以通过配置过滤器来限制查询条件:
>>> User.query.filter_by(role=user_role).all()
[<User u'susan'>, <User u'david'>]
还可以获取到SQLAlchemy生成的原生的查询语句:
>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username,
users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'
如果关闭了shell窗口以后,之前创建的Python对象就都消失了,但是会存在于数据库表中。你可以开一个新的窗口,然后导入model并重建这些对象。如下一个未曾导入就尝试查询名字为“User”的role的例子:
>>> user_role = Role.query.filter_by(name='User').first()
常用的过滤器
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上, |
limit() | 使用指定的值限制原查询返回的结果数量 |
offset() | 偏移原查询返回的结果 |
order_by() | 排序 |
group_by() | 分组 |
常用查询函数
函数 | 说明 |
---|---|
all() | 所有 |
first() | 返回第一个没有则None |
first_or_404() | 返回第一个没有则终止请求返回404错误响应 |
get() | 返回指定对应主键的值,None |
get_or_404() | 返回指定对应主键的值,404 |
count() | 返回总个数 |
paginate() | 返回paginate对象,包含指定范围内的结果 |
其实前面部分的数据库操作可以直接在视图方法中使用
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
if user is None:
user = User(username = form.name.data)
db.session.add(user)
session['known'] = False
else:
session['known'] = True
session['name'] = form.name.data
form.name.data = ''
return redirect(url_for('index'))
return render_template('index.html',
form = form, name = session.get('name'), known = session.get('known', False))
每次用户提交name到后台应用程序会首先使用 filter_by() 去数据库查询,并且会有一个known变量被传递到前台用于format问候语。对应的模板改动如下,新的模板会使用known变量来新增一条问候语,对于第一次访问和多次访问的用户问候语内容会有不同:
{% extends "commonBase.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
<p>Happy to see you again!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
在shell中测试数据库操作,我们需要导入数据库实例db和对应的models,每次开一个新的shell都这样做未免显得繁琐了。Flask-Script的shell命令行能够配置成每次自动导入特定对象。为了把一些对象加入shell命令的导入列表,我们要给shell命令注册一个make_context的回调函数,具体如下:
from flask.ext.script import Shell
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
manager.add_command("shell", Shell(make_context=make_shell_context))
make_shell_context()方法注册了程序,数据库实例和models,所以它们都能自动被导入到shell中了:
>>> app
<Flask 'hello'>
>>> db
<SQLAlchemy engine='sqlite:///F:\\data.sqlite'>
>>> User
<class '__main__.User'>
开发进行到一定阶段,你会发现model的结构需要发生改变,相应的数据库表结构也应该要更新。Flask-SQLAlchemy调用create_all()来新建表当且只发生在这些表不存在的时候,因此更新表结构的唯一办法就是先删除旧的表,当让这样不可避免地会把所有存储的数据也一并销毁了。更好的做法是使用数据库迁移框架,就像代码版本控制工具会监控代码改动一样,一个数据库迁移框架能够跟踪数据库表的变化,并且能把新的改动应用到到旧的表中。在这本书中使用的是Flask-Migrate。
安装 pip install Flask-Migrate
初始化配置:
from flask.ext.migrate import Migrate, MigrateCommand
# ...
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
为了将数据库迁移的命令暴露出来,我们把MigrateCommand类添加到了Flask-Script的manager对象中,在这个例子中,暴露出来的MigrateCommand命令为db。在使用数据库迁移之前,需要首先通过init命令来创建一个迁移的资源库:
python hello.py db init
Creating directory /home/flask/flasky/migrations...done
Creating directory /home/flask/flasky/migrations/versions...done
Generating /home/flask/flasky/migrations/alembic.ini...done
Generating /home/flask/flasky/migrations/env.py...done
Generating /home/flask/flasky/migrations/env.pyc...done
Generating /home/flask/flasky/migrations/README...done
Generating /home/flask/flasky/migrations/script.py.mako...done
Please edit configuration/connection/logging settings in
'/home/flask/flasky/migrations/alembic.ini' before proceeding.
init命令创建了迁移的文件夹,所有迁移脚本都会被存储在这个文件件中。
在Alembic中,数据库迁移是通过migration脚本来完成的,这个脚本有两个方法分别叫做upgrade() 和downgrade()。upgrade()方法会把新的数据库改动作为迁移的一部分,而downgrade则移除最新的改动。通过添加和移除改动,Alembic能够配置数据库到任何历史节点上。
(venv) $ python hello.py db migrate -m "initial migration"
INFO [alembic.migration] Context impl SQLiteImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate] Detected added table 'roles'
INFO [alembic.autogenerate] Detected added table 'users'
INFO [alembic.autogenerate.compare] Detected added index
'ix_users_username' on '['username']'
Generating /home/flask/flasky/migrations/versions/1bc
594146bb5_initial_migration.py...done
检查并修正迁移脚本之后,我们可以使用db upgrade 来更新数据库了,你可以把data.sqlite删除以后再执行命令,你会发现删除的数据库通过migration命令重又建了。
前一篇笔记中,我们已经实际演练了基本的TDD流程,那么这篇笔记我们就先来谈谈编写这些测试的用处吧。
相信很多的读者和心中存在很多疑问:
如果井不是很深,那么打起水来是比较简单的 ( 就是说在开发小型项目的时候,并不困难)
但是井越来越深了,打起水来就不是一件简单的事情。(好比在开发大型项目)。
而TDD理念就好比是一个棘轮,使用它,你可以保存当前的进度,而且保证进度绝不倒退。这样打起水来就不必要一直这么聪明了。
或许一个函数本来只是一个if语句,但是也许几天后他会再添加一个for循环,或许日后会越来越复杂,不知不觉间变成一个基于元素的多态树结构解析器了。
好了,继续我们的实战。
这也是TDD的优点之一,我们永远不会忘记接下来要干嘛。
测试结束了,那么我们继续编写测试吧。
#coding:utf-8
from selenium import webdriver
import unittest
from selenium.webdriver.common.keys import Keys
class NewVisitorTest(unittest.TestCase):
def setUp(self): # 特殊方法,在测试前运行
self.browser = webdriver.Firefox()
# self.browser.implicitly_wait(3)
def tearDown(self): # 特殊方法,在测试完成后运行。出错了也会运行
self.browser.quit()
def test_can_start_a_list_and_retrieve_it_later(self):
# j3n5en 进入首页
self.browser.get("http://localhost:8000")
# 标题有"TO-Do"字样
self.assertIn("To-Do", self.browser.title)
header_text = self.browser.find_element_by_tag_name('h1').text
# 头部也有To-Do字眼
self.assertIn('To-Do', header_text)
# 在一个文本框输入 "购买显示屏"
inputbox = self.browser.find_element_by_id('id_new_item')
self.assertEqual(
inputbox.get_attribute('placeholder'),
'输入备忘录'
)
inputbox.send_keys("购买显示屏")
# 点击回车,完成输入
inputbox.send_keys(Keys.ENTER)
# 表格中出现"1、购买显示屏"
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_element_by_tag_name('tr')
self.assertTrue(
any(row.text == '1:购买显示屏' for row in rows)
)
self.fail("完成测试")
# J再次输入“安装显示屏”
# 页面再次更新
# 页面中显示两条事项
# 滚去睡觉,关闭浏览器
if __name__ == "__main__":
unittest.main(warnings='ignore')
单元测试规则之一:不测试常量、其实要测试的是:逻辑、流程控制和配置。
之前我们把html插在了views.py里面了,其实正确的方法应该是使用模板。那么我们使用模板重构一下之前的代码吧。
在重构之前我们先运行下下测试,python3 manage.py test 以确保重构前后变现一致。ok~ 测试是通过的,上,写代码去。
# lists/templates/home.html
<html>
<title>To-Do lists</title>
</html>
# /lists/views.py
def home_page(request):
return render(request, 'home.html')
重构完了,跑下测试,看看重构前后表现是否一致。 ok~ 没问题
接下来我们编写模板使代码能通过功能测试。
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>To-Do</h1>
<input id="id_new_item" type="text" placeholder="输入备忘录">
<table id="id_list_table">
<tr><td>1:购买显示屏</td></tr>
</table>
</body>
</html>
啊哈,,我用假的代码骗过了测试。但是很明显这是假的。下一篇将学习如何保存用户输入,和渲染到模板。
Happy Hcking ~ ~ ~
这里我们编写表单,让用户的输入和提交变为POST请求。这里我们修改模板文件。
# lists/home.html<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>To-Do</h1>
<form method="post">
<input id="id_new_item" type="text" placeholder="输入备忘录">
</form>
<table id="id_list_table">
<tr><td>1:购买显示屏</td></tr>
</table>
</body>
</html>
运行测试看看变绿没。oh ~ no。。红了。。。而且只知道没找到“id_list_table”但是不知道为什么会这样。
那样,我们在判断id_list_table的时候停下来一阵,让我们肉眼能看见吧。
import time
time.sleep(10)
table = self.browser.find_element_by_id('id_list_table')
这样的话我们看到了403错误。
这是Django一个很有趣的错误。其实他是为了防止跨站请求伪造(csrf)而做的一些限制。我们需要在表单中加入{% csrf_token %}
<form method="post">
<input id="id_new_item" type="text" placeholder="输入备忘录">
{% csrf_token %}
</form>
ok ~ 删了sleep 。没问题。
下面我们在服务器中接收并处理这个请求
先编写测试
待续
习惯了说走就走的旅行,这次旅行可谓是“密谋已久”,还没开始考试就在惦记,也一直在想象会不会发生有趣的故事。啊哈哈哈。
Take a break and keep on.
其实这次去武汉是我第一次坐火车,一直认为火车是“脏乱差”的代名词,其实还好啦,不过坐了10几个小时火车的我们,其实也是萌萌哒的。
第一站是湖北省博物馆。
平日里整天对着电脑,网络,总是在前瞻未来,那么去博物馆回顾一下历史不也挺好的吗?看着古人留下的痕迹,在脑海里想象他们曾经发生的故事,不也挺有趣的吗?哈哈哈
但愿网园每个小伙伴都能开开心心。心之所愿,无所不成
额哼~我们也去了一座别具异域特色的寺庙——古德寺。
一进,哇太美了!!我人几乎在欧洲游嘿嘿~~~古德寺的建筑混合了欧亚宗教建筑的特色,增添了几分异域的神秘。
来到江边怎能不渡一下江呢?你看,用了飘柔,就素芥末自信,飘逸的头发帅的一逼。
无边落木萧萧下,不尽长江滚滚来。
来到武汉怎么能不去一下武汉的最好学府——武汉大学呢?
尽管不是樱花盛开的季节,而且校园里大肆动土兴工,但我们也能领略到这座校园的美。
秒速5厘米,那是樱花飘落的速度,那么怎样的速度,才能走完我与你之间的距离?
昔人已乘黄鹤去,此地空余黄鹤楼。黄鹤一去不复返,白云千载空悠悠。
其实我很明显的感受到不再是“此地空余黄鹤楼”了,这个黄鹤楼明显是假货,,,不再是以前的黄鹤楼,是重修过的,变得非常地商业化。
装逼圣地——昙华林
昙华林给人感觉蛮像厦门鼓浪屿~~~~里面有很多老建筑的教育医疗机构和古建筑及旧址。
希望大伙都能快快乐乐地生活,开开心心地成长。嘻嘻。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.