[Python自学] day-20 (Django-ORM、Ajax)

一、外键跨表操作(一对多)

在 [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='123', 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: 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='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':'123123'},
                    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关联的主机
posted @ 2019-12-18 22:21  风间悠香  阅读(438)  评论(0编辑  收藏  举报