Python-系统管理高级教程-二-

123 阅读57分钟

Python 系统管理高级教程(二)

原文:Pro Python System Administration

协议:CC BY-NC-SA 4.0

三、为 IP 地址统计创建一个 Web 应用

在这一章中,我们将构建一个简单的应用来跟踪内部网络上分配的所有 IP 地址。本章涵盖了开发该应用的所有阶段,从收集和设置需求开始,通过实现阶段的各个方面来设计应用。

设计应用

理想情况下,应用设计不应该基于将要用来实现它的技术。话虽如此,这种独立性很难实现,而且在大多数情况下是不实际的,因为每种技术都暗示着它自己的实现模式和最佳实践。

在本章中,我们将在解释将要使用的技术之前定义需求和应用设计。这样,即使在您自己的工作中,您将使用不同的技术,您也会更容易理解如何重用设计阶段。

列出要求

在开发任何应用时,最重要的考虑是准确理解您想从中获得什么。远离你在别处看到的用户界面图像,或者你过去可能使用过的其他(可能类似的)应用的功能。相反,拿一张纸,用简短的句子写下你希望你的应用做什么。

我们想象的组织是一个相当大的企业,具有相当复杂的网络基础设施,因此有效地分配和使用 IP 地址空间非常重要。过去,地址记录在简单的电子表格中,不同的团队使用不同的结构来表示相同的信息。在这里,没有分配 IP 地址范围的权限,因此团队之间有效和清晰的沟通非常重要。新系统正在引入,而旧系统正在退役。组策略阻止服务器使用动态 IP 分配;只有用户机器可以从 DHCP 获得地址信息。根据这一简要描述,我们来列出以下要求:

  • 该系统必须是集中的,但是可以被许多不同的用户访问。
  • 应用必须能够存储 IP 范围和个人 IP 地址。
  • 应用必须提供一种方法来创建范围和单个 IP 地址的分层组织。
  • 用户必须能够添加、删除和修改条目。
  • 用户必须能够搜索信息。
  • 系统必须能够检查使用 IP 地址的机器是否有响应。
  • 对于所有 IP 地址,系统应该尝试获取名称记录。
  • 必须要求用户输入他们所做的任何 IP 保留的描述。
  • 扩展系统使用 DHCP 应该很容易。

既然我们已经定义了所有的需求,我们就可以在开发阶段的任何时候回顾这些需求,并验证我们的应用是否完全符合预期。我们不会实现不必要的功能;通过将实际的实现与需求集进行比较,我们将总是知道我们已经取得了多少进展,还有多少工作要做。展望未来,如果有必要,我们甚至可以将个人任务委派给其他人。如果在某个时候我们发现我们遗漏了一些重要的功能,我们总是可以回到我们的列表并相应地修改它,但是这将是一个有意识的决定,这将阻止我们在开发过程中实现任何新的功能。

做出设计决策

一旦我们建立了需求,我们就可以进行一些关于如何实现它们的设计决策。每个设计决策必须试图解决需求列表中陈述的一些目标。

因为这不是一个大规模的项目,所以不需要创建正式的设计文档;同样的非正式发言清单在这里就足够了。因此,根据刚才陈述的需求,我们可以对应用开发和结构做出以下决定:

  • 该应用将是基于网络的。
  • 它将运行在专用的网络服务器上,组织中的任何人都可以通过自己的网络浏览器进行访问。
  • 该应用将使用 Python 编写,并将使用 Django 框架。
  • 实施分为两个阶段:基本的 IP 分配和预留功能,以及与 DHCP 的集成。(我们将在本章中处理第一个阶段,然后在第四章中继续讨论 DHCP 集成。)

就是这样;尽管这个列表很短,但它确保了我们不会偏离我们最初陈述的目标,如果我们真的需要做一些改变,那将被记录下来。这里的列表主要代表设计的非功能方面;我们将在接下来的章节中讨论更具体的细节。形式上,这应该构成一个详细的设计文档,但是我将只描述两件事:我们的应用将操作什么数据,以及应用将如何处理这些数据。

定义数据库模式

从刚才陈述的需求中,我们知道我们需要记录以下数据:

  • IP 范围和/或单个 IP 地址
  • 当前范围所属的父范围
  • 对于每条记录,是否允许为空

IP 地址如何工作

在继续之前,让我们检查一下 IP 寻址是如何工作的,这样您将更好地理解我们将要做出的一些具体的数据库布局和结构决策。这里提供的描述有些简化;如果你想了解更多关于 IP 网络的知识,特别是关于 IP 地址的知识,我推荐维基百科上关于 en.wikipedia.org/wiki/Classl… CIDR 的词条:

简而言之,每个 IP 地址都有两个部分:网络地址部分,用于标识特定地址所属的网络,以及该网络中的主机地址。IPV4 中的完整 IP 地址总是 32 位长。在无类域间路由(CIDR) 推出之前,只有三种可用的网络块或网络类:A 类(8 位定义网络地址,允许超过 1600 万个唯一主机地址)、B 类(16 位网络地址,超过 65,000 个唯一主机地址)和 C 类(24 位网络地址,256 个唯一主机地址)。这是非常低效的,因为它不允许细粒度的地址和范围分配,所以 CIDR 方案被引入,它允许我们使用任何长度的网络地址。在 CIDR 符号中,每个 IP 地址后面都有一个数字,用来定义网络部分包含多少位。因此,地址 192.168.1.1/24 告诉我们,这是一个来自 C 类网络的 IP,其前 24 位是网络地址。

这张图片展示了 IP 地址的各种配置,我稍后会解释。该示例使用的网络地址范围比默认的 C 类小得多,因此您可以看到它是如何工作的。

9781484202180_unFig03-01.jpg

  • (A)显示了 IP 地址 192.168.1.52,以及它是如何分成两部分的:网络地址和主机地址。
  • 在(B)中,主机地址设置为 0,从而有效地定义了网络。因此,如果您想引用 192.168.1.52 地址所属的网络范围,您可以将其写成 192.168.1.32/27。
  • 如果我们将主机地址设置为全 1,我们将获得该范围内最后一个可能的 IP 地址,也称为广播 IP 。在中的示例中,它是地址 192.168.1.63。
  • 最后,在(D)中,您可以看到 192.168.1.93/27 是如何超出范围的,因此与 192.168.1.52/27 处于不同的 IP 网络范围;它的网络部分是不同的。其实是在一个相邻的网络范围内,192.168.1.64/27。

这应该对 IP 编号方案有所启发,您可以看到理解这一点如何帮助我们更有效地定义我们的数据库模式。

当您查看 IP 地址是如何构造的时,您可能会注意到较大的网络范围包含较小的网络范围,因此一个 24 位网络可能包含两个 25 位网络,或四个 26 位网络,依此类推;这完全取决于网络基础设施。这种结构让我们很容易检查网络之间的父子关系。

现在,我们需要决定如何存储这些信息。将它存储为四个独立的十进制数(四个八位字节)和一些位是一个显而易见的选择,但是正如您可能已经猜到的,这对任何数据库系统都没有帮助。像“给我所有属于这个范围的 IP”这样的搜索在客户端计算量会很大。因此,我们将把所有 IP 号码转换成 32 位整数,并照此存储。我们还将以位为单位分别存储网络大小,因此计算范围内的第一个和最后一个地址将非常简单。

我来举例说明一下。如果我们取之前使用的 IP 地址 192.168.1.52/27,用按位记数法表示,就会得到下面这个二进制数:11000000000000100110100。这个数字可以表示为 32 位整数(十进制表示法):3232235828。现在我们可以找到它的网络地址。我们知道网络范围是由前 27 位定义的,所以我们需要做的就是对这个数和一个由 27 个 1 和 5 个 0 组成的数进行二进制 AND 运算(1111111111111111111100000 b = 4294967264d):

3232235828D AND 4294967264D = 3232235808D

或者,用二进制表示:

11000000101010000000000100100000B

将这个结果与“IP 地址如何工作”边栏中的例子进行比较,您会发现结果是一致的。

找到上边界同样容易;我们需要将可用地址的最大数量添加到之前计算的结果中。因为 27 位网络空间留下 5 位来定义主机地址,所以最大(或广播)地址是 2⁵ = 32。因此,我们给定地址的网络表示为 3232235808D,其中最后一个地址是 3232235808D + 32D = 3232235840D。从这里,我们可以很容易地找到所有在同一网络范围内的地址。

基于这些信息,我们准备定义我们的数据库模式,它非常简单,只包含一个表。表 3-1 描述了模式中的每一列。

表 3-1 。网络定义方案中的字段

|

圆柱

|

数据类型

|

评论

| | --- | --- | --- | | 记录 ID | 整数 | 主键,它是唯一的,并且随着每个新记录自动递增。 | | 地址 | 整数 | 一个键,它必须被定义,并且是一个表示 32 位网络地址的整数。 | | 网络规模 | 整数 | 一个密钥,它必须被定义并决定地址的网络部分的位数。 | | 描述 | 文本 | 必须定义,描述这个 IP 是做什么的。 |

创建应用工作流

由于这个应用相对简单,我们不需要使用正式的规范语言,如统一建模语言(UML),来定义应用的行为和工作流。这一阶段的主要目标是写下想法和布局结构,这样我们就可以在实现时随时参考文档,并且我们可以确认实现与最初设计的没有什么不同。

我发现只写几个语句是有用的,这些语句简要地描述了对于我们需求列表中的每个功能需求,将会发生什么,以及信息将如何呈现给最终用户。功能需求是我们的应用需要执行的功能。不要将它们与非功能需求相混淆,例如性能或可用性需求,它们不会影响应用的工作流程。

搜索和显示功能

一个常见的需求是搜索功能。即使我们不打算搜索,而只想查看列出的所有地址和网络范围,这也是一个广泛的搜索请求,要求系统显示所有可用信息。

因为我们已经决定为信息创建一个层次结构,所以 search 函数将通过在描述中查找 IP 地址或子字符串,并返回匹配条目的列表。显示功能将显示关于当前所选地址的信息(地址、网络位数以及范围的起始和结束地址),并列出所有子条目——即所选条目中的所有地址或网络。单击它们中的任何一个都会导致一个搜索和显示调用,它会沿着树向下。

显示功能还应该提供到父条目的链接,这样用户可以双向移动。如果搜索查询是空的或者匹配树中最顶端的节点,应该没有向上移动一级的选项。网络树(或超级网络)中最顶端的节点总是 0.0.0.0/0。对于每个子条目,view 函数应该调用一个健康检查函数来查看地址是否有响应。另外,一个名称解析过程被调用来获得一个 DNS 名称。这些信息应该相应地显示出来。

如果当前选择的树节点是一个网络地址,应该向用户显示一个到 Add New Entry 表单的链接。

添加功能

添加功能允许用户添加新的子条目。该表单要求输入新条目的详细信息,比如 IP 地址和描述,并创建一个相应的数据库条目。如果成功完成,表单将返回到之前的视图。

当我们添加一个新条目时,这个函数必须确认该条目是有效的,并且提供的 IP 地址存在。我们还需要检查该地址是否是任何当前父网络的子集。

删除功能

删除选项应该出现在每个条目旁边的地址列表中。单击它应该会产生一个简单的 JavaScript 确认对话框,如果确认删除,则必须从数据库中删除相应的条目。

如果条目是一个网络地址,所有子条目都应该递归删除。例如,如果我有一个包含网络 B 的网络 A,而网络 B 又包含地址 C,当我删除网络 A 时,网络 B 和地址 C 条目也应该被删除。

修改功能

当前地址列表中的所有条目都应该有一个修改选项。单击 Modify 应该会显示一个类似于添加新条目的表单,其中所有字段都填充了当前信息。

如果条目是一个网络地址,只有描述应该是可更改的。如果条目是主机 IP 地址,则在使用新设置更新数据库行之前,应该执行完整性检查(例如地址是否重复或者是否在有效的网络范围内)。

系统健康检查功能

当列出所有子条目时,视图函数应该为每个不是网络地址的地址调用一个系统健康检查。运行状况检查函数执行简单的 ICMP 检查(ping ),如果收到响应,则返回 True,否则返回 False。

名称解析功能

正如我们对健康检查函数所做的那样,我们将创建另一个过程,该过程将为网络外部的所有地址调用名称解析。名称解析将执行反向 DNS 查找,并返回 DNS 名称(如果可用)。如果没有 DNS 记录,将返回一个空字符串。

Django 框架的基本概念

正如我前面提到的,我们将使用 Django web 框架来开发应用。我选择 Django 是因为它是一个多功能的工具,可以极大地简化 web 应用开发。

姜戈是什么?

简而言之,Django 是一个高级 web 开发框架。Django 为快速 web 应用开发提供了工具。它的设计方式鼓励开发人员编写更干净、更一致的代码;同时也让他们少写代码。为开发人员提供了 web 开发中常用的各种高级功能,因此他们不需要重写别人已经开发的东西。Django 还加强了模块化,使开发人员能够编写一个无需修改或修改很少就可以在许多不同项目中使用的模块。

以下是 Django 框架的一些要点。

对象到关系数据库映射器?

我们使用 Python 类来定义我们的数据模型,Django 自动将它们转换成数据库表和关系。除此之外,Django 提供了一个直接来自 Python 的数据库访问 API,所以我们很少需要自己编写任何 SQL 代码。此外,我们可以在各种数据库系统(MySQL、SQLite 和其他)之间切换,而无需对代码进行任何更改。

管理界面

当我们定义我们的数据方案时,Django 不仅自动创建数据库和所有需要的表,它还生成一个全功能的管理界面来管理我们的数据。

灵活的模板系统

所有可显示的组件或视图都被分离到模板中,所以我们永远不会发现自己在程序中生成 HTML 代码。相反,代码和 HTML 设计是分开的。模板语言学习起来非常简单,但是灵活且对设计者友好,所以我们可以把设计工作交给其他人。

开源社区支持

最后但同样重要的是,Django 是开源的,并得到了活跃的开发者社区的支持。Django 发展非常迅速,每年都有几次重大升级,并且已经出现了一段时间,证明了自己是一个成熟可靠的产品。

模型/视图/控制器模式

在深入其实现细节之前,让我们先来探索 Django 所基于的最重要的设计模式:模型-视图-控制器(MVC)。任何遵循这种模式的 web 应用都被分成三个不同的部分:数据模型、视图和控制器。

数据模型组件

数据模型(或只是模型)部分定义了应用正在使用或操作的数据。这通常是数据库数据结构,但也可以是数据访问方法和函数。在 Django 中,所有的数据结构都被定义为 Python 类,框架自动在数据库上创建相应的数据模式。

视图组件

大多数 web 框架中的视图部分负责向最终用户显示数据。它是一组生成 HTML 代码的函数,这些代码被发送回 web 浏览器。Django 更进了一步,将传统上所谓的视图组件分成两个不同的实体:视图和模板。用 Django 的术语来说,视图是决定将显示哪些数据的代码,模板是定义如何显示数据的组件。

控制器组件

按照惯例,控制器组件负责从数据库中检索数据(或访问模型),对数据进行操作,并将其传递给视图组件。在 Django 中,控制器组件并不明显,也没有与其他组件分开——整个框架充当一个控制器组件。因为数据模型被定义为一组 Python 类,所以更加智能,知道如何对数据进行基本操作。视图(但不是模板!)还包含一些应用逻辑,所有这些都由框架控制。

安装 Django 框架

我建议你下载并使用 www.djangoproject.com 发布的最新 Django 代码。在撰写本文时,最新的版本是 1.6,这里所有的例子和代码都是基于这个版本的 Django。如果您要使用 1.6 以外的版本,请阅读发行说明,了解可能影响功能的任何更改。通常会有清晰的说明,告诉你如何让你的代码适应 Django 的新版本。根据我的经验,这项任务通常非常简单,不需要开发人员做大量的工作。

我假设您的系统上已经安装了 Python 2.6+。本章示例将使用的数据库引擎是 SQLite,因此相应的包和 Python 绑定也必须安装。在大多数现代 Linux 发行版中,这是标准设置,很可能会出现在您的系统中。如果您有疑问,可以使用以下命令进行检查:

$ python
Python 2.7.5 (default, Feb 19 2014, 13:47:28)
[GCC 4.8.2 20131212 (Red Hat 4.8.2-7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlite3
>>> sqlite3.version
'2.6.0'
>>>

如果您正在使用一个非主流的 Linux 发行版,或者如果在初始安装过程中没有安装软件包,请参考您的 Linux 发行版的文档,以获取有关安装最新 Python 2.7.x 发行版和 SQLite 软件包的信息。

Image 截至本文撰写时,Django 1.6 需要 Python 2.6.5 及以上版本。下一个 Django 版本 1.7 将完全放弃 Python 2.6,支持的最低 Python 版本将是 2.7。Django 版及以上正式支持 Python 3,所以也可以使用。

大多数主流 Linux 发行版都有一个相当最新的 Django 版本,作为一个包提供。例如,在 Fedora 系统上安装 Django 的方式如下:

$ sudo yum install python-django

您还可以使用 Python 包管理器(PIP)来安装所需的包:

$ sudo pip install django

使用 PIP 的优点是软件包通常是最新的。在撰写本文时,Fedora 存储库中的包是 1.6.4,PyPI (Python 包索引,PIP 查找包的地方)中的包是 1.6.5,这是最新的可用版本。主要的缺点是,您最终会在系统上部署一个应用,而这个应用对于系统的包管理器来说是未知的。

您可以通过从 Python 命令行界面导入其模块来测试 Django 安装:

# python
Python 2.7.5 (default, Feb 19 2014, 13:47:28)
[GCC 4.8.2 20131212 (Red Hat 4.8.2-7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.get_version()
'1.6.4'
>>>

Django 应用的结构

Django 将任何网站都视为一个项目。在 Django 的术语中,项目是一组 web 应用和项目(或站点)特定的配置。您可以在不同的站点重用相同的应用,只需将它们部署到新的项目中,它们就会自动使用新的设置,比如数据库凭证。一个项目可以包含任意数量的应用。术语项目可能听起来有点混乱;我觉得网站或者网站更合适。

创建新项目很简单。假设您已经正确安装了 Django,那么您只需要在您想要创建新项目目录的目录中运行 django-admin.py 命令。Django 的管理工具将用基本的配置文件创建一个简单的项目框架。

我们将使用/var/app/vhosts/www _ example _ com/作为项目的基本目录,该目录将包含所有 Django 应用:

$ mkdir -p /var/app/virtual/
$ cd /var/app/virtual
$ django-admin.py startproject www_example_com
$ ls -lR www_example_com/
total 8
-rw-r--r--  1 rytis  staff  258 10 Jun 21:08 manage.py
drwxr-xr-x  6 rytis  staff  204 10 Jun 21:08 www_example_com

www_example_com//www_example_com:
total 24
-rw-r--r--  1 rytis  staff     0 10 Jun 21:08 __init__.py
-rw-r--r--  1 rytis  staff  1999 10 Jun 21:08 settings.py
-rw-r--r--  1 rytis  staff   306 10 Jun 21:08 urls.py
-rw-r--r--  1 rytis  staff   405 10 Jun 21:08 wsgi.py

在项目目录中,您会找到以下文件:

  • manage.py:一个自动生成的脚本,您将使用它来管理您的项目。使用该工具可以创建新的数据库表、验证模式或转储 SQL 脚本。该工具还允许您调用命令提示界面来访问数据模型。
  • www_example_com/settings.py:保存数据库信息和应用特定设置的配置文件。
  • www_example_com/urls.py:充当 URL 调度程序的配置文件。在这里,您可以定义哪些视图应该响应哪些 URL。
  • www_example_com/wsgi.py:一个 wsgi 配置文件,如果 Django 应用在一个兼容 WSGI 的 web 服务器(比如 Apache)上运行,并且启用了 mod_wsgi,那么可以使用这个文件。

Image 注意配置文件的位置特定于你的项目。在本章中,我们的项目是在/var/app/virtual/www _ example _ com/中创建的,所以当您看到对 manage.py、settings.py 和 urls.py 文件的引用时,请假定该位置。

一旦创建了新项目,就需要指定 Django 应该使用的数据库引擎。如前所述,在我们的例子中,我们将使用 SQLite。要实现这一点,我们需要在 settings.py 配置文件(在本章后面称为设置文件)中做两处更改:指定数据库引擎和数据库文件的绝对文件名:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

当项目和数据库配置完成后,我们可以通过在项目目录中发出以下命令来创建应用:

$  python manage.py startapp ip_addresses
$ ls –l ip_addresses/
total 12
-rw-r--r-- 1 root root   0 2014-05-24 14:55 __init__.py
-rw-r--r-- 1 root root  57 2014-05-24 14:55 models.py
-rw-r--r-- 1 root root 514 2014-05-24 14:55 tests.py
-rw-r--r-- 1 root root  26 2014-05-24 14:55 views.py

就像 Django 管理工具一样,项目管理脚本为我们的新应用创建了一个框架。现在我们已经建立了项目(或网站)并配置了一个应用,我们需要做的是定义数据模型,编写视图方法,创建 URL 结构,最后设计模板。所有这些我将在下面的章节中详细描述,但是首先我仍然需要展示如何让其他人看到我们的新网站。

该应用不能立即使用;我们需要在设置文件中将它添加到 INSTALLED_APPS 列表中:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'ip_addresses',
)

在 Apache Web 服务器上使用 Django

Django 自带轻量级 web 服务器,是用 Python 编写的。这是快速测试或开发过程中的一个很好的工具,但是我强烈建议不要在生产环境中使用它。我在使用它时从未遇到任何问题,但是正如 Django 背后的开发人员所说,他们从事 web 框架业务,并不是来开发健壮的 web 服务器的。

web 服务器最明显的选择之一是 Apache web 服务。它被广泛应用于互联网上的绝大多数网站。许多 Linux 发行版默认包含 Apache 安装包。设置 Apache 很容易,它既服务于静态 CSS 样式表和图像又服务于动态生成的页面(就像在 Django 应用中一样)。

我们的示例将假设以下信息:

  • 网站名称:www.example.com
  • 服务器的 IP 地址:192.168.0.1
  • Django 代码存储的目录:/var/app/vhosts/www.example.com/
  • 存储静态内容的目录:/var/www/vhosts/www _ example _ com/

Image 注意你可能想知道为什么代码和内容目录是分开的。分开的原因是这是一种额外的安全措施。正如你将在本章后面看到的,我们将指示 web 服务器为所有对虚拟服务器的请求调用 mod_python 模块。例外是以/static/开头的所有 URIs,这将是我们的静态内容。现在,如果由于某种原因我们在配置文件中犯了一个错误,导致 mod_python 没有被调用,并且代码目录是 DocumentRoot 指令的一部分,那么我们所有的 python 文件都将变成可下载的。因此,请始终将您的代码文件单独保存在 DocumentRoot 之外!

清单 3-1 显示了 Apache web 服务器配置文件中的 VirtualServer 定义。根据您的 Linux 发行版,这一部分可能直接包含在 httpd.conf 中,或者作为一个单独的配置文件与其他 VirtualServer 定义放在一起。

清单 3-1 。Django Web 应用的虚拟服务器定义

<VirtualHost 192.168.0.1:80>
    ServerName www.example.com
    DocumentRoot /var/www/virtual/www.example.com
    ErrorLog /var/log/apache2/www.example.com-error.log
    CustomLog /var/log/apache2/www.example.com-access.log combined
    SetHandler mod_python
    PythonHandler django.core.handlers.modpython
    PythonPath sys.path+['/var/app/virtual/']
    SetEnv DJANGO_SETTINGS_MODULE www_example_com.settings
    SetEnv PYTHON_EGG_CACHE /tmp
    <Location "/static/">
        SetHandler None
    </Location>
</VirtualHost>

配置的第一部分处理基本配置,例如设置服务器名称、所有静态内容的基本目录以及日志文件位置。接下来是 mod_python 配置,其中第一行告诉 Apache 将每个 web 服务器阶段的执行传递给 mod_python 模块:

SetHandler mod_python

该指令后面是模块配置设置。

什么是 APACHE 处理程序

所有的要求。由 Apache web 服务器分阶段处理。例如,对一个简单的 index.html 文件的请求可能涉及三个阶段:将 URI 翻译成文件的绝对位置;读取文件并在 HTTP 响应中发送它;并记录该事件。每个请求涉及的阶段取决于服务器配置。每个阶段都由一个处理程序处理。Apache 服务器只有基本的处理程序;更复杂的函数由作为可加载模块一部分的处理程序实现,mod_python 就是其中之一。Python 模块有所有可能的 Apache 阶段的处理程序,但是默认情况下没有调用处理程序。每个阶段都需要与配置文件中适当的处理程序相关联。

Django 只需要一个处理程序,即通用的 PythonHandler,它在向请求者提供实际内容的阶段被调用。Django 框架自带处理程序,不需要默认的 mod_python.publisher 处理程序。

下面的语句告诉 Apache 调用 Django 的处理程序:

PythonHandler django.core.handlers.modpython

正如你已经知道的,Django 中的每个网站实际上都是一个 Python 模块,有它的配置文件。Django 处理程序需要这些信息,这样它就可以加载配置并找到合适的函数。下面两行提供了这些信息。第一个指令将我们的基本目录添加到默认的 Python 路径中,第二个指令设置一个环境变量,该变量标识哪个框架将用于获取要加载的模块的名称。

PythonPath sys.path+['/var/app/virtual/']
SetEnv DJANGO_SETTINGS_MODULE ip_accounting.settings

我们还需要确定临时 Python 文件的存储位置。我们确保用户可以写这个目录,我们用它来运行 Apache web 服务器:

SetEnv PYTHON_EGG_CACHE /tmp

最后,让我们定义异常,这样静态内容(所有以/static/开头的内容)就不会交给 mod_python 进行处理。相反,将调用默认的 Apache 处理程序;它将简单地服务于任何请求的文件:

<Location "/static/">
    SetHandler None
</Location>

如果您按照这些说明来配置 Django,并且已经创建了您的第一个应用,并指示 Apache 相应地为其提供服务,那么您现在应该能够启动 web 浏览器并导航到 Django web 应用。此时,数据模型还没有创建,甚至 URL dispatcher 也没有配置,所以 Django 将只提供通用的“它工作了!”页面,如图 3-1 中的所示。

9781484202180_Fig03-01.jpg

图 3-1 。标准 Django 应用问候页面

Image 提示如果您看到的是“服务器错误”消息而不是标准页面,请检查包含 Python 异常或 Apache 错误消息的 Apache 错误日志文件,这可以帮助您确定错误的原因。

实现基本功能

一旦准备工作完成?包括 Django 安装和 Apache web 服务器的设置已经完成,我们可以继续开发 web 应用了。这个过程可以分为以下几个部分:

  • 创建模型
  • 定义 URL 模式
  • 创建视图

以我的经验,这个过程是非常迭代的;随着开发的进行,我继续修改我的模型,添加新的 URL,并创建新的视图。这种方法允许我非常快速地让一些东西工作起来,并且测试一些功能,即使整个应用还没有完成。不要认为这种方法是混乱的。恰恰相反;我只处理我在设计阶段确定并写下的元素。因此,这个过程仅仅是将一个巨大的工作分解成更小的、更易管理的块,这些块可以单独地、分阶段地进行开发和测试。

定义数据库模型

在继续之前,回头看一下表 3-1 并回顾我们将在数据模型中使用的字段。因为 Django 将对象映射到关系数据库,并且是自动进行的,所以我们需要为应用中使用的每个概念创建一个类定义,它将被映射到数据库中的表。

表 3-2 。常用的 Django 字段类型

|

字段类名

|

描述

| | --- | --- | | 布尔菲尔德 | 该字段只接受 True 或 False 值,除非它用于 MySQL 数据库,在这种情况下,该字段相应地存储值 1 或 0。在测试字段值时,请记住这一点。 | | 加菲 | 使用该字段存储字符串。它需要一个 max_length 参数来设置它可以存储的字符串的最大长度。不要使用此字段存储大量文本;请改用 TextField。 | | 戴达菲尔德 | 将日期存储为 Python datetime.date 类的实例。该字段类接受两个可选参数:auto_now,如果设置为 True,则在每次保存对象时将字段值设置为当前日期;auto_now_add,如果设置为 True,则仅在首次创建时将字段值设置为当前日期。这两个参数都强制 Django 使用当前日期,并且不能被覆盖。 | | 日期 | 将日期和时间存储为 Python datetime.datetime 实例。使用与 DateField 相同的可选参数。 | | 德西马菲尔德 | 用于存储固定精度的十进制数。需要两个参数:max_digits,用于设置数字的最大位数 decimal _ places,用于设置小数位数。 | | 邮箱 | 类似于 CharField,但也检查有效的电子邮件地址。 | | 文件字段 | 用于存储上传的文件。请注意,文件不是存储在数据库中,而是存储在本地文件系统中。该字段需要一个参数 path_to,它指向相对于 MEDIA_ROOT 目录的。您可以使用 strftime 变量根据当前日期和时间构造路径名和文件名。必须在当前项目的设置文件中设置 MEDIA_ROOT。 | | 浮田 | 存储浮点数。 | | ImageField(图像字段) | 非常类似于 FileField,但是额外执行一个文件是有效图像的检查。还有两个可选参数:height_field 和 width_field,它们存储模型类变量的名称,并将根据上传的图像尺寸自动填充。使用此字段类型需要 Python 影像库(PIL)。 | | -你是什么意思 | 存储整数值。 | | 正积分域 | 存储整数值,但只允许正整数。 | | NullBooleanField | 像 BooleanField 一样存储 True 和 False,但也不接受任何值。在需要是/否/未定义选项的组合时很有用。 | | 斯拉格菲尔德 | 像 CharField 一样存储文本,但只允许字母数字字符、下划线和连字符。用于存储 URL(没有域部分!).max_length 参数默认为 50,但可以被覆盖。 | | TextField 对象 | 用于存储大块文本。 | | 时间字段 | 将时间存储为 Python datetime.time 实例。接受与 DateField 相同的可选参数。 | | URLField 先生 | 用于存储包括域名在内的 URL。有一个可选的参数 verify_exists,它检查 URL 是否有效,是否实际加载,并且不返回 404 或任何其他错误。 | | XMLField | 一个文本字段,它还检查文本是否是有效的 XML,是否对应于 RELAX NG(www.relaxng.org)定义的 XML 模式。需要参数 schema_path,该参数必须指向有效的架构定义文件。 |

我们只有一个表,所以让我们为它定义类,如清单 3-2 所示。将这段代码添加到 models.py 文件中,就在默认内容的下面。

清单 3-2 。定义应用网络地址模型的数据类

class NetworkAddress(models.Model):
    address = models.IPAddressField()
    network_size = models.PositiveIntegerField()
    description = models.CharField(max_length=400)
    parent = models.ForeignKey('self')

这段代码非常简单明了。我们首先定义一个新的类 NetworkAddress,它继承自 Django 的模型。模型类,在 django.db 模块中定义。所以这个类变成了一个定制模型,Django 将用它来创建数据库表。这个模型类还将用于动态创建数据库 API。稍后我将展示如何使用这个 API。

在这个类中,我们通过用 models 类中的适当对象初始化类变量来定义三个字段。Django 提供了许多不同类型的字段,表 3-2 列出了最常用的类型。

要创建数据库表,我们只需使用带有 syncdb 选项的 manage.py 工具。当我们第一次运行它时,它还会为设置文件中列出的其他应用创建表(身份验证、Django 内容类型以及会话和站点管理)。内置身份验证应用需要一个管理员帐户,因此它会多问几个问题:

$ python manage.py syncdb
Creating tables ...
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table ip_addresses_networkaddress

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'rytis'):
Email address: rytis@example.com
Password:
Password (again):
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)
$

这个命令行对话框已经成功地在数据库中创建了所有必需的表。为了确切地了解我们的表在数据库中的结构,我们使用以下命令:

$ python manage.py sql ip_addresses
BEGIN;
CREATE TABLE "ip_addresses_networkaddress" (
    "id" integer NOT NULL PRIMARY KEY,
    "address" char(15) NOT NULL,
    "network_size" integer unsigned NOT NULL,
    "description" varchar(400) NOT NULL,
    "parent_id" integer REFERENCES "ip_addresses_networkaddress" ("id")
)
;
COMMIT;
$

如您所见,Django 使用变量名作为表中字段的名称,表名由应用和模型类名构成。这很方便,因为它确实提供了一定程度的名称间距,所以您不必担心您的类名与另一个应用的类名冲突。

URL 配置

在 Django 开发过程中,您会发现自己经常更改 URL 配置,因为您将添加新的视图和功能。为了不使这个过程失控,你需要为如何定义新的 URL 制定一些基本规则。虽然 Django 让您可以完全控制整个过程,但是通过选择合理的 URL 结构和命名约定,对他人(尤其是对自己)好一点。

对于如何创建 URL,没有明确的规则或指导方针。作为一名系统管理员,您可能不会开发面向大量用户的 web 系统,因此您可以更轻松地组织它们。然而,我想提出一些我认为非常有用的指导方针

  • 总是以应用的名称开始。在 IP 地址示例中,所有 URL(包括域名)都将是 http://www . example . com/IP _ address/[...].如果你想在你的网站中使用另一个应用,你不必担心 URL 名称重叠。例如,视图功能就很常见。在我们的例子中,如果我们没有将应用名称放在前面,并且我们有两个应用 A 和 B,如果它们都想使用 URL /view/的话,我们就会遇到问题。
  • 将模型名称放在应用名称之后。如果需要同一类型的更具体的对象子集,请在模型名称后添加选择标准。尽可能避免使用对象 id!因此,继续我们的示例,我们将拥有 ip_addresses/networkaddress/,它列出了所有顶级网络。如果我们导航到/IP _ addresses/network address/109 . 168 . 0 . 0/
  • 如果需要对任何对象进行操作,请在特定对象名称后添加操作谓词。因此,在我们的示例中,如果我们想要一个到网络地址的删除函数的链接,我们将使用/IP _ addresses/network address/192 . 168 . 0 . 1/delete。

这些指导原则可以通过以下示例 URL 进行总结:

http://www.example.com/<application>/<model>/<object>/<action>/

URL 映射在 urls.py 模块中定义,其默认设置如清单 3-3 所示。

清单 3-3 。站点范围的 urls.py 文件的默认内容

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'www_example_com.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),

    url(r'^admin/', include(admin.site.urls)),
)

这个文件的结构非常简单明了。最重要的部分是 urlpatterns 变量,它是 URL 元组的列表。每个条目(元组)有三个部分:

url(<regular expression>, <callback function>, <dictionary (optional)>)

当用户从 Django web 应用请求一个页面时,会发生这样的情况:请求被发送到 Apache web 服务器,该服务器又会调用它的 Django 处理程序。Django 框架将检查 urlpatterns 中的所有条目,并尝试将每个正则表达式与所请求的 URL 进行匹配。当找到匹配时,Django 将调用一个与正则表达式相关联的回调函数。它将传递一个 HttpRequest 对象(我将在视图部分讨论这一点)和一个可选的从 URL 获取的参数列表。

我强烈建议您不要在主 urls.py 文件中定义任何特定于应用的 URL 规则;使用您正在开发的应用的本地配置。通过这种方式,您可以将应用 URL 从网站中分离出来,从而允许您在不同的项目中重用相同的应用。

让我解释一下这是如何工作的。解耦相当简单;您只需在应用模块中定义特定于应用的 URL,并在所有以应用名称开头的请求中引用该文件。因此,在我们的示例中,我们将在主项目 urls.py 中包含以下条目:

urlpatterns = ('',
    [...]
    url(r'^ip_addresses/', include('www_example_com.ip_addresses.urls')),
    [...]
)

而特定于应用的配置文件 ip_addresses/urls.py 包含:

urlpatterns = patterns('',
    [...]
)

如您所见,main urls.py 将捕获所有以 ip_addresses/开头的 URL,URL 的其余部分被发送到 ip_addresses/urls.py 进行进一步处理。

使用管理界面

我们现在可以继续创建一些视图和表单来显示记录,并添加和删除它们,但是在我们这样做之前,下面是如何启用 Django 管理界面。这是一个非常方便的工具,它提供了对数据的即时访问,具有完整而丰富的功能,允许您添加、删除、修改、搜索和过滤存储在数据库中的记录。它在开发阶段也非常有用,允许您在创建表单添加新记录之前添加新记录并创建显示视图。

启用管理界面

Image 注意在 Django 的早期版本中,管理界面默认是禁用的。在 Django 的最新版本中,管理界面会自动为您启用,因此您不需要做任何事情。浏览这里的说明并熟悉配置文件结构仍然是一个好主意。

启用管理界面只需做很少的工作:将其添加到站点配置中的应用列表,启用 URL 规则,并配置 Apache 为界面提供静态内容(主要是 CSS 和 JS 脚本)。您可以修改 settings.py 模块中的 INSTALLED_APPS 列表,使其包含管理包:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
    'ip_addresses',
)

完成之后,您需要重新运行 syncdb 命令,以便在数据库中为管理应用创建新表:

$ python manage.py syncdb
Creating table django_admin_log
Installing index for admin.LogEntry model
$

取消对 urls.py 模块中与管理插件相关的所有行的注释。你要确保你的 urls.py 看起来像清单 3-4 中的。

清单 3-4 。在 urls.py 模块中启用管理界面

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'www_example_com.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),

    url(r'^admin/', include(admin.site.urls)),

    # ip_addresses application
    url(r'^ip_addresses/', include('ip_addresses.urls')),
)

您在 DocumentRoot 目录中创建一个链接,这样/opt/local/django-trunk/django/contrib/admin/media 的内容就可以由 Apache 从 URL www.example.com/static/admi…

$ ln –s /usr/share/django/django/contrib/admin/media \
/var/www/virtual/www.example.com/static/admin

一旦您完成了所有这些准备工作,您应该能够导航到 www.example.com/admin 并看到管理界面登录页面,如图 3-2 所示。

9781484202180_Fig03-02.jpg

图 3-2 。Django 管理登录页面

您可以使用先前在第一次运行 syncdb 时创建的管理员帐户登录。登录后,您将看到基本的用户和站点管理选项,如图 3-3 所示。

9781484202180_Fig03-03.jpg

图 3-3 。Django 管理界面的默认视图

允许管理插件管理新模型

您可能已经注意到,Django 管理界面还没有提供任何管理 NetworkAddress 模型的选项。这是因为它没有找到任何这样做的指令。向管理界面添加任何数据模型类都非常容易;您需要做的就是在您的应用目录 admin.py 中创建一个新的 Python 模块,包含清单 3-5 中的代码。

清单 3-5 。将网络地址类添加到管理界面

from www_example_com.ip_addresses.models import NetworkAddress
from django.contrib import admin

class NetworkAddressAdmin(admin.ModelAdmin):
    pass

admin.site.register(NetworkAddress, NetworkAddressAdmin)

在我们的示例中,我们首先从标准 Django 包中导入 NetworkAddress 类和管理模块。然后,我们为我们想要置于管理模块控制之下的每个模型定义一个管理类。管理类的命名约定是 Admin。该类必须从 admin 继承。ModelAdmin 类,它定义了模型管理界面的默认行为。

对于我们的简单模型,没有必要调整默认行为。它允许查看/添加/删除/修改等基本功能,并且因为我们将创建自己的具有附加功能的界面(例如以层次顺序显示信息),所以我们不需要 Django 管理模块的任何额外功能。

你可以用自动生成的界面玩一玩;尝试添加新条目并修改现有条目。此外,尝试输入无效信息,比如格式错误的 IP 地址,并检查 Django 管理界面对错误的反应。您会注意到无效的 IP 地址不被接受;但是,没有逻辑检查网络大小是否在适用的范围内:1–32。(我们将不得不在表单级别使用验证,我将在后面描述。)

查看记录

让我们从最简单的视图开始,它的目的是表示数据库中定义的所有网络的信息。现在,您必须使用前面创建的管理界面来添加新网络和定义关系。

首先,我们需要定义 URL 映射规则,以便将请求重定向到适当的视图函数:

urlpatterns = patterns(ip_addresses.views',
    url(r'^networkaddress/$', 'display'),
    url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/$',
     'display'),
)

第一个规则匹配 URL /ip_address/networkaddress/第二个规则搜索类似/IP _ address/network address/a . b . c . d/x/的 URL。它还调用 display 函数,但这一次它传递关键字参数 address,该参数用字符串 a.b.c.d/x 初始化。

让我们通过定义一个视图的简化版本来快速测试一下这是否可行。现阶段我们想知道的是,我们的两条规则是否如预期的那样起作用。清单 3-6 是一个简单的 views.py 文件的例子,它将测试我们的 URL。

清单 3-6 。测试 URL 调度程序规则的简单视图

from ip_addresses.models import *
from django.http import HttpResponse

def display(request, address=None):
    if not address:
        msg = "Top of the address tree"
    else:
        msg = "Top level address: %s" % address
    return HttpResponse(msg)

这里发生的事情非常简单。我们导入模型类和 HttpResponse 类。Django 框架期望一个 HttpResponse 的实例或者一个由它调用的任何视图函数产生的异常。显然,view 函数在这一点上并没有多大作用;它将只显示来自 URL 的 IP 地址,或者告诉你它在地址树的顶端,如果在 URL 中没有找到 IP。

在开始创建更复杂的视图之前,这是整理 URL 映射正则表达式的好方法。在调试视图功能时,您需要知道您的映射是否按预期运行。

Image 注意同时包含 IP 地址和网络大小的原因是,只有这两个地址对才能创建唯一的对象。如果您只使用 IP 地址,在大多数情况下,它可能是不明确的。例如,192.168.0.0 (/24)和 192.168.0.0 (/25)不是同一个网络,尽管它们的 IP 地址相同。

现在,在继续之前,让我们在数据库中创建一些条目。您必须使用 Django 管理界面,因为没有用于输入数据的定制表单。表 3-3 包含示例数据,你可以用它来创建类似的条目,并将本书中的结果与你在实施过程中得到的结果进行比较。

表 3-3 。一个样本 IP 网络和地址数据集

Table3-3.jpg

手动添加的内容似乎很多。如果您想手动创建所有记录,那很好,但是 Django 有另一个特性:您可以将初始数据作为 fixture 文件提供。Django 版本支持三种格式:XML、YAML 和 JSON。这在开发和测试阶段非常有用。您只需创建一次初始数据,然后在需要时使用确切的数据集重新创建数据库。

清单 3-7 显示了我们将用来初始化数据库的样本夹具文件的一部分。我在这里选择使用 JSON,主要是因为它的简单性、可读性和可支持性。

清单 3-7 。用于加载初始数据的 sample_data.json 文件摘录

[
...
    {
        "model":  "ip_addresses.networkaddress",
        "pk": 1,
        "fields": {
            "address":  "192.168.0.0",
            "network_size": 24,
            "description": "First top level network"
        }
    },
...
    {
        "model":  "ip_addresses.networkaddress",
        "pk": 3,
        "fields": {
            "address":  "192.168.0.0",
            "network_size": 25,
            "description": "Subnet 1-1",
            "parent": 1

    },
...
]

文件的结构非常简单明了。每条记录都以定义模型类开始,后跟主键,主键是一个整数,除非您已经显式地重新定义了它。最后,所有类字段都在“字段”部分的“键:”值”对中列出。

如果记录之间有任何关系,它们通过使用主键值来定义,就像在这个例子中一样;子网 1-1 有一个父记录,通过将“parent”设置为值 1(父记录的主键)来引用它。

如果该字段是可选的,您可以跳过它。创建文件后,使用以下命令加载数据:

$ python manage.py loaddata sample_data.json
Installed 20 object(s) from 1 fixture(s)
$

使用模板

模板在 Django 框架模型中扮演着重要的角色。正是模板允许开发人员将应用逻辑从表示中分离出来。同样,模型定义数据结构,视图函数负责数据查询和过滤,模板定义数据如何呈现给最终用户。

Django 附带了一种灵活而复杂的模板语言。让我们看看如何将模板与视图函数获得的数据一起使用。首先,我们需要定义一个视图,该视图将查询数据库并获取我们将呈现给用户的信息。清单 3-8 显示了新的显示功能。

清单 3-8 。使用模板显示数据的视图功能

def display(request, address=None):
    if not address:
        parent = None
    else:
        ip, net_size = address.split('/')
        parent = NetworkAddress.objects.get(address=ip, network_size=int(net_size))
    addr_list = NetworkAddress.objects.filter(parent=parent)
    return render_to_response('display.html',
                              {'parent': parent, 'addresses_list': addr_list})

正如您已经知道的,Django 的 URL dispatcher 调用 display 函数时要么没有初始 IP 地址(当用户请求树的顶端列表时),要么没有初始 IP 地址(请求显示子网的内容)。如果地址字段为空,我们将显示所有没有父节点的树节点。如果地址字段不为空,我们需要获取父节点设置为给定地址的树节点列表。结果存储在 addr_list 中,并传递给模板。

需要显示两个实体:关于当前树节点的信息和其子节点的列表。所以我们必须将两者作为变量传递给模板渲染过程。在我们的示例中,我们使用一个名为 render_to_response 的快捷函数,它接受两个参数:模板文件的名称和模板将用来呈现 HTML 输出的变量字典。

可以使用以下 import 语句导入 render_to_response 快捷方式:

from django.shortcuts import render_to_response

正如您所看到的,我们指定了模板名,而没有任何前面的目录路径,那么 Django 如何知道在哪里寻找模板呢?默认情况下,settings.py 配置文件中启用了以下模板加载器:

TEMPLATE_LOADERS = (
                    'django.template.loaders.filesystem.load_template_source',
                    'django.template.loaders.app_directories.load_template_source',
)

我们使用 app _ directories 加载程序提供的功能。这个加载器查找存储在 templates/子目录下的应用目录中的模板。将模板存储在应用中非常有用,因为这允许开发人员为每个应用分发一组默认模板。因此,回到我们的示例,我们需要在应用目录 ip_addresses 中创建一个名为“templates”的子目录。然后我们创建清单 3-9 所示的模板,它负责显示由显示视图函数传递给它的信息。

清单 3-9 。显示视图的模板

{% if parent %}
Current address: {{ parent.address }}/{{ parent.network_size }}</h1>
<a href="../../{% if parent.parent %}{{ parent.parent.address }}/{{
 parent.parent.network_size }}/{% endif %}">Go back</a></h2>
{% else %}
At the top of the networks tree</h1>
{% endif %}

{% if addresses_list %}
    <ul>
    {% for address in addresses_list %}
        <li><a href="{% if parent %}../../{% endif %}{{ address.address }}/{{
 address.network_size }}{% ifequal address.network_size 32 %}/modify/{% endifequal %}">{{
 address.address }}/{{ address.network_size }}
           </a>
            {% ifequal address.network_size 32 %}(host){% else %}(network){% endifequal %}
            {{ address.description }}
            (<a href="{% if parent %}../../{% endif %}
                      {{ address.address }}/{{ address.network_size }}/delete/">delete</a> |
            <a href="{% if parent %}../../{% endif %}
                     {{ address.address }}/{{ address.network_size }}/modify/">modify</a>)
        </li>
    {% endfor %}
    </ul>
{% else %}
{% ifequal parent.network_size 32 %}
This is a node IP
{% else %}
No addresses or subnets in this range
{% endifequal %}
{% endif %}
<a href="add/">Add new subnet or node IP</a></h2>

您可能已经猜到模板语言标记是用{%括起来的...%}或{{...}}.这两种形式的区别在于,前者用于包围命令和过程控制语句,例如比较和验证操作符,而后者用于指示需要在指定位置显示变量的内容。

引用对象属性时,所有变量都遵循相同的 Python 约定。例如,在模板中,parent 是 NetworkAddress 模型类的一个实例,因此 parent 具有属性 Address。为了在模板中显示该变量,我们需要将其作为 parent.address 引用。

表 3-4 列出了你会经常用到的基本命令结构。

表 3-4 。Django 模板语言最常见的元素

|

结构

|

描述

| | --- | --- | | {% if %}{% else %}{% endif %} | 最常用于测试变量是否已定义且内容不为空。根据结果,您可以显示变量的值,也可以提供一条信息性消息,告知找不到该值。 | | {% for in %}{ % endfor % } | 遍历中的所有项,并将单个列表项分配给,您可以在 for 构造中使用它。 | | { % ifqual% }{% else %}{% endifequal %} | 比较两个变量和,并根据结果处理两个模板块中的一个。 | | {% comment %}{% endcomment %} | 这两个运算符之间的所有内容都将被忽略。 |

正如您在模板中看到的,我已经添加了 URL 链接来删除、修改和添加记录。即使在这个阶段,所有这些都是可能的,因为我们最初设定了需求,并且在开发过程的任何阶段,我们都精确地知道需要做什么。

在这种情况下,应用还没有准备好执行这些操作,但是我们需要进行布局设计并在模板中实现它。如果您需要将模板交给其他人,这尤其有用,因为那个人不必猜测您可能需要什么操作和什么链接,即使功能尚未实现,他也可以创建它们。

图 3-4 显示了当我们导航到一个预先创建的网络地址时,应用网页的外观。

9781484202180_Fig03-04.jpg

图 3-4 。网络地址列表

删除记录

我们已经有了一个到列出了每个 IP 地址的 delete 函数的链接,如您所知,它的基本 URL 与列表函数的相同,但是它还附加了/delete/例如,以下是 192.168.0.0/25 网络的删除 URL:

http://www.example.com/ip_addresses/networkaddress/192.168.0.0/25/delete/

首先,我们需要“教”Django 识别这个 URL 并调用删除函数(或视图)。让我们通过将以下 URL 规则添加到 urls.py 文件来实现这一点:

url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/delete/$',
      'delete'),

清单 3-10 显示了 Django 框架在遇到匹配该规则的 URL 时将调用的删除函数。

清单 3-10 。删除视图

def delete(request, address=None):
    ip, net_size = address.split('/')
    parent = NetworkAddress.objects.get(address=ip,
                                        network_size=int(net_size)).parent
    NetworkAddress.objects.get(address=ip, network_size=int(net_size)).delete()
    redirect_to = '../../../'
    if parent:
        redirect_to += '%s/%s/' % (parent.address, int(parent.network_size))
    return HttpResponseRedirect(redirect_to)

在我们的示例中,地址变量总是包含 x.x.x.x/y 格式的 IP 地址,其中 x.x.x.x 是 IP 地址,y 是网络位数。我们不以这种格式存储地址信息,所以我们必须将其拆分为两部分,然后才能使用它来查找所需的记录。

在我们删除对象之前,让我们通过运行下面的 object get 方法来找出它的父对象:

parent = NetworkAddress.objects.get(address=ip, network_size=int(net_size)).parent

一旦我们检索到了对象,让我们简单地调用 delete()方法,该方法可用于任何 Django 模型对象。

您可能想知道作为我们刚刚删除的树节点的子节点的对象会发生什么情况。Django 框架足够智能,可以运行一个递归 SQL 查询,该查询将跟踪外键并删除树中所有相关的对象。

对象删除完成后,我们通过返回 HttpResponseRedirect 对象并以 path 作为其初始化参数来重定向到当前视图。

Image 提示你注意到我们如何在重定向 URL 中使用相对路径了吗?我们这样做是因为我们不知道如果有人重用代码,项目甚至应用将被调用。我们知道的是 URL 结构,所以我们可以计算出我们需要重定向到哪里,并使用相对路径。尽量避免使用绝对路径和在生成的 URL 中嵌入应用名称。

添加新记录

这是需要用户输入的功能。因为我们的模型相当简单,所以只需要填写几个字段,特别是 IP 地址、网络大小和描述。根据用户单击添加链接时所处的位置,将自动分配一个父树节点。例如,如果用户导航到www . example . com/ip _ addresses/network address/192 . 168 . 1 . 0/24/并点击添加新 IP 链接,新记录将自动获得 192.168.0.1/24 作为父记录。

Django 中有两种处理数据输入的方式:硬方式和 Django 方式。如果我们选择硬方法,我们需要在模板中定义表单,手动处理请求 HTTP POST 变量,执行验证,并进行数据类型转换。或者,我们可以选择使用 Django 表单对象和小部件,它们会自动为我们完成所有这些工作。

因此,首先我们需要定义一个表单模型类,用于生成 HTML 表单小部件。我们通过在 models.py 中定义清单 3-11 中所示的类来实现这一点。

清单 3-11 。地址添加表单类

from django.forms import ModelForm

[...]

class NetworkAddressAddForm(ModelForm):
    class Meta:
        model = NetworkAddress
        exclude = ('parent', )

这里发生的事情是,我们定义了一个表单类,它使用一个数据模型类作为原型。换句话说,这告诉 Django 生成一个表单来接受数据模型中定义的数据。我们可以选择定义任意的表单类,使用任意的字段集,但是在这个例子中,我们只需要数据模型中的三个字段。

坚持住。我们有四个字段,其中一个是父对象。但是我们不希望用户能够选择父对象,仅仅因为它在创建时就已经知道了。另一个原因是,对于大型数据库,父列表可能会变得太大而无法处理。因此,我们必须使用排除列表来指明哪些字段不需要显示在表单中。

第二步是定义处理视图的表单。这个视图与普通的只显示数据的视图函数略有不同,因为它可以通过两种不同的方式调用:作为 HTTP GET ,这意味着用户刚刚导航到表单页面;或者作为 HTTP POST ,这意味着用户提交了表单数据。

在 HTTP GET 的情况下,我们只显示空表单。如果我们收到一个 HTTP POST,我们将必须检查表单是否有效。如果表单数据有效,我们必须调用表单的 save()函数,这将在数据库中创建一个新对象。如果表单无效,它将再次显示,请求中的字段条目已经填充,错误消息解释了错误。

我们如何验证表单?非常简单——通过调用另一个 form 方法:is_valid(),如列表 3-12 所示。我们甚至不需要考虑错误信息;这些也是根据模型的数据类型自动创建的。

清单 3-12 。Add 函数的视图方法

from django.template import RequestContext

[...]

def add(request, address=None):
    if request.method == 'POST':
        parent = None
        if address:
            ip, net_size = address.split('/')
            parent = NetworkAddress.objects.get(address=ip,
                                                network_size=int(net_size))
        new_address = NetworkAddress(parent=parent)
        form = NetworkAddressAddForm(request.POST, instance=new_address)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("..")
    else:
        form = NetworkAddressAddForm()
    return render_to_response('add.html', {'form': form,},
                              context_instance=RequestContext(request))

在这个视图中,我们还执行了创建新对象的附加步骤。通常从 POST 数据创建新表单如下所示:

form = NetworkAddressAddForm(request.POST)

但是要记住;在我们的表单中没有父字段,我们需要从 URL 中的地址部分派生它。因此,我们需要自己找到父对象,并将其分配给新对象:

new_address = NetworkAddress(parent=parent)
form = NetworkAddressAddForm(request.POST, instance=new_address)

用实例参数调用表单初始化函数会强制 Django 使用分配给它的对象,而不是创建一个新的对象。

你可以看到我们使用了模板 add.html,并将表单对象传递给它。清单 3-13 显示了模板的样子。

清单 3-13 。添加表单模板

<form action="." method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Add" />
</form>

是的,它是那么短,但它做了很多。首先,它将呈现一个 HTML 表单,带有适当的字段和一个提交按钮。如果提交的表单数据无效,它也会显示错误消息。

演示文稿(如图 3-5 所示)是完全可定制的,但为了简单起见,我们只使用。as_p 标签,因此字段将显示在< p >标签内,以便更好地对齐。

9781484202180_Fig03-05.jpg

图 3-5 。HTML 页面上的表单小部件

Image 注意Django 的新版本(从 1.5 开始)强制执行跨站点请求伪造(CSRF) 默认检查。简而言之,CSRF 是指恶意网站试图使用输入到该网站的数据在您的网站上执行某些操作。这很糟糕,因为这意味着有人可以伪装成合法网站,并可以收集敏感的用户数据。为了确保这种情况不会发生,Django 在每次构建表单时都会生成一个惟一的令牌,这个令牌会和表单数据一起发送回去。Django 然后检查提供的令牌是否与本地存储的令牌匹配。如果匹配,则请求是真实的;否则,其他人正在试图发送数据,这样的请求需要被忽略。

最后,我们确保向 urls.py 文件添加两条新规则,一条用于向子网范围添加地址,另一条用于添加顶级地址:

url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/add/$',
      'add'),
  url(r'^networkaddress/add/$', 'add'),

修改现有记录

对象修改的表单和视图与添加表单和视图非常相似。唯一的区别是允许用户编辑的字段将会更少。实际上,如果用户决定更改 IP,他或她需要删除记录并在另一个网络中重新创建。因此,我们将只允许用户更改记录的描述。

因此,我们的表单对象中唯一的字段是描述,如清单 3-14 所示。

清单 3-14 。修改表单类

class NetworkAddressModifyForm(ModelForm):
    class Meta:
        model = NetworkAddress
        fields = ('description',)

正如您所看到的,我们没有排除字段,而是使用了字段列表,它告诉 Django 应该包括哪些字段;所有其他字段都将被忽略。

视图方法与用于添加新记录的方法非常相似。事实上,除了一点之外,一切都是一样的:在第一次查看表单时,表单已经预先填充了数据库中的数据,因为用户正在更改现有的数据,而不是创建新的记录。

保存更改是一样的,因为 Django 发现记录已经存在并更新它,而不是添加一个新的。从清单 3-15 中可以看到,甚至连模板都是不做任何改动就重用的。

清单 3-15 。修改视图方法

def modify(request, address=None):
    if request.method == 'POST':
        # submitting changes
        ip, net_size = address.split('/')
        address_obj = NetworkAddress.objects.get(address=ip,
                                                 network_size=int(net_size))
        form = NetworkAddressModifyForm(request.POST, instance=address_obj)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("..")
    else:
        # first time display
        ip, net_size = address.split('/')
        address_obj = NetworkAddress.objects.get(address=ip,
                                                 network_size=int(net_size))
        form = NetworkAddressModifyForm(initial={ 'description':
                                                   address_obj.description, })
    return render_to_response('add.html', {'form': form,},
                              context_instance=RequestContext(request))

我们还向 url dispatcher 配置文件 urls.py 添加了两条新规则:

url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/modify/$',
    'modify'),
url(r'^networkaddress/modify/$', 'modify'),

摘要

在这一章中,我们介绍了如何设计一个应用,以及如何从需求收集和规范到设计再到实际的实现。还解释了如何使用 Django 框架进行快速 web 应用开发。

  • 总是从需求规格开始。这将作为一个参考点,并简化测试。它还有助于管理用户期望。
  • 首先设计数据模型,确保设计符合需求。
  • 将应用从项目(或网站)中分离出来,以便可以多次重用。

四、将 IP 地址应用与 DHCP 集成

在前一章中,我们实现了一个简单的 IP 记账应用,允许用户跟踪他们的 IP 地址资产。我描述了应用的整个生命周期,从需求收集到设计阶段,最后到实现。重点是需求和设计阶段的重要性,因为这允许开发者验证其实现。

您可能已经注意到,尽管我们实现了大部分的初始需求,但是我们并没有得到所有的需求!我故意漏掉了几个,比如搜索功能,DNS 解析,主动检查。我这样做主要是为了演示验证您的实现并显示其中缺少的东西是多么容易,但也只是为了将章节保持在可管理的大小,而不是让信息淹没您。

因此,在本章中,我们将实现缺失的组件,并通过添加对 DHCP 服务的支持来扩展原始设计的新功能。

扩展设计和要求

我在前一章提到了“支持 DHCP”作为一个需求,但是我们真正想要的是什么呢?让我们看看 DHCP 在一个典型的组织中是如何使用的。我将假设 ISC DHCP 服务,它在大多数 Linux 发行版中广泛可用。

在子网中分配地址时,我们有以下选项:

  • 静态分配 IP 地址,在这种情况下,我们为每台设备配置自己的 IP 地址。
  • 根据使用 DHCP 服务的一组规则,动态分配 IP 地址。

Image 提示在继续本章之前,您可能需要安装 ISC DHCP 服务器软件包。您可以通过使用 Linux 发行版中可用的软件包管理器来实现这一点(在 RedHat Linux 上,可以使用命令 yum install dhcp 来实现,或者在基于 Debian 的系统上,可以使用 apt-get install isc-dhcp-server)。或者,你可以从 www.isc.org/software/dh… 的 ISC DHCP 官方网站下载。

让我们快速回顾一下 DHCP 能做什么以及它是如何配置的。ISC DHCP 允许您定义非常复杂的规则集。最简单的集合根本不包含任何规则,在这种情况下,假设地址池中有可用的空闲 IP,那么对 IP 的任何请求都将被准许,并且从可用池中分配一个唯一的地址。

一个常用的规则是根据硬件 MAC 地址分配 IP 地址。这种方法允许您始终为同一台机器分配相同的 IP 地址,但不要求在服务器上进行本地配置。我们可以使用 DHCP group 指令来配置这样的主机:

group {
    ...
    host host001 {
        hardware ethernet 00:11:22:33:44:55;
        fixed-address 192.168.0.1;
    }
    ...
}

客户端分组的一种更高级的用法是 DHCP 客户端类别分离,通过这种方法,客户端被分组到满足某些标准的类别中。ISC DHCP 服务器为这种分离提供了许多选项。例如,您可以使用各种 DHCP 请求字段(甚至是它们的一部分)来对节点进行分组,例如使用 DHCP 主机名的一部分来标识发送请求的内容。您还可以通过阅读 dhcp-options 的 UNIX 手册页来了解哪些 DHCP 选项可用。以下示例使用 DHCP 选项 vendor-class-identifier 将所有 Sun Ultra 5 计算机归为一类:

class "sun-ultra-5" {
    match if option vendor-class-identifier "SUNW.Ultra-5_10";
}

对于第二个示例,此代码匹配 DHCP 主机名的开头,如果它以“server”开头,则将其放入单独的类中:

class "server" {
    match if substring (option hostname, 0, 6) = "server";
}

为了简单起见,让我们假设所有子网都在 DHCP 服务器上的同一个物理网络上,这意味着我们在定义新子网时将使用 shared-network 指令。

正如您所看到的,简单的调查过程正逐渐演变为做出某些设计决策。这种任务模糊的情况应该尽可能避免,我在这里展示它只是为了说明为了适应任何特定产品的限制(或特性)而改变设计是多么容易。

首先,让我们忽略我们所知道的关于特定 DHCP 服务器产品的一切,列出我们希望它做的所有事情。我们想象中的组织有多个网络,这些网络被细分为更小的子网,而这些子网又可以包含更小的子网。通常,如果子网被细分为更小的网络,它很少包含物理(或虚拟)主机的 IP 地址。这种网络中存在的唯一 IP 是网络设备的 IP 地址,如路由器、交换机和负载平衡器,它们都不是从 DHCP 获得 IP 的。因此,我们将只创建 DHCP 管理的子网,这些子网位于子网树的底部,不会细分为更小的网络。

在 DHCP 管理的网络中,我们希望有以下内容:

  • 完全不受 DHCP 服务器控制的静态分配的地址。换句话说,DHCP 服务器应该不知道该范围,并且不应该配置为提供该范围内的任何地址。每个 IP 地址都是在使用它的设备上手动配置的。
  • DHCP 服务器分配的静态地址。例如,我们希望根据请求者的 MAC 地址提供 IP 地址。
  • 根据客户端的属性(如硬件供应商、DHCP 参数值等)分配 IP 地址。因为我们确实知道我们需要多少 IP 地址,所以我们必须能够分配一个预定义的地址范围。我们也不希望仅仅局限于一组 DHCP 选项;我们应该完全控制所有可能的选择。
  • 分配给所有其他客户端的 IP 地址。和前面的需求一样,我们需要能够指定在这里使用的 IP 范围。

正如您所看到的,列出的需求与前面列出的非常相似,但是它们不包含任何对特定实现的引用。这种方法有两个主要优点:您可以自由选择任何您认为最适合该目的的产品,并且您可以将实现外包给其他团队。只要结果满足要求,我们并不真正关心技术实现细节。此外,拥有这份清单有助于你快速识别和选择合适的产品。

除了网络管理和 IP 分配要求,我们还有一些运营需求:

  • 我们需要生成配置,但不是立即应用,这样就可以手动检查和应用更改。
  • 我们不需要将对配置文件的手动更改传播回应用数据库。例如,如果我们手动将一些主机添加到 DHCP 配置中,我们不需要应用相应地更新数据库条目。
  • 在这个阶段,我们不希望应用控制 DHCP 服务;这将使用标准操作系统命令手动完成。

现在我们已经确定了需要什么,我们可以开始制定基本的设计决策:

  • 我们将使用 ISC DHCP,因为它允许我们实现所有列出的要求。
  • 我们将使用相同的 web 应用框架和语言,因为这个项目是另一个项目的扩展。
  • 配置文件将由同一个 web 应用生成(也就是说,没有从数据库读取并生成配置文件的外部工具)。

正如前面的例子一样,我们现在需要做两件事:定义扩展数据模型和创建应用工作流。

扩展数据库模式

这一次,数据模型比我们只需要收集网络和 IP 地址信息时要复杂得多。现在我们需要存储 DHCP 服务器的网络拓扑视图,包括每个 DHCP 子网内的所有分类规则和地址范围。因此,我们将把它分解成定义 DB 模型类、编写视图函数和测试的几个迭代。这种渐进的方法更容易处理,我们也更容易发现错误。

目前,我们只确定了我们将拥有以下数据模型类:

  • DHCP 网络,指向它的“赞助商”网络类别。这只能为没有任何子网的网络创建。
  • 地址池模型,它定义了 DHCP 网络中的地址范围,并且必须有相关的规则。
  • 规则模型,定义了对 DHCP 请求进行分类的规则。每个规则可以分配给一个或多个地址池。
  • “静态”DHCP 地址规则模型,允许根据请求者的硬件 MAC 地址分配 IP。

向工作流程添加内容

工作流程中还有一些额外的东西。首先,我们需要添加一个链接,为每个没有子网的网络创建(或删除)DHCP 网络。我们还需要允许用户添加和删除关于 DHCP 网络池、规则和静态 IP 地址的信息。这些选项中的每一个都可以在 DHCP 网络列表中找到。

添加 DHCP 网络数据

在第一次迭代中,我们将添加对 DHCP 网络定义的支持。我们将使用一种类似于大型项目的方法:定义数据模型,定义工作流,然后进入实现阶段。

数据模型

让我们从添加一个新数据类开始,它将存储关于 DHCP 网络的信息。该类将指向其“赞助商”物理网络类,并包含客户端所需的几个 DHCP 选项,如路由器地址、DNS 服务器和域名。清单 4-1 展示了我们将要添加到 models.py 文件中的内容。

清单 4-1 。DHCP 子网的数据模型类别

class DHCPNetwork(models.Model):
    physical_net = models.OneToOneField(NetworkAddress)
    router = models.IPAddressField()
    dns_server = models.ForeignKey(DNSServer)
    domain_name = models.ForeignKey(DomainName)

    def __unicode__(self):
        return "DHCP subnet for %s" % physical_net

在本例中,我们还引用了两个新实体: DNSServer 和 DomainName。它们的类也在 models.py 中定义,它们只包含关于 DNS 服务器的 IP 和域名的信息以及简短的注释。将它们与 DHCPNetwork 类分开的原因是,如果我们想要更改 DNS 服务器的 IP 地址,我们不需要遍历每个 DHCP 网络条目并进行更改。您可以在 Apress 网站上的源代码中找到其他类的定义。

其他工作流程

通用 DHCP 网络支持需要哪些额外的工作流?显然,我们希望在子网中添加或删除一个 DHCP 网络,假设该子网可以有一个相应的 DHCP 网络。定义了 DHCP 网络后,我们还想修改它的设置。与前一章一样,每个工作流操作都有自己的视图功能,添加和修改也有自己的数据输入表单。正如您已经知道的,除非在 URL 配置文件中定义视图,否则视图是不起作用的,因此 Django 框架知道当它收到用户的请求时要调用什么视图函数。

添加功能

首先,我们需要知道我们是否可以为子网提供“添加 DHCP 网络”功能。最简单、最合理的方法是查看网络显示视图,查看是否有任何地址条目的子网大小未设置为 32 位。如果存在子网大小不是 32 位的条目,则该子网不能启用 DHCP 否则,我们可以提供一个到 DHCP add 函数的链接。因此,网络视图将执行检查并传递一个布尔变量,我们将在模板中查询该变量,它将显示一条消息或提供一个链接。下面是视图代码中的快速检查:

for address in addr_list:
    if address.network_size != 32:
        has_subnets = True

以及对模板的补充:

<h3><a href="add/">Add new subnet or node IP</a></h3>
<h3>{% if has_subnets %}
DHCP support cannot be enabled for networks with subnets
{% else %}
<a href="dhcp/add/">Enable DHCP support</a>
{% endif %}
</h3>

你能看到产生的 URL 是什么吗?该结构遵循我们之前定义的 URL 的相同约定:

http://www.example.com/<application>/<model>/<object>/<action>/

到目前为止,对象是一对 IP 地址和它们的网络大小,它们唯一地标识了数据库中的每个对象。现在,对象是物理网络中的 DHCP 网络。DHCP 网络本身没有唯一标识它的东西。因此,让我们将/dhcp/添加到 IP/网络大小对中,这告诉我们这是这个特定网络的 dhcp 对象。假设用于添加 DHCP 网络的新视图名为 add_dhcp,这是需要添加到 URL 映射文件中的内容:

(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/
dhcp/add/$', 'add_dhcp'),

这个视图将遵循相同的表单处理模式,它看起来非常类似于我们用来添加新网络的视图。它还要求 form 类为模板自动生成一个表单模型:

class DHCPNetworkAddForm(ModelForm):
    class Meta:
        model = DHCPNetwork
        exclude = ('physical_net',)

我们排除物理网络字段,因为它在创建时已经是已知的;它是作为 URL 参数提供的。清单 4-2 展示了我们的 dhcp_add 函数,其中我们甚至使用了之前使用过的相同模板。

清单 4-2 。处理 DHCP 网络添加功能的视图

def add_dhcp(request, address=None):
    if request.method == 'POST':
        network_addr = None
        if address:
            ip, net_size = address.split('/')
            network_addr = NetworkAddress.objects.get(address=ip,
                                                      network_size=int(net_size))
        dhcp_net = DHCPNetwork(physical_net=network_addr)
        form = DHCPNetworkAddForm(request.POST, instance=dhcp_net)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("../..")
    else:
        form = DHCPNetworkAddForm()
    return render_to_response('add.html',
                              {'form': form,},
                              context_instance=RequestContext(request))

您可能想知道 DNS 和域名域发生了什么变化;它们是模型定义中的外键,那么用户应该在这里输入什么呢?在图 4-1 中,你可以看到 Django 将要显示的内容。

9781484202180_Fig04-01.jpg

图 4-1 。呈现的 DHCP 添加表单

Django 引擎足够聪明,能够判断出您希望为用户提供相关表中的对象选择,因此它生成了所有对象的下拉列表!这真的很聪明,它节省了你大量的编码。因此,您需要做的就是输入路由器详细信息,从列表中选择 DNS 服务器和域名,然后单击 Add 按钮。您可以通过转到管理界面并选择 DHCP Networks 视图来验证记录是否已成功创建。

尝试浏览并为其他网络启用 DHCP 支持。请注意,当您向上浏览地址树时,将不会向您提供启用 DHCP 的选项。

此时,我们还需要修改网络的显示模板,以便显示网络的 DHCP 设置的详细信息,并提供修改和删除设置的链接。

修改功能

修改视图功能与添加功能非常相似,除了不是为初始视图创建一个空表单,而是检索现有数据并显示在表单中。所以在清单 4-3 中,我们首先搜索一个现有的 DHCP 网络对象,然后将它传递给 form 类。

清单 4-3 。处理 DHCP 网络修改功能的视图

def modify_dhcp(request, address=None):
    ip, net_size = address.split('/')
    network_addr = NetworkAddress.objects.get(address=ip, network_size=int(net_size))
    dhcp_net = DHCPNetwork.objects.get(physical_net=network_addr)
    if request.method == 'POST':
        # submiting changes
        form = DHCPNetworkAddForm(request.POST, instance=dhcp_net)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("../..")
    else:
        # first time display
        form = DHCPNetworkAddForm(instance=dhcp_net)
    return render_to_response('add.html',
                              {'form': form,},
                              context_instance=RequestContext(request))

删除功能

这是一个简单的函数,搜索 DHCP 网络对象并删除它。目前,我们还没有定义任何相关的数据结构,比如 DHCP 池或规则,但是值得一提的是,所有相关的对象也会被自动删除。

使用地址池扩展 DHCP 配置

到目前为止,我们已经有了不错的代码来处理通用的 DHCP 子网信息,比如路由器、DNS 和域服务器地址。如果您需要任何额外的字段,您可以通过修改 DHCP 网络数据模型类,添加新的字段实例来轻松添加它们。您可能已经注意到,没有一个视图函数直接引用模型字段。Django 框架会自动处理新条目的添加。模板解析器将选择它们并相应地生成输入字段。

现在我们将进行第二次迭代,我们将添加对地址池数据的支持。如您所知,地址池是子网内的一个地址范围,可以根据客户端的类别分配给一组特定的客户端。例如,C 类子网有 254 个可分配给节点的可用地址。然后,我们可以指示 DHCP 服务器将前 10 个地址分配给主机名以 server 开头的主机;另外 10 个将用于请求太阳微系统公司的机器;等等。

地址池数据模型

典型的地址池允许定义特定于地址池的附加 DHCP 选项。例如,您可能希望增加某些池上的 DHCP 租用时间。并非所有服务器都需要短期 DHCP 地址,因此您需要增加这些服务器在池中的租用时间。或者您可能希望所有工作站使用不同的 DNS 服务器。在这个例子中,我们不允许任何额外的选项。因此,模型类看起来相对简单,只包含三个字段:指向其父 DHCP 网络对象的指针和两个边界地址。清单 4-4 显示了代码。

清单 4-4 。DHCP 池数据模型类

class DHCPAddressPool(models.Model):
    DHCPNetwork = models.ForeignKey(DHCPNetwork)
    range_start  = models.IPAddressField()
    range_finish = models.IPAddressField()

一旦将它添加到 models.py 中,其中还需要定义表单模型类,在 admins.py 文件中创建适当的记录,并运行 syncdb 命令,Django 将在数据库中创建一个表。请查看第三章中的“定义数据库模型”部分,了解详细说明。

DHCP 网络详细信息

作为第一个工作流,也是查看功能,我们将定义 DHCP 网络查看功能。我们已经在物理网络列表页面上显示了一些通用信息,但现在我们将会有更多与 DHCP 配置相关的项目,所以最好将它们显示在单独的页面上。本页将包含有关地址池和静态 IP 分配规则以及分类规则的信息。到目前为止,您应该对添加新视图相当熟悉了,并且您应该知道这涉及三个步骤:向 urls.py 文件添加 URL-to-view 映射函数规则;定义视图函数,并为视图创建模板。

下面是我们将用来调用 DHCP 显示视图的 URL 映射规则:

(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/dhcp/$',
 'display_dhcp'),

对于 DHCP 显示视图,我们引入了两个新函数:一个从 URL 编码的 IP/network_size 对中获取地址对象,另一个从相同的数据中获取 DHCP 网络对象。由于大多数函数需要执行这些操作,现在是时候将它们分开了,如清单 4-5 所示。

清单 4-5 。DHCP 池显示视图和助手功能

def display_dhcp(request, address=None):
    dhcp_net = get_dhcp_object_from_address(address)
    dhcp_pools = DHCPAddressPool.objects.filter(dhcp_network=dhcp_net)
    return render_to_response('display_dhcp.html', {'dhcp_net': dhcp_net,
                                                    'dhcp_pools': dhcp_pools,})

def get_network_object_from_address(address):
    ip, net_size = address.split('/')
    return NetworkAddress.objects.get(address=ip, network_size=int(net_size))

def get_dhcp_object_from_address(address):
    return DHCPNetwork.objects.get(physical_net=get_network_object_from_address(address))

DHCP 详细信息页面(清单 4-6 )显示了 DHCP 网络的基本信息,还列出了所有可用的池(如果它们已定义)。

清单 4-6 。DHCP 详细信息显示页面

<h1> DHCP details for {{ dhcp_net.physical_net.address }}/{{ dhcp_net.physical_net.network_size }} network</h1>
<h2><a href="../">Go back to network details</a></h2>
<ul>
<li>Router: {{ dhcp_net.router }}
<li>DNS: {{ dhcp_net.dns_server }}
<li>Domain: {{ dhcp_net.domain_name }}
</ul>
<p>( <a href="modify/">modify</a> | <a href="delete/">delete</a> )</p>
{% if dhcp_pools %}
<p>
<h3>Following DHCP pools are available:</h3>
<ul>
{% for pool in dhcp_pools %}
<li>{{ pool.range_start }} - {{ pool.range_finish }}
( <a href="../dhcp_pool/{{ pool.range_start }}/{{ pool.range_finish }}/delete/">delete</a> )
</li>
{% endfor %}
</ul>
</p>
{% else %}
<h3>There are no DHCP pools defined</h3>
{% endif %}
<p>
( <a href="../dhcp_pool/add/">add new pool</a> )
</p>

同样,这是另一个非常标准的视图模板;然而,有几件事值得一提。首先,模板解析器非常智能,它允许您从作为参数传递给模板的对象中引用相关对象。如您所见,我们不直接传递物理网络对象—只传递 DHCP 网络对象;但是因为 DHCP 网络有一个引用其“父”对象的外键,所以我们可以简单地说 dhcp_net.physical_net.address,Django 模板引擎将找到要显示的正确信息。

您可能已经注意到的另一件事是到 Delete 函数的链接。URL 的对象部分变得相当长,被定义为

<network_address>/<network_size>/dhcp_pool/<range_start>/<range_finish>

严格地从数据建模的角度来看,这个键包含冗余信息,因为具有给定范围地址的 DHCP 地址池只能属于一个物理网络;因此,不需要在 URL 中指定网络地址。然而,因为我们在所有的模板中都使用了相对 URL,所以在这里包含它也容易得多。这是一个很好的例子,说明严格的规则有时需要妥协,以在代码的其他领域实现更高的效率和简单性。

添加和删除功能

添加和删除在结构和功能上与物理网络和 DHCP 网络视图中的等效功能几乎相同。Add 函数重用相同的 add.html 模板,而 Delete 函数则引用 DHCPAddressPool。

修改 URL 结构

我喜欢从错误中学习,因为我认为这是最有效的学习方法。显然,从别人的错误中学习更好。所以我故意引入了一些不是真正的错误,但可以被称为设计中的缺陷的东西,我把它留到了这一章的这一点上。

如果你已经仔细阅读了所有的代码示例,你一定已经注意到了一件事:尽管我们的代码在功能上是完美的,但感觉就是不对。猜到了吗?继续,再看一下所有模板和视图函数的例子。你在那里注意到了什么普遍现象?

没错。我们已经在模板和视图函数中使用了相对 URL。这是一个非常简单的技巧,而且在大多数时候都非常有效,尤其是在小项目中。它甚至适用于分离的应用,因为当您使用相对路径时,地址解析从另一端有效地工作,与应用 URL 的起始深度无关。

问题是,有这么多的类模型和函数,记住每个模型的 URL 的结构变得相当困难。我们已经设置了关于格式化 URL 的严格规则(记住,总是<模型> / <对象> / <方法>),并且使用有限数量的方法(到目前为止只有添加、删除、修改和隐式显示),我们很容易应付。然而,随着模型和 URL 数量的增加,管理和维护所有的 URL 变得更加困难。为什么你需要改变 URL 结构?原因有很多:重组站点、在层次结构中添加新的应用,或者仅仅是修复开发过程中的一个错误。

当我们谈论改变 URL 结构时,我现在需要提到我让另一个“bug”悄悄进来了。还记得我们说过的 / / 的 URL 结构吗?同样,我在引用 DHCP 网络和 DHCP 池模型时总是在开头使用 networkaddress/我应该做的是分别使用 dhcpnetwork 和 dhcpaddresspool 前缀。

现在我们有了一个真正有效的理由来返工或修复代码,我们应该如何处理它呢?如果有一种工具或功能允许您获得任何想要链接的对象的 URL,那将是理想的。

模型类中 URL 的生成

Django 框架允许您为每个模型定义一个额外的方法来返回对象的绝对 URL。例如,我们可以这样为网络地址类定义这个方法:

def get_absolute_url(self):
    return '/networkaddress/%s/%s/' % (self.address, self.network_size)

有了这个定义,我们就可以在所有模板中使用这个函数来获取对象的 URL:

<a href="{{ address.get_absolute_url }}">{{ address }}</a>

这允许我们引用对象 URL,而不用考虑 URL 结构。我们需要的只是一个对 URL 的引用,我们通过引用对象的 get_absolute_url 属性来获取该值。如果出于某种原因,我们决定改变 URL 结构,我们将不需要修改任何模板代码,因为引用是在它之外生成的。

URL 的反向解析

这种方法仍然存在一个问题;如果您还记得,URL 现在将在两个位置定义:URL 配置文件和模型定义。因此,即使我们不需要重新访问整套模板和视图函数,我们仍然需要确保 get_absolute_url 返回的任何内容也在 URLConfig 文件中定义。

Django 对这个问题也有一个解决方案:您可以使用 permalink decorator(decorator 是一个修改它所修饰的函数的行为的类)进一步将您的模型从 URLConfig 文件中分离出来。您需要将视图方法名和视图方法参数(作为列表或字典)传递给装饰器,然后它会为您计算出匹配的 URL。让我们看看这个例子:

@models.permalink
def get_absolute_url(self):
    return ('views.networkaddress_display', (),
            {'address': '%s/%s' % (self.address, self.network_size)})

这里我们没有使用参数列表,但是因为它是必需的,所以我们只传递一个空列表。我的偏好是使用字典来传递 URL 中使用的所有参数,因此我们不需要记住每个变量的数量和位置。

让我提醒您这个视图的 URL 配置是什么样子的:

(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/$', views.networkaddress_display),

给定这个组合(视图函数和参数),permalink 将找到匹配的 URL 并返回它。

不过,这里有一个陷阱。有些情况下,装饰者不能唯一地识别匹配的 URL:

(r'^networkaddress/$', views.networkaddress_display),
(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/$',
                                                 views.networkaddress_display),

将名称分配给 URL 模式

在这种情况下,有两个 URL 调用同一个视图函数,所以反向 URL 匹配器(试图从视图名中找到匹配的 URL)会被弄糊涂,因为不止一个 URL 指向同一个视图。

如果是这种情况,您可以为您的 URL 模式指定名称,以便它们都可以被唯一地识别:

url(r'^networkaddress/$', views.networkaddress_display,
name='networkaddress-displaytop'),
url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/$',
                      views.networkaddress_display, name='networkaddress-display'),

现在,即使两种 URL 模式调用相同的函数,也可以使用它们唯一的名称单独引用它们。

最后,下面是模型类将如何解析其对象的 URL:

@models.permalink
def get_absolute_url(self):
    return ('networkaddress-display', (),
            {'address': '%s/%s' % (self.address, self.network_size)})

模板中 URL 引用的使用

显然,模型代码只能为每个对象返回一个 URL。这样的模型类对应用的功能没有可见性;它仅设计用于表示应用操作的数据。所以通常模型实例会返回用于显示对象的 URL——换句话说,一个表示 URL

在我们的应用中,我们有多个与数据实体相关联的函数,比如添加、删除和修改。因为我们有一个定义良好的 url 结构,并且所有动作“关键字”都附加在末尾,所以我们可以在对象上使用 get_absolute_url 来获取它的基本 URL,然后在模板中附加动作词。但是这种方法不合适,因为 URL 信息将包含在 URLConfig 和使用它的每个模板中。

在前面的示例中,我们在模板中使用了{{ object.get_absolute_url }}结构来引用 url。Django 还有一个 URL 解析器模板标签,它能够通过名称引用 URL。然后,您需要向它传递一个参数,以便它能够匹配并生成所需的 URL:

{% url "networkaddress-display" address %}

清单 4-7 显示了如何使用 URL 标签的一个更详细的例子。

清单 4-7 。URL 解析器模板标记的示例

{% if addresses_list %}
    <ul>
    {% for address in addresses_list %}
        <li><a href="{% url 'networkaddress-display' address %}">
                    {{ address.address }}/{{ address.network_size }}</a>
            {% ifequal address.network_size 32 %}(host){% else %}(network){% endifequal %}
            {{ address.description }}
            (<a href="{% url 'networkaddress-delete' address %}">delete</a> |
            <a href="{% url 'networkaddress-modify' address %}">modify</a>)
        </li>
    {% endfor %}
    </ul>
{% else %}
    {% ifequal parent.network_size 32 %}
        This is a node IP
        <ul>
        <li>Description: {{ parent.description }}
            ( <a href="{% url 'networkaddress-modify' parent %}">modify</a> )</li>
        </ul>
    {% else %}
        No addresses or subnets in this range
    {% endifequal %}
{% endif %}

所有的 URL 模式名称都在 URLConfig 文件中定义,如清单 4-8 所示。

清单 4-8 。网络地址 URL 模式

urlpatterns = patterns('',
   url(r'^networkaddress/$', views.networkaddress_display,
        name='networkaddress-displaytop'),
   url(r'^networkaddress/add/$', views.networkaddress_add,
        name='networkaddress-addtop'),
   url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/$',
        views.networkaddress_display, name='networkaddress-display'),
url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/
       delete/$', views.networkaddress_delete, name='networkaddress-delete'),

url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/
       add/$', views.networkaddress_add, name='networkaddress-add'),

url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/
       modify/$', views.networkaddress_modify, name='networkaddress-modify'),

最后,所有的 URL 都被分离并定义在一个位置 URLConfig 文件。每当您选择更改它们时,您只需要在一个地方这样做,无论是模型、视图还是模板都不需要修改。

添加客户分类

为了充分利用地址池,我们需要具备客户端分类功能。换句话说,我们必须定义一些规则来识别是什么在发送请求,然后从适当的地址池中分配 IP 地址。由于我们没有实现“向导”类型的应用,所有规则都需要是 ISC DHCP 能够理解的格式的纯文本。这对于对配置文件语法知之甚少的人没有帮助,但是对于那些必须管理相当大的 DHCP 配置的人来说,这确实很方便。

对数据模型的补充

新数据模型的类定义相当简单,只包含两个字段:规则文本和描述。我们还需要扩展 Pool 类,以便它引用适当的 Class Rule 对象,如清单 4-9 所示。

清单 4-9 。扩展 DHCP 池模型并引入规则模型

class DHCPAddressPool(models.Model):
    dhcp_network = models.ForeignKey(DHCPNetwork)
    class_rule = models.ForeignKey(ClassRule)
    range_start  = models.IPAddressField()
    range_finish = models.IPAddressField()

    def __unicode__(self):
        return "(%s - %s)" % (self.range_start, self.range_finish)

class ClassRule(models.Model):
    rule = models.TextField()
    description = models.CharField(max_length=400)

    def __unicode__(self):
        return self.id

模板继承

由于规则管理将是通用的,我们希望在我们的所有页面中提供到显示和管理页面的链接,这样用户就可以直接跳转到它。到目前为止,我们有两个显示页面来显示物理网络和 DHCP 网络,但将来我们可能会有更多。那么我们如何给所有页面添加一个链接呢?显然,编辑每个页面并不是一个理想的解决方案。

Django 模板管理系统允许模板继承,因此您可以定义一个容器模板,然后在此基础上创建其他模板。基本模板包含占位符,从它继承的每个模板将提供放置在这些占位符中的元素。

这里有一个例子。首先,让我们定义所有其他人将继承的基础模板;清单 4-10 显示了代码。

清单 4-10 。基本模板

{% block menu %}
<ul>
    <li><a href="{% url 'networkaddress-displaytop' %}">Network address management</a></li>
    <li><a href="{% url 'classrule-displaytop' %}">Class rule management</a></li>
</ul>
{% endblock %}
<hr/>
{% block contents %}
{% endblock %}

接下来,我们定义两个块:一个菜单块和一个内容块。因为我们的主要目标是分离菜单模板代码并重用它,所以我们不需要将它放在一个单独的块中——但是这样做是一个很好的实践,因为这允许其他模板在需要的时候用其他的东西替换这个菜单。{% block %}标记之外的任何内容都不能从其他模板中访问,因此是不可更改的。标记中包含的任何内容都是默认值,如果继承模板不重写该块,将会显示这些内容。

第二个块用于保存其他显示页面的内容,因此它被留空。继承模板将替换它的内容;可选地,他们也可以覆盖菜单部分。清单 4-11 显示了新的 display.html 模板,它现在继承自 base.html。

清单 4-11 。让 display.html 继承 base.html

{% extends "base.html" %}
{% block contents %}
<contents of the original display.html>
{% endblock %}

同样,我们也需要更改 display_dhcp.html。完成后,两个页面都将包含一个通用菜单,允许应用用户在网络配置和类规则配置之间切换。

班规管理

我们将使相同的规则集适用于我们系统中的所有 DHCP 地址池。因此,在我们为任何 DHCP 地址池分配特定规则之前,我们首先需要定义这个规则。我们这样做是为了让用户能够重用现有的规则。如果在许多不同的子网中重复使用相同的规则,这种方法很好。但是,如果您的规则是特定的,并且不太可能被重用,那么这不是最好的方法,因为您最终会得到大量的一次性条目,并且列表很快会变得难以管理。

如果是后者,您可能需要考虑为规则和子网定义类别,以便它们可以自动分组。然后,当您创建一个新的 DHCP 网络时,您可以选择您想要查看的类别。

之前,我们已经为系统中的所有模型创建了添加、修改、删除和显示视图。这似乎是一个重复的过程,你不觉得吗?如果有一种方法可以自动执行像对象的基本创建、修改和删除这样的任务,那就太好了。你猜怎么着?Django 框架提供了这种功能;它被称为通用视图

通用视图

通用视图是对传递给它们的任何对象执行基本和常见任务的视图。Django 附带了四种类型的通用视图:

  • 重定向到其他页面或呈现任何给定模板(通常是静态内容)的视图。
  • 生成对象列表或显示任何特定对象的详细信息的视图。
  • 根据对象的创建日期列出对象的视图。如果你正在创建一个博客或新闻网站,这些会更有用。
  • 用于添加、删除和修改对象的视图。

通用视图可以从 django.views.generic 库中导入。通常在 URLConfig 文件中需要它们,因为这是将 URL 映射到视图的地方。你可以在 Django 官方文档中找到每个泛型和基类视图的完整列表和详细描述:docs . Django project . com/en/dev/ref/class-based-views/。你会发现有两种类型的类视图:基类和泛型。如果您正在定义自己的视图类,基本视图将用作基础视图,而通用视图可以立即使用。

由于类规则模型的简单性,我们将使用通用视图来管理模型的对象。我们不使用通用视图来管理其他模型的原因是,我们希望在视图所做的事情上留下更多的灵活性。在以后的某个阶段,我们可能想要扩展视图函数来执行额外的检查和任务,这对于一般视图来说是不容易做到的。

对象列表的显示

重要的事情先来。让我们调用一个通用视图来显示所有可用的类规则对象的列表。使用泛型类视图的最佳方式是创建一个从泛型类继承的自定义类,定义一些指定其行为的参数(如模板名、模型类名),然后在 urls.py 中使用该类。

清单 4-12 中的代码说明了如何定义一个定制的通用视图。这段代码通常放在 views.py 中,与其他视图函数和类放在一起。

清单 4-12 。分类规则查询集

class ClassRuleDisplay(ListView):
    model = ClassRule
    template_name = 'display_classrule.html'

这里的模型条目包含我们想要显示的模型类名,template_name 是模板文件的名称。如果我们选择不定义模板的名称,Django 会尝试自动为它生成文件名,并尝试加载一个名为 <application_name>/ <model_name>_list.html 的文件。我们选择指定模板名称,这样就少了一件担心事情不正常的事情。</model_name></application_name>

我们还需要添加 URL 到视图的映射,就像您在前面几节中对所有其他视图所做的那样。但是,这一次我们将使用一个通用视图 list_detail.object_list,并向它传递包含通用视图所需的所有信息的 queryset 对象:

url(r'^classrule/$', views.ClassRuleDisplay.as_view(), name='classrule_displaytop'),

最后,我们需要创建一个模板来很好地显示所有的对象。我们已经添加了到细节、修改和删除函数的链接,我们将在下一节中定义这些函数,所以代码看起来像清单 4-13 中的。

清单 4-13 。类别规则列表模板

{% extends "base.html" %}
{% block contents %}
<h1>List of all Class Rules</h1>
{% if object_list %}
    <ul>
    {% for rule in object_list %}
        <li>{{ rule.description }}
                ( <a href="{% url 'classrule_display' rule.id %}">details</a> |
                  <a href="{% url 'classrule_modify' rule.id %}">modify</a> |
                  <a href="{% url 'classrule_delete' rule.id %}">delete</a> )</li>
    {% endfor %}
    </ul>
{% else %}
No class rules defined yet.
{% endif %}
<h3><a href="{% url classrule_add %}">Add new rule</a></h3>
{% endblock %}

这是快速显示任何一组对象列表的一种简单得多的方式,并且不需要您编写一行视图代码。

对象的详细视图

类似地,我们将使用通用视图来显示任何。特定的类规则对象。这里唯一的区别是,我们需要向通用视图传递一个特定的对象 ID,以便视图代码可以从列表中选择适当的对象。

我们必须定义一个新的定制视图类,它继承自 Django 的一个内置泛型类:

class ClassRuleDetailDisplay(DetailView):

    queryset = ClassRule.objects.all()
    template_name = 'display_classrule.html'

    def get_object(self):
        objectsuper(ClassRuleDetailDisplay, self).get_object()
        return object

在 list 类中,我们为 Django 提供了模型名称。这里,我们必须创建一个 queryset 对象,这在大多数情况下就足够了。我还覆盖了 get_object 方法,调用该方法来检索单个对象。在我们的例子中,我们不需要这样做,因为默认的实现已经足够了。事实上,如你所见,我们在这里所做的只是调用父方法的实现。但是您可以在这里添加额外功能,比如从不同的对象获取额外的数据,或者在访问这个对象时更新另一个模型中的字段。

新的 URL 规则将如下所示;它包含对 pk(或“主键”)的引用,告诉通用视图需要将哪个对象传递给模板:

url(r'^classrule/(?P<pk>\d+)/$', views.ClassRuleDetailDisplay.as_view(), name='classrule_display'),

最后,让我们更新模板的版本,如清单 4-14 所示。它现在检查对象是否包含任何内容,在这种情况下,它显示关于它的详细信息;否则,它将显示一个类规则列表。

清单 4-14 。显示列表和对象详细信息的更新视图

{% extends "base.html" %}
{% block contents %}
{% if object %}
    <h1>Class Rules details</h1>
    <ul>
        <li>ID: {{ object.id }}</li>
        <li>Description: {{ object.description }}</li>
        <li>Rule text:
            <pre>
                {{ object.rule }}
            </pre>
        </li>
    </ul>
    ( <a href="{% url 'classrule-modify' object.id %}">modify</a> |
      <a href="{% url 'classrule-delete' object.id %}">delete</a> )
{% else %}
    <h1>List of all Class Rules</h1>
    {% if object_list %}
        <ul>
        {% for rule in object_list %}
            <li>{{ rule.description }}
                   ( <a href="{% url 'classrule-display' rule.id %}">details</a> |
                     <a href="{% url 'classrule-modify' rule.id %}">modify</a> |
                     <a href="{% url 'classrule-delete' rule.id %}">delete</a> )</li>
        {% endfor %}
        </ul>
    {% else %}
        No class rules defined yet.
    {% endif %}
    <h3><a href="{% url 'classrule-add' %}">Add new rule</a></h3>
{% endif %}
{% endblock %}

Image 注意默认情况下,模板对象名称是 object。通用列表视图将 _list 附加到该名称上。因此,在详细视图中,您将收到作为单个对象实例的 object 或作为对象列表的 object_list。通过在 queryset 字典中将 context_object_name 设置为您喜欢的任何名称,您可以随时更改模板名称。

添加或修改的新对象

使用通用视图添加对象也同样简单。您需要通过从通用视图类继承来为其提供基本信息,并为这些动作定义 URL 模式。通用视图需要以下信息:

  • 模型类名,因此视图知道它正在处理哪种类型的对象。
  • 模型表单类,所以表单生成框架知道如何生成表单表示。
  • 动作后重定向 URL,它告诉视图在数据提交后将用户重定向到哪里。这应该是一个表示 URL 的字符串。如果没有指定,Django 会尝试对对象应用 get_absolute_url,所以要确保定义了对象的 get_absolute_url 方法。然而,使用 get_absolute_url 的优点是,如果修改 url,不需要在两个地方更改它。

在清单 4-15 中,我们定义了两个类;一个是模型类,一个是模型表单类。严格地说,这里不需要 Model Form 类,因为我们有一个只有两个字段的非常简单的模型,但是我更喜欢显式地定义它们;如果我想在以后扩展和修改模型,这就更容易了。注意 get_absolute_url 返回反向解析的 url。这些修改应该在 models.py 文件中完成:

清单 4-15 。类规则模型和表单类

class ClassRule(models.Model):
    rule = models.TextField()
    description = models.CharField(max_length=400)

    def __unicode__(self):
        return self.description[:20]

    @models.permalink
    def get_absolute_url(self):
        return ('classrule_display', (), {'object_id': self.id})

class ClassRuleForm(ModelForm):
    class Meta:
        model = ClassRule

在 views.py 中,我们定义了两个继承自通用视图的类:一个用于添加新条目,另一个用于更新现有记录。注意,更新类需要知道模型类。它使用这个类来查找数据库中的相关对象,所以当您单击修改链接时,表单将预加载现有数据。

class ClassRuleCreate(CreateView):
    form_class = ClassRuleForm
    template_name = 'add.html'

class ClassRuleUpdate(UpdateView):
    model = ClassRule
    form_class = ClassRuleForm
    template_name = 'add.html'

我们甚至可以重用我们用来添加或修改其他对象的表单。因为我们保持了表单的通用性,并让模板处理程序生成所有必需的字段集,所以它不需要任何更改。

最后,让我们为 add 和 Modify 函数添加两个 URL 模式,并确保使用与模板中引用的相同的 URL 模式名称:

url(r'^classrule/(?P<pk>\d+)/modify/$', views.ClassRuleUpdate.as_view(), name='classrule_modify'),
url(r'^classrule/add/$', views.ClassRuleCreate.as_view(), name='classrule_add'),

对象的删除

删除对象包括一个中间步骤:要求用户确认操作。这是在一般的 delete 视图中通过使用简单的逻辑实现的——如果 HTTP 请求是 GET,则意味着用户单击了删除链接,因此需要显示确认页面(该页面指向同一个 URL)。如果 HTTP 请求是 POST,这意味着用户单击了 Confirm 按钮,并且表单已经通过 HTTP POST 调用提交,在这种情况下,视图将继续删除对象。

通用删除视图有一个警告。它需要一个删除后的 URL 换句话说,它需要知道在对象被删除后将用户带到哪里。显而易见的解决方案是反向查找 URL 并使用它。

清单 4-16 。基于通用类视图的自定义删除视图

class ClassRuleDelete(DeleteView):
    model = ClassRule
    success_url = reverse_lazy('classrule_displaytop')
    template_name = 'delete_confirm_classrule.html'

确认模板只是要求确认并将数据重新提交到同一个 URL,但是现在使用 HTTP POST 方法:

<form method="post" action=".">
<p>Are you sure?</p>
<input type="submit" />
</form>

最后,我们向 URL 模式列表添加了另一项内容:

url(r'^classrule/(?P<pk>\d+)/delete/$', views.ClassRuleDelete.as_view(), name='classrule_delete'),

Image 注意正如您可能已经猜到的,修改和删除视图不仅需要了解它们所操作的对象的类型,还必须唯一地标识它们所修改或删除的对象。对象 ID 作为 pk 变量从 URL 模式传递给它们。

生成 DHCP 配置文件

我们已经得到了我们需要的所有信息,但就目前的形式来看,这些信息没有多大用处。所有数据都在数据库表中,尽管它详细说明了 DHCP 服务器应该如何配置,但它不能以这种形式使用。我们需要编写一个视图来生成 DHCP 服务器能够理解的配置文件。

让我们回过头来重新看看 DHCP 配置文件应该是什么样子。因为我们使用的是 ISC DHCP 服务器,所以配置文件(仅包括我们感兴趣的元素)具有以下结构:

<dhcpd configuration items or generic DHCP options>

<class definitions>

<network definition>
    <subnet definition>
        <subnet options>
        <pool definitions>

让我们将这个配置文件作为 web 资源提供。因此,我们需要以与生成用户界面页面类似的方式来处理它:我们需要定义一个视图,该视图提供数据以及在页面上展示这些数据的模板——在本例中,是一个纯文本文档。

我们从视图开始,如清单 4-17 所示。

清单 4-17 。为 DHCP 配置文件收集数据的视图

def dhcpd_conf_generate(request):
    class_rules = ClassRule.objects.all()
    networks = []
    for net in DHCPNetwork.objects.all():
        networks.append( { 'dhcp_net': net,
                           'pools': DHCPAddressPool.objects.filter(dhcp_network=net),
                         } )

    return render_to_response('dhcpd.conf.txt',
                              {'class_rules': class_rules,
                               'networks': networks,
                              },
                              mimetype='text/plain')

我们不在数据库中保存 DHCP 服务器配置项;因此,我们将把它们直接放入模板中。类规则简单地列在任何其他结构之外,所以我们生成系统上所有类规则的列表,并将其作为列表传递。

每个 DHCP 子网可能在其范围内定义了几个不同的 DHCP 地址池,因此这些地址池只需要出现在特定的 DHCP 地址池定义中。因此,我们遍历所有可用的 DHCP 网络,并生成一个包含以下内容的列表:

  • DHCP 地址对象
  • 与给定 DHCP 网络相关的所有 DHCP 池的列表

最后,我们告诉 Django 将文档的 MIME 类型改为“text/plain”。如果我们只是要下载它,这没多大关系。但是,如果您尝试在 web 浏览器中打开这个文件,您会看到整个文档显示在一行上,因为 web 浏览器会认为它是一个有效的 HTML 文档。因此,为了在浏览器中查看时保留格式,我们需要对响应进行格式化,以表明文档是一个平面文本文件。

最后,在清单 4-18 中,我们有一个模板,它把所有的数据放在一个可以被 DHCP 服务器使用的结构中。

清单 4-18 。DHCP 配置文件的模板

 1 {% autoescape off %}
 2 ignore client-updates;
 3 ddns-update-style interim;
 4
 5 {% if class_rules %}
 6     {% for cr in class_rules %}
 7         # {{cr.description }}
 8         class "class_rule_{{ cr.id }}" {
 9             {{ cr.rule }};
10         }
11     {% endfor %}
12 {% endif %}
13
14 {% if networks %}
15     {% for net in networks %}
16         shared-network network_{{ net.dhcp_net.id }} {
17             subnet {{ net.dhcp_net.physical_net.address }} netmask {{                net.dhcp_net.physical_net.get_netmask }} {
18                 option routers {{ net.dhcp_net.router }};
19                 option domain-name-servers {{ net.dhcp_net.dns_server.address }};
20                 option domain-name {{ net.dhcp_net.domain_name.name }};
21
22                 {% if net.pools %}
23                     {% for pool in net.pools %}
24                         pool {
25                             allow members of "class_rule_{{ pool.class_rule.id }}";
26                             range {{ pool.range_start }} {{ pool.range_finish }};
27                         }
28                     {% endfor %}
29                 {% endif %}
30             }
31         }
32     {% endfor %}
33 {% endif %}
34
35 {% endautoescape %}

现在让我们更详细地看一些行。

  • Line 1:Django 模板引擎有一个内置的文本转义功能,可以将所有不符合 HTML 的字符转换成 HTML 代码表示。例如,字符(")将被替换为&quot;字符串。因为我们提供的是平面文本文档,所以我们需要用原始符号表示所有字符,而不是 HTML 编码。所以我们关闭了 autoescape 功能,默认情况下它是打开的。
  • 第 2–3 行:这些只是标准的 DHCP 服务器配置项目,您可能希望将其替换为适合您环境的项目。
  • 第 5–12 行:查看 class_rules 列表是否为空的简单检查,随后是遍历所有元素并显示它们的循环。
  • 第 14–15 行:再次检查网络列表是否为空,然后是循环语句。
  • 第 17 行:这里你可以看到我们是如何引用相关对象的。我们没有将任何关于物理网络的信息直接传递给模板,但是我们仍然可以通过 DHCP 网络对象访问它,该对象有一个指向相关物理网络对象的外键。只要这种关系是明确的(一个 DHCP 网络只能属于一个物理网络),就可以使用这种语法来访问相关信息。
  • 类似地,我们正在访问相关的路由器和 DNS 对象。
  • 第 22–23 行:检查 DHCP 网络是否有可用的地址池,如果有,就遍历这些地址池。
  • 第 25–26 行:注意,我们是根据类名和网络名的对象 id 来生成它们的。这是确保名称唯一的最简单方法,也可用于在配置文件中进行交叉引用。

您可能已经注意到,我们正在使用物理网络对象的 get_netmask 属性。这个字段不存在,那么它是什么?DHCP 服务器希望子网被定义为由基本网络地址和网络掩码组成的对。我们在模型中没有网络掩码字段,但从网络大小中推导出来非常简单,网络大小用位数表示;清单 4-19 显示了代码。

清单 4-19 。根据网络大小计算网络掩码

def get_netmask(self):
    bit_netmask = 0;
    bit_netmask = pow(2, self.network_size) - 1
    bit_netmask = bit_netmask << (32 - self.network_size)
    nmask_array = []
    forin range(4):
        dec = bit_netmask & 255
        bit_netmask = bit_netmask >> 8
        nmask_array.insert(0, str(dec))
    return ".".join(nmask_array)

这个函数的逻辑非常简单:

  • 将整数变量中的位数设置为 1(即设置为 1)。这可以表示为 2^ -1。
  • 将结果左移,用 0 填充剩余的位数。网络掩码中的总位数总是 32。
  • 每 8 位(共 4 组),转换成十进制数串。
  • 连接所有数字,使用点符号分隔各个数字。

最后,我们需要添加一个额外的 URL 模式来调用这个视图:

url(r'^dhcpd.conf/$', views.dhcpd_conf_generate, name='dhcp-conf-generate')

下面是一个 DHCP 配置文件的例子,它是由我输入到数据库中的一些样本数据生成的:

ignore client-updates;
ddns-update-style interim;
        # class rule 1
        class "class_rule_1" {
            match if substring (option host-name, 0, 6) = "server";;
        }
        # test rule (gen form)
        class "class_rule_2" {
            test rule - gen form;
        }

        shared-network network_4 {
            subnet 192.168.0.128 netmask 255.255.255.128 {
                option routers 192.168.0.130;
                option domain-name-servers 208.67.222.222;
                option domain-name domain1.example.com;
            }
        }
        shared-network network_5 {
            subnet 192.168.0.0 netmask 255.255.255.128 {
                option routers 192.168.0.113;
                option domain-name-servers 208.67.220.220;
                option domain-name domain2.example.com;
                        pool {
                            allow members of "class_rule_1";
                            range 192.168.0.1 192.168.0.20;
                        }
                }
        }

其他修改

大部分工作已经完成,但是我们仍然需要添加一些东西来满足最初的需求:节点 IP 的主机名解析和状态检查。

将 IP 解析为主机名

为了获得关于 IP 地址的更多信息,让我们做一个反向的域名解析,在每个地址条目旁边打印一个完全限定的域名。我们可以在两个地方实现这种查找:我们可以修改显示视图并在那里进行主机查找,然后将信息传递给模板;或者,我们可以用一个额外的函数来扩展模型类,该函数返回 IP 地址的主机名,如果主机名无法解析,则返回一个空字符串。

让我们选择第二个选项,因为它更优雅,并且不需要改变视图和模板之间的接口。这里有一个模型类的附加方法,它使用 Python 的套接字库中的 gethostbyaddr 函数来执行反向查找。结果是一个元组:(,,

),我们使用第一个条目(主机名)作为结果。
import socket

...
class NetworkAddress(models.Model):
...
    def get_hostname(self):
        try:
            fqdn = socket.gethostbyaddr(str(self.address))[0]
        except:
            fqdn = ''
        return fqdn

以及模板中的一个小变化,以显示附加属性(如果可用):

{% for address in addresses_list %}
  <li><a href="{% url networkaddress-display address %}">{{ address.address }}/
                                                         {{ address.network_size }}</a>
    {% ifequal address.network_size 32 %}(host){% else %}(network){% endifequal %}
      {{ address.description }}
      {% if address.get_hostname %} ({{ address.get_hostname }}) {% endif %}
    (<a href="{% url networkaddress-delete address %}">delete</a> |
     <a href="{% url networkaddress-modify address %}">modify</a>)
  </li>
{% endfor %}

检查地址是否在使用中

让我们实现一个简单的函数来检查 IP 地址是否在使用中。为此,我们需要向 IP 地址发送 ICMP 回应消息,并等待响应。严格地说,这不是检查地址是否在使用中的有效测试,因为可能有一些情况是 IP 地址被使用了,但是没有响应 ping 请求。例如,防火墙可能阻止 ICMP 通信,或者该通信可能在服务器级别被阻止。然而,在大多数情况下,这个简单的测试是非常有效的;请记住,此测试指示的故障不一定意味着服务器的实际故障或地址未被使用。

实现遵循定义一个视图并将一个新的 URL 模式添加到 URLConfig 文件的常见模式。由于使用 Python 套接字库实现 ICMP 相对复杂(它要求在原始模式下使用套接字,这又要求应用以 root 用户身份运行),我们将调用系统 ping 工具并根据返回代码做出决定,如清单 4-20 所示。

清单 4-20 。对 IP 地址进行 ICMP 检查的视图

def networkaddress_ping(request, address=None):
    if responding_to_ping(address):
        msg = "Ping OK"
    else:
        msg = "No response"
    return HttpResponse(msg)

def responding_to_ping(address, timeout=1):
    import subprocess
    rc = subprocess.call("ping -c 1 -W %d %s" % (timeout, address),
                         shell=True, stdout=open('/dev/null', 'w'),
                         stderr=subprocess.STDOUT)
    if rc == 0:
        return True
    else:
        return False

这里我们强制 ping 只发送一个数据包,超时设置为 1 秒。虽然这可能会降低准确性,但响应会快得多。大多数本地网络应该在这些限制内运行,但是如果您需要更高的准确性,您可以增加默认超时并指示 ping 发送多个探测数据包。

您还需要添加两个额外的 URL 模式:

url(r'^networkaddress/(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/ping/$',
    views.networkaddress_ping, name='networkaddress-ping'),
url(r'^networkaddress/$', views.networkaddress_ping,
name='networkaddress-ping-url'),

第一个模式捕获一个 IP 地址以及它需要在给定地址上执行的方法(/ping/)。第二行只是用于日常管理——稍后您会发现为什么需要它。

为什么我们将这种检查实现为对 web 服务器的单独调用?生成要显示的 IP 地址列表,逐个 ping 每个地址,然后将 ping 结果和 IP 地址一起传递给模板,不是更容易吗?是的,我们可以这样做,但是这种方法有一个主要问题:应用响应时间。在现实生活中,您可能有非常大的网络,可能需要在数百台服务器上执行 ping 检查。即使您以多线程的方式实现这种检查——换句话说,尝试同时调用 ping 函数——您仍然会花费 1 秒、2 秒甚至更多的时间来完成请求。从可用性的角度来看,这是不可接受的;如果系统反应慢,用户不会喜欢它。

因此,我们在这里要做的是显示子网中所有地址的列表,然后使用 JavaScript 异步调用 ping URL。用户不会立即获得状态报告,但至少会立即显示包含其他信息和操作链接的页面。

这种方法的另一个好处是,您根本不需要对显示视图进行任何更改——只需对显示模板进行一些小的修改(添加一个占位符来保存状态信息)。JavaScript 将被放在基础模板中,因此所有页面都自动获得该功能。

因为这本书不是关于 JavaScript 的,所以我将只限于一个简单的解释和一个如何使用它的例子。清单 4-21 使用 jQuery 库执行异步 AJAX 调用来获得结果并相应地更新网页。

清单 4-21 。修改的地址列表循环代码

{% for address in addresses_list %}
        <li><a href="{% url networkaddress-display address %}">{{ address.address }}/
            {{ address.network_size }}</a>
            {% ifequal address.network_size 32 %}(host){% else %}(network){% endifequal %}
            {{ address.description }}
            {% if address.get_hostname %} ({{ address.get_hostname }}) {% endif %}
            (<a href="{% url networkaddress-delete address %}">delete</a> |
            <a href="{% url networkaddress-modify address %}">modify</a>)
            {% ifequal address.network_size 32 %}
            [Status: <span class="address"
                 id="ip_{{ address.get_formated_address }}">Unknown</span> ]
            {% endifequal %}
        </li>
    {% endfor %}

附加行检查地址是否可能是节点 IP,然后插入一个 HTML 标记,它将用于更新文档中该位置的信息。这个标签有两个属性:Class 和 ID。我们将使用 Class 属性来标识哪些标签包含 IP 地址并需要检查,并使用 ID 属性来保存 IP 地址的值。

你可能想知道这个 get_formated_address 方法是什么,为什么我们不直接使用地址。原因是 jQuery 期望 HTML 标签 ID 名称中不能有点,ID 名称也需要以字母开头;因此,我们必须为它添加 ip_ 前缀。此方法只是替换所有出现的(。)和地址字段中的(_)。

最后,我们添加一些 JavaScript 来遍历属于同一个地址类的所有标签,并对 web 服务器执行 AJAX 异步调用。结果将被用作标签的 HTML 内容。清单 4-22 中的代码已经被添加到基础模板中,所有其他模板都继承自基础模板。

清单 4-22 。执行异步调用并更新状态页面的 JavaScript

<html>
<head>
<script type="text/javascript" src "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.3.2.min.js">
</script>
<script type="text/javascript">
    $(document).ready(function(){
        $(".address").each(function () {
            var curId = $(this).attr('id');
            updateStatus(curId);
        });
    });

    function updateStatus(attrId) {
        address = attrId.replace('ip_', '');
        address = address.replace(/_/g, '.');
        $.ajax({
            url: '{% url networkaddress-ping-url %}' + address + '/ping/',
            success: function(response) {
                $('#' + attrId).html(response);
            }
        });
    }

</script>
</head>

现在你明白为什么我们需要占位符 URL 模式了。JavaScript 也部分由 Django 生成——我们使用反向 URL 查找插入网络地址 URL。因为我们不能生成一个完整的 URL(其中包含地址部分),所以这是一个通用的 URL,它将被 JavaScript 修改。我们只使用它的第一部分;因此,我们需要在 URLConfig 中定义它。

因此,这段 JavaScript 代码的逻辑如下:

  • 删除 ip_ 前缀。
  • 用点替换下划线。
  • 执行 AJAX 异步调用。
  • 结果出来后更新网页。

现在,当用户导航到列表页面时,它将立即显示,然后随着结果的可用,逐渐更新每个 IP 地址的状态报告。

动态 DHCP 租约管理

到目前为止,我们的项目是基于我们需要生成一个静态 DHCP 配置文件的假设。到目前为止,我们构建的应用允许我们输入所需的数据,输出是一个配置文件,可以由 ISC DHCP 服务器使用。

这种方法通常已经足够好了,尤其是在 DHCP 配置相当静态的环境中。当您需要频繁地添加和删除静态分配时,问题就出现了。每次修改后,您都必须部署新的配置文件并重启 DHCP 服务器进程。当您重新启动 DHCP 进程时,会有一小段时间 DHCP 服务器不可用。当服务关闭时,所有对 DHCP 地址的请求都将失败,您可能最终会遇到缺少 IP 地址的客户端。

这个问题的解决方案是使用 OMAPI(对象管理应用编程接口)的动态租赁管理。OMAPI 是 ISC DHCP 服务器的 API 接口,它允许您操作服务运行实例的内部数据结构。

在这一节中,我将展示如何使用 OMAPI 操作 DHCP 分配。我们不打算改变到目前为止编写的应用;这只是给出一个如何动态管理 DHCP 租约的想法。

对 OMAPI 采用 Python 接口

我们将使用 Torge Szczepanek 博士的 pypureomapi 库来访问 ISC DHCP OMAPI 接口。该项目可在以下网址获得:github.com/CygnusNetworks/pypureomapi如果你想安装它的源代码。

这个包也可以从 PyPI 包库中获得,并且可以用 PIP 工具安装:

# pip install pypureomapi
Downloading/unpacking pypureomapi
  Downloading pypureomapi-0.3.tar.gz
  Running setup.py egg_info for package pypureomapi

Installing collected packages: pypureomapi
  Running setup.py install for pypureomapi

Successfully installed pypureomapi
Cleaning up...

设置 ISC DHCP 服务器

ISC DHCP 的默认配置不允许通过 OMAPI 进行管理。如果您想要动态管理服务,您必须创建一个定义身份验证和连接详细信息的附加配置。

让我们从最基本的 ISC DHCP 服务器配置文件开始(在基于 RedHat 的系统上,这个文件是/etc/dhcp/dhcpd.conf),它包含以下配置:

subnet 192.168.0.0 netmask 255.255.255.0 {

}

Image 注意请记住,在本节中,我们将使用最小的 DHCP 服务器配置,这足以说明 OMAPI 的功能。在现实生活中,您可能希望扩展这种配置,使其符合您的环境要求。

首先,我们需要为将要连接到 ISC DHCP 服务器的用户生成一个 HMAC-MD5 密钥:

# dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 256 -n USER omapikey
Komapikey.+157+08556
#

这将创建两个文件,一个包含密钥,另一个包含元数据信息:

# ls -l Komapikey*
-rw------- 1 root root  70 Jul  6 14:49 Komapikey.+157+08556.key
-rw------- 1 root root 185 Jul  6 14:49 Komapikey.+157+08556.private
#

两个文件都包含密钥,在我的例子中是“qkhrf 1 lax E4 cnuaa 2t/go A0 vbfeub 5 ROS+53 ge w2 bzq = ":

# awk '/Key/ {print $2}' Komapikey.+157+08556.private
QKHRF1laxE4cNUAa2t/GOa0VBFeUb5ROS+53gEw2BzQ=
#

让我们使用密钥和我们使用的用户名(omapikey ),并更新 ISC DHCP 配置文件:

key omapikey {
  algorithm hmac-md5;
  secret QKHRF1laxE4cNUAa2t/GOa0VBFeUb5ROS+53gEw2BzQ=;
}

omapi-key omapikey;
omapi-port 7911;

subnet 192.168.0.0 netmask 255.255.255.0 {

}

当您重新启动 DHCP 服务时,您会看到它现在正在侦听 OMAPI 命令的定义端口:

# netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1330/sshd
tcp        0      0 0.0.0.0:7911            0.0.0.0:*               LISTEN      8994/dhcpd
#

添加新的主机租赁记录

在我们开始修改 DHCP 租约记录之前,让我们确保租约文件中没有现有的租约记录。基于 RedHat 的系统上的文件是/var/lib/dhcpd/dhcpd.leases,在这种情况下,其内容如下所示。如您所见,那里没有主机记录:

# cat /var/lib/dhcpd/dhcpd.leases
# The format of this file is documented in the dhcpd.leases(5) manual page.
# This lease file was written by isc-dhcp-4.2.6

server-duid "\000\001\000\001\033L\014\234\010\000'\037\337\302";
#

让我们使用 OMAPI 连接到 ISC DHCP,并创建一个新的租用记录:

>>> import pypureomapi
>>> USER='omapikey'
>>> KEY='QKHRF1laxE4cNUAa2t/GOa0VBFeUb5ROS+53gEw2BzQ='
>>> omapi = pypureomapi.Omapi('127.0.0.1', 7911, USER, KEY)
>>> omapi.add_host('192.168.0.100', '00:11:22:33:44:55')
>>>

现在,如果您看一下租约文件,您会发现插入了一条新记录:

# cat /var/lib/dhcpd/dhcpd.leases
# The format of this file is documented in the dhcpd.leases(5) manual page.
# This lease file was written by isc-dhcp-4.2.6

server-duid "\000\001\000\001\033L\014\234\010\000'\037\337\302";

host nh53b963617fbd63378fe0 {
  dynamic;
  hardware ethernet 00:11:22:33:44:55;
  fixed-address 192.168.0.100;
}

删除主机租赁记录

类似地,您可以从租约数据库中删除主机记录:

>>> import pypureomapi
>>> USER='omapikey'
>>> KEY='QKHRF1laxE4cNUAa2t/GOa0VBFeUb5ROS+53gEw2BzQ='
>>> omapi = pypureomapi.Omapi('127.0.0.1', 7911, USER, KEY)
>>> omapi.del_host('00:11:22:33:44:55')
>>>

您将看到租赁记录没有从数据库中删除;相反,它被标记为已删除:

# cat dhcpd.leases
# The format of this file is documented in the dhcpd.leases(5) manual page.
# This lease file was written by isc-dhcp-4.2.6

host nh53b963617fbd63378fe0 {
  dynamic;
  hardware ethernet 00:11:22:33:44:55;
  fixed-address 192.168.0.100;
}
server-duid "\000\001\000\001\033L\014\234\010\000'\037\337\302";

host nh53b963617fbd63378fe0 {
  dynamic;
  deleted;
}

摘要

在本章中,我们扩展了网络地址管理应用的功能,增加了对 DHCP 的支持,还执行了一些检查,如 DNS 查找和 ICMP pings,以确保地址在使用中。

  • 通用视图有助于减少您需要编写的代码量;使用它们来执行一般任务,如显示对象信息和基本操作,如删除、修改和添加。
  • 您可以修改响应 MIME 类型,允许 Django 生成各种各样的内容——HTML、XML、文本,甚至二进制文档。
  • 考虑用户体验,以及当数据量增长时,您的应用是否会同样快速地执行各种任务。如果需要,可以使用 JavaScript 推迟内容加载。
  • 您不需要有可用的库或编写自己的功能来执行某些任务。如果需要,您可以使用系统工具(如 ping)来执行这些任务。
  • 您可以使用 OMAPI 接口来动态更新 ISC DHCP 运行配置。