Python多版本环境管理之pyenv中说到使用pyenv, 但没有写安装的过程。本以为按照官网安装即可,后来在给同事安装时,发现了问题。

官网上说,在使用Zsh,需要将环境变量加到~/.zshenv中,而不是~/.bash_profile,但其实这样还是会提示没有安装pyenv。正确的配置是将环境变量加到~/.zshrc中。

Fabric用于发布和执行一些系统管理任务,非常方便。

目前[学习笔记](用于发布的脚本如下,很方便。

1
2
3
4
5
6
7
8
9
10
11
12

from fabric.api import *
env.hosts = ['43.242.128.158']
env.user = 'dengsl'
def deploy():
code_dir = '/home/dengsl/program/nodejs/blog'
with cd(code_dir):
run("git pull")
#deploy static site
run("hexo clean")
run("hexo g")
run("cp -r public/* /home/dengsl/program/html/blog/note")

Middleware相当于Django的底层插件,用于改变Django的输入和输出。最近遇到一个问题是有一些Android无法显示HTTPS页面中夹带的HTTP图片, 于是想到在返回结果中修改,于是想到Middleware.

编写一个Middleware也比较简单,根据需求定义响应的hook方法就好。这里定义如下

1
2
3
4
5
6
7
class StaticPathFilter(object):

def process_response(self, request, response):
if not response.streaming:
if request.scheme == 'https':
response.content = re.sub(r"http://static.eyaos.com", r"https://static.eyaos.com", response.content)
return response

我这里是StaticPathFilter放在apps.common.middleware模块里, 把’apps.common.middleware.StaticPathFilter’添加到MIDDLEWARE_CLASSES即可。

context_processors用于给Django模版添加context,例如django.template.context_processors.media用于添加MEDIA_URL,django.template.context_processors.static用于添加STATIC_URL。之所以使用context_processors, 是想全局添加某个变量,而不需要在每个View中添加。

查看django.template.context_processors.static的实现,就可以模仿着写自己的context_processors

1
2
3
4
5
6
def static(request):
"""
Adds static-related context variables to the context.

"""

return {'STATIC_URL': settings.STATIC_URL}

在git操作时,需要输入完整命令比较麻烦,此时可以配置别名。

配置status的别名

git config –global alias.st status

配置check的别名

git config –global alias.ch check

最近在看Django文档Making queries时看到descriptor, 发现描述器很强大.

Descriptors are a powerful, general purpose protocol. They are the mechanism behind properties, methods, static methods, class methods, and super(). They are used used throughout Python itself to implement the new style classes introduced in version 2.2. Descriptors simplify the underlying C-code and offer a flexible set of new tools for everyday Python programs.

但是我还是不懂为什么需要描述符,直到看了Python描述符(descriptor)解密, 才知道描述符的用途。按照视频中的讲解,在理解一遍,加深印象。

假设在做一个农产品销售系统,每个订单是一个产品,每个产品有description, weight, price三个字段

1
2
3
4
5
6
7
8
9
class LineItem(object):

def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price

def subtotal(self):
return self.weight * self.price

但这里存在一个问题,即weight可以为负的。

1
2
3
4
5
6
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.subtotal()
69.5
>>> raisins.weight = -20 #负值
>>> raisins.subtotal()
-139.0

这是一个严重的问题,亚马逊刚起步时就有这个问题。 在Jeff Bezos and Amazon: Birth of a Salesman里有描述。

传统的做法是添加getter和setter方法

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
class LineItem(object):

def __init__(self, description, weight, price):
self.description = description
self.set_weight(weight)
self.price = price

def subtotal(self):
return self.get_weight() * self.price

def get_weight(self):
return self.__weight

def set_weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')


if __name__ == "__main__":
raisins = LineItem('Golden raisins', 10, 6.95)
print raisins.subtotal()
raisins.weight
print raisins.subtotal()

这种方法存在一些问题,

  • 之前可以使用raisins.weight, 现在不能使用
  • 和以前的代码不兼容,以前可以使用raisins.weight, 现在必须raisins.get_weight和set_weight

好在Python提供更好的解决办法, property是其中一种

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
class LineItem(object):

def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price

def subtotal(self):
return self.weight * self.price

@property
def weight(self):
return self.__weight

@weight.setter
def weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')


if __name__ == "__main__":
raisins = LineItem('Golden raisins', 10, 6.95)
print raisins.subtotal()
print raisins.weight
print raisins.subtotal()
raisins.weight = -2.0

使用property存在一个问题是,当我们需要对price也做非0限制时,需要重复setter设置。此时Descriptor派上用场了。

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
35
36
37
class Quantity(object):
__counter = 0
def __init__(self):
prefix = '_' + self.__class__.__name__
key = self.__class__.__counter
self.target_name = '%s_%s' % (prefix, key)
self.__class__.__counter += 1

def __get__(self, instance, owner):
return getattr(instance, self.target_name)

def __set__(self, instance, value):
if value > 0:
setattr(instance, self.target_name, value)
else:
raise ValueError('value must be > 0')

class LineItem(object):
weight = Quantity()
price = Quantity()

def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price

def subtotal(self):
return self.weight * self.price


if __name__ == "__main__":
raisins = LineItem('Golden raisins', 10, 6.95)
print raisins.subtotal()
print raisins.weight
print raisins.subtotal()
raisins.weight = -2.0
raisins.price = -1

在Quantity类里, instance是指LineItem实例, owner指LineItem类。

具体查看视频,非常不错。

参考资料

因为服务器上HTTP和HTTPS一起存在,所以分享接口这里出了一些问题。

在调用分享接口生成签名时,签名用的url必须是调用JS接口页面的完整URL,但因为存在http和https两种,而Django其实不知道客户端到底是访问http还是https,所以产生了问题。如果写死http, 则访问https时,微信分享签名出错;相反的,如果写死https, 则访问https时,微信分享签名会出错。有什么解决的办法?

查看request对象,知道scheme这个属性,于是看到SECURE_PROXY_SSL_HEADER设置。

大意就是当在settings里配置了SECURE_PROXY_SSL_HEADER,Django就会到request.META里读取相关参数,如果有设置https,则这是一个https请求。而相关参数需要代理服务器设置,我这里使用Nginx。

于是在Nginx配置里,当访问的是https时,就加上

1
proxy_set_header HTTP_X_FORWARDED_PROTO https;

然后在Django的settings配置里加上

1
SECURE_PROXY_SSL_HEADER = ('HTTP_HTTP_X_FORWARDED_PROTO', 'https')

这里之所以会多一个HTTP_是因为Django默认会给request.ME如此配置后,request.scheme就会返回http, 当请求是https时,则会返回https。分享的签名正确,问题得到解决。