从flaskbb,june到lolipop(二)

这篇文章紧接着上一篇文章,主要讨论的是python的动态性和热插拔,由于项目的限制,讨论当然不太会很深入。如果希望有更深刻的认识,这篇文章大概很有裨益:Python descriptor。当然,阅读官方的文档也是一种不错的方法。

让我们把重点放在后面,最开始是几个上次没来得及讨论的点。

june中引入disable field

所谓disable field就是常见的填写框为灰色的那种,june中没有这种field式样。当然这个和june作者的思路有关系,june的作者认为,人们有自由修改昵称的权利。但是,就我个人而言,并不希望给予用户这样的权限,和大多数标准的论坛实现一样,用户需要为自己注册的时候做出的选择负责。

既然没有提供这个式样,就要考虑改出这样的式样。

一般来说,jinja中只需要对某一个field加入disabled=True这个参数就是了,但是june中是这样的{{ horizontal_field(form.username,'input-block-level') }},很明显,这样直接添加是不行的。需要改horizontal_field的源代码。当然,这样就可以了{{field(class_=class,disabled = True,placeholder=field.description)}}。这里需要考虑一个问题,是否应该尽可能复用以前的代码,也就是,在原来的样式宏中,加入一个控制变量,并添加一点代码,来控制是否应该disabled这一个field。您可以试试,但是个人倾向于新建一个差不多的样式宏,虽然引入了重复代码,但是对已经写好的内容不会有任何干扰,可以做到最大程度的向下兼容。

通过uwsgi来运行您的论坛项目

老实说,这是一个相当老的话题,甚至在前几篇中,我专门写了两篇文章来讨论django的uwsgi的运行。

但是对于flask而言,依然值得讨论。

对于我的uwsgi.py文件(一般都需要这个文件,即使您的文件名不是这个),相当简单。

from app import create_app
app = create_app()

这里说一下,create_app函数,如果您有兴趣翻翻lolipop的源代码的话,您会发现这个函数承担了所有app的初始化。比如过滤器的注册啊,上下文处理器的注册啊,配置字段的传递啊什么的。

uwsgi不需要app.run(),你只需要把app交给她就好了。

如果您的flask处于virtualenv的环境下,直接用uwsgi运行会提示找不到模块,有两种方法可以解决。

一种比较笨的方法是,在uwsgi.py中最开始添加这样的两句代码,模拟激活virtualenv的过程。

activate_this = '/home/aprocysanae/github/lolipop/env/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))

另一种是推荐的方法,在uwsgi的配置文件中加入virtualenv的路径。哈,你说第一种方法有什么意义?额……卖萌?

所有的配置可以在lolipop的源代码里面查到,我就不多说了。

关于uwsgi,照例给一份文档:WSGIquickstart,文档的页面风格很顺眼,让人看得下去。于是我又想黑一黑sqlalchemy的页面风格……

python热插拔与函数式编程

ok,该说到都说到了,下面是本篇的重点。

说一说之所以研究这部分的诱因。WTForm初始化的时候,可以传入一个obj,如果这个obj里面有内容,会自动填充对应行。对了,就是需要编辑帖子之类的操作需要的预填充。

如果传递的是一个orm得到的类,当然简单愉快。但是,如果是多个呢?

我当时由于分离了user和profile这两张表,所以势必得给WTForm传递两个类,但是,很明显,obj只能传递一个类进去。难道我们需要放弃原来的表结构,使用新的表结构么?如果是三个呢?四个呢?

别忘了python是一门动态语言,对类属性可以进行随意到近乎放纵的增改正是她的特点之一。

稍微举个最简单的例子。

class A(object):
    def __init__(self):
        self.a = 1
        self.b = 2

>>> a = A()
>>> print a.b
>>> a.c = 5
>>> print a.c

顺便说一句,object并不是必要的,不过还是推荐总是加上—-加上反正是不会错的。

能看出些东西了么,我们给A增加了一个叫c的属性,而且居然她什么都没说就接受了。不过这是为什么呢。运行a.dict,看出些什么了么。是的,A的全部属性只是用一个字典储存而已,增加一个属性,只是相当于在这个字典中增加一对键值。有兴趣的话,你可以看看函数什么的在这个字典中显示是什么……嘛,也不用猜,python中一切都是对象,函数的话,只是一个函数对象而已。

我们考虑一下WTForm的填充功能是怎么实现的,当然啦,我们传进去的就是类么,而且是orm的类,当然是通过识别对应的属性来填入对应的field不是么。

于是很明显了,把user中有用的属性直接塞入profile就好了,对吧。

对也不对,思路其实是正确的,但是WTForm的一些奇怪表现使得这种做法不成功—-具体原因我还不知道。这个留在后面说。

我们先考虑怎么读取并塞入。

缺少的属性,我们当然知道,但是我们只能通过传入字符串来指明哪些属性缺失。如果您学过java的话,这个过程大概叫做反射,不过我也不知道,因为我没学过java。

python中可以用getattr(obj,str)来获得字符串对应属性的值。设置属性倒好说,__setattr__(str,value)

然后我们考虑获取user和profile的类。强烈不推荐单次从数据库只读一个的做法,因为相当没有效率。前面说过了,user和profile的id是一样的,那么,传入同样的分页类就可以了。但是这样我们怎么确定user和profile的对应呢?做字典就好了。

上面说的我自己都没看懂……于是我机智地贴上了代码。

paginator = User.query.order_by(User.id).paginate(page)
profiles = Profile.query.order_by(Profile.id).paginate(page).items

profiles_dict = {}
    for profile in profiles:
        profiles_dict[profile.id] = profile

一点需要说明的是,paginage这个分页类的items才是可迭代的(因为是一个列表),而她本身不是。

上面的做法,最小化了数据库的读取,然后查询速度也很快。

然后,我们有了两个items(users和profiles),怎么逐个处理他们呢?写两个for循环?太难看了!

我们借鉴一点函数式编程的方法。

python中提供了一些函数式编程的特性,也提供了几个函数供使用。lambda,map,reduce,filter。后两个在这次的项目中用不到,于是不讨论。lambda提供了匿名函数的机制,map能以线程安全的方式,对给定列表的每个元素施以一个处理函数。

上面说得很清楚了,更多的恐怕只能看看书,这里直接给代码。

顺便一说,函数式编程的更多东西请参看SICP,我最近也在看。

def fill_object(items,profiles,*args):
    profiles_dict = {}
    for profile in profiles:
        profiles_dict[profile.id] = profile
    for arg in args:
        items = map(lambda o:_add_attr(o,profiles_dict,arg),items)
    return items

def _add_attr(item,profiles_dict,arg):
    profile = profiles_dict.get(item.id)
    item.__setattr__(arg,getattr(profile,arg))
    return item

上面的代码就是遵照上面所述的来写的,第一次看不太容易懂,但是结合分析再看一次,就会很简单的。

上面用到了聚合函数(*args),因为我们希望可以传入多个属性字符串。

调用的话:paginator.items = fill_object(paginator.items,profiles,'avatar','twitter'),是不是很优雅呢?

然后说一下,这样处理其实也不行,如果直接把处理过的profile类传入的话,WTForm的一些问题导致无法解析出插入的项。那简单,我们定义一个最干净不过的类怎么样。比如这个:

class MaxinObject(object):
    pass

直接对这个类增加我们希望的属性就好了。事实上,工作得很好。

上面就是今天的一点干货,感觉怎么样呢,米娜桑?

说点其他的

一个月之前吧,v2ex上的网友给了一个社工库的地址,查了一下吓尿了,个人资料账号密码暴露无疑,还是明文的。虽然最重要的几个网站密码都是单独的,但是通用密码肯定是沦陷了,后怕不已,然后用了lasspass,所有密码随机16位字符串。不过现在想想,貌似也不是很必要,手机登陆网站麻烦了很多……

最近瑞波币大涨,上次领的2000个币早先卖了1000个,现在手上的1000个还值500。虽然觉得怎么都有点虚,但是觉得提款太麻烦了,还是决定观望了,反正是捡来的无所谓。

今天ingress好好努力了一把,冲到了七级,几个八级大神对我期望甚高,不过我是懒比啊懒比……orz

如此就是今天的内容,阅读到这里的您辛苦了!

以上!

一年又一年,一直没有妹子的AS……