原文:Django 站点部署和管理常用命令
作者:Breaker <breaker.zy_AT_gmail>
通过 The Django Book 学习建立、部署和管理 Django 站点的笔记,内容重点 章节 12:部署 Django
Django 与 The Django Book 版本对应关系
Django 0.96 参考 Django Book 1.0
Django 1.0 或以上 参考 Django Book 2.0
本文使用 Django 1.1,以下简称 Django 为 dj
建立 Django 工程和应用
1. 建立 Django 站点工程
django-admin.py startproject projectname将创建目录 projectname,projectname 是一个工程 (Project),一个 Project 下可以有多个 dj 应用 (App)
一个 App 包括模型 (Model) 和视图 (View),按 Python 的包结构组织
最简单的情况是,不创建 App,Python 代码都放到 Project 根目录下的 views.py 中
如果要使用 dj 的 Model(数据库层),则必需建立一个 App
2. 建立 App
python manage.py startapp appname建立 App 后,在 settings.py 中将 appname 加入到 Project 的 INSTALLED_APPS 中:
INSTALLED_APPS = ( 'django.contrib.auth', # INSTALLED_APPS 中已有一些默认的 app 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'projectname.appname', # 使用 Python 的目录层级格式:包名.子包名.模块名 ) 3. 验证 Modelpython manage.py validate验证 (validate) Model 不需要创建数据库
4. 打印 Model 建立数据库表的 SQL 语句
python manage.py sqlall appname如果 validate 失败,这里也会失败
dj 分析 Model 和 settings.py 中 DBMS 的设置 推导出 SQL 语句
5. 建立 Model 数据库表
python manage.py syncdbdj 自动连接 DBMS,根据 Model 建立数据库表,即执行上面打印的 SQL 语句
但是修改了 Model 之后,不能直接用 syncdb 同步修改表,必需先把表删了,再用 syncdb
6. 创建站点管理员用户
python manage.py createsuperuser如果 settings.py 的 INSTALLED_APPS 中有 django.contrib.auth,当第一次运行 syncdb 时会询问是否创建站点管理员用户
如果当时不创建管理员的话,后来可以运行 createsuperuser 来创建
使用 django-admin.py 管理站点时,需要 django.contrib.auth
7. 启动 DBMS 客户端管理程序
python manage.py dbshell启动设置的 DBMS 的客户端管理程序,如 MySQL 的 mysql.exe
8. 启动 Django 站点的 Python 交互解释器
python manage.py shell启动一个 Python shell,在其中可以执行 Model 类的方法,向数据库中插入/更新数据
django-admin.py 和 manage.py 的关系
django-admin.py
属于 dj 的全局管理工具,dj 安装后,放在 <Python 安装目录>/lib/site-packages/django/bin
用 django-admin.py help 查看用法
manage.py
每个 dj Project 的管理工具,manage.py 的功能 django-admin.py 都能完成
manage.py 默认导入同目录下的 settings.py 最为该 Project 的设置文件
如果用 django-admin.py 管理 Project,需要指定 dj 工程设置模块(一般为 settings.py),否则 django-admin.py 不知道管理哪个 Project
django-admin.py 指定 工程设置模块 的方法:
1. 使用 --settings
django-admin.py [command] --settings="myproject.settings.main" # 以当前路径下的 settings.py 为 工程设置模块 django-admin.py runserver 0.0.0.0:8080 --pythonpath="." --settings="settings"--setting: 指定 工程设置模块,使用 Python 的目录层级格式
--pythonpath: 可能在 PYTHONPATH (sys.path) 中找不到 --settings 指定的路径,这时需要指定 --pythonpath
2. 使用 DJANGO_SETTINGS_MODULE 环境变量
如果没指定 --settings,就从 DJANGO_SETTINGS_MODULE 环境变量中获得:
# 类 Unix 环境变量 export DJANGO_SETTINGS_MODULE=projectname.settings.main # Windows 环境变量 set DJANGO_SETTINGS_MODULE=myproject.settings.mainDjango 内建 Web 服务器
python manage.py runserver 0.0.0.0:8080使用 dj 内建的 Web 服务器启动 dj 站点
从 HTTP 返回头域中可知 dj 内建的 Web 服务器名为 WSGIServer
dj 内建服务器是 调试/测试服务器,Apache mod_python、FastCGI 等是 部署服务器,dj 内建服务器暴露的 request.META 信息较多
Apache mod_python 部署 Django
使用 Apache mod_python 部署时的问题:
mod_python 缓存 Python 代码
dj 内建服务器 runserver 会自动扫描 Project 设置文件 (settings.py) 的改动,当保存新的设置文件后,runserver 的控制台中会打印信息,但 mod_python 不会自动扫描 Project 设置文件的改动,更改设置文件后,必需重启 Apache
mod_python 和 mod_php 方式不同:对于 PHP,更改代码后会立即作用,而对于 Python,更改代码后,因为 mod_python 会预载入 Python 代码(缓存),所以需要重启 Apache
可在 Apache 配置文件中设置 MaxRequestsPerChild 1,强制在每个请求时都重新载入所有代码(影响性能)
经测试 (Apache 2.2.6, mod_python 3.3.2),发现即使设置 MaxRequestsPerChild 1,更改后的 Python 代码仍会缓存两三次不定,即浏览器需要刷新两三次地址可以得到最新的页面内容
mod_python 中的 Python 源代码暴露
Python 源代码暴露:浏览器直接访问作为服务器脚本程序 .py、.pyc 文件的 URL 时,应该不显示、返回下载这些文件的内容,否则会造成源码暴露安全问题
Apache mod_python 的 Django 配置示例:
################################################################################ # mod_python 方式使用 Django # BEGIN # # Location 指定 URI,而 Directory 指定本地路径 <Location "/dj/"> SetHandler python-program PythonHandler django.core.handlers.modpython # 设置 PYTHONPATH,同 django-admin.py --pythonpath PythonPath "['C:\\web\\pub\\dj'] + sys.path" # 设置 dj 工程设置模块,同 django-admin.py --setting SetEnv DJANGO_SETTINGS_MODULE djproject.settings_release # 可设置多个 dj Project 的 <Location> 目录,此时需要有 PythonInterpreter # PythonInterpreter 的值并不重要,只起标识作用,在多个 <Location> 中各不同 # PythonInterpreter djproject PythonDebug Off </Location> # # END ################################################################################1. dj Project 目录 <Location> 下的 .py、.pyc 文件不会暴露,请求这里的 URL 会按照 urls.py 的 URL 映射规则处理
2. 如果设置了通用 CGI Python 脚本处理 AddHandler cgi-script .py .pyc,在 dj Project 目录 <Location> 之外也不会暴露代码
3. 以上两种情况之外,.py、.pyc 分别会按 MIME 类型 text/plain、application/octet-stream 处理,会暴露代码
mod_python 中的资源文件
资源文件:Django 动态网页中引用的 css、js、文本、图片、视频、音频 等资源文件
资源文件不应该放到 dj Project 目录下,因为请求这里的 URL 会按照 urls.py 的 URL 映射规则处理,如果不加入资源文件的 URL 映射规则,将无法找到资源文件(404 错误)
几种解决方法:
1. 使用另一个 Web 服务,或虚拟服务 <VirtualHost> 来伺服这些资源文件,在 dj 的页面中引用这里的 URL
2. 在特定的目录下伺服资源文件
如果这个目录是 dj Project 的 <Location> 之外的目录则不需要设置
如果这个目录是 dj Project 的子目录,则需要用 SetHandler None 覆盖 dj Project 目录的 SetHandler python-program 设置,如:
<Location "/dj/media/"> SetHandler None </Location>3. 利用 <LocationMatch> 正则表达式匹配资源文件,再用 SetHandler None 覆盖 dj Project 目录的 SetHandler python-program 设置,如:
<LocationMatch "\.(jpg|gif|png)$"> SetHandler None </LocationMatch>采用 FastCGI 方式部署与 mod_python 的情况不同,FastCGI 方式可用 mod_rewrite 规则让 Apache 伺服 dj Project 目录下的资源文件,见下面 FastCGI 方式部署
错误与调试
print 打印调试
在 dj 内建服务器 runserver 中,print 语句会打印到 runserver 启动的控制台
在 mod_python 中,print 语句无法输出到控制台或文件,也不会出现在 Apache 的日志文件中
如果需要日志功能,可利用 Python 标准日志包 Pythons standard logging package
Django 中的 Python 错误
Python 语法、逻辑错误被 dj 捕捉,但不会传播到 Apache,不会出现在 Apache 的错误日志中
但是,如果是设置错误(settings.py、urls.py 等),会记录在 Apache 的错误日志中,以 Python 的 traceback 形式,并且返回一个 500 服务器内部错误页面
mod_python 常见错误
下面错误导致 dj 崩溃:
1. 使用 pyexpat 模块(XML 解析),与 Apache 内置的版本冲突,参考 Expat Causing Apache Crash
2. 同时使用 mod_python 和 mod_php,且都使用 MySQL 作为数据库,php 和 python 的 MySQL 模块版本冲突,见 mod_python 的 FAQ
Apache FastCGI 部署 Django
FastCGI 可分为两种方式:
1. 独立运行的 FastCGI 服务,如:
a. 用 dj 内建的 runfcgi 启动 FastCGI 服务,runfcgi 需要 flup 库的支持
b. 使用 lighttpd,它内建 FastCGI 服务
2. 让 Apache 启动子进程 FastCGI 服务 (Web Server-spawned Processes),一般用在共享主机托管服务中,因为共享主机服务商一般不提供更改 Apache 主配置文件的权限,并且不能创建 socket 监听服务 runfcgi
使用 Django 内建的 runfcgi
manage.py runfcgi 命令选项
查看 runfcgi 的所有选项:
manage.py runfcgi help如果使用 Model,即使只是查看 help 帮助,也需启动数据库服务
protocol: runfcgi 支持多种网关协议,如 fcgi、scgi、ajp,默认 protocol=fcgi
method: runfcgi 工作方式 prefork、threaded,默认 method=prefork,不过 Windows 下没有实现 flup.server.fcgi_fork
socket: socket=/tmp/fcgi.sock 以 UNIX domain socket 方式监听 fcgi,在 Windows 下是命名管道,实测时 Windows 下不能成功
daemonize: daemonize=true 或 false,表示是否在后台运行 fcgi,在 Windows 下看不出效果
例如,在 localhost:3033 上以 threaded 方式运行 runfcgi:
manage.py runfcgi method=threaded host=localhost port=3033用 django-admin.py 启动 runfcgi 时的模板搜索路径
用 Python 语句 print('**** TEMPLATE_DIRS=%s' % TEMPLATE_DIRS) 查看模板搜索路径
1. 如果在 settings.py 中指定 TEMPLATE_DIRS 为:
TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates').replace('\\', '/'), # 这是元组,注意后面的逗号 , )则 django-admin.py runfcgi 需要一个绝对路径指定 dj Project 的位置:django-admin.py runfcgi --settings="settings" --pythonpath="/full/path/to/dj-project" method=threaded host=localhost port=30332. 如果在 settings.py 中将 TEMPLATE_DIRS 展开为全路径:
TEMPLATE_DIRS = ( os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates').replace('\\', '/'), )则 django-admin.py runfcgi 可以使用相对路径 --pythonpath=".":django-admin.py runfcgi --settings="settings" --pythonpath="." method=threaded host=localhost port=3033Apache mod_fastcgi 和 mod_rewrite 配置
################################################################################ # mod_fastcgi + 独立 FastCGI 服务 方式使用 Django # BEGIN # FastCGIExternalServer C:/web/pub/dj/dj.fcgi -host localhost:3033 <Directory "C:/web/pub/dj"> Options Indexes FollowSymLinks AllowOverride FileInfo Order allow,deny Allow from all </Directory> RewriteEngine On # 不存在文件时,才进行重定向 RewriteCond %{REQUEST_FILENAME} !-f # 重定向以 /dj/ 打头的 URI RewriteRule ^/dj/(.*)$ /dj/dj.fcgi/dj/$1 [QSA,L] # # END ################################################################################1. 注意 FastCGIExternalServer 指定的文件 C:/web/pub/dj/dj.fcgi 不一定要存在,但其目录 C:/web/pub/dj 必需存在
2. RewriteRule 的 /dj/dj.fcgi/dj/$1 分为两部分:
a. 前部分 /dj/dj.fcgi,和 FastCGIExternalServer 设置的 .fcgi 匹配
b. 后部分 /dj/$1,是 Apache 请求 fcgi 服务的 URI
3. urls.py 中的 URI 模式应该是 '^dj/xxx/$' 形式(dj 会去掉前缀 /),和 2.b 的 /dj/$1 对应
FastCGI 中的 Python 源代码暴露和资源文件
mod_rewrite 配置 RewriteCond %{REQUEST_FILENAME} !-f 指定当请求的 URI 找不到文件时,才开始重定向,所以可在 dj Project 下存放资源文件
这引发一些问题:
1. 当请求 dj Project 下的 .py、.pyc 文件时,会返回其文件内容,造成源码暴露
2. 如果同时使用通用 CGI Python 脚本处理 AddHandler cgi-script .py .pyc,当请求 dj Project 目录下的 .py、.pyc 时,Apache 会以 CGI 方式运行 Python 程序,但它们是 dj 程序而非通用 CGI 程序,将导致 Apache 内部 500 错误
解决方法:
1. 解决问题 2:修改 Apache 配置,在 dj Project 的目录下,用 RemoveHandler .py .pyc 或 SetHandler cgi-script 的方法,覆盖继承的 handler
2. 解决问题 1:使用 mod_rewrite 重定向,隐藏 .py、.pyc 文件,如 RewriteRule .*\.pyc?$ /ERR404/ [L,NC]
FastCGI 页面请求过程
HTTP 请求过程如下:
1. 浏览器 URL=http://localhost/dj/xxx/, URI=/dj/xxx/
2. 经 Apache 的 mod_rewrite 重定向规则 /dj/xxx/ => /dj/dj.fcgi/dj/xxx/,Apache 请求 FastCGI 服务的 URI=/dj/xxx/
3. 经 dj 的 urls.py URL 匹配规则 URI=/dj/xxx/(dj 会去掉前缀 /),调用匹配的 View 函数 app.views.xxx_handler
让 Apache 启动子进程 FastCGI 服务
一般在 Linux 共享主机托管服务中使用这种方法
FastCGI 启动脚本
FastCGI 启动脚本 dj.fcgi:
#!/usr/bin/python2.6 #coding=utf-8 import sys, os # 加入 PYTHONPATH sys.path.insert(0, "/home/somebody/public_html/dj") # dj Project 的工作目录 # os.chdir(r"/home/somebody/public_html/dj/mydj") # dj Project 的设置模块,环境变量 DJANGO_SETTINGS_MODULE os.environ['DJANGO_SETTINGS_MODULE'] = "mydj.settings_release" from django.core.servers.fastcgi import runfastcgi runfastcgi(method = "threaded", daemonize = "false")该脚本以 CGI 方式运行,Apache 配置中需要启动该脚本目录的 CGI 执行权限 Options ExecCGIrunfastcgi() 函数是 Django 内建 FastCGI 服务 manage.py runfcgi 的内部运行函数,runfastcgi() 可接受的参数就是 manage.py runfcgi 的选项
runfastcgi(method = "threaded", daemonize = "false") 默认以 UNIX domain socket 方式启动子进程 FastCGI 服务,子进程为 Python 解释器
Windows 下使用 UNIX domain socket 方式报错(可能没实现),Apache 日志:
File "D:\tool\develop\Python26\lib\site-packages\flup\server\fcgi_base.py", line 978, in _setupSocket sock = socket.fromfd(FCGI_LISTENSOCK_FILENO, socket.AF_INET, AttributeError: 'module' object has no attribute 'fromfd'Windows 下使用监听 socket 方式运行 runfastcgi(settings = "settings", pythonpath=".", method = "threaded", host = "localhost", port = "3033", daemonize = "false"),可看到 Apache 产生 10 个 Python 解释器子进程,都监听 TCP 3033 端口(没有发生监听端口重复错误)
mod_rewrite 重定向
Apache 目录级配置 .htaccess 中
RemoveHandler .py .pyc AddHandler fcgid-script .fcgi # 有的版本 mod_fastcgi 使用 fastcgi-script,而不是 fcgid-script # AddHandler fastcgi-script .fcgi RewriteEngine On # 防止代码暴露 RewriteRule .*\.pyc?$ /ERR404/ [L,NC] # 不存在文件时,才进行重定向 RewriteCond %{REQUEST_FILENAME} !-f # .htaccess 目录下的所有请求都交给 FastCGI 启动脚本 dj.fcgi 处理 RewriteRule ^(.*)$ dj.fcgi/$1 [QSA,L]加载更改的 Python 代码
更改 dj Project 代码后,只需重新加载 FastCGI 启动脚本 dj.fcgi,便可重新加载更改后的 Python 代码。不用重启 Apache,只需更新 dj.fcgi 的文件时间戳,Apache 便会重新加载 dj.fcgi,如使用 touch mysite.fcgi
部署 Django 工程的不同版本
以示例说明,一个 dj Project 有以下不同版本,它们有各自特定的 设置 或 工程设置模块:
1. 开发平台是 Windows,实际部署平台是 Linux,两者是不同的主机,根据主机名使用不同的设置
2. 调试版 (Debug) 和 发布版 (Release),使用不同的工程设置模块 settings.py
Debug 版 工程设置模块 settings_debug.py 如下(演示如何区分各版本,无关代码从略):
#coding=utf-8 import os.path import socket # 假设本地 Windows 主机名为 my-host # IS_LOCALHOST 表示当前 dj Project 是否运行在本地主机,即开发平台上 MY_HOSTNAME = socket.gethostname().lower() IS_LOCALHOST = (MY_HOSTNAME.find('my-host') != -1 or MY_HOSTNAME.find('localhost') != -1) # 开启调试开关 DEBUG = True TEMPLATE_DEBUG = DEBUG # 根据 IS_LOCALHOST 设置不同的数据库连接参数 DATABASE_ENGINE = 'mysql' DATABASE_NAME = 'local_db' if IS_LOCALHOST else 'deploy_db' DATABASE_USER = 'local_user' if IS_LOCALHOST else 'deploy_user' DATABASE_PASSWORD = 'local_pwd' if IS_LOCALHOST else 'deploy_pwd' DATABASE_HOST = '' DATABASE_PORT = '' # 根据 IS_LOCALHOST 设置不同的资源文件的存储位置 MEDIA_ROOT = 'C:/web/pub/res/' if IS_LOCALHOST else '/home/somebody/public_html/res/'Release 版 工程设置模块 settings_release.py 如下:
#coding=utf-8 # 导入 Debug 版 工程设置模块 from settings_debug import * # 关闭调试开关 DEBUG = False TEMPLATE_DEBUG = False # 设置邮箱地址,dj Project 出错时,发邮件通知 EMAIL_SUBJECT_PREFIX = '[django][mydj]' EMAIL_HOST = 'localhost' ADMINS = ( ('somebody', 'somebody@somedomain'), ) SEND_BROKEN_LINK_EMAILS = True MANAGERS = ADMINS用 manage.py runfcgi --settings 选项、runfastcgi() 的 settings 参数 或 DJANGO_SETTINGS_MODULE 环境变量指定不同的 工程设置模块 settings_debug.py、settings_release.py