辅助Django开发的一些隐藏资源(文档)

距离上次使用 Django 还是一年前了,最近又回到 Django 上来,慢慢读它的文档,发现了1年之前马马虎虎使用 Django 的时候没有发现的好东西。

Admin可以自动生成文档

大家都知道 Django 可以自动根据你的 Model 生成 admin,但是我还是第一次知道这个 admin 可以生成文档呢。效果如下,可以展示项目的 App 中可以用的 Tags,Model 等。

Admin自动生成的文档首页

对我来说,比较有用的是可以展示某个 Model 的所有可以用的属性,这样就不用翻代码了,一目了然。尤其是对于没有 Model 之间的关系的 related_name ,有一些没有自己定义,即使自己定义了可能也比较乱。通过这个文档来看,这个 Model 上面的可调用的东西就很清晰了。

安装方法:

  1. 将 django.contrib.admindocs 添加到 INSTALLED_APPS
  2. 在 urls.py 中添加  path('admin/doc/', include('django.contrib.admindocs.urls'))
  3. 安装 http://docutils.sourceforge.net/ (pip install docutils)

更多安装信息可以参考 Django文档

Classy Class-Based Views网站

去年写了一篇文章介绍 Django 的 Class-based-View,但是 Django 的 View 看起来比较杂乱,也比较多(好像一旦用上了多继承,就会搞得这样)。想要一个 View 或 Mixin 的时候,经常怀疑自己选择的这个是不是最合适的。

http://ccbv.co.uk 这个网站可以清楚地展示 Django(可以选择 Django 的版本) 所有可以用的 View。每个 View 的详情页展示了这个 View 的属性和可以用的方法(如果用 dir的话,你可能会被显示出的信息吓到)。并且可以查看方法的文档和源码,也可以跳转到 Django 在 Github 的源代码。

还能画出继承关系的类图,详见恨晚。

 

《罪与罚》摘录

去年去参加了 WEPLAY 游戏展,会展上《寂静岭》的音乐制作人山冈晃先生接受采访的时候说,《寂静岭》音乐的灵感来自于一本俄罗斯小说《罪与罚》,所以我决定去读一下这本书。

花了半年时间才读完这本书,读完之后发现,这个情节和游戏中的剧情有很微妙的关系。《罪与罚》中主线讲述的是大学生罗佳迫于生活的穷困,内心又有着伟大的思想,认为杀死一个蝼蚁一般的老太婆,获得短期内的钱来供自己这段时间的发展,对人类社会来说是有益的,就像拿破仑一样。然而,内心是有这样的想法,但是实践起来却畏手畏脚,最终鬼使神差践行了自己的计划,良心却备受谴责。经历了很长时间的煎熬,最后去警察局自首。我认为罗佳的悲剧来自于年纪轻轻却不甘于平庸,急切地想有非常舒适的环境去发展自己的矛盾。现实中的自己无法满足自己对自己的期望,从而对自己产生了恨,表现地对自己的妹妹和妈妈冷漠,对自己的处境也漠不关心。

除了主人公罗佳,书中还描述了很多人的悲剧。无法自制经常跑去饮酒的小官吏,硬生生断送的自己的前程,还有原本幸福的家庭;命途多舛的官吏妻子,被逼卖淫的女儿;被世俗嘲笑,被富婆包养的斯维德里盖洛夫,即使放弃了一切也换不到杜尼娅的芳心,最终自杀;对儿子百般溺爱,精神失常的罗佳的妈妈。

这本书所描述的悲剧如此突出,我觉得很大部分原因是作者陀思妥耶夫斯基喜欢让人物的感情剧烈变化,文章有打断的独白和对话,用了很多语气词,惊叹词,使得这些人物的台词感染力非常强,很容易代入。

读完这本书,也没有什么很特别的想法,就是感受到了这么多悲剧。生者还是好好活着,多考虑一下别人,小心翼翼的,生活太容易破碎了。

以下是我在 Kindle 上划出的一些摘录。

 

这种理论把芸芸众生人为地分为“非凡的人”和“平凡的人”两类:“一类是低级的人(平凡的人),也就是说,可以称之为仅仅是繁殖同类的材料;另一类是真正意义上的人,也就是具有天赋和才干,能在自己所处的社会提出新见解的人。”

个人究竟该以何种方式与途径发展自己的个性呢?

但他犯罪后良心不安,被他蔑视的道德规范暗暗地惩罚着他的灵魂,他噩梦连连,疑神疑鬼,惶惶不可终日,几次打算到警察分局去投案自首。这样,这部小说所写的真正惩罚就是道德与良心的惩罚,它所描绘的是发生在主人公内心深处的道德和心理斗争。

“那是由于没有别的人可找,也没有别的路可走啊!不是吗,任何人总得至少有一条路可走啊。因为有时候一个人必须有条路可走!

此外,要了解任何一个人,都必须一步一步地细心观察,才不致产生错误和偏见,否则,以后纠正错误和消除成见就十分困难了。

啊。当然,即使只吃黑面包只喝白开水,她也不会出卖自己的灵魂,也不会因为耽于舒适而放弃精神方面的自由;

啊,必要时我们会压制我们的道德情感,并且把自由、宁静,甚至良心,所有的一切,都拿到旧货市场上去拍卖。连生命也毫不顾惜!只要我们热爱的人能够幸福。

一种新的、无法克服的感觉以每分钟逐渐增强之势控制了他:这就是对劈面相逢的、环绕四周的一切都怀着某种无比强烈的、几乎是生理性的反感,一种持续不断的、怒气冲冲的、恨之入骨的反感。他憎恶劈面相逢的一切人,——憎恶他们的面孔、步伐、举止。假如有谁来与他攀谈,他简直要啐他一脸唾沫,可能还会咬他一口……

科学却告诉我们:首先你应该只爱自己,因为世界上的一切都以个人利益为基础。你只爱你自己,那么你就会把自己的事情办妥,你的长上衣也就会完整如一。经济学的真理进一步告诉我们,社会上办得好的私人事业越多,也就是说完整如一的长上衣越多,社会的基础就越牢固,公共事业也就会办得越发兴旺。

“您的那个讲师在莫斯科受审时被问到为何伪造有奖债券时,答道:‘大家都千方百计致富,所以我也想快速发财。’原话我记不太清了,但意思就是不劳而获,尽快地大发横财!大家都习惯于坐享其成,以别人的思想为思想,吃别人的现成饭。哈,伟大的时刻来临了,每个人都露出了自己的本性,都在看用什么法子发财……”

他只知道一点:“这一切必须就在今天结束,一次性地结束,立即结束;否则他决不回家,因为他不愿意如此活着。”如何结束?用什么法子结束?对此他毫不知晓,甚至不愿加以考虑。他驱走了这个念头,因为这个念头让他苦恼不已。他只是感觉到并且知道,一切总归都必须改变,这样变或者那样变,“不管怎样变都行”,他怀着天不怕地不怕、无可动摇的自信和决心反复喃喃着这句话。

“没什么可锁的人是幸福的,对吗?”他笑盈盈地对索尼娅说。

比方说,哪怕是人类的立法者和规章制度的创立者,从远古时代,直至后来的莱喀古士[17]、梭伦[18]、穆罕默德[19]、拿破仑等等,无一例外,都是罪犯,唯一的原因在于,他们在制定新法规的同时,也就破坏了世所公认的、神圣不可侵犯的、代代相传的古老法规,而且,当然啰,他们也不会面对流血而停步不前,只要流血(有时流的完全是无辜的、为维护古代的法规而英勇献身者的鲜血)能帮助他们成功。尤其令人注目的是,这些人类的恩人和规章制度的创立者,大多数都是血流成河的特别可怕的屠夫。

第一类人永远是现在的主人,第二类人则是未来的主人。第一类人保存这世界,增殖人口;第二类人则推动世界向前发展,并引导它奔向目的地。无论是第一类人还是第二类人,都有完全相同的生存权利。

“有良心的人一旦意识到自己的错误,就会深感痛苦。这也是对他的一种苦役之外的惩罚。”

追求进步,并跻身于“我们的年轻的一代”的行列——完全是出于一时的青春激情。他是那些数不胜数、各式各样的言行庸俗、思想幼稚、志大才疏而又刚愎自用者之一,这类人对于最流行的时髦思想必定是顷刻间便趋之若鹜,紧紧依附,为的是立即把它庸俗化,并在一瞬间把他们有时竭诚效劳的一切漫画化。

也许,在这件事上起决定作用的是那种特殊的穷人的自尊心。就是因为这种自尊心,千千万万的穷人每逢日常生活中人人必须遵守的某些社会习俗时,都会倾箱倒箧地把自己节衣缩食积攒起来的一点点钱全都花光,只是为了证明自己“毫不逊色于别人”,以及不让那些人对他们横加“指责”。

你们还不知道,还不知道,这是怎样的一颗心灵,这是怎样的一个姑娘!她竟会偷钱,她!如果你们需要的话,她为了济人之难会脱下自己身上的最后一件衣服,光着脚去把它卖掉,再把钱送给你们,瞧啊她就是这样一个姑娘!因为我的孩子啼饥号寒,她甚至去领了黄色执照,她完全是为了我们才去出卖自己的呀!……哎哟,死鬼,死鬼呀!哎哟,死鬼,死鬼呀!你看见了吗?你看见了吗?这就是为你办的丧后酬客宴!上帝啊!你们倒是保护保护她呀,究竟为什么光是站着呢!罗季昂·罗曼诺维奇!您为什么不出来主持正义啊?难道连您都不相信她?你们连她的一个小指头都抵不上,你们所有的人,所有的人,所有的人,所有的人!上帝啊!最后只好请您保护她了!”

你瞧:你已经知道,我母亲几乎是一无所有。妹妹侥幸受了些教育,也注定只能东奔西跑当家庭教师。她们把一切希望都寄托在我的身上。我已经上了大学,但在大学里我无法维持自己的生活,迫不得已只好暂时退学。假如我就这样勉强拖下去,那么十年二十年之后(如果天从人愿,情况好转),我仍然有希望当上一名教师,或者成为一个官吏,可以拿到一千卢布的年薪……(他似乎是倒背如流)然而到那个时候,母亲早已因为操劳和愁苦而憔悴不堪了,我还是不能使她感到安慰,而妹妹……唉,妹妹的情况也许更糟!……难道竟然可以一辈子对这一切都漠不关心,置若罔闻,把母亲忘记到九霄云外,让妹妹低首下心地,譬如说吧,任人侮辱?为了什么?是不是为了在埋葬她们以后,挣钱去养活其他的人——妻子和孩子,而以后又一文不名地让他们活在世上餐风饮露?唔……唔,于是我下定决心要占有老太婆的钱,把它们用做我最近几年的费用,不再使母亲担忧受苦,用这些钱维持我大学的生活,实现我大学毕业后最初的一些计划——大刀阔斧、不遗余力地彻底改变这一切,为自己开创一个崭新的前程,踏上一条独立自主的新路……唔……唔,我要说的就全在这里了……唔,当然喽,我杀死了这个老太婆,——我做了一件坏事……唉,不说了!”

一百只兔子永远拼不成一匹马,一百个疑点永远拼不成一个证据,有句英国谚语不是这样说的吗?不过这仅仅是一种理智的说法,可是一时冲动起来的感情是难以控制的。

其实,要对某些人做出公正的评判,就必须预先放弃一些先入的偏见,改变那种对待我们周围的人和事的习惯态度。

最后,我采取了一个征服女人的心的最高明、最有效的绝招,这个绝招永远都不会使人失望,对任何人都绝对管用,无一例外。这个绝招是众所周知的,它就是阿谀奉承。世界上最难的事是开诚相见,而最容易的事是阿谀奉承。开诚相见,只要有百分之一的虚假,那么马上就会出现不和谐,而麻烦就会随之而来。至于阿谀奉承,哪怕从头至尾都是虚假,但还是令人感到高兴,听起来不会不舒服;哪怕觉得肉麻,但还是觉得舒服。不管阿谀奉承是如何肉麻,其中肯定至少有一半使人觉得真实。

“罪行?什么罪行?”他突然出人意外地狂叫,“我杀死的是一只可恶的、有害的虱子,一个对任何人都没有益处的放高利贷的老太婆,一个穷人的吸血鬼,杀了她可以赎四十桩罪行,这也算是犯罪吗?我可不认为这是罪行,也没有想去赎罪。为什么大家都指着我说:‘犯罪,犯罪!’直到现在我才明白,我的胆怯是非常愚蠢的,现在我已决定去受这种不应该受到的耻辱!我这样做,只是由于自己的卑鄙和无能,也许还由于这个……波尔菲里建议的好处!……”

但是他对自己进行了严格的审判,他那变得冷酷无情的良心,在他以往的行为中没有发现任何特别严重的罪过,除了人人都免不了的一般的失误以外。

 

Gitlab自动部署方案(Systemd+nc)

Gitlab 的 CI 还是比较有好的,只要配置好一些 Shared Runner,所有的项目都可以共享这些 Runner 来跑 CI 了,这样比较节省资源,这些 Runner 勤勤恳恳地跑完了一个就去跑下一个。

但是 CD 自动部署稍稍有些麻烦,Gitlab 这边能做到的就是跑完所有的测试之后发送一个 Curl 通知,告诉 Build 所有的测试通过了,可以开始部署了。.gitlab-ci.yml 文件类似下面:

这样 Runner 在所有的 test stage 执行完了之后,就会执行 deploy 这个 stage。

最后 deploy 这个地方只写了一个 curl ,那么接收 Gitlab 的通知,然后执行部署这个步骤就有些麻烦了。可能需要一个 HTTP 服务监听端口,在网上找了一些方案,有用 PHP 写的脚本,也有用 Python 写的,但是这样就有了更多的依赖要维护了。

找了一圈,发现 netcat 这个小巧的工具就能实现监听端口->响应请求->执行脚本部署的需求。

下面的命令可以一直监听 9999 端口,有请求的话就响应 echo 的内容。然后结束命令。

配合 && 连接起来,就可以做到通过 Curl 触发部署操作了。

通过 systemd,可以将这个脚本管理起来,让它永远重启,这样一次部署之后,马上就可以重新监听,等待下一次部署命令。注意要添加 StartLimitInterval ,限制一下执行的频率。最终的 systemd service 如下:

这样就可以实现每次向 master push 代码,自动测试成功并且马上推送到测试环境中。 update.sh 脚本的最后可以加一个 Curl 命令向钉钉或者 slack 发送提醒。

 

PyCon2018 Review (Part 1)

1. Python 依赖管理的未来?

像 npm 这种工具一般都会产生 .lock 文件的,将版本锁死。Python 并没有这种 lock 文件,这样的话我们部署的时候就有不确定性,可能最终的环境会和预期不一致(What do you want 和 What do you need 的问题)。有一种方案就是 pip freeze > requirements.txt ,但是这种导出的依赖是扁平的,你不是一个包是你项目的依赖,还是你所依赖的 Flask 的依赖的依赖。

所以就有了 Pipenv,使用它来安装,就会产生一个 Pipfile,放着人类可读的依赖包,每次安装之后就会自动产生一个 .lock 文件,是一个 json 格式的文件,对机器处理很友好。每一个依赖都带有 sha256 hash,可以将最终部署的环境锁定。此外,Pipenv 还带有很多非常 Nice 的功能,比如画依赖图、管理 venv、切换 Python 版本等等。需要注意的是,这和 setup.py 是完全不一样的东西,一个是应用的依赖管理,一个是库的打包(这个好问题是最后一位观众提出的)。

推荐指数:5 (讲了Python打包的历史,现场带有Nice的演示,所有的问题都有满意的回答,演讲思路清晰)

演讲者:Kenneth Reitz(requests作者,应该所有的 Pythoner 都知道这个人吧),演讲5天前刚从 Heroku 跳槽到 DigitalOcean。

相关项目:pipPipenv

Youtube:Kenneth Reitz – Pipenv: The Future of Python Dependency Management – PyCon 2018

2. importlib.resources in Python3.7

目前来说,如果你想在你的库或者应用中添加静态文件(类似分词这种元数据的话),方法是放到一个目录中,然后在代码中拼出来路径,打开并读取这个文件。首先这样代码冗长(虽然Python来写只需要5行左右)但是依然很长,并且如果你的程序是从 zip 文件中执行的话,那么就有问题了,你打开文件的时候并不会有这个系统路径存在。

所以 LinkedIn 的 Warsaw (这个库主要是他们开发的)给出了一个 Talk 介绍 Python3.7 中新增的 importlib.resource 库的解决方案:在静态文件所在的文件夹添加一个 __init__.py 文件,那么这个文件夹就变成了一个“module”,使用 importlib.resources 中只要是可以 import 的 module,那么这个 module 中的静态文件就可以通过 importlib 来读取。解决了 zip 文件的情况,还提供了很多 API,还有一些效率上的提升。以后的程序中感觉用这种方式加载静态文件比较靠谱。

推荐指数:3

演讲者:Warsaw from Linkedin

相关项目:https://github.com/linkedin/shivimportlib.resources

Youtube: Barry Warsaw – Get your resources faster, with importlib.resources – PyCon 2018

3. Pythoner为什么要了解Systemd?

Instagram 是业界有名的 Python 用户,他们的博客经常会发表干货,演讲也通常价值很高。不过这个 talk 感觉没有发挥出 Instagram 一贯的水平啊,嘉宾嗓子有点沙哑,加上口音,听懂的难度有点大,理解万岁哈哈。

内容是关于 Systemd 的,教你如何将 Python 程序作为系统的 service 来管理,涉及了服务管理的小历史,这部分是一个不错的 Systemd 入门教程吧。后半部分推销了 facebook 的一个项目。

pystemd 是一个 libsystemd 的 Python wrapper,提供了很多不错的 feature,例如运行一个命令时,将整个系统变成 Read-only 的;或者提供一个与系统的 tmp 相隔离的 tmp; 或者提供一个网络的 firewall,限制只有特定的 IP 可以访问等;或者限制 CPU 和内存等资源的使用,之前我想实现一个多租户的 Python 环境,这个库看起来可以满足我大部分的需求。当然,基本也有所有 systemd 的相关操作。后面 Demo 部分很不错,虽然最后一个貌似失败了。

推荐指数:4

演讲者:Alvaro Leiva Geisse from Instagram & Facebook

相关项目:systemd,pystemd

Youtube:Alvaro Leiva Geisse – Systemd: why you should care as a Python developer – PyCon 2018

4. 命令行UI也需要设计

这是去年的 PyCon 的视频,最近从“稍后观看”里面找到的,发现自己还没看完,就顺带看了。

黑乎乎的命令行并不是没有UI了,相反,UI的复杂程度不亚于GUI,也需要精心的设计。我使用 PostgreSQL 和 MySQL 从没依赖过 GUI, 因为 mycli 和 pgcli 实在是太方便了。命令历史,自动补全,颜色,拿什么 GUI 跟我换我都不换!这两款项目的作者来自 Amjith Ramanujam 来自 Netflix,演讲也很精彩。

命令行不像GUI,GUI上面如果有什么新 feature,就会有一个新的选项或者 icon,用户就知道了。但是命令行呢,要么花时间读手册,要么你有个不错的朋友跟你说一些技巧,不然你很难发现一些实用的 feature。这里演讲者举了几个例子,有时候你要想办法让用户见到这些 nice feature。

第二点,注意力要放在用户身上,首先想到怎么用最好,而不是首先想到难以实现。这里作者提到实现 mycli 的时候,要做到根据数据库的数据自动提示,但是市面上并没有开源的 SQL 引擎,要自己写一个工作量和难度实在太大了,但是作者也实现了。

第三点,配置是罪恶之源,只留给用户去配置一些主观的东西,比如颜色。其他的不要去让用户决定。

最后就是一段演示了,展示了怎么写命令行GUI,虽然写了十几行而已,但是已经完爆很多 REPL 了。

推荐指数: 4

演讲者:Amjith Ramanujam from Netflix

相关项目:myclipgclifish-shellbpythonprompt_toolkit

Youtube:Amjith Ramanujam Awesome Command Line Tools PyCon 2017

 先发这些吧,后面遇到好看的继续更,大家如果看到质量高的也欢迎推荐。2018年貌似有170多个视频,全部看完要好久,一起看的话大家可以互通质量,节省时间啊。

 

分布式定时任务的重复执行问题

定时执行某项任务是非常常见的一个需求,简单的 crontabl 就可以完成。我们最近的一个 Django 项目中用了 Apscheduler 来调度定时任务,遇到了一些问题。

首先,Apscheduler 定位是一个依赖于你的应用的任务调度库,非常轻巧:

That APScheduler is not a daemon or service itself, nor does it come with any command line tools. It is primarily meant to be run inside existing applications.

Apscheduler 是跟随你的应用来启动的,没有持久化,没有命令行操作任务的工作,运行在应用的内部(这其中很挫的一个问题是,每次运行 Django manage.py 都会启动这玩意,甚至跑 migrate 都会启动,不过这很可能是我们的用法有问题)。如果一些小巧的项目用起来倒是不错,但是如果需要对任务进行管理和控制,就有很多限制了。

Apscheduler 分布式的问题

我们遇到的问题是:Gunicorn 部署 Django 的时候会起来 4 个进程实例,我们 Django 应用内的 Apscheduler 也会跟着起来 4 个,那么遇到定时制定任务的时候,每个任务都会执行 4 次。这就很蛋疼了。

我们先是用了一个临时的解决方案,就是让任务的执行有幂等性。任务是拉去一部分数据到 MySQL 数据库中,那么就在数据库建立一些 Uniqe 索引,后面重复执行的任务放不进去。这样的做法显然会浪费一些资源,但是从结果上看是可以保证正确的,数据肯定不会被重复存储。缺点是每个任务都要考虑到同步的问题,或是类似的唯一索引,或是加锁。

然后又有了一种解决方案,manage.py 在运行的时候使用一个文件锁,这样无论有多少个 Django 在运行,都只会有一个 Apscheduler 存在。这样这个问题就可以被忽略了,大家可以再污染实际的定时任务逻辑了。可这种锁这对本地的多个 Django 有效,如果分布式部署 Django ,那么问题又来了,每一个机器的多个 Django 中都会存在一个 Apscheduler。对于解决单机多个 Apscheduler 的方案中,还有一个比较灵巧的,但是稍微复杂一些。原理是让 Gunicorn 先载入 app 然后再 fork 进程,载入 app 的时候开启一个 scheduler 线程,fork 的时候就不会再开线程了。这样每一个 Django 中都有 worker 可以用(jobstore 必须不能使用内存,要用外部可共享的),但是只有一个 scheduler 存在)。具体的步骤如下:

  1. Gunicorn 启动的命令带上 --preload 参数,先载入 app 再 fork 进程,这样下面的代码只会在 master 执行一次:
  2. jobstore 选择非 memory 的选项。否则只有一个 worker 在跑(和 scheduler 在同一个 Django 中的那个),使用外部的 jobstore 就可以让所有的 worker 去跑 scheduler 了
  3. Scheduler 选择 BackgroundScheduler 。因为 BackgroundScheduler 的实现是,调用 start() 会开一个线程来调度。这样我们在 master 进程 load app 的时候会调用一次,Gunicorn fork 出来不会再调用,所以就一直有一个线程存在。

Celery 的解决方案

Celery 作为一个分布式的任务队列,解决方案就比较简单了:直接就只有一个 scheduler 存在,它调度任务,多个 worker 来执行。

You have to ensure only a single scheduler is running for a schedule at a time, otherwise you’d end up with duplicate tasks. Using a centralized approach means the schedule doesn’t have to be synchronized, and the service can operate without using locks.

既然只有一个 scheduler ,那么周期任务既不需要同步,也不需要加锁。缺点就是一个单点。(部署 scheduler 的地方需要提供一个可读写的文件让 scheduler 保存最后一次执行的任务,这个 scheduler 其实是有状态的。)

对于可用性要求不是特别高、周期性任务实时性要求不是特别高的项目来说,其实也够用了。而且我认为 celery 应该会实现类似调度失败重试的机制,不至于和 crontab 那么简陋。

其实我设想的任务调度应该是多个 scheduler 对于某任务、某时间生成一个唯一 token,然后拿 token 去队列里放任务。应该可以解决分布式的问题,即高可用又是幂等的。

 

其余的一些方案:

  1. http://elasticjob.io/