本文是Django系列文章的第一篇,主要针对网络工程师,前置学习技能是Python、TextFSM、Netmiko与Nornir。当然你也可以只会Python,文章里自动化以外的部分也是可以的。学习路线参考系列文章的顺序学习即可。
笔者写的Django系列的特点是,以实际场景为导向,逐步深入,将知识点一一展开,围绕着我们最容易学最常用的知识点展开,摒弃了庞杂的前端,零HTML代码带大家写一个网络自动化运维系统。
本系列将分5部分发表,本章节主要介绍django,快速搭建一个django工程
第二部分:不着一行SQL语句,搞定数据库
第三部分:不着一行HTML代码,写一个酷炫的后台管理界面!
第四部分(3个章节):三个实战案例,给django添加自动化的翅膀,有自动化功能CMDB系统的搭建
《从零开始NetDevOps》是本人8年多的NetDevOps实战总结的一本书(且称之为书,通过公众号连载的方式,集结成册,希望有天能以实体书的方式和大家相见)。
第七章 基于Django的自动化运维系统
当我们写了很多脚本,实现了很多场景和需求的时候,想要将结果分享给周围的同事,或者是希望在不同的主机都能访问并执行相关代码。一种思路是将Python脚本封装成exe可执行程序,这种情况有时候又存在着版本管理的问题,且受限于操作系统。笔者比较推荐的一种方式是编写简单的Web应用,通过这种B/S架构(Browser/Server,浏览器/服务器模式),我们通过浏览器就可以访问到我们的服务,在Web界面上进行交互就可以实现相关功能。我们申请到一台虚机,部署上我们的Web服务就可以在任意网络可达的地方访问相关功能,且不需要用户在本地安装Python等环境。
本章节笔者将带领大家编写一个基于Django的自动化运维系统,以实际应用为导向,结合Django的基础知识,边写边讲。
7.1 Django简介及安装
7.1.1 Django简介
Django是一个由Python编写的开源Web应用框架,它最初是被开发出来用于新闻内容为主的网站的,是一款CMS(内容管理系统)软件,这套框架是以以比利时的吉普赛爵士吉他手Django Reinhardt来命名的。它于2005年7月在发布,在pypi中发布的第一个版本可以追溯到2010年5月18日的1.0.1版本,目前已经进入了4.0时代。
由于Python是跨平台的语言,所以Django也是一个跨平台的Web开发框架,其开发出来的Web工程可以部署到各个平台。目前的Django已经对Python2不再支持,所以大家在实际使用中Python一定要选择Python3版本。
Django诞生只出的目的就是为了让我们能更加快捷地开发数据库驱动的网站,它强调快速开发和DRY(Do Not Repeat Yourself)原则,有着非常良好的生态,众多功能强大的第三方插件。
编写Web应用,对于Python而言有太多的选择,其中两颗明星是Django与Flask。笔者作为Django的重度使用者,更推荐大家使用Django来进行我们网络自动化应用的开发框架。相对其他Web框架而言,结合我们实际所需的场景,笔者认为它的优势有三:
- 框架集成度比较高,功能更加丰富,层次比较清晰,开箱即用。
- 有完整的ORM(Object Relational Mapping)层帮助我们处理便捷地处理与数据库的交互。
- 低代码开发的Admin管理界面,可以快速开发出我们所需的管理后台。
Django的设计模式是基于经典的MVC设计模式的一个变种——MTV模式,在这个模式中:
- M(model)代表的是数据处理层,用于将数据在数据库和web应用之间进行相互转换,比如将用户提交的数据存储到数据库,将用户查询的数据从数据库查询后并返回给用户。
- T(template)代表的是表现层,主要用于处理页面的展示,将获取的业务数据进行渲染,产生对应的HTML页面。Django使用的是与Jinja2类似的模板引擎DTL(Django Template Language),实际上Jinja2也是从Django的模板引擎汲取的灵感。
- V(view)从字面意义上它感觉是视图层,实际上它主要处理业务逻辑,Django使用各种“视图”来封装处理用户请求和对应响应的逻辑。我们在这一层去调用数据层,获取相关数据进行处理后,结合表现层进行渲染处理。
- django-mtv
在这种模式之下,还有一个隐藏的URL控制器,它负责接受用户发起的请求,之后根据路由配置将请求提交给Django对应的view函数(业务逻辑函数)进行业务逻辑处理,view函数会根据用户请求,可能调用model数据层的相关ORM接口与数据库进行交互,实现增删查改,并借助模板层获取HTML的模板,与业务处理的数据结合进行渲染,返回一个HTML的页面响应给用户,至此完成一次对用户请求的应答。这个过程中MTV模式各司其职,泾渭分明的同时,又通过框架井然有序地组织在一起。
7.1.2 Django的安装与工程创建
关于版本选择
Django发展势头在后期非常迅猛,当时笔者2014年底还在使用1.5版本的Django,后来经历了1.8、1.10、2.0、2.2,直到现在Django都步入到了4.0时代。鉴于对LTS(Long Term Support,长期支持)版本的考虑(这种版本相对会比较稳定,且有更长期的官方维护),本章节选择使用3.2.16版本,实际3.2是一个LTS版本,在此大版本内,我们选择最新的小版本即可。从目前已经发布的各个版本的支持策略而言,结笔者对一些已知的插件对Django版本的支持,Django3.2版本是目前最合适的选择。
Django release roadmap
安装
Django的安装比较简单,使用pip即可安装,执行命令pip install django==3.2.16
,安装完毕之后,我们在命令行窗口执行一段命令python -m django --version
,如果显示“3.2.16”(我们安装的版本号)代表我们安装成功。
Django安装过程会依赖很多其他的Python包,这些都会自动安装,如果这些包与大家常用的某些Python包有冲突,大家可以考虑使用Python虚拟环境的方式安装,具体操作参考最终章的相关文章。
创建第一个Django工程
在安装完成Django后,我们的环境变脸个会增加一个可执行的命令django-admin,使用这个命令我们就可以创建一个Django工程的框架,执行命令django-admin startproject NetAutoOps
,则会在当前文件夹帮助我们创建一个同名文件夹NetAutoOps(这个工程名大家可以按需使用自己想用的任意工程名,个人建议使用大驼峰命名方法)。文件夹的目录结构如下:
NetAtuoOps
│ manage.py
│
└─NetAtuoOps
asgi.py
settings.py
urls.py
wsgi.py
__init__.py
在NetAtuoOps文件夹内会有一个同名文件夹NetAtuoOps和manage.py:
- 根目录下的manage.py是本Django工程的一个命令行接口,通过它我们可以实现众多功能,创建用户、搭建APP框架、启动一个简单的Web服务器等等。
- settings.py是工程的相关配置,包含了数据库、编写的APP、静态文件目录等。
- urls.py是整个工程的路由配置文件,记录了各类url与view函数的对应关系。
- asgi.py、wsgi.py是Django的asgi和wsgi的两种web协议接口的程序入口,对于初学者而言可以暂时忽视。
在创建完我们的Django工程框架之后,我们进入到我们的NetAutoOps的根目录底下,执行命令python manage.py runserver
Django工程默认会将Web服务启动在127.0.0.1的8000端口。也可以运行命令python manage.py runserver 0.0.0.0:8000
,其中“0.0.0.0”代表的是宿主机的任意IP均可以访问此Web服务,8000端口代表的是将Web服务启动在端口8000上面。二者均可以不写,或者只写一个(Django都可以智能识别),或者根据实际情况调整二者之一。如果部署到服务器希望其他IP可以访问,则需将127.0.0.1改为0.0.0.0。端口我们根据实际情况可以调整到80或者8080等比较常见的Web服务端口。执行完上述命令之后,会在命令行窗口中有如下回显:
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
December 21, 2022 - 21:59:07
Django version 3.2.16, using settings 'NetAtuoOps.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CTRL-BREAK.
其中的18条数据未迁移的提示文本我们可以暂时忽略。在浏览器中我们打开http://127.0.0.1:8000网址,即可看到一个Django工程的Web界面,如下图
image-20221221140020215
当我们创建完工程运行上述命令即可访问这个页面,Django会帮助我们自动创建一个db.sqlite3的数据库文件。它是一个二进制的轻量级数据库,Django有一个开箱即用的特点,我们无需安装MySQL、PostgreSQL等数据库,做一些轻度的测试和开发,sqlite3数据库完全可以胜任。随着本章节逐步深入,我们会为大家讲解数据库的安装和Django的一些技术细节。
7.1.3 Pycharm的配置
我们以社区版本的Pycharm为例,打开Pycharm软件,点击左上角的文件(File)->打开(Open),通过界面选择Django工程的根目录。然后要创建一个”运行/调试“配置,在右上方点击点击下拉框,选择编辑配置。
image-20230112184529649
添加一个Python的“运行/调试”按照下图设置一个运行调试配置,通过弹出窗口的左上角,添加一个Python的运行调试配置,此处我们命名为django_manage。
image-20230112192623462
选择脚本路径为manage.py,在“形参”中输入“runserver”,我们也可以在runserver后面继续写“0.0.0.0:8000”类似的参数。选择Python解释器为我们安装了Django的Python环境,将工作目录调整到我们当前的工程目录,点击确定即可完成配置。
这样右上角我们就会多了一个“django_manage”的运行调试配置,我们点击运行按钮可以拉起Django的Web服务,也可以点击刷新进行重启;点击调试(Debug)按钮拉起服务进入调试模式,可以对工程代码进行调试。
image-20230112194039070
有时候我们会通过Pycharm中的终端运行"python manage.py runserver"等命令,如果大家电脑中有了多套Python环境,这个时候也建议在终端配置中将环境变量中的Python路径要与django_manage配置中的Python解释器一致。如下图进行设置。
image-20230112184145446
7.2 创建CMDB APP
Django工程整体代表了一个基于Django开发的Web工程,在里面我们可以创建任意个APP,每个APP代表的是一组功能比较内聚的应用,比如我们会创建一个CMDB的APP,用于管理网络设备资产资源。工程和APP的关系一般而言是一对多的关系,即一个工程内有多个APP。Django可以帮助我们自动创建一个APP所需的相关文件,即帮助我们搭建一个APP的框架。
接下来我们就为大家演示一下,如何创建一个APP,以及在这个APP内部不断丰富其功能,初步领略Django的相关特点和魅力。
我们在工程的根目录下执行命令python manage.py startapp cmdb
,其中cmdb是我们这个APP的名称,APP的名称笔者建议使用蛇形命名法,以字母、数字、下划线组成。
执行完以上命令之后,我们再次观察目录结构:
NetAtuoOps
│ db.sqlite3
│ manage.py
│
├─cmdb
│ │ admin.py
│ │ apps.py
│ │ models.py
│ │ tests.py
│ │ views.py
│ │ __init__.py
│ │
│ └─migrations
│ __init__.py
│
└─NetAtuoOps
│ asgi.py
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
manage.py就是Django为我们提供的一个命令行工具,通过它我们可以快速创建好一个APP的框架,对于初学者,笔者认为几个比较重要的文件是:
- models.py,我们编写的数据模型放在这个文件里。
- views.py,我们编写的业务逻辑函数都放在这个文件里。
- admin.py,用于注册后台管理界面。
其他几个文件:
- apps.py是用于进行APP的一些基础配置,几乎不会进行修改。
- migrations中的文件用于记录数据模型变化,属于迁移脚本,后续我们会简单讲解。
7.2.1 编写我们的第一个列表页
当APP的框架都搭好之后,我们就可以着手编写自己的页面了。根据之前的MTV模型,我们了解到需要通过URL控制器将一个URL映射到一个对应的view函数中。我们先编写一个最简单的view函数,在cmdb\views.py中创建一个index函数,它的第一个参数使用Django约定俗成的 request,然后返回一个HTTP的响应(对应django.shortcuts.HttpResponse类,可以直接传入HTML字符串返回)。
from django.shortcuts import render
from django.shortcuts import HttpResponse
def index(request):
return HttpResponse('<h1>这是我的设备列表页,待优化</h1>')
index函数就是我们处理某URL的一个view函数,由于是处理用户请求,所以它的第一个参数是用户的请求,这个变量约定俗成为request,Django会将用户通过浏览器的请求进行封装,将用户信息、用户请求的路径、请求的参数等包装到这个request对象中,后续我们会结合实例演示。
编写好view函数之后,我们就要设计一个URL来与这个函数对应,我们将其先编写到根URL控制器文件NetAutoOps\urlls.py中,这个文件是由Django帮助我们创建的,在文件的开头也有相关注释帮助我们解释如何编写URL路由。
"""NetAtuoOps URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
import cmdb.views as cmdb_views
urlpatterns = [
path('admin/', admin.site.urls),
path('cmdb/index/', cmdb_views.index, name='cmdb_index')
]
URL控制器中最核心的是urlpatterns变量,我们可以将它看作是一个path对象(这个对象比较复杂,大家可以简单理解其为一个path对象)的列表。每个成员path对象对应一个URL及其对应处理请求的函数。第一个是Django帮我们生成的admin后台管理的URL,我们可以暂时忽视,在这个成员之后追加我们自定义的URL分发逻辑,创建一个path对象。我们导入对应的views模块,因为将来可能有众多APP,我们先暂时起了一个别名cmdb_view,通过path对象的构建,将访问“cmdb/index”的请求映射到了cmdb_views.index的view函数,后面的name大家可以忽视。
我们再次通过命令行python manage.py runserver
将服务拉起,如果之前已经拉起,一般文件发生变化,系统会自动检测到并自动尝试重启,有时候部分文件修改必须手工重启服务,大家结合控制台的信息来决定是否重启服务。
拉起服务之后,我们在浏览器访问"http://127.0.0.1:8000/cmdb/index/",即可获取到我们的页面:
image-20221221200419177
接下来,我们将对代码进行优化,首先是对URL进行优化,我们将当前APP的URL全部写入了根URL配置文件中,后续随着代码的不断丰富,根URL会比较臃肿,且多人写作开发时特别容易冲突。所以我们一般二级路由,所有请求的URL以“cmdb”开头的,都指向二级路由:
- 首先在cmdb文件夹中创建一个urls.py,在这个二级URL路由配置中将“index”的路由指向对应的view函数。
- 然后在根URL路由文件中,将以“cmdb”开头的URL指向我们刚才创建的二级路由。
二级路由,cmdb\urls.py的内容如下:
from django.urls import path
from cmdb import views
urlpatterns = [
path('index/', views.index, name='cmdb_index')
]
根路由 、将“cmdb”开头的URL都指向我们刚编写的二级路由,使用到了include函数,这个在Django的自动创建的根路由配置文件中有相关代码说明,我们“照猫画虎”即可,NetAutoOps\urls.py的内容如下:
"""NetAtuoOps URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('cmdb/', include('cmdb.urls'))
]
我们参考了”Including another URLconf“的相关指导,将另外一个URL路由文件导入到了根路由中,与“cmdb”的请求想对应,这样用户请求“cmdb/index/”的时候就会因为“cmdb/”部分与第二条路由匹配,当Django发现第二条路由有一个二级路由,则会进入二级路由进一步匹配,后续的“index/”与二级路由cmdb/urls.py中的第一条完成匹配,将用户的请求传给了index这个view函数。
我们通过浏览器访问,与之前的效果相同,但是二级路由的拆分,可以让路由更加的有层次,且可以提高开发效率。
将路由改造完成之后,我们要优化自己的页面,我们先从简单的做起,从表格Excel中读取设备列表(使用Nornir篇章的网络设备清单),然后使用Django的模板语言渲染到一个HTML页面中。从表格中读取数据的代码我们之前讲过,如何使用Django的模板系统呢?
我们需要修改一些工程中的settings.py文件,即NetAutoOps\settings.py文件:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 修改大约在57行的这段代码,将templates目录包含到模板目录中
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
我们在工程的根目录底下创建templates文件夹,然后在settings.py文件中的大约57行,将此文件夹追加到模板目录中。在templates中我们以APP名称cmdb创建文件夹,并编写index.html文件,其内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>NetAutoOps</title>
</head>
<body>
{% for dev in devs %}
{{ dev.name }} {{ dev.hostname }} {{ dev.username }} <br>
{% endfor %}
</body>
</html>
这个模板文件与Jinja2如出一辙,我们假设获取到的数据是devs,是一个设备信息字典的列表,然后for循环展开打印。
在准备好模板之后我们重新迭代一下view函数,我们这个时候使用到了Django的一个render函数,它可以快速地将模板与数据渲染成HTTP响应返回给用户。其源代码如下:
def render(request, template_name, context=None, content_type=None, status=None, using=None):
"""
Return a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments.
"""
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
我们调用这个函数的时候,第一个参数是request,即用户的请求;第二个参数是template_name,即模板路径,它会根据settings.py中的DIRS中定义的路径依次去查找我们的模板;第三个参数context,可选,默认为空,即我们需要在模板中渲染的数据,必须是字典类型,在模板中我们可以直接把key作为变量名称使用,比如上述HTML模板中我们定义的是devs这个设备列表,所以context的key要与devs这个变量设备名一致。
我们的view函数index进行相关迭代,cmdb/views.py代码如下:
import pandas as pd
from django.shortcuts import render
def index(request):
devs_df = pd.read_excel('cmdb/inventory.xlsx')
devs = devs_df.to_dict(orient='records')
data = {'devs':devs}
return render(request, 'cmdb/index.html', context=data)
在这段代码中,我们将数据封装到字典中,设备列表放到字典对应的devs(key的名称),HTML无需再从字典中取出,类似context['devs']这种用法,直接使用对应的devs即可。同时request作为用户请求的相关数据也可以直接在HTML中使用。
我们重启服务,通过浏览器访问对应URL后,会得到如下的一个页面。
image-20221222202040036
前端代码中我们未过多着墨,只是简单输出了设备的相关信息。
现在的文件目录是这样一个结构:
│ db.sqlite3
│ manage.py
│
├─cmdb
│ │ admin.py
│ │ apps.py
│ │ inventory.xlsx
│ │ models.py
│ │ tests.py
│ │ urls.py
│ │ views.py
│ │ __init__.py
│ │
│ └─migrations
│ __init__.py
│
├─NetAtuoOps
│ asgi.py
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
│
└─templates
└─cmdb
index.html
我们在APP目录(示例中对应cmdb目录)下创建了二级路由urls.py,对URL进行了分级处理。在根目录下创建了templates目录,用于存放所有的HTML模板,以APP名称创建各自的模板子模板,方便管理这个动作涉及到在settings.py中追加templates目录,如果不追加,我们则需要在APP底下创建templates目录,然后再创建APP目录,再编写模板,这种方式比较赘余,所以大家一般将目录放在根目录下进行集中管理,如果是集中管理,一定要修改DIRS变量,追加对应的模板目录。
我们回头去看我们的代码,会发现实际有效代码行数大概在20行,我们就编写出了自己的第一个APP。整个过程各个组件分工明确,代码结构清新有逻辑,对掌握一定Python开发能力的网络工程师而言难度适中,这就是Django的魅力。