《django by example》实践 上线运行

作者: 学以致用123 | 来源:发表于2018-04-27 15:12 被阅读126次

    点我查看本文集的说明及目录。


    本文是《 django by example 》第十三章 上线运行 的内容。


    CH13 上线运行


    上一章,我们为项目创建了 RESTful API 。本章,我们将学习如何实现以下功能:

    • 配置生产环境

    • 创建自定义中间件

    • 实现自定义管理命令


    生产环境上线运行


    现在是时候将 Django 项目部署到生产环境了。我们将根据下面的步骤实现项目上线:

    1. 为生产环境配置项目;
    2. 使用 PostgreSQL 数据库;
    3. 提供静态资源;
    4. 使用 SSL 加密。

    配置多种环境


    实际项目需要处理多种环境。我们至少需要本地和生产环境,还可能包括其它环境。项目的大部分设置在多种环境中通用,但有些设置需要根据环境进行更改。下面我们来为多种环境配置项目,使一切有条不紊。

    在 educa 项目目录下创建 settings 目录。将项目的 settings.py 文件重命名为 settings/base.py,并创建下图中的其它文件,settings 目录结构为:

    CH13-1.png

    这些文件包括:

    • base.py : 包含通用设置和缺省设置的基础设置文件;

    • local.py :本地环境使用的自定义设置;

    • pro.py : 生产环境使用的自定义设置。

    编辑 settings/base.py 文件,找到下面一行:

    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    

    替换为:

    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(os.path.join(__file__,os.pardir))))
    

    这里将设置文件移动到下一级,所以需要 BASE_DIR 通过 os.pardir 指向父路径。

    编辑 settings/local.py 文件,并添加下面的代码:

    from .base import *
    DEBUG = True
    
    DATABASES = {
           'default': {
               'ENGINE': 'django.db.backends.sqlite3',
               'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
           }
     }
    

    这是本地环境的设置文件。文件导入 base.py 定义的所有设置并为这个环境配置特定设置。由于 DEBUG 和 DATABASE 在每种环境中的值不同,这里从 base.py 文件中复制 DEBUG 和 DATABASE 设置,我们可以从 base.py 中移除这两项设置。

    编辑 settings/pro.py 文件并让其看起来是这样的:

    from .base import *
    DEBUG = False
    
    ADMINS = (
           ('Antonio M', 'email@mydomain.com'),
    )
    
    ALLOWED_HOSTS = ['educaproject.com', 'www.educaproject.com']
    
    DATABASES = {
           'default': {
              
           }
     }
    

    这是生产环境的设置。我们来看一下这些选项:

    • DEBUG : 生产环境必须将 DEBUG 设为 False 。不这样做可能导致每个人都能看到调试信息及敏感配置。
    • ADMINS:DEBUG 设置为 False 并且视图出现异常时,异常信息会通过邮件上报给 ADMINS 设置的用户。这里需要将 name/e-mail 对改为自己的信息。
    • ALLOWED_HOSTS :由于 DEBUG 设置为 False ,Django 只允许这个列表中的主机提供服务。这是一个安全机制,这里已经包含了网站使用的域名 educaproject.comwww.educaproject.com
    • DATABASES : 保留了空的设置,下一步将实现生产环境的数据库设置。

    注意:

    处理多种环境时,创建一个基础设置文件,并为每种环境配置一个设置文件。环境设置文件应该继承通用设置并重写环境特定配置。

    我们已经将配置文件从默认的 settings.py 文件更改到另一个文件。在指定要使用的配置模块之前,manage.py 工具不能执行任何命令。在 shell 中运行管理命令时,需要添加一个 —settings 设置或设置 DJANGO_SETTINGS_MODULE 环境变量。打开 shell并运行下面的命令:

    export DJANGO_SETTINGS_MODULE=educa.settings.pro
    

    这将为当前 shell 会话设置 DJANGO_SETTINGS_MODULE 环境变量。如果你不想每次启动 shell 执行上面的命令,请将这个命令配置到 .bashrc 或者 .bash_profile 文件中。如果不想在 .bashrc 或 .bash_profile 中设置这个变量,运行命令是需要包含 —settings ,比如:

    python manage.py migrate –-settings=educa.settings.pro
    

    现在,我们已经为多种环境组织好设置了。

    安装 PostgreSQL

    我们整本书使用的都是 SQLite 数据库。这样做很简单并且容易启动,但是生产环境需要像 PostgreSQL、MySQL、Oricle 等更加强大的数据库。我们将为我们的项目使用 PostGreSQL 数据库。Django 推荐使用 PostGreSQL 数据库。Django 内置 django.contrib.postgres 模块便于我们使用特定的 PostGreSQL 功能。https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/ 有这个模块的详细介绍。

    如果使用 Linux,这样安装 Python 操作 PostGreSQL 的依赖关系:

    sudo apt-get install libpq-dev python-dev
    

    然后使用以下命令安装 PostgreSQL:

    sudo apt-get install postgresql postgresql-contrib
    

    如果使用 mac OS X 或者 Windows ,可以从 https://www.postgresql.org/download/ 下载 PostgreSQL 。

    我们来创建 PostGreSQL 用户,打开 shell 并运行以下命令:

    su postgres
    createuser -dP educa
    

    将需要输入密码及这个用户的权限。输入密码和权限后使用下面的命令创建一个新的数据库:

    createdb -E urf8 -U educa educa
    

    然后,编辑 settings/pro.py 文件并修改 DATABASES 设置:

    DATABASES = {'default': {'ENGINE': 'django.db.backends.postgresql_psycopg2',
                             'NAME': 'educa',
                             'USER': 'educa',
                             'PASSWORD': '******',  
                             },
                           }
    

    将上述数据替换为自己创建的用户的数据库名称和凭据。 新的数据库是空的, 运行以下命令来实现所有数据库迁移:

    python manage.py migrate –-settings=educa.settings.pro
    

    最后,使用以下命令创建 superuser:

    python manage.py createsuperuser –-settings=educa.settings.pro
    

    检查你的项目


    Django 内置可以在任何时刻检查项目的 check 管理命令。这个命令检查 Django 项目中的应用程序并输出错误或警告。如何添加 —deploy 选项,则仅仅进行生产相关的检查。打开 shell 运行以下命令来执行检查:

    python manage.py check --deploy –-settings=educa.settings.pro
    

    你将会看到输出中没有错误但是又几个警告。这意味着检查是成功地,但是应该逐条检查警告使项目在生产环境中更加安全的运行。这里我们不再深入讨论这个问题,但是一定要记住在上线之前检查项目来识别任何相关问题。

    使用 WSGI 提供 Django 服务


    Django 的主要部署平台是 WSGI 。WSGI 是 Web Server Gateway Interface 的缩写,它是 Python 语言中 Web服务器和 Web应用程序之间或框架之间的通用接口标准

    我们使用 startproject 命令创建新项目时, Django 在项目目录中生成一个 wsgi.py 文件。这个文件包括可调用 WSGI 配置(可以访问应用)。 使用 Django 开发服务器运行项目和在生产环境运行项目都需要使用 WSGI 。

    我们可以从http://wsgi.readthedocs.io/en/latest/ 了解到更多 WSGI 相关细节。

    安装 uWSGI


    整本书我们都在使用 Django 开发服务器在本地运行项目。然而,生产环境需要一个真正的 web 服务器来部署我们的项目。

    uWSGI 是一个非常快的 Python 应用服务器。它使用 WSGI 配置与我们的 Python 应用通信。uWSGI 将 web 请求转化为 Django 可以处理的格式。

    使用以下命令安装 uWSGI 服务器:

    pip install uwsgi==2.0.11.1
    

    如果使用 Mac OS X,我们可以通过 brew install uwsgi 命令使用 Homebrew 安装 uWSGI 。如果希望在 Windows 中安装 uWSGI,则需要使用 Cygwin 。然而,我们推荐在 UNIX 环境中使用 uWSGI。

    配置 uWSGI


    我们可以从命令行运行 uWSGI 。打开 shell 并从 educa 项目目录运行以下命令:

    uwsgi --module=educa.wsgi:application \
    --env=DJANGO_SETTINGS_MODULE=educa.settings.pro \
    --http=127.0.0.1:80 \
    --uid=1000 \
    --virtualenv=/home/zenx/env/educa/
    

    如果使用的账户没有足够权限,这些命令需要添加 sudo 前缀。

    我们使用这个命令在本地运行 uWSGI ,并设置了以下参数:

    • 使用 educa.wsgi:application WSGI;
    • 从生产环境加载设置;
    • 使用虚拟环境,将 virtualenv 选项设置为你的虚拟环境目录。如果没有使用虚拟环境,则可以跳过这一步。

    如果没有在项目目录运行这个命令,使用项目的路径设置 —chdir=/path/to/educa/ 。

    在浏览器中打开 http://127.0.0.1/,你将看到生成的 HTML , 但是并没有加载任何 CSS 或者图片。由于我们没有配置 uWSGI 提供静态文件,所以这很好理解。

    我们可以使用 .ini. 文件定义 uWSGI 自定义配置。这比向命令行添加选项更加方便。在 educa 目录下创建如下文件结构:

    CH13-2.png

    编辑 uwsgi.ini 文件并添加以下代码:

    # variables
    projectname = educa
    base = /home/zenx/educa
    
    # configuration
    master = true
    virtualenv = /home/zenx/env/%(projectname)
    pythonpath = %(base)
    chdir = %(base)
    env = DJANGO_SETTINGS_MODULE=%(projectname).settings.pro
    module = educa.wsgi:application
    socket = /tmp/%(projectname).sock
    

    我们定义了如下变量:

    • projectname : Django 项目的名称,这里使用 educa 。
    • base : educa 项目的绝对路径。使用你的绝对路径进行设置。

    下面是可能会使用的 uWSGI 选项。我们可以定义任意其它与 uWSGI 选项不同的变量,这里设置了这些选项:

    • master :启动 master 处理;
    • virtualenv : 虚拟环境路径,设置为你的虚拟环境目录。
    • pythonpath :添加你的 Python 路径的路径。
    • chdir : 项目目录的路径,这样 uWSGI 在加载应用之前可以更改路径。
    • env : 环境变量。这里包含指向生产环境配置的 DJANGO_SETTINGS_MODULE 变量。
    • module :使用的 WSGI 模块。我们将其设置为项目 wsgi 模块中包括的可调用 application 。
    • socket : 绑定到服务器的 UNIX/TCP socket 。

    socket 选项是为了便于与第三方 router (比如 Nginx ) 通信,http 选项用于 uWSGI 接收 HTTP请求并自己实现路由。我们将使用 socket 运行 uWSGI 。由于我们将使用 Nginx 作为我们的 web服务器,所以使用 socket 与 uWSGI 进行通信。

    我们可以从 http://uwsgi-docs.readthedocs.io/en/latest/Options.html 了解所有 uWSGI 选项。

    现在,我们可以使用以下命令运行自定义配置的 uWSGI :

    uwsgi --ini config/uwsgi.ini
    

    由于通过 socket 运行,现在还不能通过浏览器访问 uWSGI 实例。下一步,我们来完成生产环境。

    安装 Nginx


    网站上线时,我们需要提供动态内容,还需要提供静态文件(比如 CSS、JavaScript 文件和图像)。 尽管 uWSGI 能够提供静态文件,但这将为 HTTP请求增加不必要的开销。 因此,建议在 uWSGI 之前设置一个类似 Nginx 的 Web服务器来提供静态文件。
    Nginx 是一款专注于高并发性、高性能和低内存使用率的 Web服务器。 Nginx 还充当反向代理,接收 HTTP请求并将它们路由到不同的后端。 一般而言,需要在前面使用诸如 Nginx 之类的Web服务器,以便快速有效的提供静态文件,并将动态请求转发到 uWSGI 。 通过使用 Nginx ,我们还可以使用规则及反向代理功能。

    使用以下命令安装 Nginx :

    sudo apt-get install nginx
    

    Mac OS X 系统可以使用 brew install nginx 安装 Nginx 。 http://nginx.org/en/download.html 网站有 Windows 系统安装 Nginx 使用的二进制文件。

    生产环境


    下图表示生产环境最终的流程:

    CH13-3.png

    Client browser (客户端浏览器) 发出 HTTP 请求后将进行以下工作:

    1. Nginx 接收 HTTP 请求;
    2. 如果请求静态文件, Nginx 直接提供静态文件。如果请求动态页面, Nginx 通过 socket 将请求发送到 uWSGI 。
    3. uWSGI 将请求发送到 Django ,Django 的 HTTP 响应传回 Nginx ,Nginx 将其发送到 Client browser 。

    配置 Nginx


    在 config/ 目录下新建一个 nginx.conf 文件,添加以下代码:

    # the upstream component nginx needs to connect to
    upstream educa {
       server unix:///tmp/educa.sock;
    }
    server {
       listen      80;
       server_name  www.educaproject.com educaproject.com;
       location / {
           include     /etc/nginx/uwsgi_params;
           uwsgi_pass  educa;
    } }
    

    这是 Nginx 基础配置,我们设置了一个名为 educa 的 upstream 来指向 uWSGI 创建的 socket 。我们使用服务器指令并添加以下配置:

    1. 告诉 Nginx 监听 80 端口;
    2. 将服务器名称设置为 www.educaproject.comeducaproject.com 。Nginx将为这两个域的请求提供服务。
    3. 最后,指定所有 / 路径的内容必须路由到 educa socket( uWSGI )。并包含 Nginx 内置的默认 uWSGI 配置。

    我们可以从 http://nginx.org/en/docs/ 找到 Nginx 文档。 Nginx 主配置文件位于 /etc/nginx/nginx.conf ,它包含 /etc/nginx/sites-enabled/ 中找到的所有配置文件。为了让 Nginx 加载自定义配置文件,这样创建一个链接:

    sudo ln -s /home/zenx/educa/config/nginx.conf /etc/nginx/sites-enabled/
    educa.conf
    

    将 /home/zenx/educa/ 替换为自己的绝对路径。 如果 uWSGI 还没有运行,打开一个 shell 并运行 uWSGI :

    uwsgi --ini config/uwsgi.ini
    

    打开第二个 shell 使用以下命令运行 Nginx :

    server nginx start
    

    由于这里使用的是示例域名,因此需要将其重定向到本地主机。 编辑 /etc/hosts 文件,并添加下列内容:

    127.0.0.1 educaproject.com
    127.0.0.1 www.educaproject.com
    

    这样,两个名称都路由到了我们的本地服务器。在生产服务器中,我们不需要这样做。在生产服务器中,我们将在 域名 DNS 配置中主机指向我们的服务器。

    在浏览器中打开 http://educaproject.com/ 。应该可以能够看到没有加载任何静态资源的网站了。我们的生产环境快要部署成功了。

    提供静态和文件资源


    为了实现最佳性能,我们将使用 Nginx 直接提供静态资源。

    编辑 settings/base.py 文件并添加下面的代码:

    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR,'static/')
    

    这里需要使用 Django 输出静态资源。 collect static 命令可以将所有应用的静态文件拷贝到 STATIC_ROOT 目录。

    打开 shell 并运行下面的命令:

    python manage.py collectstatic
    

    你将看到下面的输出:

    You have requested to collect static files at the destination
    location as specified in your settings.
    
    This will overwrite existing files!
    Are you sure you want to do this?
    
    

    输入 yes 以便 Django 拷贝所有文件。你将看到下面的输出。

    96 static files copied to '/Users/apple/profile/django_by_example/educa/educa3/static'.
    

    现在,编辑 config/nginx.conf 文件并在 server 中添加下面的代码:

    location /static/ {
           alias /home/zenx/educa/static/;
       }
       location /media/ {
           alias /home/zenx/educa/media/;
       }
    

    记得使用项目路径替换 /home/zenx/educa/ 路径。Nginx 可以提供 /static/ 或 /media/ 路径下的静态文件了。 这些命令告诉 Nginx 直接提供 /static/ 或 /media/ 路径下的静态资源。

    使用以下命令重新加载 Nginx 配置文件:

    service nginx reload
    

    在浏览器中打开 http://educaproject.com/ 。现在应该可以看到静态文件了。我们已经成功配置 Nginx 来提供静态资源了。

    使用 SSL 进行安全连接


    SSL 协议( Secure Sockets Layer ) 正在成为为网站提供安全连接服务的标准。 强烈建议您的网站使用 HTTPS 提供服务。 我们将在 Nginx 中配置 SSL 证书来安全地为网站提供服务。

    创建 SSL 证书


    在 educa 项目中新建一个名为 ssl 的目录。然后在命令行使用以下命令生成一个 SSL 证书:

    sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/
    educa.key -out ssl/educa.crt
    

    你正在生成一个私钥和一个有效期为一年的 2048-bit 的 SSL 证书。你将需要输入以下信息:

       Country Name (2 letter code) [AU]:
       State or Province Name (full name) [Some-State]:
       Locality Name (eg, city) []: Madrid
       Organization Name (eg, company) [Internet Widgits Pty Ltd]: Zenx IT
       Organizational Unit Name (eg, section) []:
       Common Name (e.g. server FQDN or YOUR name) []: educaproject.com
       Email Address []: email@domain.com
    

    你可以使用自己的数据填写需要的内容。其中最重要的字段为 Common Name ,这个字段指定需要验证的域名,我们这里使用 educaproject.com。

    通常会在 ssl 目录下生成一个 educa.key 的私钥文件和一个 educa.crt 证书。

    使用 SSL 验证 Nginx


    编辑 nginx.conf 文件并修改服务器指令以包含以下SSL指令:

    server { listen listen
    80;
    443 ssl;
    ssl_certificate
    ssl_certificate_key /home/zenx/educa/ssl/educa.key; server_name www.educaproject.com educaproject.com; # ...
    }
    

    现在,服务器通过 80 端口监听 HTTP ,并通过 443 端口监听 HTTPS 。我们使用 ssl_certificate 指定 SSL 证书的路径,并使用 ssl_certificate_key 指定证书秘钥。

    使用以下密钥重启 Nginx :

    sudo service nginx restart
    

    Nginx 将加载新的配置。在浏览器中打开 https://educaproject.com ,你应该可以看到下图所在的警告信息:

    CH13-4.png

    由于浏览器的不同,上面的信息可能会有所不同。信息提醒我们网站没有使用可信任的证书。浏览器无法验证网站的身份。这是因为我们自己签署了证书,而没有从可信任的证书颁发机构获得证书。当你拥有一个真实的域名时,可以申请一个可信的 CA 来处理 SSL 证书,以便浏览器验证其身份。

    如果要为真实域名获取一个可信的证书,可以参考 Linux Foundation 创建的 Let's Encrypt 项目。 这是一个旨在简化获取和更新可信 SSL 证书的协作项目。 https://letsencrypt.org/ 网站有更多相关信息。
    点击 Add Exception 按钮告诉浏览器我们信任这个证书。 您将看到浏览器的 URL 旁边显示一个锁形图标,如以下屏幕截图所示:

    CH13-5.png

    如果点击锁形图标,将会显示 SSL 证书详情。

    为项目配置 SSL


    Django 内置一些 SSL 配置。编辑 settings/pro.py 设置文件并添加下面的代码:

       SECURE_SSL_REDIRECT = True
       CSRF_COOKIE_SECURE = True
    

    这些设置为:

    • SECURE_SSL_REDIRECT : HTTP 请求是否需要重定向到 HTTPS 请求。
    • CSRF_COOKIE_SECURE : 为跨网站请求伪造防御建立安全 cookie 。

    太棒了,我们已经配置了一个生产环境,可以为项目提供卓越的性能。

    创建自定义中间件


    我们已经了解了包含项目中间件的 MIDDLEWARE_CLASSES 设置。中间件是一个类,它内置可以全局执行的特定方法,我们可以把它想象成低级别的插件系统,在请求或响应过程中执行的 Hook 。 每个中间件负责为所有请求或响应执行特定操作。

    注意:

    由于中间件为每个请求执行特定方法,请避免为中间件添加昂贵的过程。

    接收到 HTTP 请求后,中间件按照 MIDDLEWARE_CLASSES 设置中设定的顺序执行。Django 生成一个 HTTP 响应后,中间件方法将按照反向顺序执行。

    下图展示了在请求阶段和响应阶段中间件方法的执行顺序。 它还显示了(可能)调用的中间件方法:

    CH13-6.png

    请求阶段将执行这些中间件方法:

    1. process_request(request) : 在 Django 决定请求执行哪个视图之前调用。request 是一个 HttpRequest 实例;
    2. process_view (request, view_func, view_args, view_kwargs ) :在 Django 调用视图之前调用。它可以访问视图函数及接收的参数。

    响应阶段将执行这些中间件方法:

    1. process_exception( request, exception ) :视图函数引发 Exception 异常时调用。
    2. process_template_response(request, response) :视图执行完后返回的响应对象有一个 render() 方法时调用(如果一个 TemplateResponse 或者等效的对象)。
    3. process_response(request, response) : 所有响应返回浏览器之前都要调用。

    由于中间件可能需要使用前面中间件方法设置到请求中的数据集,MIDDLEWARE_CLASSES 设置中中间件的顺序非常重要。注意,即使由于前面的中间件返回了 HTTP 响应而跳过了 process_request() 或者 process_view() ,也将调用中间件的 process_response() 方法。这意味着,process_response() 不能依赖请求阶段的数据集。如果中间件处理异常并返回响应,则不会执行前面的中间件类。

    注意:

    向 MIDDLEWARE_CLASSES 添加新的中间件时,确保把它放在正确的位置。中间件方法在请求阶段按照设置中的顺序执行,在响应阶段按照反向顺序执行。

    我们可以从 https://docs.djangoproject.com/en/1.11/topics/http/middleware/ 了解更多中间件的信息。

    我们将创建自定义中间件来实现通过自定义子域访问课程。每个课程细节视图的 URL (看起来像 https://educaproject.com/course/django/)也可以通过使用课程内容的子域访问,例如 https://django.educaproject.com/

    创建子域中间件


    中间件可以位于项目的任何位置。然而,推荐在应用目录下创建一个 middleware.py 文件。

    在 courses 应用目录下新建一个 middleware.py 文件并添加以下代码:

    from django.core.urlresolvers import reverse
    from django.shortcuts import get_object_or_404, redirect
    
    from .models import Course
    
    
    class SubdomainCourseMiddleware(object):
        """
        Provides subdomains for courses
        """
    
        def process_request(self, request):
            host_parts = request.get_host().split('.')
            if len(host_parts) > 2 and host_parts[0] != 'www':
                # get course for the given subdomain
                course = get_object_or_404(Course, slug=host_parts[0])
                course_url = reverse('course_detail', args=[course.slug])
                # redirect current request to the course_detail view
                url = '{}://{}{}'.format(request.scheme, '.'.join(host_parts[1:]),
                                         course_url)
                return redirect(url)
    

    我们创建了一个执行 process_request() 的中间件。接收到 HTTP 请求后,我们完成以下任务:

    1. 获取请求使用的主机名并对其进行拆分。例如,如果用户访问 mycourse.educaproject.com,这里将生成 ['mycourse', 'educaproject', 'com'] 。

    2. 通过检查主机名拆分是否生成2个以下元素确定主机名是否含有子域。如果主机名包含子域,并且子域不是 'www',则尝试使用子域提供的 slug 查找对应课程。

    3. 如果没有找到课程,引发一个 Http404 异常。使用主域将浏览器重定向到课程详细信息 URL 。

    编辑项目的 settings/base.py 文件并将 'courses.middleware.SubdomainCourseMiddleware' 添加到 MIDDLEWARE_CLASSES 列表的底部。如下所示:

    MIDDLEWARE = ['django.middleware.security.SecurityMiddleware',
                  'django.contrib.sessions.middleware.SessionMiddleware',
                  # 'django.middleware.cache.UpdateCacheMiddleware',
                  'django.middleware.common.CommonMiddleware',
                  # 'django.moddleware.cache.FetchFromCacheMiddleware',
                  'django.middleware.csrf.CsrfViewMiddleware',
                  'django.contrib.auth.middleware.AuthenticationMiddleware',
                  'django.contrib.messages.middleware.MessageMiddleware',
                  'django.middleware.clickjacking.XFrameOptionsMiddleware',
                  'course.middleware.SubdomainCourseMiddleware' ]
    

    现在,我们刚刚创建的中间件将对每个请求进行操作。

    使用 Nginx 提供多个子域


    Nginx 需要为任何可能的子域提供网站服务。编辑 config/nginx.conf 文件并找到以下行:

    server_name  www.educaproject.com educaproject.com;
    

    将其替换为:

    server_name  *.educaproject.com educaproject.com;
    

    通过使用星号,规则将适用于 educaproject.com 的所有子域。 为了在本地测试中间件,我们将任何需要测试的子域添加到 /etc/hosts 。 要使用slug 为 django 的 Course 对象测试中间件,请将下面一行添加到 /etc/hosts 文件中:

     127.0.0.1 django.educaproject.com
    

    然后,在浏览器中打开 https://django.educaproject.com/ 。 中间件将通过子域找到课程并将浏览器重定向到https://educaproject.com/course/django/

    执行自定义管理命令


    Django 允许应用注册用于 manage.py 的自定义管理命令。例如,我们在第九章使用了管理命令 makemessages 和 compilemessages 来创建和编译翻译文件。

    管理命令由继承 django.core.management.BaseCommand 的 Command 类组成的 Python 模块实现。我们可以创建简单命令,或者接收位置和可选参数作为输入。

    Django 从 INSTALLED_APPS 设置中每个激活的应用的 management/commands/ 目录中查找管理命令。找到的每个模块都将注册为管理命令(命令名称为模块名称)。
    https://docs.djangoproject.com/en/1.11/howto/custom-management-commands/ 包含更多自定义管理命令的介绍。

    我们将创建一个自定义管理命令来提醒学生至少注册一门课程。命令将向注册了一段时间但是没有报读任何课程的用户发送邮件提醒。

    在 students 应用目录下创建以下文件结构:


    CH13-7.png

    编辑 enroll_reminder.py 文件并添加以下代码:

    import datetime
    
    from django.conf import settings
    from django.contrib.auth.models import User
    from django.core.mail import send_mass_mail
    from django.core.management.base import BaseCommand
    from django.db.models import Count
    
    
    class Command(BaseCommand):
        help = 'Sends an e-mail reminder to users registered more \
               than N days that are not enrolled into any courses yet'
    
        def add_arguments(self, parser):
            parser.add_argument('--days', dest='days', type=int)
    
        def handle(self, *args, **options):
            emails = []
            subject = 'Enroll in a course'
            date_joined = datetime.date.today() - datetime.timedelta(
                days=options['days'])
    
            users = User.objects.annotate(
                course_count=Count('courses_enrolled')).filter(course_count=0,
                                                               date_joined__lte=date_joined)
            for user in users:
                message = 'Dear {},\n\nWe noticed that you didn\'t enroll in any courses yet.What are you waiting for ?'.format(
                    user.first_name)
                emails.append(
                    (subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]))
            send_mass_mail(emails)
            self.stdout.write('Sent {} reminders'.format(len(emails)))
    

    这就是我们的 enroll_reminder 命令,上面的代码如下所述:

    Command 命令继承 BaseCommand。

    添加 help 属性。这个属性提供运行 python manage.py help enroll_reminder 时打印的简单描述。

    使用 add_arguments() 方法添加 —days 名称参数,这个参数用于指定用户注册但是没有报读任何课程的最小日期以便于提供提醒。

    handle() 方法包含实际命令。获取命令行解析到的 days 属性,然后查询注册超过特定日期但是没有报读任何课程的用户,查询通过聚合计算每个用户报读的课程总数得到;为每个符合条件的用户生成提醒邮件并放入 email 列表中。最后,使用 send_mass_mail() 函数发送邮件,send_mass_mail() 函数可以打开一次 SMTP 链接发送多封邮件。

    我们已经创建了第一个管理命令,打开 shell 并运行命令:

    python manage.py enroll_reminder --days=20
    

    如果您没有本地 STMP 服务器,查看第二章中关于为 Django 项目配置 STMP 设置的内容。我们还可以将以下内容添加到 settings/base.py 文件中以便在开发过程中将邮件打印到标准输出:

    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
    

    我们来安排运行管理命令,以便服务器每天早上8点运行该命令。如果您使用的是基于 UNIX 的系统,例如 Linux 或Mac OS X ,请打开 shell 并运行 crontab -e 编辑 cron ,添加以下行:

    0 8 * * * python /path/to/educa/manage.py enroll_reminder --days=20
       --settings=educa.settings.pro
    

    如果你不熟悉 cron ,可以在 https://en/wikipedia.org/wiki/Cron 找到更多资料。

    如果您使用 Windows,可以使用任务管理器安全任务。 http://windows.microsoft.com/en-au/windows/schedule-task#1TC=windows-7 包含更多相关内容。

    定期执行任务的另一个选择是使用 Celery 创建任何和日程。我们在第七章使用过 Celery 来执行异步任务。这种情况不需要创建管理命令以及使用 Cron 创建日程,我们可以创建异步任务并使用 Celery 心跳日程来执行任务即可。使用 Celery 调度定期任务的更多信息见 http://celery.readthedocs.org/en/latest/userguide/periodic-tasks.html

    注意:

    使用 Cron 或 Windows 计划任务控制面板时,使用独立脚本实现管理命令。

    Django 内置 Python 调用管理命令的方法。我们可以使用代码运行管理命令:

    from django.core import management
    management.call_command('enroll_reminder',days=20)
    

    恭喜你!现在可以为应用创建自定义管理命令并定期执行了。

    总结


    本章,我们使用 uWSGI 和 Nginx 配置了生成环境,并且学习了如何创建自定义中间件以及如何创建自定义管理命令。

    相关文章

      网友评论

        本文标题:《django by example》实践 上线运行

        本文链接:https://www.haomeiwen.com/subject/sftblftx.html