本文介绍项目部署前的一些必要的步骤。
添加主页
目前我们的项目没有主页显示,看上去有点尴尬,我们创建一个。
在core应用里,添加urls.py,再从playground/urls.py里复制代码并作少许修改。我们不必添加实际的视图,直接使用一个通用的模板视图,并提供参数指向我们的一个页面 core/index.html。
from django.urls import path from django.views.generic import TemplateView # URLConf urlpatterns = [ path('', TemplateView.as_view(template_name='core/index.html')) ]
我们再创建网页 core/templates/core/index.html,正如之前说过的,为了避免被其他应用覆盖,在templates下再新建文件夹core,然后放置index.html。vscode输入感叹号 ! ,然后再按【TAB】键,就生成了基本的html框架。
接着,我们修改storefront/urls.py文件,设置主页面指向 core.urls指定的页面。
urlpatterns = [ path('', include('core.urls')), ... ]
完成之后,我们访问 http://127.0.0.1:8000,就指向了我们刚建的index.html,当然目前是空网页,我们需要完善。
管理静态资产
之前我们在《掌握Django(十三)–上传文件》中提供了附件,解压后在”Code\8- Preparing for Production\Static Files”文件夹下有logo.svg和sytles.css两个文件。
在core应用下新建 static/core 文件夹,再将附件的两个静态文件拖入其中。然后修改 core/templates/core/index.html,首先添加 {% load static %} 标记,然后在使用静态文件的时候,用类似 {% static ‘core/styles.css’ %} 这样的方法引入。
{% load static %} <!DOCTYPE html><html lang="en"><head> ... <link rel="stylesheet" href="{% static 'core/styles.css' %}"> </head> <body> <div class="home-page"> <img src="{% static 'core/logo.svg' %}" alt="Logo" class="logo"> </div> </body> </html>
完成之后,我们访问首页,就能看到图片了。
收集静态资产
前面我们没做配置,Django就能自动识别静态资产,这是怎么回事呢?
当我们在开发环境时,也就是DEBUG为True时,Django会自动将各个应用的static文件夹下的内容拷贝到一个统一的地方,并提供静态文件服务,这样我们就能使用静态文件了。但在生产环境下,该工作不是自动进行的,有一个专门的命令来收集静态资产,但在该命令运行前,我们需要做些配置。
查看storefront/settings.py,我们之前在上传文件这节谈到过MEDIA_URL和MEDIA_ROOT,静态资产的配置也类似。默认STATIC_URL已有了,我们还需配置一个STATIC_ROOT,这是一个文件系统全路径,我们运行收集静态资产命令时,各应用的静态资产复制的目的地就是STATIC_ROOT指定的位置。
STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static')
设置好STATIC_ROOT后,我们运行静态资产收集命令:
python manage.py collectstatic
完成后,我们能看到项目根目录新建了一个文件夹static,里面包含各个应用的静态资产。当然应用必须包括在 settings的INSTALLED_APPS里,否则该应用的静态资产是不会被收集的。
我们可不希望static文件夹里的文件被上传到github上,我们在.gitignore添加一行:
*.pyc /media/ /static/
静态资产服务
Django没有提供静态文件服务,在生产环境中,尽管我们收集了各应用的静态文件,但不会提供服务,第三方库whitenoise可以做这个事情。
pipenv install whitenoise
然后我们还要设置whitenoise的中间件,尽量放在前面,但要在SecurityMiddleware后面,这里就恰好放在它之后。
MIDDLEWARE = [
...
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]
完成了whitenoise的设置,在生产环境我们就能提供静态文件服务了。
配置日志
日志配置是非常重要的,用于诊断问题。在生产环境部署之后,有些功能如果不工作,我们可以通过日志查找原因。
转到 settings模块,添加 LOGGING 设置。
- version,这里设置为1
- disable_existing_loggers,作为最佳实践,始终保持为False,这样就能看到Django其他logger发送的日志。
- handlers,用于指定如何处理获取的日志信息。我们可以指定发送到控制台(console),或记录到文件中(file)。我们需要指定handler的类,记录到文件还要指定文件名,并根据需要设定handler的formatter(下面会说到)。
- loggers,可以指定接收某个应用比如playground或仅接收具体模块 playground.views,如果键保持为空字符串,表示接收所有应用的日志信息,我们就保持为空。
然后可以指定handlers,我们指定收到指定应用的消息后,我们用哪些handler来处理,我们就指定之前定义的两个handler (console 和 file )。
也可以指定日志的级别,表示仅接收高于或等于指定级别的日志,比如设置‘ERROR’,意思是只接收ERROR和CRITICAL级别的日志。
- 可选的formatters,我们可以定义日志的格式。比如我们可以定义一个’simple’格式只显示日志信息,另一个’verbose’格式显示更多的信息。可以google搜索”python logging logrecord”查看相关日志记录的属性。
https://docs.python.org/3/library/logging.html#logrecord-attributes
根据这个框架,我们可以添加多个logger,方便地指定比如playground的错误级别及以上日志由console处理,而store的警告级别及以上日志由file处理等等,总之非常灵活。
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler'
},
'file': {
'class': 'logging.FileHandler',
'filename': 'general.log',
'formatter': 'verbose'
}
},
'loggers':{
'':{
'handlers': ['console','file'],
'level': os.environ.get('DJANGO_LOG_LEVEL','INFO')
# 这里不直接指定日志级别,而是从环境变量获取,这样更灵活
# 如果环境变量未指定,则用默认的INFO级别
}
},
'formatters': {
'verbose': {
'format': '{asctime} ({levelname}) - {name} - {message}',
'style': '{'
# 左大括号 { 表示 str.format,这里我们就用这个
# 美元号 $ 表示 使用str的Template
}
}
}
记录日志
我们转到 /playground/views.py,去掉之前的缓存代码。
通过 logger=logging.getLogger(__name__)得到一个logger,__name__获取当前模块的名称,这里就是 playground.views,我们也可以用 logging.getLogger(‘playground.views’),但最佳实践就是用__name__,这样模块改名也不会受影响。logger有各种方法对应日志级别,比如info,debug,error等等。
...
import logginglogger = logging.getLogger(__name__)
# playground.views
class HelloView(APIView):
def get(self, request):
try:
logger.info('Calling httpbin')
response = requests.get('https://httpbin.org/delay/2')
logger.info('Received the response')
data = response.json()
except requests.ConnectionError:
logger.critical('httpbin is offline')
return render(request, 'hello.html', {'name': 'kelemi'})
完成代码修改后,我们访问 http://127.0.0.1:8000/playground/hello
我们能看到控制台有输出如下信息,这是我们在代码中加的info级别的信息,但由于console的handler只设定了class,没有定义formatter,所以只有messsage,没有日志时间、级别等其他信息。
Calling httpbin
Received the response
我们到file中去查看日志,根据我们的设置是 general.log文件,我们查看生成的该文件,发现由于设了formatter为verbose,所以信息比较完整。由于我们设置了disable_existing_loggers为False,还看到其他logger比如silk发的警告日志,这是比较合适的,使我们了解系统的其他日志。
...
2023-03-13 14:31:05,469 (INFO) - django.utils.autoreload - Watching for file changes with StatReloader
2023-03-13 14:31:05,623 (WARNING) - silk.profiling.profiler - Cannot execute silk_profile as silk is not installed correctly.
2023-03-13 14:31:15,698 (INFO) - playground.views - Calling httpbin
2023-03-13 14:31:22,209 (INFO) - playground.views - Received the response
另外,我们要说的是在代码中使用logger写日志会使代码变得冗长难以维护,所以我们不能到处写日志,要明智地选择使用场景。还需要注意的是,日志是公开的,在出现问题时都会查它,所以不要写敏感信息如信用卡号、用户名密码等。
最后,我们不希望general.log也作为存储库保存起来,所以需要在.gitignore中忽略它。
*.pyc
/media/
/static/
general.log
管理开发环境和正式环境设置
我们需要将开发环境的设置与正式环境分开。
查看我们的settings模块,目前是开发环境的设置,比如本地数据库、用户、密码等。部署正式环境时,我们需要修改成正式环境的设置;回到开发环境修改时又得改回来,来来去去非常乏味。
很重要的一点,我们不能在这里存储正式环境数据库密码,用github时存储代码的话,其他人能看到该密码。所以在部署正式环境时,我们应该将数据库密码存储在某个环境变量中,部署时读取并写入settings中。
下面我们就来看看如何分离开发环境与正式环境的设置。
首先在storefront下新建文件夹 settings,然后将settings.py 移到该文件夹中,并重命名为common.py。
再新建文件dev.py和prod.py分别表示开发环境和生产环境的配置,这两个文件分别导入 common模块所有内容。
from .common import *
我们再来看看可以将哪些配置移到各个环境的配置中。
- 将common.py里的 SECRET_KEY 移到dev.py文件,而正式环境的SECRET_KEY将从环境变量里读出,我们添加 SECRET_KEY = os.environ[‘SECRET_KEY’]
- 去掉common.py的DEBUG,在dev中设置为True,而在prod中设置为False。DEBUG为False里需要另外设置 ALLOWED_HOSTS, 我们将common的ALLOWED_HOSTS移到prod,暂时保持为空,后续部署后我们再添加。
- 将 DATABASES 从common移到dev。而prod的DATABASES后续我们将从环境变量中获取,这里暂不处理。
差不多了,由于之前我们的配置文件都从storefront.settings中读取,我们需要修改指向。查找所有项目文件,搜索键值
‘DJANGO_SETTINGS_MODULE’
将找到的配置值 从storefront.settings 改成 storefront.settings.dev。
在部署正式环境时,我们只要传入环境变量即可:
DJANGO_SETTINGS_MODULE=storefront.settings.prod
我们来运行下runserver:
python manage.py runserver
再检查各个API端点访问正常。
再试下自动化测试能否通过:pytest
能通过,但有警告说whitenoise找不到目录,主要是common.py有个变量是BASE_DIR,现在common.py已移到settings文件夹了,我们需要在后面再加一个parent。
BASE_DIR = Path(__file__).resolve().parent.parent.parent
使用Gunicorn服务
到目前为止,我们使用Django内建的WEB服务,在开发过程中,这个很棒,每当我们修改代码,该服务就重启并能即时看到效果。但在生产环境中,我们需要更快更健壮的服务器,我们使用gunicorn。
停止Django内建的WEB服务器,然后安装gunicorn:
pipenv install gunicorn
项目文件夹storefront下有一个文件叫wsgi.py文件(可以读成威士忌),wsgi是Web Server Gateway Interface的简称。
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'storefront.settings.dev')
application = get_wsgi_application()
wsgi首先获取环境变量 DJANGO_SETTINGS_MODULE,然后得到一个wsgi应用程序实例。所以如果要用gunicorn启用我们的应用程序,必须使用 wsgi模块作为进入点。
gunicorn storefront.wsgi
这样就启动了WEB服务,端口也是8000。
访问 http://127.0.0.1:8000,能看到我们的应用。
与Django内建WEB服务不同的是,gunicorn更快并适合在生产环境中部署。另外,gnuicorn不实时体现代码的修改,如果有修改,我们需要重新启动gunicorn。
小结
本文介绍了项目部署前的一些必要步骤,包括管理静态资产、日志收集设置、开发环境的配置文件分离,以及gunicorn的服务接管等内容。
下一篇是《掌握Django》系列的最后一篇,介绍Django项目的部署。