对于Python的Django框架部署的一些建议
“Django应用、配置文件以及其他各种相关目录的最佳布局是什么样的?”
总是有朋友问我们这个问题,因此我想花一点时间,写一下我们究竟是如何看待这个问题的,这样我们就可以很容易让其他人参照这个文档。请注意,这里是基于 Django 1.7.1 版写的,但是可以很容易应用在 Django 1.4 版之后任何版本。
虽然 Django 1.4 发布时,它包含了一个改进后的项目布局(这还用了很长一段时间),但本文有一些优化项目布局的更好建议。
为什么这种布局比较好
我们在这里推荐的项目布局有几个优点,即:
- 让你获得、重新打包并复用单个的Django应用来用于其他的项目。这通常是不明确的,正如你正在构建一个不管是否要复用的应用。在一开始以想要复用的方式构建应用,会让这一切变得更加简单。
- 鼓励设计可复用的应用。
- 环境的详细设置。在一个单一的整体配置文件中,if DEBUG==True 没有什么意义。这使得很容易能看到哪些配置是共享的,哪些是在每个环境的基础上可覆写的。
- 环境的具体安装要求(PIP requirements)。
- 如果有必要,项目级的模板和静态文件可以覆写应用级的默认值。
- 小而更具体的测试文件更易于阅读和理解。
假设你有两个应用 blog 和 users,以及两个开发环境 dev 和 prod。你的项目布局结构应该是这样的:
myproject/
manage.py
myproject/
__init__.py
urls.py
wsgi.py
settings/
__init__.py
base.py
dev.py
prod.py
blog/
__init__.py
models.py
managers.py
views.py
urls.py
templates/
blog/
base.html
list.html
detail.html
static/
…
tests/
__init__.py
test_models.py
test_managers.py
test_views.py
users/
__init__.py
models.py
views.py
urls.py
templates/
users/
base.html
list.html
detail.html
static/
…
tests/
__init__.py
test_models.py
test_views.py
static/
css/
…
js/
…
templates/
base.html
index.html
requirements/
base.txt
dev.txt
test.txt
prod.txt
本文的剩余部分介绍了如何将项目迁移到这个布局,以及为什么这种布局比较好。
当前默认布局
我们将调用示例项目foo,我知道这是一个非常有创意的名字。我们假设在这里,我们将要启动foo.com。但当我们希望将我们的项目名称映射最终域名时,该项目将以不以任何意义要求的方式存在在这里。
如果你使用 django-admin.py startproject foo 命令开启这个项目,你会得到一个像这样的目录结构:
foo/ manage.py foo/ __init__.py settings.py urls.py wsgi.py
这种布局是一个好起点,我们有一个顶级目录foo,里面包含了manage.py文件和项目目录foo/foo/。在这个目录,你可以查询到源代码控制系统(比如 Git) 。
你应该想到子目录foo/foo/就是这个项目。这里的所有文件,不是一个Django应用程序,就是与项目相关的配套文件。
修改配置
这里的任务是修正不好的配置文件。我将这个布局向新用户展示,我往往惊讶于这几个人怎么知道这甚至可能做到。事实上,当大家都知道这些配置只是Python代码时,他们也不将它们当做Python代码。
因此,让我们来改进配置。对于oo项目而言,将有4个开发环境:dev、stage、jenkins 和 production。给每个开发环境一个它们自己的配置文件。这个过程中要做的事情是:
在foo/foo/目录下新建一个配置目录,并在里面创建一个空的__init __.py文件。
将foo/foo/settings.py移动并重命名为foo/foo/settings/base.py。
在foo/foo/settings/目录下创建单独的dev.py、stage.py、jenkins.py 和 production.py文件。这四种环境的特定配置文件应该包含如下内容:
from base import *
为什么这很重要呢?对于本地开发你想要设置DEBUG=True,但很容易不小心将这个推到生产代码中,因此需要打开 foo/foo/settings/production.py 文件,在初始导入base后加上DEBUG=False。现在,对于这种愚蠢的错误,你的生产环境是安全的。
还有什么可以定制?很明显你可以针对不同的数据库,甚至是不同的主机来配置staging、jenkins和production等开发环境。然后在每个环境配置文件中来调整那些配置。
使用这些配置
无论你通常使用哪种方法,使用这些配置都非常简单。要使用该操作系统的环境,你只要做:
export DJANGO_SETTINGS_MODULE=“foo.settings.jenkins”
现在你就在使用 jenkins 的配置。
或者,也许你更喜欢把它们作为这样的命令行选项:
./manage.py migrate —settings=foo.settings.production
同样的,如果你使用 gunicorn,命令则如下:
gunicorn -w 4 -b 127.0.0.1:8001 —settings=foo.settings.dev
还有什么可自定义的配置?
另一个实用建议是将一些默认的集合配置从元组改为列表。例如 INSTALLED_APPS,将它从:
INSTALLED_APPS =( ... )
改为:
INSTALLED_APPS = [ … ]
现在,基于每个环境的特定配置文件,我们可以更轻松地在 foo/settings/base.py文件中添加和删除应用。例如,你可能只想在dev环境而不是其他环境中安装Django调试工具栏。
这个技巧对 TEMPLATE_DIRS和MIDDLEWARE_CLASSES 配置也非常有用。
我们经常使用的另一个技巧是把应用分为两个列表,一个是项目的必要前提,另一个用于实际项目应用。如下面所示:
PREREQ_APPS = [ ‘django.contrib.auth', ‘django.contrib.contenttypes', … ‘debug_toolbar', ‘imagekit', ‘haystack', ] PROJECT_APPS = [ ‘homepage', ‘users', ‘blog', ] INSTALLED_APPS = PREREQ_APPS + PROJECT_APPS
为什么这个有用?第一,它有助于更好地区分Django核心应用、第三方应用及你自己的内部项目的具体应用。对于测试和代码覆盖率等事情,写明你的特定应用的PROJECT_APPS列表往往就派上了用场。你有写一个应用列表,因此你可以轻松自动地确保它们的测试运行,记录的测试覆盖率只包括它们而不包括任何第三方的应用,且无需在两个不同的地方维护这个列表。
修改要求
大多是项目有一个requirements.txt文件,它用如下命令安装:
pip install -r requirements.txt
对于简单的小项目这以足够了,但requirements.txt文件有一个鲜为人知的特点是,你可以使用-r参数来包括其他文件。因此,对于所有常见的安装要求,我们可以创建一个base.txt文件;然后,如果我们需要能够运行测试,我们可以创建一个包含如下内容的特定的requirements/test.txt文件:
pytest==2.5.2
coverage==3.7.1
我承认这没有巨大的好处,但它确实有助于区分什么是每个开发环境的要求。同时,对于其性能,它不会安装一堆在实际生产中用不上的东西,来减少生产环境中的pip安装时间。
测试文件
我们为什么要拆分很大的测试文件呢?其中的一个主要原因是,如果你在一个tests.py文件中对每个应用写了足够多的测试,那么这个文件最终将变得非常臃肿。这样的代码可读性很差,并且你不得不在编辑器中花很多时间来滚动浏览代码。
当你和其他开发者一起工作时,小文件也能让你在代码合并时少遇到冲突。小文件是你的朋友。
URLs
对于小型项目,把所有的URL定义放在foo/urls.py文件中,让它们在同一个地方。但是,如果你的目标是代码的清晰和可复用,你最好在每个应用中定义它们的url,再将它们包含在你的主项目中。你不应如下所做:
urlpatterns = patterns(‘', url(r'^$', HomePageView.as_view(), name=‘home'), url(r'^blog/$', BlogList.as_view(), name=‘blog_list'), url(r'^blog/(?P<pk>d+)/$', BlogDetail.as_view(), name=‘blog_detail'), … url(r'^user/list/$', UserList.as_view(), name=‘user_list'), url(r'^user/(?P<username>w+)/$', UserDetail.as_view(), name=‘user_detail'), )
你应该这样做:
urlpatterns = patterns(‘', url(r'^$', HomePageView.as_view(), name=‘home'), url(r'^blog/‘, include(‘blog.urls')), url(r'^user/‘, include(‘user.urls')), )
模板和静态资源
每个应用中都有templates/和static/目录,这让一个应用可以基本上复用到其他的项目中。
对于一个很酷的功能,我们全在一个包中获得应用提供的默认模板和任何相关的静态资源,如特殊的Javascript。
但是,它也让我们可以覆写每个项目主目录foo/templates/下的模板。我们通过增加一个 templates/blog/detail.html 模板覆写默认的 blog/templates/blog/detail.html 模板。
复用Django应用
假设你已经使用这个布局一段时间,有一天你会意识到你的新项目需要一个blog应用,这个从你的foo项目出来的应用将是完美的。所以你复制、粘贴文件……错误!现在你有这个应用的两个副本。假定你还记得,在一个副本中进行Bug修复和新功能增添需要手动地在项目间迁移。
相反,为你的博客创建一个新的目录,并把foo/blog/目录中的内容放入其中。同时,调整现有的foo项目和你的新项目来进行安装。
如果需要的话,它们仍然可以跟踪这两个不同版本的应用,或持续更新,且获得它们不断发展中的所有bug修复和新功能。你仍然可以在每个项目的基础上,根据需求覆写模板和静态资源,所以这样做真的没有任何问题。