二十、设置应用程序的样式并部署


为设置样式,我们将使用 Bootstrap 库,这是一组工具,用于为 Web 应用程序设置样式,使其在任何现代设备上都看起来很专业,无论是大型的平板显示器还是智能手机

  • 为此,我们将使用应用程序 django-bootstrap4,这也让你能够练习使用其他 Django 开发人员开发的应用程序

我们将把项目“学习笔记”部署到 Heroku,这个网站让你能够将项目推送到其服务器,让任何有互联网连接的人都可以使用它

  • 我们还将使用版本控制系统 Git 来跟踪对这个项目所做的修改

1. 设置项目“学习笔记”的样式

本节简要介绍应用程序 django-bootstrap4,并演示如何将其集成到项目中,为部署做好准备

(1) 应用程序 django-bootstrap4

这个应用程序下载必要的 Bootstrap 文件,将其放到项目的合适位置,让你能够在项目的模板中使用样式设置指令

为安装 django-bootstrap4,在活动状态的虚拟环境中执行如下命令:

pip install django-bootstrap4

请添加图片描述
接下来,需要在 settings.py 的 INSTALLED_APPS 中添加如下代码,在项目中包含应用程序 django- bootstrap4:

settings.py

--snip--
INSTALLED_APPS = [
    # My apps
    'learning_logs',
    'users',

    # Third party apps
    # - 新建一个名为“第三方应用程序”的片段,用于指定其他开发人员开发的应用程序,并在其中添加 'bootstrap4'
    # - 务必将这个片段放在“我的应用程序”和“Django默认添加的应用程序”之间
    'bootstrap4',

    # Default django apps
    'django.contrib.admin',
    --snip--
]

(2) 使用 Bootstrap 设置项目“学习笔记”的样式

Bootstrap 是一个大型样式设置工具集,还提供了大量模板,可应用于项目以创建独特的总体风格

  • 对Bootstrap初学者来说,这些模板比样式设置工具用起来容易得多

要查看 Bootstrap 提供的模板,可访问其官方网站,单击 Examples 并找到 Navbars

我们将使用模板 Navbars static,它提供了简单的顶部导航栏以及用于放置页面内容的容器

(3) 修改 base.html

我们需要修改模板 base.html,以使用前述 Bootstrap 模板

1) 定义 HTML 头部

对 base.html 所做的第一项修改是,在其中定义 HTML 头部,使得显示“学习笔记”的每个页面时,浏览器标题栏都显示该网站名

此外,还要添加一些在模板中使用 Bootstrap 所需的信息

请删除 base.html 的全部代码,并输入下面的代码:

base.html

<!--加载 django-bootstrap4 中的模板标签集-->
{% load bootstrap4 %}

<!--将这个文件声明为使用英语编写的 HTML 文档-->
<!doctype html>
<html lang="en">
<!--HTML 文件分为两个主要部分:头部(head)和主体(body)-->
<!--HTML 文件的头部不包含任何内容,只是向浏览器提供正确显示页面所需的信息-->
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<!--包含一个 title 元素,在浏览器中打开网站“学习笔记”的页面时,浏览器的标题栏将显示该元素的内容-->
    <title>Learning Log</title>

<!--使用 django-bootstrap4 的一个自定义模板标签,让 Django 包含所有的 Bootstrap 样式文件-->
<!--接下来的标签启用你可能在页面中使用的所有交互式行为,如可折叠的导航栏-->
    {% bootstrap_css %}
    {% bootstrap_javascript jquery='full' %}
<!--结束标签-->
</head>

2) 定义导航栏

定义页面顶部导航栏的代码很长,因为需要同时支持较窄的手机屏幕和较宽的台式计算机显示器

我们将分三部分定义导航栏

a. 第一部分

base.html

--snip--
<!--第一个元素为起始标签<body>-->
<!--HTML 文件的主体包含用户将在页面上看到的内容-->
<body>

<!--<nav>元素表示页面的导航链接部分-->
<!--对于这个元素内的所有内容,都将根据此处的 navbar 和 navbar-expand-md 等选择器定义的 Bootstrap 样式规则来设置样式-->
<!--navbar-light 和 bg-light 使用一种浅色主题来设置导航栏的颜色-->
<!--mb-4 中的 mb 表示下边距(margin-bottom),这个选择器确保导航栏和页面其他部分之间有一些空白区域-->
<!--border 在浅色背景周围添加很细的边框,将导航栏与页面其他部分分开-->
  <nav class="navbar navbar-expand-md navbar-light bg-light mb-4 border">

<!--指定在导航栏最左端显示项目名,并将其设置为到主页的链接,因为它将出现在这个项目的每个页面中-->
<!--选择器 navbar-brand 设置这个链接的样式,使其比其他链接更显眼,这是一种网站推广方式-->
    <a class="navbar-brand" href="{% url 'learning_logs:index' %}">Learning Log</a>

<!--定义了一个按钮,它将在浏览器窗口太窄、无法水平显示整个导航栏时显示出来-->
<!--如果用户单击这个按钮,将出现一个下拉列表,其中包含所有的导航元素-->
<!--在用户缩小浏览器窗口或在屏幕较小的移动设备上显示网站时,collapse 会导致导航栏折叠起来-->
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
b. 第二部分

base.html

<!--此处的 div(division 的缩写)开启了导航栏的一个新区域-->
<!--我们创建页面时,将其分隔成多个区域,并指定要应用于各个区域的样式和行为规则-->
<!--在<div>起始标签中定义的样式和行为规则将影响下一个结束标签</div>之前的所有元素-->
<!--这里指定了屏幕或窗口太窄时将折叠起来的导航栏部分的起始位置-->
    <div class="collapse navbar-collapse" id="navbarCollapse">
<!--定义了一组链接-->
      <ul class="navbar-nav mr-auto">
<!--Bootstrap 将导航元素定义为无序列表项,但使用的样式规则让它们一点也不像列表-->
<!--导航栏中的每个链接或元素都能以列表项的方式定义-->
<!--这里只有一个列表项——到显示所有主题的页面的链接-->
        <li class="nav-item">
          <a class="nav-link" href="{% ulr 'learning_logs:topics' %}">Topics</a>
        </li>
      </ul>
c. 第三部分

base.html

<!--使用起始标签<ul>定义了另一组链接(你可根据需要在页面中包含任意数量的链接编组),这组链接与登录和注册相关,出现在导航栏最右端-->
<!--选择器 ml-auto 表示自动左边距(margin-left-automatic),它根据导航栏包含的其他元素设置左边距,确保这组链接位于屏幕右边-->
      <ul class="navbar-nav ml-auto">
<!--if 代码块与以前使用的条件代码块相同,它根据用户是否已登录显示相应的消息-->
<!--这个代码块比以前长一些,因为它现在包含一些样式规则-->
        {% if user.is_authenticated %}
          <li class="nav-item">
<!--<span>元素用于设置区域内一系列文本或元素的样式-->
<!--这起初可能令人迷惑:为什么不嵌套<div>呢?毕竟有很多页面深度嵌套了<div>元素-->
<!--这是因为<div>元素创建区域,而<span>元素不会-->
<!--这里只是要设置导航栏中信息性文本(如已登录用户的名称)的样式,旨在让其外观与链接不同,以免用户忍不住去单击,因此使用了<span>-->
            <span class="navbar-text">Hello, {{ user.username }}.</span>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'users:logout' %}">Log out</a>
          </li>
        {% else %}
          <li class="nav-item">
            <a class="nav-link" href="{% url 'users:register' %}">Register</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'users:login' %}">Log in</a>
          </li>
        {% endif %}
      </ul>
<!--指出<div>元素(它包含将在屏幕太窄时折叠起来的导航栏部分)到此结束,然后指出整个导航栏到此结束-->
<!--要在导航栏中添加其他链接,可在既有的<ul>元素中添加<li>元素,并使用这里演示的样式设置指令-->
    </div>

  </nav>

3) 定义页面的主要部分

base.html

<!--base.html 的余下部分包含页面的主要部分-->
<!--<main>元素用于定义页面主体的最重要部分-->
<!--此处指定了 Bootstrap 选择器 container,这是一种对页面元素进行编组的简单方式-->
<!--我们将在这个容器中放置两个<div>元素-->
  <main role="main" class="container">
<!--第一个<div>元素包含一个 page_header 块,我们会在大多数页面中使用它来指定标题-->
<!--为突出标题,设置内边距-->
<!--内边距(padding)指的是元素内容和边框之间的距离-->
<!--选择器 pb-2 是一个 Bootstrap 指令,将元素的下内边距设置为适度的值-->
<!--外边距(margin)指的是元素的边框与其他元素之间的距离-->
<!--我们只想在标题下面添加边框,因此使用选择器 border-bottom,它在 page_header 块的下面添加较细的边框-->
      <div class="pb-2 mb-2 border-bottom">
        {% block page_header %}{% endblock page_header%}
      </div>
<!--定义了另一个<div>元素,其中包含 content 块-->
<!--我们没有对这个块指定样式,因此在具体的页面中,可根据需要设置内容的样式-->
      <div>
        {% block content %}{% endblock content%}
      </div>
<!--文件 base.html 的末尾是元素<main>、<body>和<html>的结束标签-->
  </main>

</body>

</html>

请添加图片描述
请添加图片描述

(4) 使用 jumbotron 设置主页的样式

下面使用 Bootstrap 元素 jumbotron 来修改主页

jumbotron 元素是一个大框,在页面中显得鹤立鸡群

它可以包含任何东西,通常用于在主页中呈现简要的项目描述和让用户行动起来的元素

index.html

{% extends "learning_logs/base.html" %}

<!--告诉 Django 接下来要定义 page_header 块包含的内容-->
{% block page_header %}
<!--jumbotron 就是应用了一系列样式设置指令的<div>元素-->
<!--这里使用选择器 jumbotron 应用这组来自 Bootstrap 库的样式设置指令-->
  <div class="jumbotron">
<!--h1 类表示一级标题,而选择器 display-3 让这个标题显得更窄更高-->
    <h1 class="display-3">Track your learning.</h1>

    <p class="lead">Make your own learning log, and keep a list of the topics you're learning about. Whenever you learn something new about a topic, make an entry summarizing what you've learned.</p>

<!--通过创建一个按钮(而不是文本链接)邀请用户注册账户-->
<!--它与导航栏中的链接 Register 一样链接到的注册页面,但是按钮更显眼,并且让用户知道要使用这个项目首先需要如何做-->
<!--这里的选择器让这个按钮很大,召唤用户赶快行动起来-->
<!--代码 &raquo; 是一个 HTML 实体,表示两个右尖括号(>>)-->
    <a class="btn btn-lg btn-primary" href="{% url 'users:register' %}" role="button">Register &raquo;</a>
  </div>
<!--结束 page_header 块-->
{% endblock page_header %}
<!--我们不想在这个页面中添加其他内容,因此不需要定义 content 块-->

请添加图片描述

(5) 设置登录页面的样式

login.html

{% extends "learning_logs/base.html" %}
<!--在这个模板中加载 bootstrap4 模板标签-->
{% load bootstrap4 %}

<!--定义 page_header 块,指出这个页面是做什么用的-->
<!--注意,我们从这个模板中删除了代码块 {% if form.errors %},因为 django-bootstrap4 会自动管理表单错误-->
{% block page_header %}
  <h2>Log in to your account.</h2>
{% endblock page_header %}

{% block content %}
<!--添加属性 class="form"-->
  <form method="post" action="{% url 'users:login' %}" class="form">
    {% csrf_token %}
<!--再使用模板标签 {% bootstrap_form %} 来显示表单,它替换了第19章使用的标签 {{ form.as_p }}-->
<!--模板标签 {% bootstrap_form %} 将 Bootstrap 样式规则应用于各个表单元素-->
    {% bootstrap_form form %}
<!--bootstrap4 起始模板标签 {% buttons %},它将 Bootstrap 样式应用于按钮-->
    {% buttons %}
      <button name="submit" class="btn btn-primary">Log in</button>
    {% endbuttons %}

    <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
  </form>

{% endblock content %}

请添加图片描述

(6) 设置显示所有主题的页面的样式

topics.html

{% extends "learning_logs/base.html" %}

<!--不需要标签 {% load bootstrap4 %},因为这个文件中没有使用任何 bootstrap4 自定义标签-->
<!--我们将标题 Topics 移到 page_header 块中,并给它指定标题样式,而没有使用简单的段落标签-->
{% block page_header %}
  <h1>Topics</h1>
{% endblock page_header %}

{% block content %}
  <ul>
    {% for topic in topics %}
<!--将每个主题和添加新主题的链接都设置为<h3>元素,使其在页面上显得大一些-->
      <li><h3>
        <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
      </h3></li>
    {% empty %}
      <li><h3>No topics have been added yet.</h3></li>
    {% endfor %}
  </ul>

  <h3><a href="{% url 'learning_logs:new_topic' %}">Add a new topic</a></h3>

{% endblock content %}

请添加图片描述

(7) 设置显示单个主题的页面中的条目样式

比起大部分页面,显示单个主题的页面包含更多内容,因此需要做的样式设置工作要更多一些

我们将使用 Bootstrap 的卡片(card)组件来突出每个条目

  • 卡片是带灵活的预定义样式的 < div >,非常适合用于显示主题的条目

topic.html

{% extends "learning_logs/base.html" %}

<!--首先将主题放在 page_header 块中,并删除该模板中以前使用的无序列表结构-->
{% block page_header %}
  <h3>{{ topic }}</h3>
{% endblock page_header %}

{% block content %}
  <p>
    <a href="{% url 'learning_logs:new_entry' topic.id %}">Add new entry</a>
  </p>

  {% for entry in entries %}
<!--创建一个带选择器 card 的 <div> 元素(而不是将每个条目作为一个列表项),其中包含两个嵌套的元素:一个包含条目的创建日期以及用于编辑条目的链接,另一个包含条目的内容-->
    <div class="card mb-3">
<!--嵌套的第一个元素是个标题-->
<!--它是带选择器 card-header 的 <h4> 元素,包含条目的创建日期以及用于编辑条目的链接-->
      <h4 class="card-header">
        {{ entry.date_added|date:'M d, Y H:i' }}
<!--用于编辑条目的链接放在标签 <small> 内,这让它看起来比时间戳小一些-->
        <small><a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a></small>
      </h4>
<!--第二个嵌套的元素是一个带选择器 card-body 的 <div> 元素,将条目的内容放在一个简单的框内-->
<!--注意我们只修改了影响页面外观的元素,对在页面中包含信息的 Django 代码未做任何修改-->
      <div class="card-body">
        {{ entry.text|linebreaks }}
      </div>
    </div>
  {% empty %}
    <p>There are no entries for this topic yet.</p>
  {% endfor %}

{% endblock content %}

请添加图片描述

注意 
要使用其他 Bootstrap 模板,可采用与本章类似的流程:

  1. 将要使用的模板复制到 base.html 中并修改包含实际内容的元素,以使用该模板来显示项目的信息
  2. 然后使用 Bootstrap 的样式设置工具来设置各个页面中内容的样式。

2. 部署“学习笔记”

Heroku 是一个基于 Web 的平台,供我们管理 Web 应用程序的部署

(1) 建立 Heroku 账户

Heroku 官网免费注册

  • qq 和 163 邮箱都不能注册,sina.com 的可以

Heroku 提供的免费试用服务(free tier)让你能够将项目部署到服务器并对其进行测试

注意

  • Heroku 提供的免费试用服务存在一些限制,如可部署的应用程序数量以及用户访问应用程序的频率
  • 但这些限制都很宽松,让你能够在不支付任何费用的情况下练习部署应用程序

(2) 安装 Heroku CLI

要将项目部署到 Heroku 的服务器并对其进行管理,需要使用 Heroku CLI(Command Line Interface,命令行界面)提供的工具

要安装最新的 Heroku CLI 版本,请访问 Heroku Dev Center 网站的 The Heroku CLI 页面,并根据你使用的操作系统按相关的说明做:使用只包含一行的终端命令或下载并运行安装程序
请添加图片描述

brew tap heroku/brew && brew install heroku

(3) 安装必要的包

我们还需安装三个包,以便在服务器上支持 Django 项目提供的服务

为此,在活动状态的虚拟环境中执行如下命令:

# 为管理 Heroku 使用的数据库,psycopg2 包必不可少
pip install psycopg2
# django-heroku 包用于管理应用程序的各种配置,使其能够在 Heroku 服务器上正确地运行
# 这包括管理数据库,以及将静态文件存储到合适的地方,以便能够妥善地提供它们
# 静态文件包括样式规则和 JavaScript 文件
pip install django-heroku
# gunicorn 包让服务器能够实时地支持应用程序
pip install gunicorn

(4) 创建文件 requirements.txt

Heroku 需要知道项目依赖于哪些包,因此我们使用 pip 生成一个文件,在其中列出这些包

同样,进入活动虚拟环境,并执行如下命令:

pip freeze > requirements.txt
  • 命令 freeze 让 pip 将项目中当前安装的所有包的名称都写入文件 requirements.txt

请添加图片描述
requirements.txt

asgiref==3.5.0
beautifulsoup4==4.10.0
dj-database-url==0.5.0
Django==4.0.3
django-bootstrap4==22.1
django-heroku==0.3.1
gunicorn==20.1.0
psycopg2==2.9.3
pytz==2022.1
soupsieve==2.3.1
sqlparse==0.4.2
whitenoise==6.0.0

我们部署“学习笔记”时,Heroku 将安装 requirements.txt 列出的所有包,从而创建一个环境,其中包含在本地使用的所有包

有鉴于此,我们可以深信在部署到 Heroku 后,项目的行为将与在本地系统上完全相同

当你在自己的系统上开发并维护各种项目时,这将是一个巨大的优点

(5) 指定 Python 版本

如果没有指定 Python 版本,Heroku 将使用其当前的 Python 默认版本

下面来确保 Heroku 使用我们使用的Python版本

为此,在活动状态的虚拟环境中,执行命令:

(ll_env) chengrui@chengruis-MacBook-Air learning_log % python --version
Python 3.10.2

上述输出表明,我使用的是 Python 3.10.2。请在 manage.py 所在的文件夹中新建一个名为 runtime.txt 的文件,并在其中输入如下内容:

python-3.10.2

注意 
如果出现错误消息,指出不能使用指定的Python版本,请访问 Heroku Dev Center 网站 Language Support 页面的 Specifying a Python Runtime 部分,了解支持的Python版本,并使用与你使用的Python版本最接近的版本

(6) 为部署到 Heroku 而修改 settings.py

现在需要在 settings.py 末尾添加一个片段,在其中指定一些 Heroku 环境设置:

settings.py

# Heroku settings
# 导入了模块 django_heroku 并调用了函数 settings()
# 这个函数将一些设置修改为 Heroku 环境要求的值
import django_heroku
django_heroku.settings(locals())

(7) 创建启动进程的 Procfile

Procfile 告诉 Heroku 应该启动哪些进程,以便正确地提供项目需要的服务

请将这个只包含一行代码的文件命名为 Procfile(首字母P为大写),但不指定文件扩展名(且无格式),再将其保存到 manage.py 所在的目录中

Procfile

web: gunicorn learning_log.wsgi --log-file -
  • 这行代码让 Heroku 将 Gunicorn 用作服务器,并使用 learning_log/wsgi.py 中的设置来启动应用程序
  • 标识 log-file 告诉 Heroku 应将哪些类型的事件写入日志

(8) 使用 Git 跟踪项目文件

Git 是一个版本控制程序,让你能够在每次成功实现新功能后都拍摄项目代码的快照

无论出现什么问题(如实现新功能时不小心引入了 bug),都可轻松地恢复到最后一个可行的快照

  • 每个快照都称为提交

使用 Git 意味着在尝试实现新功能时无须担心破坏项目

将项目部署到服务器时,需要确保部署的是可行版本

1) 安装 Git

确定是否安装了 Git:

(ll_env) chengrui@chengruis-MacBook-Air learning_log % git --version
git version 2.32.0 (Apple Git-132)

2) 配置 Git

Git 跟踪是谁修改了项目,即便项目由一个人开发亦如此

为进行跟踪,Git 需要知道你的用户名和电子邮箱

因此,你必须提供用户名,但对于练习项目,可以编造一个电子邮箱:

(ll_env) chengrui@chengruis-MacBook-Air learning_log % git config --global user.name "Sally"      
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git config --global user.email "dangerousmile@sina.com"

3) 忽略文件

无须让 Git 跟踪项目中的每个文件,因此我们让它忽略一些文件

在 manage.py 所在的文件夹中创建一个名为 .gitignore 的文件(请注意,这个文件名以句点打头,且不包含扩展名),并在其中输入如下代码:

ll_env/
__pycache__/
*.sqlite3

这里让 Git 忽略目录 ll_env,因为随时都可自动重新创建它

还指定不跟踪目录__pycache__,这个目录包含 Django 运行 .py 文件时自动创建的 .pyc 文件

我们没有跟踪对本地数据库的修改,因为这是个坏习惯:如果在服务器上使用的是SQLite,将项目推送到服务器时,可能会不小心用本地测试数据库覆盖在线数据库

*.sqlite3让Git忽略所有扩展名为.sqlite3的文件

注意

  • 如果你使用的是 macOS 系统,请将 .DS_Store 添加到文件 .gitignore中
  • 文件 .DS_Store 存储的是有关文件夹设置的信息,与这个项目一点关系都没有

4) 显示隐藏的文件

大多数操作系统都会隐藏名称以句点打头的文件和文件夹,如 .gitignore

  • Windows:打开资源管理器,再打开一个文件夹,单击标签 View(查看),并确保选中了复选框 File name extensions(文件扩展名)和 Hidden items(隐藏的项目)
  • macOS:件浏览器窗口中按 Command + Shift + .(句点)
  • Linux(Ubuntu 等):可在文件浏览器中按 Ctrl + H 来显示隐藏的文件和文件夹;为让这种设置为永久性的,可打开文件浏览器(如 Nautilus),再单击选项标签(以三条线表示),并选中复选框 Show Hidden Files(显示隐藏的文件)

5) 提交项目

我们需要为“学习笔记”初始化一个 Git 仓库,将所有必要的文件都加入该仓库,并提交项目的初始状态:

# 在“学习笔记”所在的目录中初始化一个空仓库
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git init
hint: Using 'master' as the name for the initial branch. This default branch nam
--snip--
Initialized empty Git repository in /Users/chengrui/ProgramProjects/Python/python_crash_course/learning_log/.git/
# (千万别忘了末尾的句点)将未被忽略的文件都加入这个仓库
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git add .
# 标志 -a 让 Git 在这个提交中包含所有修改过的文件,而标志 -m 让 Git 记录一条日志消息
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git commit -am "Ready for development to heroku."
Auto packing the repository in background for optimum performance.
See "git help gc" for manual housekeeping.
[master (root-commit) d33cadb] Ready for development to heroku.
 6120 files changed, 805368 insertions(+)
 create mode 100644 .DS_Store
--snip--
# 输出表明当前位于分支 master,而工作树是干净(clean)的
# 每当要将项目推送到 Heroku 时,我们都希望看到这样的状态
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git status
On branch master
nothing to commit, working tree clean

(9) 推送到 Heroku

# 这将打开浏览器并在其中显示一个页面,让你能够登录 Heroku
(ll_env) chengrui@chengruis-MacBook-Air learning_log % heroku login       
--snip--
Logging in... done
Logged in as dangerousmile@sina.com
# 让 Heroku 创建一个空项目
# Heroku 生成的项目名由两个单词和一串数字组成,但以后可修改这个名称
(ll_env) chengrui@chengruis-MacBook-Air learning_log % heroku create
Creating app... done, ⬢ cryptic-sea-37272
https://cryptic-sea-37272.herokuapp.com/ | https://git.heroku.com/cryptic-sea-37272.git
# 让 Git 将项目的分支 master 推送到 Heroku 刚才创建的仓库中
# Heroku 将使用这些文件在其服务器上创建项目
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git push heroku master
--snip--
remote: -----> Launching...
remote:        Released v20
# 列出了用于访问这个项目的 URL,但这个 URL 和项目名都是可以修改的
remote:        https://cryptic-sea-37272.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/cryptic-sea-37272.git
   8fb7bf9..135d300  master -> master

执行这些命令后,项目就部署好了,但还未做全面配置

为核实正确地启动了服务器进程,请执行命令 heroku ps:

(ll_env) chengrui@chengruis-MacBook-Air learning_log % heroku ps 
# 输出指出了在接下来的一个月内,项目还可在多长时间内处于活动状态
# - Heroku 允许免费部署在一个月内最多有550小时处于活动状态
# - 项目的活动时间超过这个限制后,将显示标准的服务器错误页面        
Free dyno hours quota remaining this month: 550h 0m (100%)
Free dyno usage for this app: 0h 0m (0%)
For more information on dyno sleeping and how to upgrade, see:
https://devcenter.heroku.com/articles/dyno-sleeping

# 启动了 Procfile 指定的进程
=== web (Free): gunicorn learning_log.wsgi --log-file - (1)
web.1: up 2022/03/23 12:56:03 +0800 (~ 32s ago)

现在,可使用命令 heroku open在浏览器中打开这个应用程序了
请添加图片描述

  • 也可以启动浏览器并输入 Heroku 告诉你的 URL,但上述命令让你无须这样做
  • 你将看到“学习笔记”的主页,其样式设置正确无误,但还无法使用这个应用程序,因为尚未建立数据库

注意

  • 部署到 Heroku 的流程会不断变化,如果遇到无法解决的问题,请通过查看 Heroku 文档来获取帮助
  • 为此,可访问 Heroku Dev Center 网站首页,单击 Python,再单击到 Get Started with Python 或 Deploying Pythonand Django Apps on Heroku 的链接

(10) 在 Heroku 上建立数据库

为建立在线数据库,需要再次执行命令 migrate,并应用在开发期间生成的所有迁移

要对 Heroku 项目执行 Django 和 Python 命令,可使用命令 heroku run

下面演示了如何对 Heroku 部署执行命令 migrate:

# 首先执行命令h eroku run python manage.py migrate
(ll_env) chengrui@chengruis-MacBook-Air learning_log % heroku run python manage.py migrate
# Heroku 随后创建一个终端会话来执行命令 migrate
Running python manage.py migrate on ⬢ cryptic-sea-37272... up, run.7229 (Free)
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
# Django 应用默认迁移以及我们在开发“学习笔记”期间生成的迁移
Running migrations:
  --snip--
  Applying learning_logs.0001_initial... OK
  Applying learning_logs.0002_entry... OK
  Applying learning_logs.0003_topic_owner... OK
  Applying sessions.0001_initial... OK

现在如果访问这个部署的应用程序,将能够像在本地系统上一样使用它,但看不到在本地部署中输入的任何数据(包括超级用户账户),因为它们还没有被复制到在线服务器

  • 通常,不将本地数据复制到在线部署中,因为本地数据通常是测试数据。

你可分享“学习笔记”的 Heroku URL,让任何人都可使用它

(11) 改进 Heroku 部署

本节将通过创建超级用户来改进部署,就像在本地一样

我们还将让这个项目更安全:将 DEBUG 设置为 False,让用户在错误消息中看不到额外的信息,以防其利用这些信息来攻击服务器

1) 在 Heroku 上创建超级用户

我们知道可以使用命令 heroku run 来执行一次性命令,但也可这样执行命令:在连接到 Heroku 服务器的情况下,使用命令 heroku run bash 打开 Bash 终端会话

Bash 是众多 Linux 终端运行的语言

我们将使用 Bash 终端会话来创建超级用户,以便访问在线应用程序的管理网站:

(ll_env) chengrui@chengruis-MacBook-Air learning_log % heroku run bash
Running bash on ⬢ cryptic-sea-37272... up, run.9294 (Free)
# 查看服务器上有哪些文件和目录
# 服务器包含的文件和目录应与本地系统相同
# 可像遍历其他文件系统一样遍历这个文件系统
# 即便使用的是 Windows 系统,也应使用这里列出的命令(如 ls 而不是 dir),因为这里是在通过远程连接运行 Linux 终端
~ $ ls
db.sqlite3    learning_logs  manage.py	requirements.txt  users
learning_log  ll_env	     Procfile	runtime.txt
# 执行创建超级用户的命令,它像第18章在本地系统创建超级用户一样提示你输入相关的信息
~ $ python manage.py createsuperuser
Username (leave blank to use 'u38381'): ll_admin
Email address: dangerousmile@sina.com
Password: 
Password (again): 
Superuser created successfully.
# 执行命令 exit 返回到本地系统的终端会话
~ $ exit
exit
(ll_env) chengrui@chengruis-MacBook-Air learning_log % 

现在,你可以在在线应用程序的 URL 末尾添加 /admin/ 来登录管理网站了

对我而言,这个 URL 为 https://cryptic-sea-37272.herokuapp.com/admin/
请添加图片描述
如果有其他人使用这个项目,别忘了你能访问他们的所有数据!

  • 千万别不把这一点当回事,否则用户就不会再将数据托付给你了

2) 在 Heroku 上创建对用户友好的 URL

你很可能希望 URL 更友好,比 https://cryptic-sea-37272.herokuapp.com/admin/ 更好记

为此,只需使用一个命令重命名应用程序:

# 给应用程序命名时,可使用字母、数字和连字符,并且想怎么命名都可以,只要指定的名称未被别人使用就行
# 命令 apps:rename 将整个项目都移到了新的 URL 处
(ll_env) chengrui@chengruis-MacBook-Air learning_log % heroku apps:rename learning-log-sally0323
Renaming cryptic-sea-37272 to learning-log-sally0323... done
# 现在,项目的 URL 变成了 https://learning-log-sally0323.herokuapp.com/
# 使用以前的 URL 再也无法访问它
https://learning-log-sally0323.herokuapp.com/ | https://git.heroku.com/learning-log-sally0323.git
 ▸    Don't forget to update git remotes for all other local checkouts of the
 ▸    app.
Git remote heroku updated

注意

  • 使用 Heroku 提供的免费服务部署项目时,如果项目在指定时间内未收到请求或过于活跃,Heroku 将让项目进入休眠状态
  • 用户访问处于休眠状态的网站时,加载时间将更长,但对于后续请求,服务器的响应速度将更快
  • 这就是 Heroku 能够提供免费部署的原因所在

(12) 确保项目的安全

当前,部署的项目存在严重的安全问题:settings.py 包含设置 DEBUG=True,指定在发生错误时显示调试信息

开发项目时,Django 的错误页面显示了重要的调试信息,如果将项目部署到服务器后还保留这个设置,将给攻击者提供大量可利用的信息

在在线项目中,我们将设置一个环境变量,以控制是否显示调试信息

环境变量是在特定环境中设置的值

这是在服务器上存储敏感信息并将其与项目代码分开的方式之一

下面来修改 settings.py,使其在项目于 Heroku 上运行时检查一个环境变量:

settings.py

# Heroku settings
# 导入了模块 django_heroku 并调用了函数 settings()
# 这个函数将一些设置修改为 Heroku 环境要求的值
import django_heroku

django_heroku.settings(locals())

# 方法 os.environ.get() 从项目当前所处的环境中读取与特定环境变量相关联的值
# 如果设置了这个环境变量,就返回它的值;如果没有设置,就返回 None
# 使用环境变量来存储布尔值时,必须小心应对,因为在大多数情况下,环境变量存储的都是字符串
if os.environ.get('DEBUG') == 'TRUE':
    DEBUG = True
elif os.environ.get('DEBUG') == 'FALSE':
    DEBUG = False

请看下面在简单的 Python 终端会话中执行的代码片段:

>>> bool('False')
True

字符串 ‘False’ 对应的布尔值为 True,因为非空字符串对应的布尔值都为 True

因此,我们将使用全大写的字符串 ‘TRUE’ 和 ‘FALSE’,以明确地指出存储的不是 Python 布尔值 True 和 False

Django 读取 Heroku 中键为 ‘DEBUG’ 的环境变量时,如果其值为 ‘TRUE’,我们就将 DEBUG 设置为 True;如果其值为 ‘FALSE’,就将 DEBUG 设置为 False

(13) 提交并推送修改

将对 settings.py 所做的修改提交到 Git 仓库,再将修改推送到 Heroku:

# 执行命令 git commit,并指定一条简短而有描述性的提交消息
# 标志 -am 让 Git 提交所有修改过的文件,并记录一条日志消息
# Git 找出唯一修改过的文件,并将所做的修改提交到仓库
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git commit -am "Set DEBUG based on environment variables."
[master e2f6c6c] Set DEBUG based on environment variables.
 1 file changed, 10 insertions(+), 7 deletions(-)
 # 此处显示的状态表明:当前位于仓库的分支 master,没有任何未提交的修改
 # 推送到 Heroku 前,必须检查状态并看到刚才所说的消息
 # 如果没有看到这样的消息,就说明有未提交的修改,而这些修改将不会推送到服务器
 # - 在这种情况下,可尝试再次执行命令 commit
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git status
On branch master
nothing to commit, working tree clean
# 将修改后的仓库推送到 Heroku
# Heroku 发现仓库发生了变化,因此重建项目,确保所有的修改都生效
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git push heroku master
--snip--
remote:        Released v21
remote:        https://learning-log-sally0323.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/learning-log-sally0323.git
   135d300..e2f6c6c  master -> master

它不会重建数据库,因此这次无须执行命令 migrate

(14) 在 Heroku 上设置环境变量

现在可通过 Heroku 将 settings.py 中的 DEBUG 设置为所需的值了

命令heroku config:set设置一个环境变量:

(ll_env) chengrui@chengruis-MacBook-Air learning_log % heroku config:set DEBUG=FALSE
# 每当你在 Heroku 上设置环境变量时,Heroku 都将重启项目,让环境变量生效
Setting DEBUG and restarting ⬢ learning-log-sally0323... done, v22
DEBUG: FALSE

要核实部署现在更安全了,请输入项目的 URL,并在末尾加上未定义的路径
请添加图片描述

  • 这样的结果非常理想:接着开发这个项目时,你可以看到信息丰富的错误消息,但访问在线项目的用户看不到有关代码的重要信息

如果你在部署应用程序时遇到麻烦,需要排除故障,可执行命令 heroku config:set DEBUG='TRUE',以便访问在线项目时能够看到完整的错误报告
请添加图片描述

成功地排除故障后,务必将这个环境变量重置为’FALSE’

另外,请务必小心,一旦有用户经常访问这个在线项目,就不要这样做

(15) 创建自定义错误页面

我们可以编写外观与“学习笔记”一致的404和500错误页面模板

这些模板必须放在根模板目录中

1) 创建自定义模板

在最外层的文件夹 learning_log 中,新建文件夹 templates,再在其中新建文件 404.html 和 500.html

404.html

<!--这个简单的模板指定了通用的404错误页面包含的信息,但该页面的外观与网站其他部分一致-->
{% extends "learning_logs/base.html" %}

{% block page_header %}
  <h2>The item you requested is not available. (404)</h2>
{% endblock page_header %}

500.html

{% extends "learning_logs/base.html" %}

{% block page_header %}
  <h2>There is an internal error. (500)</h2>
{% endblock page_header %}

这些新文件要求对 settings.py 做细微的修改:

settings.py

--snip--
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # 这项修改让 Django 在根模板目录中查找错误页面模板
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        --snip--
    },
]
--snip--

2) 在本地查看错误页面

将项目推送到 Heroku 前,如果要在本地查看错误页面是什么样的,首先需要在本地设置中设置 Debug=False,以禁止显示默认的 Django 调试页面

为此,可对 settings.py 做如下修改(请确保修改的是 settings.py 中用于本地环境的部分,而不是用于 Heroku 的部分):

settings.py

--snip--
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
--snip--

请添加图片描述
查看错误页面后,将本地 DEBUG 的值重新设置为 True,为后续开发提供方便

  • 在管理 Heroku 设置的部分,确保处理 DEBUG 的方式不变

注意 
500错误页面不会显示任何有关当前用户的信息,因为发生服务器错误时,Django 不会通过响应发送任何上下文信息

3) 将修改推送到 Heroku

# 执行命令 git add .,因为我们在项目中创建了一些新文件,需要让 Git 跟踪它们
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git add .
# 提交所做的修改
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git commit -am "Added custom 404 and 500 error pages."
[master 9a97409] Added custom 404 and 500 error pages.
 4 files changed, 13 insertions(+), 1 deletion(-)
 create mode 100644 templates/404.html
 create mode 100644 templates/500.html
 # 将修改后的项目推送到 Heroku
(ll_env) chengrui@chengruis-MacBook-Air learning_log % git push heroku master
--snip--
remote: Verifying deploy... done.
To https://git.heroku.com/learning-log-sally0323.git
   e2f6c6c..9a97409  master -> master

请添加图片描述

4) 使用方法 get_object_or_404()

现在,如果用户手工请求不存在的主题或条目,将导致500错误

  • Django 尝试渲染不存在的页面,但没有足够的信息来完成这项任务,进而引发了500错误

对于这种情形,将其视为404错误更合适,为此可使用 Django 快捷函数 get_object_or_404()

views.py

# 导入 Django 快捷函数 get_object_or_404()
# - 这个函数尝试从数据库获取请求的对象,如果这个对象不存在,就引发404异常
from django.shortcuts import render, redirect, get_object_or_404
--snip--
@login_required
def topic(request, topic_id):
    """显示单个主题及其所有的条目"""
    # 用 get_object_or_404() 替换函数 get()
    topic = get_object_or_404(Topic, id=topic_id)

请添加图片描述

(16) 继续开发

将项目“学习笔记”推送到服务器后,你可能想进一步开发它或开发要部署的其他项目

更新项目的过程几乎完全相同:

  1. 首先,对本地项目做必要的修改
    如果在修改过程中创建了新文件,使用命令 git add .(千万别忘记末尾的句点)将其加入 Git 仓库中
    如果有修改要求迁移数据库,也需要执行这个命令,因为每个迁移都将生成新的迁移文件
  2. 然后,使用命令 git commit -am "commit message"将修改提交到仓库,再使用命令 git push heroku master 将修改推送到 Heroku
    如果在本地迁移了数据库,也需要迁移在线数据库
    为此,可使用一次性命令 heroku run python manage.py migrate,也可使用 heroku run bash 打开远程终端会话,并在其中执行命令 python manage.py migrate
    然后访问在线项目,确认期望看到的修改已生效

(17) 设置 SECRET_KEY

Django 根据 settings.py 中设置 SECRET_KEY 的值来实现大量的安全协议

在这个项目中,提交到仓库的设置文件包含设置 SECRET_KEY

对于一个练习项目而言,这足够了,但对于生产网站,应更细致地处理设置 SECRET_KEY

如果你创建的项目的用途很重要,务必研究如何更安全地处理设置 SECRET_KEY

(18) 将项目从 Heroku 删除

一个不错的练习是,使用同一个项目或一系列小项目执行部署过程多次,直到对部署过程了如指掌

然而,你需要知道如何删除部署的项目

  1. 在 Heroku 网站登录后,将重定向到一个页面,其中列出了你托管的所有项目
  2. 单击要删除的项目,你将看到另一个页面,其中显示了有关这个项目的信息
  3. 单击链接 Settings,再向下滚动,找到用于删除项目的链接并单击它
    请添加图片描述
  4. 这种操作是不可撤销的,因此Heroku让你手工输入要删除的项目的名称,确认你确实要删除它

也可在终端使用命令 destroy 来删除项目:

(ll_env) chengrui@chengruis-MacBook-Air learning_log % heroku apps:destroy --app learning-log-sally0323
 ▸    WARNING: This will delete ⬢ learning-log-sally0323
 ▸    including all add-ons.
 # 你将被要求再次输入项目名,确认你确实要删除它
 ▸    To proceed, type learning-log-sally0323 or re-run this
 ▸    command with --confirm learning-log-sally0323

> 

注意

  • 删除 Heroku 上的项目对本地项目没有任何影响
  • 如果没有人使用你部署的项目,就尽管去练习部署过程好了,在 Heroku 上删除项目再重新部署完全合情合理
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值