Git Product home page Git Product logo

blog_articles's Introduction

Click 🐱 to visit my blog.🤫


Looks like something went wrong!

We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.

blog_articles's People

Stargazers

 avatar

Watchers

 avatar  avatar

blog_articles's Issues

像写Python/Ruby一样写Javascript——coffeescript

作为一名Pyer,我非常python的简洁语法。也希望在写前端JS是能用到类似他的语法。。。。于是碰到了coffeescript。coffeescript能让我们编写更少的代码,使得语言特性更强。其实最爽的是写js让我感觉我在写python,不用再管那些麻烦的符号。。哇哈哈哈。。。。。

那么壮士,让我们干了这杯热 ~~ 翔 ~~ 咖啡吧。。
image
我们先来看看官方给的demo

左边是 CoffeeScript, 右边是编译后输出的 JavaScript.
image
代码量骤减啊,有木有!!其实他也大概讲到了一些常用的要点了。赋值,条件语句,函数,数组,对象,等。。。下面我就分开来仔细说说把。


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,else,else if,unless

#编译前
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)

  • 功能:数组截取
  • 参数:
    • start:开始位置
    • end:结束位置
  • 返回值:新的数组
    -注意:数组本身不发生变化
    -第一个参数是截取开始位置(数组下标是从0开始),第二个参数是结束位置,但是不包括结束位置。
    -如果只有一个参数,从开始位置截取到剩下所有数据
    -开始位置和结束位置也可以传递负数,负数表示倒着数。例如-3是指倒数第三个元素
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]

splice(start,len,data...)

  • 功能:数组插入数据、删除数据
  • 参数:
    • start:开始位置
    • len:截断的个数
    • data:插入的数据
  • 返回值:截断的数据,是数组
    -注意:操作的是数组本身
    如果只有一个参数,从开始位置截取剩下所有数据
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;

其实就是把两个下标之间的元素替换成新的数据。同样的,... 不包括结束位置的元素,.. 包括结束位置的元素

运算符

image


好了~

coffeescript的知识点大概就这些了....漏了再补吧..哈哈哈....

happy hacking!

http2 + https 装逼指南&Let's Encrypt for nginx使用方法

Let's Encrypt在2015/12/3开放了公测,先来介绍一下Let's Encrypt,它是Mozilla、Facebook、Cisco等很多巨头为了推动Web加密而新建的一个项目。more : https://letsencrypt.org/ 来看看我的成果图
image

Cooooool~

先来签证吧。

由于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 装逼指南到此结束

About Me

About Me

我叫J3n5en,一名来自广东的计算机爱好者,沉迷于前端技术、Pyhon开发。
是一个经常笑,却不是一个经常开心的怪咖。
凡事3分钟热度的怪兽。
喜欢萝莉的怪蜀黍。

我们都是赌徒,用宝贵的青春去赌未知的未来。

Contact Me:

Email: [email protected]
Blog: http://blog.j3n5en,com

猴年猴赛雷,每天100分钟,学习Flask Web 开发 の D3

《猴年猴赛雷,每天100分钟,学习Flask Web 开发》

​ 今天是计划的第三天。ok~继续!

第三章、Web表单

这里主要讲得是Flask-WTF扩展的使用。

安装: pip install flask-wtf

1. 跨站请求伪造(CSRF)保护

为了CSRF保护,Flask-WTF需要程序设置一个密钥。

#hello.py
app = Flask(__name__)
app.config['SECRET_KEY'] = 'Some string,By J3n5n'

app.config字典可以用来存储框架、扩展、程序的配置变量。

2. 表单类

在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 验证Email
EqualTo 比较两个字段的值
IPAddress 验证IPv4网络地址
Length 验证长度
NumberRange 数字范围
Optional 无输入值时跳过其他函数
Required 确保不为空
Regexp 使用正则验证输入值
URL 验证URL
AnyOf 确保输入值在可选的列表中
NoneOf 确保输入值不在可选的列表中

3. 把表单渲染成HTML

假设视图函数把一个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>

4. 在视图函数中处理表单

#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.

5. 重定向和用户会话

@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而不是返回异常。

6. Flash消息

一般请求完成后,需要用户知道状态发生了变化,就像是确认信息,错误信息,警告,提示,之类的。这种功能就是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 !

EOF

猴年猴赛雷,每天100分钟,学习Flask Web 开发 の D2

​ 今天是计划的第二天。ok~继续!

第二章、模版

Flask中使用的是Jinja2,当初我在使用Django的时候也是喜欢用这款模版引擎。这里就当是巩固学习把。

1. 渲染模板(render_template)

默认情况下,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函数的第一个参数是模板的文件名。随后的参数都是键值对。

2. 变量

在模板中使用 {{ 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标签都去掉

3. 控制结构

  • 条件控制语句
{% if user %}
    Hello , {{ user }} !
{% else %}    
    Hello , Stranger !
{% endif %}
  • for循环
<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 %}

4. 自定义错误页面

如果用户输入了不可用的路由,那么将会显示一个状态码为404的错误页面。但是默认的404页面奇丑无比。那我们就只能让其丑下去?当然不是。我们可以自定义错误页面。例如:

@app.errorhandler(404)
def page_not_found(e):
  retuan render_template('404.html'), 404

5. 链接

在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

6. 静态文件

为了处理静态文件,Flask中有一个特殊的路由即 /static/<filename> 例如调用 url_for('static', filename='css/style.css' , _external=True) 返回的地址是 http://localhost:5000/static/css/style.css

#EOF#

猴年猴赛雷,每天100分钟,学习Flask Web 开发 の D5

​ 今天是计划的第五天。今晚顾着看我是歌手,竟然拖到这么晚。。。

第五章、电子邮件

很多时候我们都希望我们的程序能通过Email与用户进行交流。在这里我们利用Flask-Mail来实现

安装 pip install flask-mail

1. 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后面,当初这个坑了我

注:在实际生产中我们应该把账号信息等重要信息保存到环境变量中,而不是程序里。

2. 在Python Shell中发送Email

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)

3. 在程序中集成发送Email功能

为了更方便,更灵活地发送右键,我们可以把程序发送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))

4. 异步发送Email

在运行上面的例子时,我们可以明显发现发送邮件时,程序被堵塞了,浏览器就像失去了响应一样。为了避免这种情况,我们可以把发送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 !

EOF

解放F5 —— browsersync

嘤嘤嘤.....双十一剁手剁了一个显示屏,想右边打代码左边即时显示....

image

这时可能会想到livereoad ~ ~......的确,配合浏览器插件来使用,livereload是一个很好用的自动刷新软件.

但是这篇博文的主角是 "browsersync" 一个更新,更方便的开发工具... 这里给出中文网 http://www.browsersync.cn

Browsersync可以在不同的浏览器,不同设备下自动刷新修改的页面,更神器的是在其中一个浏览器中的动作可以同步到其他浏览器,其他设备中.
image
image

而且修改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仍然会正确地判断文件变化是否是css。

UI界面及其他

下面是一个BrowserSync的控制台输出示例:
image
可以看到还有一个叫做UI的一个地址,它是BrowserSync提供的一个简易控制面板。BrowserSync最常用的几个配置选项,都可以在这个面板里调整。

在面板里面你还会发现那个经典的远程调试工具weinre也在这:

image

你可能会发现browsersync不像livereload需要安装浏览器插件,

这是因为browsersync会新建一个服务器对你的文件进行预览,并在页面中插入JS,

而这段JS用来新建web socket连接,一旦有监听的文件发生变化,BrowserSync会通知浏览器.

更多的玩法?读文档去吧!!!

happy hacking ~ ~ ~

Time waits for no one. —— 《穿梭时空的少女》观后感

image

Time waits for no one.
有花堪折直须折,莫待无花空折枝。

女主角绀野因偶然的机会而获得了能够穿梭时空的作弊技能,而且还拥有“怎么跑、怎么跳都不会走光的反重力裙”神级装备
作为一个17岁的少女,尚未沾染上社会的险恶与世故。

为了吃到布丁 , 跑! 跳!施展技能回去吃!

为了唱k唱久一点,跑!跳!施展技能回去唱!

为了吃到铁板烧,跑!跳!施展技能回去吃!

.......

为了逃避被告白的尴尬场景,跑!跳!施展技能回去逃避!
image
挺喜欢这样的设定的。

正值青春的我们不也是这么莽撞率真吗?想到就做的直爽,还有一往直前的意气奋发,而这个世界是我的,只需要不断向前奔跑,一定就能到达想去的地方。

未卜先知的力量乍看之下虽为绀野带来不少便利,让她跳脱了现世规则之外。

暂时得以操弄超然力量来掌控生活周遭瞬息万变的人际关系。

但人生毕竟不是照著公式来运作,绀野每每遇到自己处理不来的窘境便试图重新回到原点强加以扭转改变,反而却将局势弄得越来越僵。

显见即使身负绝世超能力,真琴还是逃脱不出青春的因果定律,反倒是更加深陷其中。

最喜欢的那一段是绀野发现自己还有一次穿梭时空的机会,她奋力地向前跑,面朝一个城市的灯火辉煌,为着心中的少年而无畏向前。
image
在时空穿梭间,无数的画面显现,与他相识的过程不断在眼前闪现。他刚刚来到学校的样子,与功介和自己的相识,三个人一起玩耍嬉闹、无忧无虑的时光。

那些记忆,都在闪闪发光,明明看似那么普普通通微不足道,可又那么令人怀念。

最后,是三个人一起撑伞的画面,伴着一句曾令自己脸红心跳的话,响彻心头。


试想,倘若我也用拥有了能够穿梭时空的能力。我会去做什么呢?

是像女主那样穿梭回去吃布丁?还是像其他人说的那样,穿梭回去买彩票?

是像女主那样穿梭回去尽兴地唱一次K?还是像穿越小说一样,把未来的技术带回过去,开挂的人生,开辟自己的天地?

是像女主那样穿梭回去吃铁板烧?还是像别人的作文一样,穿梭回去,把自己过去的错误一一纠正。

。。。

我想,我会哪里都不去。因为我认为“现在就是最好的时光”。

过去的错,就让他错了吧,或许正因为这些错,才有更正确的未来。

能穿梭时光,那便拥有了未卜先知、预知未来的能力,

但我并不希望我的未来是已知的,我希望他是神秘的,是变幻莫测的。

我并不对我能到达哪个“位置”感兴趣,我只对在到达那个“位置”途径的风景感兴趣。

我就是我,我不会去改变,现在的我,就是最好的我。

那么,*年,珍惜当下吧。

珍惜现在的时光,珍惜现在所拥有的,珍惜身边的人(嗯,也就是在你身边的我)。

#EOF#

在 Django 中使用 simditor

之前我的博客一直用的是DjangoUeditor作为我的富文本编辑器,然而由于种种原因 djangoueditor 的代码高亮失效了,

后来发现了simditor,UI 好有feel,就懒得改 djangoueditor 了,,直接换 simeditor,,,在这里记录一些过程。。。。
image

来来来,,,,我们先看看她那撩人的外表
开始结合吧!!

#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 开发 の D1

​ 今天是大年初一,我将在这几天里,每天抽出至少100分钟来学习Flask Web开发,并记录过程。

所读教材:《Flask Web 开发》

使用编辑器:Pycharm。

编程环境:win10 + Python2.7

  • 第一章

  • 安装

安装Flask和安装其他Python包一样可以使用pip进行快速的安装pip install flask

  • 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 请求上下文 用户回话,用于存储请求之间需要记住的值的词典
  • 请求钩子
  1. before_first_request: 注册一个函数,在处理第一个请求之前运行。
  2. before_requesr:每次请求之前运行。
  3. after_request:如果没有未处理的异常,在每次请求之后运行。
  4. tear_request:在每次请求之后运行。
@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扩展之——Flask-Script
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上使用DjangoUeditor 1.8的图片上传功能

SAE上是没有目录读写权限的,所以要在SAE使用Ueditor的图片上传功能需要借助SAE的Storage服务。并对DjangoUeditor 进行修改,,,

1、开通Sae的Storage功能:

在SAE控制台开通Storage服务,并新增一个domain,按需求填写即可。

image

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开发测试驱动方法》读书笔记之一

前言:

在双十一剁了一本《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

跑起来后我们再运行测试程序看看。

image

Cool~ ~测试通过了。

为了纪念第一次测试成功,我们是不是应该把他保存下来呢?

https://coding.net/u/jensen/p/Python-web-TDD

Cool ~~

-第二章: 使用unittest模块扩展功能测试

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的代码了....现在我们再来运行一下单元测试吧.
image
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~ ~

猴年猴赛雷,每天100分钟,学习Flask Web 开发 の D4

《猴年猴赛雷,每天100分钟,学习Flask Web 开发》

​ 今天是计划的第四天。ok~继续!

第四章、数据库

1. 使用Flask-SQLAlchemy管理数据库

这本书里面使用的数据库框架是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的所有功能。

2. 定义模型

#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。

3. 关系

一对多。

class Role(db.Model):
    # ...
    users = db.relationship('User', backref='role')
class User(db.Model):
    # ...
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

4. 数据库操作

4.1. 创建表

在Flask-SQLAlchemy中使用db.create_all() 创建表。

python hello.py shell
>>from hello import db
>>db.create_all()

如果数据库已经存在的话则不会重新创建或更新,那么粗暴的解决方法就是先删除,再重建。

db.drop_all()
db.create_all()

4.2. 插入项

如下例子尝试插入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中的对象都会恢复到数据库中的状态。

4.3. 修改行

数据库session中的add()方法同样也能被用于更新模型,如下的例子把role从“Admin”重命名为“Administrator”:

>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()

4.4. 删除行

可以使用session中的delete()方法来删除数据,更其他操作一样,删除也要通过session.commit()才能生效:

>>> db.session.delete(mod_role)
>>> db.session.commit()

4.5 查询行

最基本的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对象,包含指定范围内的结果

4.6. 在视图中操作数据库

其实前面部分的数据库操作可以直接在视图方法中使用

@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 %}

4.7. 集成Python Shell

在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'>

5. 使用Flask-Migrate来做数据库的Migrations

开发进行到一定阶段,你会发现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)

5.1. 创建迁移仓库

为了将数据库迁移的命令暴露出来,我们把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命令创建了迁移的文件夹,所有迁移脚本都会被存储在这个文件件中。

5.2.创建迁移脚本

在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

5.3. Upgrading数据库

检查并修正迁移脚本之后,我们可以使用db upgrade 来更新数据库了,你可以把data.sqlite删除以后再执行命令,你会发现删除的数据库通过migration命令重又建了。

《Python Web开发测试驱动方法》读书笔记之二

前一篇笔记中,我们已经实际演练了基本的TDD流程,那么这篇笔记我们就先来谈谈编写这些测试的用处吧。

相信很多的读者和心中存在很多疑问:

  • 编写的测试会不会有点多,
  • 测试好像有点重复了吧.? 单元测试和功能测试里面
  • 每次修改都要循环"单元测试/编写代码",这有必要吗?
    这里把项目开发比喻成用绳子在水井里面打一桶水.

如果井不是很深,那么打起水来是比较简单的 ( 就是说在开发小型项目的时候,并不困难)

但是井越来越深了,打起水来就不是一件简单的事情。(好比在开发大型项目)。

而TDD理念就好比是一个棘轮,使用它,你可以保存当前的进度,而且保证进度绝不倒退。这样打起水来就不必要一直这么聪明了。

  • 但是测试真的要写的这么细吗?(棘轮的齿轮有必要这么细吗?)
    我们现在先为简单的函数写好测试,占好位置,当这个函数变得越来越复杂,测试也不会变得很乱,那也就不会产生心理障碍了。

或许一个函数本来只是一个if语句,但是也许几天后他会再添加一个for循环,或许日后会越来越复杂,不知不觉间变成一个基于元素的多态树结构解析器了。

理都懂,然并卵?ok~ 那我们继续实践咯。

好了,继续我们的实战。

使用selenium测试用户交互

完了,之前写代码写到哪里了?运行测试看看吧。
image

这也是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')

运行看看。
image
未找到

标签。那么我们给他加h1呗。

单元测试规则之一:不测试常量、其实要测试的是:逻辑、流程控制和配置。
之前我们把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>

1111

啊哈,,我用假的代码骗过了测试。但是很明显这是假的。下一篇将学习如何保存用户输入,和渲染到模板。

Happy Hcking ~ ~ ~

《Python Web开发测试驱动方法》读书笔记之三

上一篇博文中,我们利用作弊的代码把用户的输入写死了,欺骗了测试joy。很明显这是不可行的。那么我们要怎么做的?其实我们需要让用户的点击变成一个请求,然后让django接受到这个请求并保存起来,最后渲染到模板。这篇博文主要记录的有“编写表单,发送请求”、“服务器处理请求”、“模板渲染变量”还有“Django的ORM和模型”。

一、编写表单、发送请求。

这里我们编写表单,让用户的输入和提交变为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”但是不知道为什么会这样。
image
那样,我们在判断id_list_table的时候停下来一阵,让我们肉眼能看见吧。

import time
time.sleep(10)
table = self.browser.find_element_by_id('id_list_table')

这样的话我们看到了403错误。
image
这是Django一个很有趣的错误。其实他是为了防止跨站请求伪造(csrf)而做的一些限制。我们需要在表单中加入{% csrf_token %}

<form method="post">
    <input id="id_new_item" type="text" placeholder="输入备忘录">
    {% csrf_token %}
</form>

ok ~ 删了sleep 。没问题。

下面我们在服务器中接收并处理这个请求

先编写测试

待续

记2016/01/15~19武汉游

习惯了说走就走的旅行,这次旅行可谓是“密谋已久”,还没开始考试就在惦记,也一直在想象会不会发生有趣的故事。啊哈哈哈。

Take a break and keep on.

生活忙忙碌碌,为何不休息一下再继续呢?

其实这次去武汉是我第一次坐火车,一直认为火车是“脏乱差”的代名词,其实还好啦,不过坐了10几个小时火车的我们,其实也是萌萌哒的。
image
第一站是湖北省博物馆。

平日里整天对着电脑,网络,总是在前瞻未来,那么去博物馆回顾一下历史不也挺好的吗?看着古人留下的痕迹,在脑海里想象他们曾经发生的故事,不也挺有趣的吗?哈哈哈
image
但愿网园每个小伙伴都能开开心心。心之所愿,无所不成
qq 20160208164438


额哼~我们也去了一座别具异域特色的寺庙——古德寺。

一进,哇太美了!!我人几乎在欧洲游嘿嘿~~~古德寺的建筑混合了欧亚宗教建筑的特色,增添了几分异域的神秘。
image
image

image

来到江边怎能不渡一下江呢?你看,用了飘柔,就素芥末自信,飘逸的头发帅的一逼。
image

无边落木萧萧下,不尽长江滚滚来。


来到武汉怎么能不去一下武汉的最好学府——武汉大学呢?

尽管不是樱花盛开的季节,而且校园里大肆动土兴工,但我们也能领略到这座校园的美。
image

秒速5厘米,那是樱花飘落的速度,那么怎样的速度,才能走完我与你之间的距离?


昔人已乘黄鹤去,此地空余黄鹤楼。黄鹤一去不复返,白云千载空悠悠。

image
其实我很明显的感受到不再是“此地空余黄鹤楼”了,这个黄鹤楼明显是假货,,,不再是以前的黄鹤楼,是重修过的,变得非常地商业化。

恩恩,对酒当歌,人生几何,我们应当趁着年轻,及时行乐,老了再想去都去不了咯。

装逼圣地——昙华林

昙华林给人感觉蛮像厦门鼓浪屿~~~~里面有很多老建筑的教育医疗机构和古建筑及旧址。

特别是一堆国外风格建筑群。可谓是逼格满满,文艺气息爆棚。
qq 20160208170102

image

最后的最后,发一下ci的图。。嘻嘻
image
image
image

image

希望大伙都能快快乐乐地生活,开开心心地成长。嘻嘻。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.