Django项目结构工程化
假设我们项目的名字为sort,当我们通过IDE或是django-admin startproject
命令初始化了一个Django项目时,项目结构往往看起来像这样
(venv) Ember:sort admin$ tree -L 2
.
├── manage.py
└── sort
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
其中我们知道,manage.py
是项目的管理脚本文件。sort文件夹中则存放着与项目配置相关的文件。asgi.py
和wsgi.py
负责提供了项目网关入口;settings.py
提供了项目整体的设置;urls.py
中描述了项目最高级路由。
现在这个目录看起来还是比较好理解的,下面让我们添加几个实际的Django-app
(venv) Ember:sort admin$ python manage.py startapp foo
(venv) Ember:sort admin$ python manage.py startapp bar
(venv) Ember:sort admin$ tree -L 2
.
├── bar
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── foo
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── sort
├── __init__.py
├── __pycache__
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
如你所见,我们为项目添加了两个app,一个foo和一个bar。项目结构现在看起来像这样。
这时候问题就出现了,按照Django项目默认组织的逻辑,app直接创建在根目录下,而项目的配置信息则存在于根目录下的同名文件夹内,这就造成了下列问题
- 项目下与项目同名的文件夹让人迷惑
- app目录和项目配置文件夹没有明确差异
下面我们来分别解决这一问题
分离项目配置
在实际业务中,项目配置往往都存放于项目根目录路径下,因此在这里我们不妨也这么做,简单来说就是将sort文件夹中的东西全部拿出来。
我们先一股脑的把东西都拿出来,然后删除sort文件夹,现在你的项目看起来应该是这样的。
(venv) Ember:sort lvtiancheng$ tree -L 2
.
├── __init__.py
├── bar
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── foo
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── asgi.py
├── manage.py
├── settings.py
├── urls.py
└── wsgi.py
仅仅把内容拿出来是不够的,我们还需要改变拿出来的文件的信息。先让我们看看asgi.py
# asgi.py
"""
ASGI config for sort project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
# 原来的信息表示他的配置文件参考sort.settings,我们把settings提升到根目录,因此可以去掉sort
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sort.settings')
# 修改后
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
application = get_asgi_application()
wsgi.py
和asgi.py
更改的逻辑基本相同,都是去掉sort
然后是manage.py
;作为项目的入口文件,我们看到他同样需要更换settings的路径
# manage.py
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
# 修改前
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sort.settings')
# 修改后
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
urls.py
的配置位于settings.py
内,修改settings.py
时一并修改
下面修改settings.py
# settings.py
# 修改项目根目录
# BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
BASE_DIR = Path(__file__).resolve(strict=True).parent
# 修改urls
# ROOT_URLCONF = 'sort.urls'
ROOT_URLCONF = 'urls'
# 修改wsgi
# WSGI_APPLICATION = 'sort.wsgi.application'
WSGI_APPLICATION = 'wsgi.application'
全部修改完成后,执行python manage.py runserver
启动开发服务器看看能否成功运行,若能运行,则修改内容完成。
整理项目apps
在实际的项目中,我们更希望把apps放在一个文件夹进行整理,下面我们就来做这一步工作。
项目下新建apps文件夹,把bar和foo都扔进去
(venv) Ember:sort lvtiancheng$ tree -L 2
.
├── __init__.py
│
├── apps
│ ├── bar
│ └── foo
├── asgi.py
├── manage.py
├── settings.py
├── urls.py
└── wsgi.py
这种情况下,在settings内对app的安装路径就要做相应的调整
INSTALLED_APPS = [
'apps.foo',
'apps.bar',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
此外,在其他python模块内引用apps也要做以下调整
/apps/foo/models.py
from django.db import models
# 新的引入方式(pycharm报错)
from apps.bar.models import Bar
# pycharm正常使用但项目无法运行的引入方式
# from bar.models import Bar
class Foo(models.Model):
fk = models.ForeignKey(Bar,on_delete=models.CASCADE)
然而,对于使用pycharm的同学来说,这可能让他们的代码失去代码提示并在IDE内报错(虽然项目能够运行)
解决这一问题,通常通过修改settings里的设置解决。
import sys
# 将apps/加入系统路径
sys.path.insert(0, str(BASE_DIR / 'apps'))
# 恢复apps安装名
INSTALLED_APPS = [
'foo',
'bar',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
修改以上两个配置项,即可实现对apps的整理和IDE的高亮提示
这也就形成了两种app组织风格,一种是在注册和引用时时刻表明它属于哪个包,这在大型项目中会比较有利。我们可以对不同的应用进行一目了然的分包管理,例如分别建立apps和libs文件夹。然而IDE会因此失去代码提醒。可能pycharm提供了一些可以更改的设置,但是我个人对探索ide没啥兴趣,如果有这方面比较了解的老哥还请不吝赐教。
第二种设置则是将人工分离的app包路径都通过settings.py注册到系统路径中,这样更加简洁,也不会影响IDE的代码高亮。唯一问题就是当应用很多的时候会比较混乱。
拆分settings
下一步,让我们来看看settings.py
之所以要拆分settings,主要还是针对不同环境部署的考虑。在实际的工程中,我们往往需要为开发、测试和生产等多个不同环境准备多套配置文件,这些配置文件囊括数据库账号密码,项目密钥等。
首先还是老样子,将settings从一个文件转化为一个python模块,再将原有的settings.py
改名为base.py
放在这个模块内,同时新建prod.py
和dev.py
分别指代开发环境和生产环境。
项目结构现在看起来像这样
.
├── __init__.py
│
├── apps
│ ├── __init__.py
│ ├── bar
│ └── foo
├── asgi.py
├── manage.py
├── settings
│ ├── __init__.py
│ ├── base.py
│ ├── dev.py
│ └── prod.py
├── urls.py
└── wsgi.py
我们在base.py
内放通用配置,在dev.py
和prod.py
内则放为不同环境准备的配置信息。当然首先,我们需要恢复base.py
(原settings.py
)内的项目根目录(因为相对路径又发生了改变)
# base.py
# 恢复默认路径
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
# BASE_DIR = Path(__file__).resolve(strict=True).parent
下面我们来举几个例子说明哪些配置应当被拆分出来
# base.py
# 项目密钥
SECRET_KEY = ''
# 是否为DEBUG状态
DEBUG = True
#允许访问主机
ALLOWED_HOSTS = []
# 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# dev.py
from .base.py import *
# 开发环境密钥
SECRET_KEY = 'xxxxxxxxxxxxxxxxx'
# 开发环境,DEBUG=True
DEBUG = True
ALLOWED_HOSTS = ["*"]
# 开发用数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# prod.py
from .base.py import *
# 生产环境密钥
SECRET_KEY = 'yyyyyyyyyyyyyyyyyy'
# 生产环境下,DEBUG=False
DEBUG = False
# 生产用数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'NAME': 'django_mysql', # 数据库名
'USER': 'root', # 账号
'PASSWORD': 'root', # 密码
'HOST': '127.0.0.1', # HOST
'POST': 3306, # 端口
}
}
观察这几个配置的例子,可以发现,拆分配置的本质就是对base.py内变量的重写。
配置拆分完成之后,我们还需指定启动项目所需的配置文件。这部分有点类似我们在分离项目配置时做的那样,只不过这次从删除变成了增加
# manage.py
# 开发环境下常用manage.py启动项目,因此默认设置采用dev
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.dev')
# asgi.py/wsgi.py
# 开发环境下采用生产环境,默认采用prod
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.prod')
此外,还可以在命令行内设置本次命令执行时采用的配置
# 临时使用prod配置
python manage.py runserver --setting=setting.prod
其他
至此,我们已经基本完成了对一个Django项目工程化的操作,但要让这个项目在维护时更加方便便捷,还可以添加以下文件
.gitignore
.gitignore
.gitignore就像他的名字描述的那样,符合该文件内描述的规则的文件将不会被加入git仓库内。通过这一文件我们可以避免向仓库内添加重复的静态文件、编译器缓存、IDE临时文件等等,下面是一个jetbrain通用的.gitignore文件例子,你也可以增加自己的规则
# Created by .ignore support plugin (hsz.mobi)
### macOS template
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
### Windows template
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
requirements.txt
需求文件内包含了这个项目需要的包,通常由pip freeze > requirements.txt
命令生成。
当我们将项目部署到新的python环境时,执行pip install -r requirements
命令即可安装全部依赖
此外,我们也可以像settings一样,为多个不同的环境准备多套依赖描述文件,只需对其内容做拆分即可
# requirements/base.txt
asgiref==3.2.10
Django==3.1
pytz==2020.1
sqlparse==0.3.1
# requirements/prod.txt
-r base.txt # 安装base.txt内的依赖
mysqlclient==1.4.4
此时,我们可以直接通过pip install -r requirements/prod.txt
命令来安装全部适用于生产环境下的依赖
题外话:Django作为一个python框架,同样适用python的虚拟环境管理器(pipenv、virtualenv)等,考虑到pipenv对容器支持并不好,而virtualenv结合virtualenvwrapper后十分好用,一般推荐采用virtualenv作为虚拟环境管理器。
总之,以上只是一些个人在使用Django做web开发的一些个人见解。要管理好一个Django项目仍然有很多可以改进的地方,本文只提供了一种相对简单而有条理的方式。python常常因其弱工程化而为人诟病,然而通过合理的项目组织和管理方式,可以在很大程度上提升项目开发的效率。要想更好地维护和管理工程,就需要你创建很多项目,并且持续地改进你的代码