Django Best Practice:How to deal with settings.py in Git

NOTE: After writing this article several months ago, I gradually realized that despite this solution works, it is error-prone, and too verbose. I'll write a new article explaing how I deal with it now.

When I started wirting my blog, one thing that confused me is different versions of settings.py. As we know, different settings.py should be kept for developement and deployment, however when it comes to using Git, things get messy. The main difficulties lies on two things:

  1. Should I upload settings.py from developement environment to Git server? If I did, does that mean I have to change the file every time after doing Git pull on deployment server?
  2. If I choose not to upload settings.py, there is a risk of losing data.

My purpose is to keep different versions of settings.py. Here's what I did:

  1. First, on the computer I do developing, commit and push everything as you normally do, including settings.py.
  2. Create a new branch called deploy on Github.
  3. Then, on deployment server, git pull from branch master.
  4. checkout deploy branch, Modify settings.py for deployment, e.g. set DEBUG=False, change MEDIA_ROOT, STATIC_ROOT, etc. The ONLY file you should change is settings.py, DO NOT TOUCH OTHER FILES !
  5. commit and push to branch deploy.
  6. Back to developing environment, type this command:
    bash git update-index --assume-unchanged my_blog/settings.py Done :)


The key part is step 5. First you should understand what git update-index --assume-unchanged is doing, basically it assumes that the file does not change. From git-scm:

--[no-]assume-unchanged
When these flags are specified, the object names recorded for the paths are not updated. Instead, these options set and unset the "assume unchanged" bit for the paths. When the "assume unchanged" bit is on, Git stops checking the working tree files for possible modifications, so you need to manually unset the bit to tell Git when you change the working tree file. This is sometimes helpful when working with a big project on a filesystem that has very slow lstat(2) system call (e.g. cifs).

This option can be also used as a coarse file-level mechanism to ignore uncommitted changes in tracked files (akin to what .gitignore does for untracked files). Git will fail (gracefully) in case it needs to modify this file in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually.

So next time you commit your work, though settings.py may has been changed, those changes won't get uploaded to Git server, which means there is no confict when you pull from master branch on deployment server no matter how many times your settings.py has been edited there. You could also achieve this by adding my_blog/settings.py to .git/info/exclude.

Finally, workflow is like this:

Coding, Push to master —> On deployment server, pull from master

Edit settings.py on deployment server (if needed) —> Push to deploy

Be careful, NEVER push to branch master from deployment server.

Now you've pushed some new commits to Github and want to apply it on your server. Here are the steps:

# step 1
make sure you're on master branch, if not, git checkout master

# step 2
$ git pull

# step 3
$ git checkout deploy

# step 4
$ git rebase master

# step 5
$ git push origin deploy

I'm open to changes and suggestions, if you have better ideas, feel free to comment.

Python fileinput 使用总结

用途

1. 多个文件作为输入, 遍历文件每一行
2. 在原处修改文件

用途1

如果我们有如下的文件夹结构:

test/  
|_____ 1.txt   content: 
|                 1_line1  
|                 1_line2    
|_____ 2.txt   content: 
|                 2_line1
|                 2_line2  
|_____ test_fileinput.py  
import fileinput  
import sys  

for line in fileinput.input(sys.argv[1:]):
     print(fileinput.filename(), fileinput.filelineno(), line)

在 Linux 下执行

$ python test_fileinput.py *.txt

输出

1_line1

1_line2
2_line1

2_line2

在Windows下直接用*.txt, 系统不认
Windows Fail

所以修改成

import glob
all_files = [f for files in sys.argv[1:] for f in glob(files)]
for line in fileinput.input(all_files):
     print(fileinput.filename(), fileinput.filelineno(), line)

这样兼容Linux/Windows, 两种形式 *.txt1.txt 2.txt 都可以作为输入。
总之最后提供一个 filelist 给 input 就行。也可以这样:

$ ls | ./filein.py

甚至并不一定是多个文件, 也可以是多个文件的内容, 例如

$ cat *.py | python fileinput_grep.py fileinput

也能正常工作, 会遍历"所有.py文件的每一行"。以上的用法对单个文件/文件内容也同样适用。

用途2: 在原处(inplace)修改文件

for line in fileinput.input('file.txt', inplace=True):
     line = ...  # edit line
     print line,  # stdout is redirected to the file


Note:

  1. fileinput 能够提供很多元数据信息, 例如上面看到的fileinput.filename(), fileinput.filelineno(),还有fileinput.isfirstline(), fileinput.isstdin() 等, 更多内容参考官方文档

  2. fileinput 支持 context manager 例如在原处修改文件最好写成

    with fileinput.input('file.txt', inplace=True) as f:
        for line in f:
        ...
    

    否则最后还得调用 fileinput.close()

  3. 一个常见的任务是,如果某一行满足某个条件,那么修改/删除这一行。这时候不要忘记原样输出别的行!

  4. 如果单纯地print会多出一些空白行,这是因为print的时候末尾自带一个换行符。解决方法有两种
    (1) Python2.X
    去掉末尾的换行,strip默认删除空白符(包括'\n', '\r', '\t', ' ')

    line.rstrip()
    print line
    

    (2) Python3.X
    print()函数有个参数是end,代表以什么结尾,默认是\n,我们用空字符做结尾

    print(line, end='')
    

用Django搭建个人博客

这篇文章将简单地描述一下博客的搭建过程。博客源代码见这里
首先要感谢stevelosh, 博客的基本设计参考了他的博客,来源于stevelosh.com 这篇文章。
博客从2013.8开始搭建,暑假主要处于学习阶段,学期途中插空写了一点,2014寒假把基本框架完成了。
对于想基于代码学习/搭建博客的行为,本人不能够更欢迎,但是请至少做到:
在运行之前修改settings.py,再不济,请至少把这里的email替换成你自己的email

ADMINS = (
    # ('Your Name', 'your_email@example.com'),
    ('laike9m', 'laike9m@gmail.com'),
)

否则,你的代码运行时的出错信息将被发送到我的邮箱,不利于你进行调试,也给我带来极大困扰。不胜感激!!

设计

在博客搭建之初,我就决定要尽可能少写前端代码。最简单的方法自然是使用CMS。实际上有不少使用Django的CMS,比如最著名的django CMS。不过调研一番之后发现一个问题:没有一个支持Python 3。考虑到博客里必然有一堆Unicode字符,我还是希望能用Python 3的,所以就放弃了CMS。
(2014.6.25 EDIT: 目前发现django CMS已经能支持Python3.4了,至少3.0.2版本以及更新的都可以。参见3.0.2 release
好在马上找到了替代品:HTML模版。最后使用了css3templates 里面的一个模板。模板基本只包含HTML/CSS/JS代码,这正是我需要的东西,因为后端本来就是想从零开始写的。

文件路径结构

用Django建网站,staticmedia 到底怎么放置?这个问题没有标准答案,反正我没有用 STATICFILES_DIRS,也就是把所有static files都放在了appname/static/appname 里面。这样的好处是文件结构比较清晰,坏处是如果app多了同样的文件可能要存很多份,会减慢网站加载速度。下面是我的文件夹结构,供参考,文件均未列出:

    my_blog
    ├─media
    │  ├─content
    │  │  └─BlogPost
    │  │      ├─2009
    │  │      ├─2013
    │  │      ├─2014
    │  │      └─images
    │  └─files
    ├─My_Blog
    │  ├─css3two_blog
    │  │  ├─migrations
    │  │  ├─static
    │  │  │  └─css3two_blog
    │  │  │      ├─documentation
    │  │  │      ├─fonts
    │  │  │      ├─images
    │  │  │      └─js
    │  │  └─templates
    │  │    └─css3two_blog
    │  ├─mytemplatetags
    │  │  └─templatetags
    │  ├─my_blog
    │  └─templates
    │      └─admin
    └─static
        ├─admin
        │  ├─css
        │  ├─img
        │  │  └─gis
        │  └─js
        │     └─admin
        └─css3two_blog
            ├─documentation
            ├─fonts
            ├─images
            └─js

画这个东西的时候才知道Windows/Linux下有个命令叫 tree,大好评!
注意在开发的时候,my_blog/static 里面可以不放东西,只要 DEBUG=True 就行。如果要接受文件上传,那么在 urlpatterns 后面添加:

+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

实际部署时该怎么来就怎么来。
有一个 app 叫做 mytemplatetags,它是专门用来装 custom template tags/filters 的,参见 官方文档:Custom template tags and filters.

主机

在哪里买主机是个需要好好思考的问题。使用VPS的话,可选择的范围很大。但是第一次建网站,没什么经验,怕配置不好。更重要的是用VPS不够短平快,而我希望博客能尽快投入使用。然后就查到了 WebFaction。虽然我并不想做广告,但真的向大家推荐这个主机服务,尤其是怕麻烦的人。Webfaction是个PaaS服务,支持许多语言/框架,而且服务很完善,不论是在它的 Q&A Community 提问还是open a ticket,工作人员的解答都很迅速。虽然是美国的主机(也可以选东南亚的),但是国内访问速度不错。
至于其它选择,外国人用 heroku 的比较多。国内主机不太了解,SAE 据说不错。

版本控制

显然是Git+Github,没什么好说的。印象深刻的是有一次在主机上玩火敲了这个命令 find . -not -name 'xxx' | xargs rm,结果大家都懂。还好WebFaction重建一个Django app就是点击几下的事情,代码又在Github上,所以很容易就恢复了。
还有,settings.py 这个文件最好ignore掉,因为部署和开发在设置上差异很大,所以这个文件在本地和服务器上需要保存不同的版本。顺便分享一下 .gitignore 吧:

    .pydevproject
    .project
    .settings/*
    db.sqlite3
    css3two_blog/migrations/*
    css3two_blog/static/css3two_blog/css/unused/*
    *.pyc

为什么settings.py没有包含在内呢?这个问题比较复杂,在我的另一篇文章 Django Best Practice: How to deal with settings.py in Git 里面有详细讲述。

使用Markdown

这是跟stevelosh学的,而且似乎越来越多的个人博客都在这么做。我的每一篇文章都是用markdown写的,然后渲染成html。关于如何渲染有不同的做法,一开始尝试了pygments + django markup,虽然可以实现代码高亮不过样式比较难看。目前采用的方法是把markdown文本发送给Github API,得到 gfm 格式的html。需要注意的是得到的 html 是没有样式的,所以又在网上找了别人写的 gfm 的 css,添加到页面里,显示出来的页面就和 Github 的 README 差不多了。
这部分的工作有一定难度,需要设计出一套 markdown 和 html 文件的保存机制,并且能正常应对 admin 界面中的修改/删除操作。具体可见 models.pyadmin.py

其它

  1. 使用 south 来应对 Model 的变更
  2. 尽量把逻辑放在 modelsviews 来完成,因为Django的 templates 实在很难用
  3. 虽然用了 HTML 模版,但是在前端仍然花了很多时间
  4. 博客完全可以用 Hyde 搭建,这样加载起来能快一些


初版:2014-2-5
EDIT:2014-2-20 加入文件路径结构小节,增补其它小节内容 EDIT:2014-6-25 django CMS 已经支持 Python3.4


top