• [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关联的主机
  • 相关阅读:
    VMware workstation中安装Ubuntu18.04server
    python一行命令安装chromedriver
    vim配置&相关问题
    博客园美化
    期望DP——HDU4035Maze
    [学习笔记]虚树
    线段树——51nod1593&CF515E 公园晨跑
    [STL] multiset
    [学习笔记] 线性基
    泛化物品优化树型DP——[HAOI2010]软件安装
  • 原文地址:https://www.cnblogs.com/leokale-zz/p/12064076.html
Copyright © 2020-2023  润新知