“Django应用、配置文件以及其他各种相关目录的最佳布局是什么样的?”
总是有朋友问我们这个问题,因此我想花一点时间,写一下我们究竟是如何看待这个问题的,这样我们就可以很容易让其他人参照这个文档。请注意,这里是基于 Django 1.7.1 版写的,但是可以很容易应用在 Django 1.4 版之后任何版本。
虽然 Django 1.4 发布时,它包含了一个改进后的项目布局(这还用了很长一段时间),但本文有一些优化项目布局的更好建议。
为什么这种布局比较好
我们在这里推荐的项目布局有几个优点,即:
- 让你获得、重新打包并复用单个的Django应用来用于其他的项目。这通常是不明确的,正如你正在构建一个不管是否要复用的应用。在一开始以想要复用的方式构建应用,会让这一切变得更加简单。
- 鼓励设计可复用的应用。
- 环境的详细设置。在一个单一的整体配置文件中,
if DEBUG==True
没有什么意义。这使得很容易能看到哪些配置是共享的,哪些是在每个环境的基础上可覆写的。 - 环境的具体安装要求(PIP requirements)。
- 如果有必要,项目级的模板和静态文件可以覆写应用级的默认值。
- 小而更具体的测试文件更易于阅读和理解。
假设你有两个应用 blog
和 users
,以及两个开发环境 dev 和 prod。你的项目布局结构应该是这样的:
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
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
命令开启这个项目,你会得到一个像这样的目录结构:
1 2 3 4 5 6 7 |
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
文件。这四种环境的特定配置文件应该包含如下内容:
1from base import *
为什么这很重要呢?对于本地开发你想要设置DEBUG=True
,但很容易不小心将这个推到生产代码中,因此需要打开 foo/foo/settings/production.py
文件,在初始导入base
后加上DEBUG=False
。现在,对于这种愚蠢的错误,你的生产环境是安全的。
还有什么可以定制?很明显你可以针对不同的数据库,甚至是不同的主机来配置staging、jenkins和production等开发环境。然后在每个环境配置文件中来调整那些配置。
使用这些配置
无论你通常使用哪种方法,使用这些配置都非常简单。要使用该操作系统的环境,你只要做:
1 |
export DJANGO_SETTINGS_MODULE=“foo.settings.jenkins” |
现在你就在使用 jenkins 的配置。
或者,也许你更喜欢把它们作为这样的命令行选项:
1 |
./manage.py migrate —settings=foo.settings.production |
同样的,如果你使用 gunicorn,命令则如下:
1 |
gunicorn -w 4 -b 127.0.0.1:8001 —settings=foo.settings.dev |
还有什么可自定义的配置?
另一个实用建议是将一些默认的集合配置从元组改为列表。例如 INSTALLED_APPS,将它从:
1 2 3 |
INSTALLED_APPS =( ... ) |
改为:
1 2 3 |
INSTALLED_APPS = [ … ] |
现在,基于每个环境的特定配置文件,我们可以更轻松地在 foo/settings/base.py
文件中添加和删除应用。例如,你可能只想在dev环境而不是其他环境中安装Django调试工具栏。
这个技巧对 TEMPLATE_DIRS
和MIDDLEWARE_CLASSES
配置也非常有用。
我们经常使用的另一个技巧是把应用分为两个列表,一个是项目的必要前提,另一个用于实际项目应用。如下面所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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
文件,它用如下命令安装:
1 |
pip install -r requirements.txt |
对于简单的小项目这以足够了,但requirements.txt
文件有一个鲜为人知的特点是,你可以使用-r
参数来包括其他文件。因此,对于所有常见的安装要求,我们可以创建一个base.txt文件;然后,如果我们需要能够运行测试,我们可以创建一个包含如下内容的特定的requirements/test.txt
文件:
1 2 3 |
-r base.txt pytest==2.5.2 coverage==3.7.1 |
我承认这没有巨大的好处,但它确实有助于区分什么是每个开发环境的要求。同时,对于其性能,它不会安装一堆在实际生产中用不上的东西,来减少生产环境中的pip安装时间。
测试文件
我们为什么要拆分很大的测试文件呢?其中的一个主要原因是,如果你在一个tests.py文件中对每个应用写了足够多的测试,那么这个文件最终将变得非常臃肿。这样的代码可读性很差,并且你不得不在编辑器中花很多时间来滚动浏览代码。
当你和其他开发者一起工作时,小文件也能让你在代码合并时少遇到冲突。小文件是你的朋友。
URLs
对于小型项目,把所有的URL定义放在foo/urls.py
文件中,让它们在同一个地方。但是,如果你的目标是代码的清晰和可复用,你最好在每个应用中定义它们的url,再将它们包含在你的主项目中。你不应如下所做:
1 2 3 4 5 6 7 8 |
urlpatterns = patterns(‘’, url(r’^$’, HomePageView.as_view(), name=‘home’), url(r’^blog/$’, BlogList.as_view(), name=‘blog_list’), url(r’^blog/(?P<pk>d+) |