一、外键跨表操作(一对多)
在 [Python自学] day-19 (2) (Django-ORM) 中,我们利用外键实现了一对多的表操作。
可以利用以下方式来获取外键指向表的数据:
def orm_test(request):
# 向UserGroup表中插入一个group (gid=1,groupname='Dev')
models.UserGroup.objects.create(groupname="Dev")
# 向UserInfo表中插入一个user (uid=1,username='Leo',password='123',group_id=1),注意这里要使用真实列名group_id
models.UserInfo.objects.create(username="Leo", password='', group_id=1) # 联合查询UserInfo以及UserGroup
# 获取第一个用户,这里只有一个用户Leo
obj = models.UserInfo.objects.filter(uid=1).first()
# 打印用户的用户名、密码(这些内容都在UserInfo表中)
print(obj.username)
print(obj.password)
# 这里注意,group是外键,指向UserGroup表,所以这里的group属性是一个对象(UserGroup表的一条记录),我们通过该对象来获取groupname
print(obj.group.groupname) return HttpResponse('ok')
但是,当我们在获取局部列数据的情况下:
def orm_test(request): # 因为使用了values,所以QuerySet v中的元素都是字典
# 在values()中,使用'__'来跨表操作
v = models.UserInfo.objects.filter(uid=1).values('username','group_id','group__groupname')
for row in v:
print(row['username'])
print(row['group_id'])
# 使用key来获取字典中的值,如果是通过render返回给模板,则模板语言也要使用v.group__groupname来获取值
print(row['group__groupname']) return HttpResponse('ok')
以上规则同样适合于使用values_list()的情况,只不过将QuerySet内部元素变成元组而已。values_list()中跨表也使用"__"。
二、实现简单资产管理
1.首先使用models.py在数据库中创建两张表
from django.db import models # Create your models here. # 创建一个业务线表
class Business(models.Model):
businame = models.CharField(max_length=32) # 创建一个主机表
class Host(models.Model):
nid = models.AutoField(primary_key=True)
hostname = models.CharField(max_length=32, db_index=True)
ip = models.GenericIPAddressField(protocol='ipv4', db_index=True)
port = models.IntegerField()
busi = models.ForeignKey('Business', on_delete=models.CASCADE, to_field='id')
在工程的setting中进行配置:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'cmdb',
'mgmt',
]
执行命令:
python manage.py makemigrations
python manage.py migrate
我们先手工在Business表中添加一些数据(这里主要为了方便,真正管理系统中,业务线肯定也是页面上进行添加删除的):
2.创建mgmt/urls.py映射
from django.contrib import admin
from django.urls import path
from django.urls import re_path from mgmt import views urlpatterns = [
path('index/', views.index),
path('host/', views.host),
]
3.添加基础版host.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Host Page</title>
</head>
<body>
<h1>主机列表</h1>
<table border="1">
<thead>
<tr>
<th>主机名</th>
<th>IP</th>
<th>端口</th>
<th>业务线</th>
</tr>
</thead>
<tbody>
{% for row in host_list %}
<tr hid="{{ row.nid }}" bid="{{ row.busi_id }}">
<td>{{ row.hostname }}</td>
<td>{{ row.ip }}</td>
<td>{{ row.port }}</td>
<td>{{ row.busi.businame }}</td>
</tr>
{% endfor %}
</tbody>
</table> <h1>业务线列表</h1>
<table border="1">
<thead>
<tr>
<th>业务线ID</th>
<th>业务线名称</th>
</tr>
</thead>
<tbody>
{% for row in busi_list %}
<tr>
<td>{{ row.id }}</td>
<td>{{ row.businame }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
4.编写对应的mgmt/views.py中的host方法
def host(request):
# 如果是get请求,则将数据库中查询到的host列表和业务线列表返回,展示在页面上
if request.method == 'GET':
host_list = models.Host.objects.all()
busi_list = models.Business.objects.all()
return render(request, 'host.html', {'host_list': host_list, 'busi_list': busi_list})
访问http://127.0.0.0:8000/mgmt/host,效果如下:
因为目前还未添加主机条目,所以为空。业务线数据我们在前面用手工的方式添加到了数据库中。
5.修改html,为主机列表添加模态对话框(用于添加主机)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Host Page</title>
<style>
.shade{
position: fixed;
top:0px ;
bottom: 0px;
left: 0px;
right: 0px;
background-color: black;
opacity: 0.5;
z-index:;
}
.hide{
display: none;
}
.add_modal{
background-color: #eeeeee;
position: fixed;
height: 300px;
width: 400px;
top:100px;
left:50%;
margin-left: -200px;
z-index:;
border: 2px solid #dddddd;
}
</style>
</head>
<body>
<h1>主机列表</h1>
<input id='add_host' type="button" value="添加主机"/>
<table border="1">
<thead>
<tr>
<th>主机名</th>
<th>IP</th>
<th>端口</th>
<th>业务线</th>
</tr>
</thead>
<tbody>
{% for row in host_list %}
<tr hid="{{ row.nid }}" bid="{{ row.busi_id }}">
<td>{{ row.hostname }}</td>
<td>{{ row.ip }}</td>
<td>{{ row.port }}</td>
<td>{{ row.busi.businame }}</td>
</tr>
{% endfor %}
</tbody>
</table> <h1>业务线列表</h1>
<table border="1">
<thead>
<tr>
<th>业务线ID</th>
<th>业务线名称</th>
</tr>
</thead>
<tbody>
{% for row in busi_list %}
<tr>
<td>{{ row.id }}</td>
<td>{{ row.businame }}</td>
</tr>
{% endfor %}
</tbody>
</table> <!-- 遮罩 -->
<div class="shade hide"></div>
<!-- 添加主机 弹窗 -->
<div class="add_modal hide">
<form action="/mgmt/host/" method="post">
<div class="group">
<input type="text" placeholder="主机名" name="hostname"/>
</div>
<div class="group">
<input type="text" placeholder="IP地址" name="ip"/>
</div>
<div class="group">
<input type="text" placeholder="端口" name="port"/>
</div>
<div class="group">
<select name="busi_id">
{% for bi in busi_list %}
<option value="{{ bi.id }}">{{ bi.businame }}</option>
{% endfor %}
</select>
</div> <input type="submit" value="提交"/>
<input id='cancel' type="button" value="取消"/>
</form>
</div> <script src="/static/jquery-1.12.4.js"></script>
<script>
$(function(){
// 为添加主机按钮绑定事件,显示遮罩层和模态框
$('#add_host').click(function(){
$('.shade,.add_modal').removeClass('hide');
});
// 为模态框的取消按钮绑定事件,隐藏遮罩层和模态框
$('#cancel').click(function(){
$('.shade,.add_modal').addClass('hide');
})
});
</script>
</body>
</html>
6.修改mgmt/views.py中的host()视图函数
def host(request):
# 如果是get请求,则将数据库中查询到的host列表和业务线列表返回,展示在页面上
if request.method == 'GET':
host_list = models.Host.objects.all()
busi_list = models.Business.objects.all()
return render(request, 'host.html', {'host_list': host_list, 'busi_list': busi_list})
elif request.method == 'POST': # 当用户使用模态框添加主机时,使用表单POST提交
# 获取表单提交的数据
host = request.POST.get('hostname')
ip = request.POST.get('ip')
port = request.POST.get('port')
# 这里的busi获取到的是select对应的busi_id
busi = request.POST.get('busi_id')
# 插入数据库
models.Host.objects.create(
hostname=host,
ip=ip,
port=port,
busi_id=busi
)
# 重定向到host页面,以GET重新请求,页面就可以显示新的值
return redirect('/mgmt/host')
接受来自模态框表单提交的数据,插入数据库,并重定向到/mgmt/host页面,展示新数据。
7.页面效果:
三、Ajax
Ajax:Asynchronous Javascript And XML,异步的JS和XML。
主要用于免刷新页面提交数据。
例如,在第二节中实现的模态框,我们在提交数据前并未对输入的数据格式进行验证,例如是否为空,格式是否满足要求等。因为在模态框中要验证内容,只能使用以前了解的绑定多个事件来验证的方法(前端方法验证)。
而有了Ajax,我们就可以不刷新页面提交数据到后台进行验证:
(这里使用的是jQuery的ajax组件)
1.首先在模态框中添加一个按钮,用于发送ajax异步请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Host Page</title>
<style>
.shade{
position: fixed;
top:0px ;
bottom: 0px;
left: 0px;
right: 0px;
background-color: black;
opacity: 0.5;
z-index: 100;
}
.hide{
display: none;
}
.add_modal{
background-color: #eeeeee;
position: fixed;
height: 300px;
width: 400px;
top:100px;
left:50%;
margin-left: -200px;
z-index: 101;
border: 2px solid #dddddd;
}
</style>
</head>
<body>
<h1>主机列表</h1>
<input id='add_host' type="button" value="添加主机"/>
<table border="1">
<thead>
<tr>
<th>主机名</th>
<th>IP</th>
<th>端口</th>
<th>业务线</th>
</tr>
</thead>
<tbody>
{% for row in host_list %}
<tr hid="{{ row.nid }}" bid="{{ row.busi_id }}">
<td>{{ row.hostname }}</td>
<td>{{ row.ip }}</td>
<td>{{ row.port }}</td>
<td>{{ row.busi.businame }}</td>
</tr>
{% endfor %}
</tbody>
</table> <h1>业务线列表</h1>
<table border="1">
<thead>
<tr>
<th>业务线ID</th>
<th>业务线名称</th>
</tr>
</thead>
<tbody>
{% for row in busi_list %}
<tr>
<td>{{ row.id }}</td>
<td>{{ row.businame }}</td>
</tr>
{% endfor %}
</tbody>
</table> <!-- 遮罩 -->
<div class="shade hide"></div>
<!-- 添加主机 弹窗 -->
<div class="add_modal hide">
<form action="/mgmt/host/" method="post">
<div class="group">
<input type="text" placeholder="主机名" name="hostname"/>
</div>
<div class="group">
<input type="text" placeholder="IP地址" name="ip"/>
</div>
<div class="group">
<input type="text" placeholder="端口" name="port"/>
</div>
<div class="group">
<select name="busi_id">
{% for bi in busi_list %}
<option value="{{ bi.id }}">{{ bi.businame }}</option>
{% endfor %}
</select>
</div> <input type="submit" value="提交"/>
<input id='ajax_submit' type="button" value="Alax提交"/>
<input id='cancel' type="button" value="取消"/>
</form>
</div> <script src="/static/jquery-1.12.4.js"></script>
<script>
$(function(){
// 为添加主机按钮绑定事件,显示遮罩层和模态框
$('#add_host').click(function(){
$('.shade,.add_modal').removeClass('hide');
}); //为ajax_submit按钮绑定事件,发送Ajax请求
$('#ajax_submit').click(function(){
$.ajax({
url: '/mgmt/test_ajax',
type: 'GET',
data: {'user':'root','pwd':''},
success:function(data) {
alert(data)
}
});
});
// 为模态框的取消按钮绑定事件,隐藏遮罩层和模态框
$('#cancel').click(function(){
$('.shade,.add_modal').addClass('hide');
})
});
</script>
</body>
</html>
添加一个按钮,叫做"Ajax提交",然后为其绑定点击事件,点击按钮是,提交Ajax异步请求,请求url为"/mgmt/test_ajax",请求方式为"GET",传递数据为 " {'user':'root','pwd':'123123'} "。
最重要的一点是,设置了一个"success:function(data){}",这是一个回调函数,当收到后台返回的数据后,被自动调用。
2.在mgmt/urls.py中添加一个映射,用于处理/mgmt/test_ajax
from django.contrib import admin
from django.urls import path
from django.urls import re_path from mgmt import views urlpatterns = [
path('index', views.index),
path('host/', views.host),
re_path('test_ajax$', views.test_ajax),
]
3.在mgmt/views.py中实现一个test_ajax()视图函数
def test_ajax(request):
# 获取Ajax请求方式和内容
print(request.method)
print(request.GET.get('user'))
print(request.GET.get('pwd'))
# 等待3s后回复数据
time.sleep(3)
return HttpResponse("Ajax回复信息")
4.页面效果
以上效果可以看出,点击"Ajax提交"按钮后,后台收到了传递的data,然后在3s后返回"Ajax回复信息",前台使用alert()显示出来。
5.修改Ajax请求的数据,将输入框的数据发送到后台验证
html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Host Page</title>
<style>
.shade{
position: fixed;
top:0px ;
bottom: 0px;
left: 0px;
right: 0px;
background-color: black;
opacity: 0.5;
z-index: 100;
}
.hide{
display: none;
}
.add_modal{
background-color: #eeeeee;
position: fixed;
height: 300px;
width: 400px;
top:100px;
left:50%;
margin-left: -200px;
z-index: 101;
border: 2px solid #dddddd;
}
.error{
color: red;
}
</style>
</head>
<body>
<h1>主机列表</h1>
<input id='add_host' type="button" value="添加主机"/>
<table border="1">
<thead>
<tr>
<th>主机名</th>
<th>IP</th>
<th>端口</th>
<th>业务线</th>
</tr>
</thead>
<tbody>
{% for row in host_list %}
<tr hid="{{ row.nid }}" bid="{{ row.busi_id }}">
<td>{{ row.hostname }}</td>
<td>{{ row.ip }}</td>
<td>{{ row.port }}</td>
<td>{{ row.busi.businame }}</td>
</tr>
{% endfor %}
</tbody>
</table> <h1>业务线列表</h1>
<table border="1">
<thead>
<tr>
<th>业务线ID</th>
<th>业务线名称</th>
</tr>
</thead>
<tbody>
{% for row in busi_list %}
<tr>
<td>{{ row.id }}</td>
<td>{{ row.businame }}</td>
</tr>
{% endfor %}
</tbody>
</table> <!-- 遮罩 -->
<div class="shade hide"></div>
<!-- 添加主机 弹窗 -->
<div class="add_modal hide">
<form action="/mgmt/host/" method="post">
<div class="group">
<input id="hostname" type="text" placeholder="主机名" name="hostname"/>
</div>
<div class="group">
<input id="ip" type="text" placeholder="IP地址" name="ip"/>
</div>
<div class="group">
<input id="port" type="text" placeholder="端口" name="port"/>
</div>
<div class="group">
<select id="busi_id" name="busi_id">
{% for bi in busi_list %}
<option value="{{ bi.id }}">{{ bi.businame }}</option>
{% endfor %}
</select>
<span id="error_label"></span>
</div>
<input type="submit" value="提交"/>
<input id='ajax_submit' type="button" value="Alax提交"/>
<input id='cancel' type="button" value="取消"/>
</form>
</div> <script src="/static/jquery-1.12.4.js"></script>
<script>
$(function(){
// 为添加主机按钮绑定事件,显示遮罩层和模态框
$('#add_host').click(function(){
$('.shade,.add_modal').removeClass('hide');
}); //为ajax_submit按钮绑定事件,发送Ajax请求
$('#ajax_submit').click(function(){
$.ajax({
url: '/mgmt/test_ajax',
type: 'POST',
data: {
'hostname':$("#hostname").val(),
'ip':$("#ip").val(),
'port':$("#port").val(),
'busi_id':$("#busi_id").val()
},
success:function(data) {
var err = JSON.parse(data);
if(err.status){
location.reload();
}else{
$("#error_label").text(err.err_msg);
$("#error_label").addClass('error');
}
}
});
});
// 为模态框的取消按钮绑定事件,隐藏遮罩层和模态框
$('#cancel').click(function(){
$('.shade,.add_modal').addClass('hide');
})
});
</script>
</body>
</html>
我们在<select>标签后面添加了一个错误提示<span>标签,当后台返回格式错误时,我们将错误信息显示在该位置。
test_ajax()视图函数:
def test_ajax(request):
host = request.POST.get('hostname')
ip = request.POST.get('ip')
port = request.POST.get('port')
# 这里的busi获取到的是select对应的busi_id
busi = request.POST.get('busi_id') err = {'status': True, 'err_msg': None}
if len(host) < 6 or len(host) > 30:
err['status'] = False
err['err_msg'] = '主机名长度必须在6-30之间'
try:
# 插入数据库
models.Host.objects.create(
hostname=host,
ip=ip,
port=port,
busi_id=busi
)
except Exception as e:
err['status'] = False
err['err_msg'] = '请求错误,请检查'
import json
return HttpResponse(json.dumps(err))
返回错误信息时,由于HttpResponse只能返回字符串,所以需要将字典序列化成字符串。然后在前端JS代码中进行反序列化。
建议:在后台返回数据时,数据以字典的形式组织,并使用JSON序列化。
6.最终效果
7.注意事项 & 获取表单数据的简单方法
1)Ajax请求的时候,后台必须使用HttpResponse来返回数据,不能使用redirect(对Ajax无效)。render是可以使用的,但是一般render用来渲染一个html页面,所以也不适合Ajax。
2)Ajax提供一个方便的获取表单填入数据的方法:
$.ajax({
url: '/mgmt/test_ajax',
type: 'POST',
/*data: {
'hostname':$("#hostname").val(),
'ip':$("#ip").val(),
'port':$("#port").val(),
'busi_id':$("#busi_id").val()
},*/
data: $("#add_form").serialize(),
success:function(data) {
var err = JSON.parse(data);
if(err.status){
location.reload();
}else{
$("#error_label").text(err.err_msg);
$("#error_label").addClass('error');
} }
});
其中data部分,直接使用" $("#表单ID").serialize() "来获取表单中所有输入标签的值。然后通过Ajax发送到后台,后台获取值得方式不变,还是使用GET或POST来获取。
四、ORM多对多关系
多对多的意思是,两个表的记录之间存在多对多关系。例如主机表和应用表之间存在多对多关系,主机A对应应用1和2,应用1又可以对应主机A和B。
这种多对多关系,由两张单独的表是不能表现的。所以要借助第三张表:关系表。
我们使用Django-ORM的时候,有以下两种方式创建关系表:
1.手工创建(比较灵活,关系表的字段可以任意添加和修改)
# 创建主机表
class Host(models.Model):
nid = models.AutoField(primary_key=True)
hostname = models.CharField(max_length=32, db_index=True)
ip = models.GenericIPAddressField(protocol='ipv4', db_index=True, null=True)
port = models.IntegerField()
busi = models.ForeignKey('Business', on_delete=models.CASCADE, to_field='id') # 创建应用表
class Application(models.Model):
appname = models.CharField(max_length=64) # 创建主机--应用关系表(用于描述多对多关系)
class HostToApp(models.Model):
hobj = models.ForeignKey('Host', to_field='nid')
aobj = models.ForeignKey('Application', to_field='id')
在这种创建方式下,关系表示我们手工通过ORM类来创建的,我们通过如下方式来操作:
# 创建一条hostA--App2的关系
models.HostToApp.objects.create(hobj_id=1, aobj_id=2)
# 创建一条hostA--App3的关系
models.HostToApp.objects.create(hobj_id=1, aobj_id=3)
# 创建一条hostB--App1的关系
models.HostToApp.objects.create(hobj_id=2, aobj_id=1)
# 创建一条hostB--App3的关系
models.HostToApp.objects.create(hobj_id=2, aobj_id=3)
2.自动创建(自动生成关系表,但是无法直接通过类操作,无法任意增加列)
# 创建主机表
class Host(models.Model):
nid = models.AutoField(primary_key=True)
hostname = models.CharField(max_length=32, db_index=True)
ip = models.GenericIPAddressField(protocol='ipv4', db_index=True, null=True)
port = models.IntegerField()
busi = models.ForeignKey('Business', on_delete=models.CASCADE, to_field='id') # 创建应用表
class Application(models.Model):
appname = models.CharField(max_length=64)
# 创建与Host表的多对多关系,有这句,Django就会帮我们自动创建一张关系表
rel = models.ManyToManyField('Host')
执行命令:
python manage.py makemigrations
python manage.py migrate
查看数据库中自动生成的关系表:
我们可以看到,Django帮我们自动生成了一张mgmt_application_rel的关系表。
由于这个关系表示自动生成的,我们无法直接使用对应的类来操作,所以只能通过Applicaiton类对象中的rel属性来操作:
# 如果我们操作的关系为App1(id=1的App)相关的,则先获得App1的对象。后面使用该对象的所有操作,都是与App1有关的。
obj = models.Application.objects.get(id=1)
print(obj.appname) # 打印id=1的App名称 # obj.rel就是操作关系表的桥梁,或者说这个对象就是关系表
# 添加一个App1---HostA的关系,add()中的参数1表示nid=1的Host,即HostA
obj.rel.add(1)
# 添加 App1---HostB的关系
obj.rel.add(2)
# 同时添加三条关系,App1---HostB、C、D
obj.rel.add(2, 3, 4)
# 同时添加四条关系,App1---HostA、C、D、E
obj.rel.add(*[1, 3, 4, 5]) # 删除App1---HostA关系
obj.rel.remove(1)
# 删除App1---HostB、C、D三条关系
obj.rel.remove(2, 3, 4)
# 删除App1---HostB、C、D三条关系
obj.rel.remove(*[2, 3, 4]) # 清除所有App1相关的关系
obj.rel.clear() # 设置App1对应的所有关系,即关系表中关于App1相关的关系,只保留App1---HostA、B、D、E,其他全部删除
obj.rel.set([1, 2, 4, 5]) # 获取关系表中与App1相关的所有条目
app1_r_list = obj.rel.all() # 这里使用all()拿到的QuerySet列表中的元素为Host对象,因为这里App1是固定的,他关系的所有元素都是Host
for row in app1_r_list:
print(row.hostname) # 打印所有与App1关联的主机