iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Python自动化开发学习-RESTfu
  • 782
分享到

Python自动化开发学习-RESTfu

PythonRESTfu 2023-01-31 06:01:58 782人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

RESTful api 是一种面向资源编程,也叫表征状态转移(英文:Representational State Transfer,简称REST)。认为网络上所有的东西都是资源,对资源的操作无非就是增删改查。 传统的方法 比如有个资产的页

RESTful api 是一种面向资源编程,也叫表征状态转移(英文:Representational State Transfer,简称REST)。
认为网络上所有的东西都是资源,对资源的操作无非就是增删改查。

传统的方法

比如有个资产的页面,URL是 www.example.com/asset。要对它进行增删改查,可能使用不同的url来区分:

  • www.example.com/addAsset :增加资产,一般是POST方法。
  • www.example.com/delAsset :删除资产,一般是POST方法。
  • www.example.com/editAsset :修改资产,一般是POST方法。
  • www.example.com/showAsset :显示资产,一般是GET方法。也可能使用 www.example.com/asset 作为url

这里的url一般使用的都是动词,表示是一个动作。

RESTful API 的规则

RESTful API 用一个url代指一个资源,既然是资源,这个词要用名词。那么这个url就是 www.example.com/asset 。增删改查都是通过这个url实现的,通过不同的method实现不同的方法,常用的是下面几个方法:

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):从服务器删除资源。

Django中,推荐使用CBV。当然FBV也不是不行。

RESTful API 设计指南

这篇貌似讲的很好,值得参考:Http://www.ruanyifeng.com/blog/2014/05/restful_api.html

使用API就会有很多序列化数据返回的操作。
之前当我们需要给前端返回序列化后的字符串时,往往都是先调用JSON.dumps()这个方法,然后再用HttpResponse()把字符串返回给前端。既然每次都要这么搞,于是djanGo给我么封装了一个新方法,直接完成序列化和返回字符串。
jsonResponse这个类是HttpRespon的子类,通过它直接就可以把字典进行序列化并返回给前端。

>>> from django.http import JsonResponse
>>> response = JsonResponse({'foo': 'bar'})
>>> response.content
'{"foo": "bar"}'

默认只能传入一个字典,并且API要返回的数据应该也就是字典。但是如果一定要序列化一个其他的类型,比如列表,可以设置safe参数:

>>> response = JsonResponse([1, 2, 3], safe=False)

如果要自定义编码器,和json方法一样,通过下面的参数指定:

>>> response = JsonResponse(data, encoder=MyJSONEncoder)

这里的 encoder 参数就是原生的 json.dumps 的cls参数。源码里最后也是调用原生的 json.dumps 把 encoder 传给cls 的。
另外,也可以只定义类中的 default 方法,但是 JsonRespons 没有专门的参数来接收,不过调用原生的 json.dumps 时,会把 json_dumps_params 参数传递过去。也就是在 JsonRespons 里,可以把所有的 json.dumps 的参数先传给 json_dumps_params 。调用原生的 json.dumps 方法的源码是这样的:

data = json.dumps(data, cls=encoder, **json_dumps_params)

所以,可以这么用:

return JsonResponse(
        data={'obj': obj},
        json_dumps_params={'default': fn},  # 这个参数是传给原生的 json.dumps 执行的参数
    )

# 上面自然是要先定义好一个fn函数的,比如下面这样
def fn(obj):
    if hasattr(obj, 'isofORMat'):
        return obj.strftime("%Y-%m-%d %T")

这段代码用来从数据库获取数据,然后在前端动态的生成表格。
完整的代码在最后,前面是一步一步把这个功能给做出来。
处理函数主要负责两件事情:

  • 数据库获取数据,返回给前端
  • 定制一个存有配置项的字典,定义好前端怎么显示这些数据,也返回给前端

准备(初始化)

在 urls.py 里写好对应关系:

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('host/', views.HostView.as_view()),
]

写一个处理函数 views.py,这里用CBV,直接返回页面

from django.views import View

class HostView(View):

    def get(self, request, *args, **kwargs):
        return render(request, 'host.html')

前端的页面先返回一个空的表格,之后再填充表格内容:

<body>
<h1>主机列表</h1>
<table border="1">
    <thead id="thead"></thead>
    <tbody id="tbody"></tbody>
</table>

<script src="/static/Jquery-1.12.4.js"></script>
<script>
    $(function () {
        init();  // 当页面加载完成,执行init()初始化方法。具体的方法写在下面
    });

    function init() {
       alert('初始化')
    }
</script>
</body>

测试一下,应该只能看到h1标签里的内容。页面初始化之后会弹一个alert。

从API接口获取数据

写一下前端的init()方法,发送一个ajax请求到一个新的url,然后接收到返回的数据后,后台看一下:

<script>
    $(function () {
        init();  // 当页面加载完成,执行init()初始化方法。具体的方法写在下面
    });

    function init() {
       $.ajax({
           url: '/api/host/',
           type: 'GET',
           dataType: 'JSON',
           success: function (arg) {
               console.log(arg)
           }
       })
    }
</script>

在 url.py 里再加一个api接口的对应关系:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('host/', views.HostView.as_view()),
    path('api/host/', views.HostApi.as_view()),
]

处理函数直接返回字典:

class HostApi(View):

    def get(self, request, *args, **kwargs):
        ret = {'status': True,
               'message': None,
               'data': None,
               'error': None,
               }
        ret['message'] = 'API接口测试'
        return JsonResponse(ret)

从API接口获取数据2

这里换个方法来实现上面的处理函数。返回的数据不用字典记录,而是用类来记录。没啥差别,就是原来是用中括号来操作的,现在可以用点来操作。最后返回的时候还是要返回字典的,可以用 .__dict__() 来得到这样的一个字典:

class BaseResponse(object):
    def __init__(self):
        self.status = True
        self.message = None
        self.data = None
        self.error = None

class HostApi(View):

    def get(self, request, *args, **kwargs):
        response = BaseResponse()  # 先实例化
        table_config = [
            {
                'title': "主机名",  # 表格的列名
                'display': 1,  # 是否显示该列,1是显示,0是不显示
            },
            {
                'title': "端口号",
                'display': 1,
            }
        ]
        response.data = {'table_config': table_config}  # 用点来操作,就是给类的属性赋值
        return JsonResponse(response.__dict__)

前端处理返回的数据

把之前前端页面里AJAX请求的success的回调函数写完整。如果返回status是True,则把参数传递给接下来的处理的函数。否则弹一个alert():

<script>
    $(function () {
        init();  // 当页面加载完成,执行init()初始化方法。具体的方法写在下面
    });

    function init() {
       $.ajax({
           url: '/api/host/',
           type: 'GET',
           dataType: 'JSON',
           success: function (arg) {
               // console.log(arg)
               if (arg.status){
                    createThead(arg.data.table_config)
               }else{
                   alert(arg.error)
               }
           }
       })
    }

    function createThead(config){
        console.log(config)
    }
</script>

如此AJAX请求也完成了:发送了请求,接收了返回结果,然后把返回的结果交给之后的函数进行处理。接下来是就是完善createThead()这个函数了。这里要根据收到的title生成表格的thead的标签:

    function createThead(config){
        // console.log(config)
        var tr = document.createElement('tr');
        $.each(config, function (k, v) {
            if(v.display){
                var th = document.createElement('th');
                th.innerHTML = v.title;
                $(tr).append(th)
            }
        });
        $('#thead').append(tr);
    }

到现在这步,可以在前端看到表格的表头的内容。并且表头是根据后端返回的字典动态生成的。

准备数据库

到这里要后端返回数据了,表结构都还没建,我这里设计了三张表:

class UserInfo(models.Model):
    """用户表"""
    name = models.CharField(max_length=32)
    age = models.IntegerField()

class BusinessUnit(models.Model):
    """业务线"""
    name = models.CharField(max_length=32)

class Host(models.Model):
    """主机列表"""
    host_type_choices = ((1, '服务器'),
                         (2, '防火墙'),
                         (3, '路由器'),
                         (4, '交换机'),
                         )
    host_type = models.IntegerField(choices=host_type_choices)
    hostname = models.CharField(max_length=32)
    port = models.IntegerField()
    business_unit = models.ForeignKey(BusinessUnit, models.CASCADE)
    user = models.ForeignKey(UserInfo, models.CASCADE)

主要用主机列表,其他2张之后可以测试一下对跨表的支持,先一起建好。然后去数据库了随便加几条数据。

后端的处理函数(view),返回更多的数据

到这里,已经可以通过后端返回的字段名在前端动态的生成表头了。接下来把表的内容也显示出来,接着完善后端的处理函数,给前端返回更多的数据。下面是处理函数,根据table_config的配置,去数据库里去对应的字段,然后返回给前端。下面是目前处理函数完整的代码:

class HostApi(View):

    def get(self, request, *args, **kwargs):
        response = BaseResponse()  # 先实例化
        table_config = [
            {
                'field': 'hostname',  # 表中对应的字段名,必须要和字段名一致,下面要用作查询条件
                'title': "主机名",  # 表格的列名
                'display': 1,  # 是否显示该列,1是显示,0是不显示
            },
            {
                'field': 'id',
                'title': "ID",
                'display': 0,  # 这一列不用显示,但是前端能接收到数据
            },
            {
                'field': 'port',
                'title': "端口号",
                'display': 1,
            },
            {
                'field': None,  # 允许添加额外的列,这个列的内容没有对应的字段
                'title': "操作",
                'display': 1,
            }
        ]
        field_list = []
        for item in table_config:
            if item['field']:
                field_list.append(item['field'])

        # 写一个try,也可以把上面的内容都放进来,
        try:
            result = models.Host.objects.values(*field_list)
            result = list(result)
            response.data = {'table_config': table_config,
                             'data_list': result,
                             }
        except Exception as e:
            response.status = False
            # response.error = str(e)  # 错误信息,用下面的模块可以看到错误产生的位置
            import traceback
            response.error = traceback.format_exc()  # 返回详细的错误信息,包括哪个文件的哪一行
            print(response.error)
        return JsonResponse(response.__dict__)

这里主要就是去数据库里获取数据,然后把获取的QuerySet转成列表也放到response对象里,方便最后返回。
这里注意table_config的配置里有2种特殊的情况:

  • display为0,前端不显示的列。但是依然要把数据传给前端,之后会用到这里的数据
  • field为None,前端要显示,但是数据不是数据库里数据的列,之后会提供填充其中内容的方法

错误信息的优化
处理函数里加了个try,可以把处理函数的全部过程都写到try里进行捕获。如果捕获到异常,就会返回异常信息给前端。前端已经用arg.status来确认是否有异常返回了,下面会再优化一下前端异常显示的效果。
另外这里用了一个traceback模块,traceback对象中包含出错的行数、位置等数据,貌似也很有用。用例子中的方法就可以拿到了。等下面的小节把前端显示优化之后,可以随便哪句语句添加或者删除个字符搞个语法错误,测试效果。

前端显示效果

这里加了一个createTbody()方法,作用是把数据填充到表格里去。另外还有一个showError()方法,作用是如果收到的是后端捕获的异常信息,在标题下面显示出来。下面也是目前前端的完整代码:

<body>
<h1>主机列表</h1>
<table border="1">
    <thead id="thead"></thead>
    <tbody id="tbody"></tbody>
</table>

<script src="/static/jquery-1.12.4.js"></script>
<script>
    $(function () {
        init();  // 当页面加载完成,执行init()初始化方法。具体的方法写在下面
    });

    function init() {
        $.ajax({
            url: '/api/host/',
            type: 'GET',
            dataType: 'JSON',
            success: function (arg) {
                // console.log(arg)
                if (arg.status){
                    createThead(arg.data.table_config);
                    createTbody(arg.data.table_config, arg.data.data_list)
                }else{
                    //alert(arg.error);
                    showError(arg.error);
                }
            }
        })
    }

    function showError(msg) {
        // 插入错误信息
        var tag = document.createElement('p');
        $(tag).html(msg).CSS('color', 'red');
        $('h1').after(tag);
    }

    function createThead(config){
        // console.log(config)
        var tr = document.createElement('tr');
        $.each(config, function (k, v) {
            if(v.display){
                var th = document.createElement('th');
                th.innerHTML = v.title;
                $(tr).append(th)
            }
        });
        $('#thead').append(tr);
    }

    function createTbody(config, list) {
        // 循环数据,每条数据有一行
        $.each(list, function (k1, row) {
            var tr = document.createElement('tr');
            // 循环配置config,每条配置就是一个字段,即一列
            $.each(config, function (k2, configitem) {
                if (configItem.display){
                    var td = document.createElement('td');
                    td.innerHTML = row[configItem.field];
                    $(tr).append(td)
                }
            });
            $('#tbody').append(tr)
        })
    }
</script>
</body>

修改table_config的内容,调整前端显示的数据
前端的表格都是通过后端传递来的数据动态生成的。在上面模板的基础上,现在要修改表格显示的内容,只需要去后端调整table_config就可以了,比如改成这样,这里有跨表操作:

        table_config = [
            {
                'field': 'hostname',  # 表中对应的字段名,必须要和字段名一致,下面要用作查询条件
                'title': "主机名",  # 表格的列名
                'display': 1,  # 是否显示该列,1是显示,0是不显示
            },
            {
                'field': 'id',
                'title': "ID",
                'display': 0,  # 这一列不用显示,但是前端能接收到数据
            },
            {
                'field': 'port',
                'title': "端口号",
                'display': 1,
            },
            {
                'field': 'business_unit__name',
                'title': "业务线",
                'display': 1,
            },
            {
                'field': 'host_type',
                'title': "主机类型",
                'display': 1,
            },
            {
                'field': None,  # 允许添加额外的列,这个列的内容没有对应的字段
                'title': "操作",
                'display': 1,
            }
        ]

主机类型暂时没有办法,因为数据库里记录的值只是数值。而这个数值具体表示的内容是在内存里的。要显示内容首先要获得 models.Host.host_type_choices 然后通过数值拿到对应的文本内容。后面继续优化后应该会有解决的办法。

封装

先暂时写到这里,现在要把前端的js代码做一个封装,做成一个通用的组件。封装的知识点在之前学习jQuery的最后讲过,这里就用上了。封装好的代码如下:

(function ($) {

    var requestURL;
    function init() {
        $.ajax({
            url: requestURL,
            type: 'GET',
            dataType: 'JSON',
            success: function (arg) {
                // console.log(arg)
                if (arg.status){
                    createThead(arg.data.table_config);
                    createTbody(arg.data.table_config, arg.data.data_list)
                }else{
                    //alert(arg.error);
                    showError(arg.error);
                }
            }
        })
    }

    function showError(msg) {
        // 插入错误信息
        var tag = document.createElement('p');
        $(tag).html(msg).css('color', 'red');
        $('h1').after(tag);
    }

    function createThead(config){
        // console.log(config)
        var tr = document.createElement('tr');
        $.each(config, function (k, v) {
            if(v.display){
                var th = document.createElement('th');
                th.innerHTML = v.title;
                $(tr).append(th)
            }
        });
        $('#thead').append(tr);
    }

    function createTbody(config, list) {
        // 循环数据,每条数据有一行
        $.each(list, function (k1, row) {
            var tr = document.createElement('tr');
            // 循环配置config,每条配置就是一个字段,一列
            $.each(config, function (k2, configItem) {
                if (configItem.display){
                    var td = document.createElement('td');
                    td.innerHTML = row[configItem.field];
                    $(tr).append(td)
                }
            });
            $('#tbody').append(tr)
        })
    }

    $.extend({
        'show_table': function (url) {
            requestURL = url;
            init();
        }
    })
})(jQuery);

现在前端页面只要先引用这个js文件,然后调用一下extend里的show_table方法就和之前一样了:

<body>
<h1>主机列表</h1>
<table border="1">
    <thead id="thead"></thead>
    <tbody id="tbody"></tbody>
</table>

<script src="/static/jquery-1.12.4.js"></script>
<script src="/static/show-table.js"></script>
<script>
    $(function () {
        $.show_table('/api/host/');
    });
</script>
</body>

封装之后的js文件,其实就是一个插件了,可以灵活的运用到其他要生成表格的场景里。

输出字符串格式化

这里要进一步定制输出的内容。之前只能输出数据库里的内容。现在是把数据库的内容作为原始数据,但是输出到页面的内容可以通过format方法格式化后再最终展示出来。table_config里再加一个text属性。text内部有content属性,这个是最终要输出的内容,可以像format那样使用{}把需要格式化的内容标记出来。然后再在text内部的kwargs里,指定前面的这些占位符所对应的具体内容,这里面又用了@来标记这不是一个字符串,而是要取对应的字段的值。
所有的{}和@标记都是等到前端再处理的,后端只是进行设置,现在的table_config如下:

        table_config = [
            {
                'field': 'hostname',  # 表中对应的字段名,必须要和字段名一致,下面要用作查询条件
                'title': "主机名",  # 表格的列名
                'display': 1,  # 是否显示该列,1是显示,0是不显示
            },
            {
                'field': 'id',
                'title': "ID",
                'display': 0,  # 这一列不用显示,但是前端能接收到数据
                'text': None,  # 上面不显示,所以这里text有没有都没关系
            },
            {
                'field': 'port',
                'title': "端口号",
                'display': 1,
                'text': {'content': '端口:{port}', 'kwargs': {'port': '@port'}}
            },
            {
                'field': 'business_unit__id',
                'title': "业务线ID",
                'display': 0,
            },
            {
                'field': 'business_unit__name',
                'title': "业务线",
                'display': 1,
                'text': {'content': '{n}(id:{id})', 'kwargs': {'n': '@business_unit__name', 'id': '@business_unit__id'}}
            },
            {
                'field': 'host_type',
                'title': "主机类型",
                'display': 1,
                'text': {'content': '{type}', 'kwargs': {'type': '@host_type'}}
            },
            {
                'field': None,  # 允许添加额外的列,这个列的内容没有对应的字段
                'title': "操作",
                'display': 1,
                'text': {'content': '<a href="/api/host/{id}">查看详细</a>', 'kwargs': {'id': '@id'}}
            },
        ]

不显示的字段,display设置为0,那么就不显示了,所以text属性是用不到的。但是其他字段里可以通过@取到这个字段的值了。
有的显示的字段,我也没设置text,那么等下前端处理的时候,还是按照之前的方法来进行展示
最后的操作字段,现在可以加上任意内容了。这里写了一个a标签,并且href里加上了主机id。

前端代码
之前已经完成了封装,所以这里就是修改js文件里的内容。
之前是通过 td.innerHTML = row[configItem.field] 显示内容的。现在这个方法保留,在没有text属性的时候继续按这个来显示。否则,显示content的内容并且根据kwargs的内容进行格式化。前端是没有格式化方法的,这里自己写了一个(下一节展开),完整的代码如下:

(function ($) {

    var requestURL;
    function init() {
        $.ajax({
            url: requestURL,
            type: 'GET',
            dataType: 'JSON',
            success: function (arg) {
                // console.log(arg)
                if (arg.status){
                    createThead(arg.data.table_config);
                    createTbody(arg.data.table_config, arg.data.data_list)
                }else{
                    //alert(arg.error);
                    showError(arg.error);
                }
            }
        })
    }

    function showError(msg) {
        // 插入错误信息
        var tag = document.createElement('p');
        $(tag).html(msg).css('color', 'red');
        $('h1').after(tag);
    }

    function createThead(config){
        // console.log(config)
        var tr = document.createElement('tr');
        $.each(config, function (k, v) {
            if(v.display){
                var th = document.createElement('th');
                th.innerHTML = v.title;
                $(tr).append(th)
            }
        });
        $('#thead').append(tr);
    }

    function createTbody(config, list) {
        // 循环数据,每条数据有一行
        $.each(list, function (k1, row) {
            var tr = document.createElement('tr');
            // 循环配置config,每条配置就是一个字段,一列
            $.each(config, function (k2, configItem) {
                if (configItem.display){
                    var td = document.createElement('td');
                    if (!configItem.text){
                        td.innerHTML = row[configItem.field];
                    }else{
                        var kwargs = {};
                        // 把configItem.text.kwargs的内容存到上面的kwargs里
                        // 没有@开头的原样放过去,以@开头的做特殊处理
                        $.each(configItem.text.kwargs, function (key, value) {
                            if(value.startsWith('@')){
                                // 如果是以@开头,需要做特殊处理
                                var _value = value.substring(1, value.length);  // 把第一个字符截掉,即去掉@
                                kwargs[key] = row[_value]
                            }else{
                                kwargs[key] = value
                            }
                        });
                        td.innerHTML = configItem.text.content.format(kwargs);
                    }

                    $(tr).append(td)
                }
            });
            $('#tbody').append(tr)
        })
    }

    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
        return this.replace(/\{(\w+)\}/g, function (substring, args2) {
            return args[args2];
        })
    };

    $.extend({
        'show_table': function (url) {
            requestURL = url;
            init();
        }
    })
})(jQuery);

在前端增加format方法

这里要在Sting对象的原型里添加一个format()方法,让前端的字符串也可以像python那样,对字符串进行格式化输出。代码就下面简单的几行,正则匹配然后用replace做替换。不过替换的内容又是一个function,逻辑有点复杂了,总之先拿着现成的用把,稍微改改大概也行。暂时没有完全理解:

    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
        return this.replace(/\{(\w+)\}/g, function (substring, args2) {
            return args[args2];
        })
    };

为td定制属性

首先table_config里再加一个属性attr,用来定制td标签的属性:

        table_config = [
            {
                'field': 'hostname',  # 表中对应的字段名,必须要和字段名一致,下面要用作查询条件
                'title': "主机名",  # 表格的列名
                'display': 1,  # 是否显示该列,1是显示,0是不显示
                'attr': {'k1': 'v1', 'k2': 'v2'}
            },
            {
                'field': 'port',
                'title': "端口号",
                'display': 1,
                'text': {'content': '端口:{port}', 'kwargs': {'port': '@port'}},
                'attr': {'original': '@port'}
            },
        ]

然后在js插件里,td.innerHTML赋值之后,添加到tr标签里之前,插入下面这段,为td标签设置属性:

                    // 为td添加属性
                    if (configItem.attr){
                        $.each(configItem.attr, function (name, value) {
                            if(value.startsWith('@')){
                                // 如果是以@开头,需要做特殊处理
                                var _value = value.substring(1, value.length);  // 把第一个字符截掉,即去掉@
                                td.setAttribute(name, row[_value]);
                            }else{
                                td.setAttribute(name, value);
                            }
                        })
                    }

                    $(tr).append(td)

这里添加属性的时候,也支持@符号。
把单元格的原始数据保留一份在td的某个属性里,这样做的好处是,如果你支持在表格里做数据修改。当你要保存修改的时候,先通过js代码检查单元格里现在的内容和之前留在td属性里的原始内容是否一致。不一致才提交给后台进行更新,如果一致,那么这个单元格不需要更新。

双@标记

用什么表情都无所谓,但是这里需要一个新的标记,标记一个新的数据显示的方法。
这里解决之前显示 models.Host.host_type_choices 的问题了。后端返回的response.data里开辟一个key(global_dict),用来存放这类数据

            # 获取global_dict
            global_dict = {
                'business_unit': list(models.BusinessUnit.objects.values_list('id', 'name')),
                'host_type': models.Host.host_type_choices,
            }

            response.data = {'table_config': table_config,
                             'data_list': result,
                             'global_dict': global_dict,
                             }

这样的数据格式不但放在内存里的choices可以用,ForeignKey使用 .values_list()方法也能生成一样的数据,所以也能用。这种方法是不跨表的,适合条目比较少的情况。如果表里行数很多的话就不适合了,一方面所有的条目都会传递给客户端,另一方面前端是遍历查找。
这里需要一个新的标记,标记是去global_dict里去查找对应的内容。所以用了两个@。那么table_config现在要这么写:

        table_config = [
            {
                'field': 'hostname',  # 表中对应的字段名,必须要和字段名一致,下面要用作查询条件
                'title': "主机名",  # 表格的列名
                'display': 1,  # 是否显示该列,1是显示,0是不显示
                'attr': {'k1': 'v1', 'k2': 'v2'}
            },
            {
                'field': 'business_unit',
                'title': "业务线_不跨表",
                'display': 1,
                'text': {'content': '{n}', 'kwargs': {'n': '@@business_unit'}}
            },
            {
                'field': 'host_type',
                'title': "主机类型",
                'display': 1,
                'text': {'content': '{type}', 'kwargs': {'type': '@@host_type'}}
            },
        ]

前端的实现
先处理response.data.global_dict数据的接收。所有的数据都是在AJAX的success方法里在参数arg里,原先已经有2个方法了,这里再增加一个方法,保存global_dict数据:

                    initGlobal(arg.data.global_dict);  // AJAX的success函数里新加这个方法
                    createThead(arg.data.table_config);
                    createTbody(arg.data.table_config, arg.data.data_list)

调用的方法,就是把这个数据暂存到一个在插件内部是全局有效的变量GLOBAL_DICT里,这样做应该是方便在插件内部的其他方法里调用:

    // 用户保存当前作用域内的“全局变量”
    var GLOBAL_DICT = {};

    function initGlobal(globalDict) {
        $.each(globalDict, function (k, v) {
            GLOBAL_DICT[k] = v;
        })
    }

然后来处理@@的解析,在原来的@的解析的if里再增加一个分支:

                        var kwargs = {};
                        // 把configItem.text.kwargs的内容存到上面的kwargs里
                        // 没有@开头的原样放过去,以@开头的做特殊处理
                        $.each(configItem.text.kwargs, function (key, value) {
                            if(value.startsWith('@@')){
                                var global_name = value.substring(2, value.length);
                                // console.log(GLOBAL_DICT[global_name]);
                                $.each(GLOBAL_DICT[global_name], function (index, arr) {
                                    if (arr[0] === row[global_name]){
                                        kwargs[key] = arr[1];
                                        return false;  // 匹配到一个,就退出遍历
                                    }
                                });
                            } else if(value.startsWith('@')){
                                // 如果是以@开头,需要做特殊处理
                                var _value = value.substring(1, value.length);  // 把第一个字符截掉,即去掉@
                                kwargs[key] = row[_value]
                            }else{
                                kwargs[key] = value
                            }
                        });

这里用的是遍历的方式来查找的,所以如果列表太长就不太适合了。放在内存中的choices应该都不会很长。如果是ForeignKey,现在有2个方法可以显示了。这个方法不跨表,但是数据太多就不适合了。

完整的代码:

路由的对应关系,urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('host/', views.HostView.as_view()),
    path('api/host/', views.HostApi.as_view()),
]

表结构,models.py

class UserInfo(models.Model):
    """用户表"""
    name = models.CharField(max_length=32)
    age = models.IntegerField()

class BusinessUnit(models.Model):
    """业务线"""
    name = models.CharField(max_length=32)

class Host(models.Model):
    """主机列表"""
    host_type_choices = ((1, '服务器'),
                         (2, '防火墙'),
                         (3, '路由器'),
                         (4, '交换机'),
                         )
    host_type = models.IntegerField(choices=host_type_choices)
    hostname = models.CharField(max_length=32)
    port = models.IntegerField()
    business_unit = models.ForeignKey(BusinessUnit, models.CASCADE)
    user = models.ForeignKey(UserInfo, models.CASCADE)

处理函数,views.py

class BaseResponse(object):
    def __init__(self):
        self.status = True
        self.message = None
        self.data = None
        self.error = None

class HostView(View):

    def get(self, request, *args, **kwargs):
        return render(request, 'host.html')

class HostApi(View):

    def get(self, request, *args, **kwargs):
        response = BaseResponse()  # 先实例化
        table_config = [
            {
                'field': 'hostname',  # 表中对应的字段名,必须要和字段名一致,下面要用作查询条件
                'title': "主机名",  # 表格的列名
                'display': 1,  # 是否显示该列,1是显示,0是不显示
                'attr': {'k1': 'v1', 'k2': 'v2'}
            },
            {
                'field': 'id',
                'title': "ID",
                'display': 0,  # 这一列不用显示,但是前端能接收到数据
                'text': None,  # 上面不显示,所以这里text有没有都没关系
            },
            {
                'field': 'port',
                'title': "端口号",
                'display': 1,
                'text': {'content': '端口:{port}', 'kwargs': {'port': '@port'}},
                'attr': {'original': '@port'}
            },
            {
                'field': 'business_unit__id',
                'title': "业务线ID",
                'display': 0,
            },
            {
                'field': 'business_unit__name',
                'title': "业务线",
                'display': 1,
                'text': {'content': '{n}(id:{id})', 'kwargs': {'n': '@business_unit__name', 'id': '@business_unit__id'}}
            },
            {
                'field': 'business_unit',
                'title': "业务线_不跨表",
                'display': 1,
                'text': {'content': '{n}', 'kwargs': {'n': '@@business_unit'}}
            },
            {
                'field': 'host_type',
                'title': "主机类型",
                'display': 1,
                'text': {'content': '{type}', 'kwargs': {'type': '@@host_type'}}
            },
            {
                'field': None,  # 允许添加额外的列,这个列的内容没有对应的字段
                'title': "操作",
                'display': 1,
                'text': {'content': '<a href="/api/host/{id}">查看详细</a>', 'kwargs': {'id': '@id'}}
            },
        ]
        field_list = []
        for item in table_config:
            if item['field']:
                field_list.append(item['field'])

        # 写一个try,也可以把上面的内容都放进来,
        try:
            result = models.Host.objects.values(*field_list)
            result = list(result)

            # 获取global_dict
            global_dict = {
                'business_unit': list(models.BusinessUnit.objects.values_list('id', 'name')),
                'host_type': models.Host.host_type_choices,
            }

            response.data = {'table_config': table_config,
                             'data_list': result,
                             'global_dict': global_dict,
                             }
        except Exception as e:
            response.status = False
            # response.error = str(e)  # 错误信息,用下面的模块可以看到错误产生的位置
            import traceback
            response.error = traceback.format_exc()  # 返回详细的错误信息,包括哪个文件的哪一行
            print(response.error)
        return JsonResponse(response.__dict__)

前端主页,host.html

<body>
<h1>主机列表</h1>
<table border="1">
    <thead id="thead"></thead>
    <tbody id="tbody"></tbody>
</table>

<script src="/static/jquery-1.12.4.js"></script>
<script src="/static/show-table.js"></script>
<script>
    $(function () {
        $.show_table('/api/host/');
    });
</script>
</body>

前端插件,show-table.js

(function ($) {

    // 用户保存当前作用域内的“全局变量”
    var GLOBAL_DICT = {};

    var requestURL;
    function init() {
        $.ajax({
            url: requestURL,
            type: 'GET',
            dataType: 'JSON',
            success: function (arg) {
                // console.log(arg)
                if (arg.status){
                    initGlobal(arg.data.global_dict);
                    createThead(arg.data.table_config);
                    createTbody(arg.data.table_config, arg.data.data_list)
                }else{
                    //alert(arg.error);
                    showError(arg.error);
                }
            }
        })
    }

    function showError(msg) {
        // 插入错误信息
        var tag = document.createElement('p');
        $(tag).html(msg).css('color', 'red');
        $('h1').after(tag);
    }

    function initGlobal(globalDict) {
        $.each(globalDict, function (k, v) {
            GLOBAL_DICT[k] = v;
        })
    }

    function createThead(config){
        // console.log(config)
        var tr = document.createElement('tr');
        $.each(config, function (k, v) {
            if(v.display){
                var th = document.createElement('th');
                th.innerHTML = v.title;
                $(tr).append(th)
            }
        });
        $('#thead').append(tr);
    }

    function createTbody(config, list) {
        // 循环数据,每条数据有一行
        $.each(list, function (k1, row) {
            var tr = document.createElement('tr');
            // 循环配置config,每条配置就是一个字段,一列
            $.each(config, function (k2, configItem) {
                if (configItem.display){
                    var td = document.createElement('td');
                    if (!configItem.text){
                        td.innerHTML = row[configItem.field];
                    }else{
                        var kwargs = {};
                        // 把configItem.text.kwargs的内容存到上面的kwargs里
                        // 没有@开头的原样放过去,以@开头的做特殊处理
                        $.each(configItem.text.kwargs, function (key, value) {
                            if(value.startsWith('@@')){
                                var global_name = value.substring(2, value.length);
                                // console.log(GLOBAL_DICT[global_name]);
                                $.each(GLOBAL_DICT[global_name], function (index, arr) {
                                    if (arr[0] === row[global_name]){
                                        kwargs[key] = arr[1];
                                        return false;  // 匹配到一个,就退出遍历
                                    }
                                });
                            } else if(value.startsWith('@')){
                                // 如果是以@开头,需要做特殊处理
                                var _value = value.substring(1, value.length);  // 把第一个字符截掉,即去掉@
                                kwargs[key] = row[_value]
                            }else{
                                kwargs[key] = value
                            }
                        });
                        td.innerHTML = configItem.text.content.format(kwargs);
                    }

                    // 为td添加属性
                    if (configItem.attr){
                        $.each(configItem.attr, function (name, value) {
                            if(value.startsWith('@')){
                                // 如果是以@开头,需要做特殊处理
                                var _value = value.substring(1, value.length);  // 把第一个字符截掉,即去掉@
                                td.setAttribute(name, row[_value]);
                            }else{
                                td.setAttribute(name, value);
                            }
                        })
                    }

                    $(tr).append(td)
                }
            });
            $('#tbody').append(tr)
        })
    }

    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
        return this.replace(/\{(\w+)\}/g, function (substring, args2) {
            return args[args2];
        })
    };

    $.extend({
        'show_table': function (url) {
            requestURL = url;
            init();
        }
    })
})(jQuery);

--结束END--

本文标题: Python自动化开发学习-RESTfu

本文链接: https://www.lsjlt.com/news/189713.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Python自动化开发学习-RESTfu
    RESTful API 是一种面向资源编程,也叫表征状态转移(英文:Representational State Transfer,简称REST)。认为网络上所有的东西都是资源,对资源的操作无非就是增删改查。 传统的方法 比如有个资产的页...
    99+
    2023-01-31
    Python RESTfu
  • Python自动化开发学习-Scrapy
    讲师博客:https://www.cnblogs.com/wupeiqi/p/6229292.html中文资料(有示例参考):http://www.scrapyd.cn/doc/ Scrapy是一个为了爬取网站数据,提取结构性数据而编写的...
    99+
    2023-01-31
    Python Scrapy
  • Python自动化开发学习1
    一、开篇的大段毒鸡汤真是够补。正好在外面旅游,一路上带着耳机就全部听完了。二、进入正题,结果还是介绍。说下版本问题,尽量还是用Python3。三、Hello World。就是个仪式感,别的没啥print("你好")四、变量。虽然驼峰规则也行...
    99+
    2023-01-31
    Python
  • Python自动化开发学习6
    假设我们要在我们的程序里表示狗,狗有如下属性:名字、品种、颜色。那么可以先定义一个模板,然后调用这个模板生成各种狗。 def dog(name,d_type,color): data = { 'name':name...
    99+
    2023-01-31
    Python
  • Python自动化开发学习7
    class A 经典类写法,查找方式深度优先class A(object) 新式类写法,查找方式广度优先上面是python2的语法,python3里可能已经没有经典类了。不管有没有,都用形式类来写就对了。上面都是上节讲的内容,再讲一下构造...
    99+
    2023-01-31
    Python
  • Python自动化开发学习-Django
    django amdin是django提供的一个后台管理页面,该管理页面提供完善的html和css,使得你在通过Model创建完数据库表之后,就可以对数据进行增删改查。 创建一个项目,或者是用已有的项目使用下面的命令创建生成数据库,这里虽...
    99+
    2023-01-31
    Python Django
  • Python自动化开发学习10
    上次讲了由于GIL锁的存在,Python的多线程是假的,用的还是CPU的单核。Python的多线程只是利用了CPU的上下文切换,上下分切换也是占用CPU的。那么什么时候用多行程?Python的多线程,适合IO密集型的任务,不适合CPU密集...
    99+
    2023-01-31
    Python
  • Python自动化开发学习3
    函数通过函数,可以定义一段代码块,之后通过函数名可以反复调用定义一个函数:def alert():     "打印Hello World"     print("Hello World")使用def来定义函数,第二行建议使用使用文档字符串进...
    99+
    2023-01-31
    Python
  • Python自动化开发学习2-2
    集合创建集合可以用set(),或者直接用{}set_a = set([1,2,3,4,5]) set_b = {1,3,5,7,9} print(set_a) print(set_b) print(type(set_a),type(set_...
    99+
    2023-01-31
    Python
  • Python自动化开发学习4-3
    JSON 和 pickle序列化:把数据对象变成字符串的形式,这样可以保存在文件中。反之就是反序列化python自带的str()可以完成序列化,然后eval()可以反序列化,但是我们先把他们忘记。不知道适用范围是多大。我们用json的规范来...
    99+
    2023-01-31
    Python
  • Python自动化开发学习20-Djan
    一边写一个示例,一边回顾一下之前的内容,引出新的知识点展开讲解 回顾-创建项目 下面就从创建项目开始,一步一步先做一个页面出来。一、先创建一个新的Django项目项目名是:week20,App名是:app01因为是通过PyCharm创建的...
    99+
    2023-01-31
    Python Djan
  • Python自动化开发学习11-Redi
    缓存系统也可以叫缓存数据库,现在主流的系统有 Redis 和 Memcached :MongoDB,比较早的缓存系统,直接持久化到硬盘Redis,现在正火的。半持久化数据,数据默认存在内存中,可以持久化到硬盘里持久保存。效率高,在单线程下...
    99+
    2023-01-31
    Python Redi
  • Python自动化开发学习21-Djan
    URL传递额外的参数 在url.py里,除了默认会传一个request给处理函数,还可以传递额外的参数,把一个字典作为第三个参数传入,之后就可以在处理函数里取到对应的值: from django.urls import path from...
    99+
    2023-01-31
    Python Djan
  • Python自动化开发学习12-Mari
    主流的关系型数据库大概有下面这些: Oracle : 甲骨文公司的企业级的数据库 SQL Server : 微软的 MySQL : 免费的数据库,现在也属于Oracle的旗下产品 MariaDB : 开源的数据库,MySQL的一个分支 ...
    99+
    2023-01-31
    Python Mari
  • Python自动化开发学习11-Rabb
    其他主流的MQ还有:ZeroMQ 和 ActiveMQ ,就知道一下好了。 安装RabbitMQ 我是在CentOS7上安装的,直接用yum安装,安装起来就比较简单了。 安装epel源 首先你得有EPEL源,没有的话可以安装一下: $ y...
    99+
    2023-01-31
    Python Rabb
  • Python自动化开发学习25-Djan
    下面要讲的是基于模板语言的实现方法,完全没有使用js。讲的时候有点混乱,把其他与效果实现无关的小知识点也顺带讲了一下。不过我最后做了小结。 准备表结构 这里讲组合搜索,所以要2个搜索条件。这里用一个选项保存在内存中的type和一个保存在数...
    99+
    2023-01-31
    Python Djan
  • Python自动化开发学习19-Djan
    接下来,我们把Django分为视图(View)、路由系统(URL)、ORM(Model)、模板(Templates )这4块进行学习。 提交数据 上节课已经用过 request.POST.get() 获取提交的数据了,现在来看看有多选框的...
    99+
    2023-01-31
    Python Djan
  • Python自动化开发学习1-2
    模块Python有他有非常丰富的标准库和第三方库。使用前要先用import命令导入,然后才能在之后的代码中调用。sys 模块import sys print(sys.path)  # 打印环境变量结果输出的是一串目录列表,是python运行...
    99+
    2023-01-31
    Python
  • Python自动化开发学习17-jQue
    jQuery 是 JavaScript 的一个类库,类似 python 中的模块。jQuery在线手册:http://jquery.cuishifeng.cn/官网:http://jquery.com/ 版本选择 目前jQuery有三个大...
    99+
    2023-01-31
    Python jQue
  • Python自动化开发学习-爬虫3(性能
    讲师的博客:https://www.cnblogs.com/wupeiqi/p/6229292.html在编写爬虫时,性能的消耗主要在IO请求中,当单进程单线程模式下请求URL时必然会引起等待,从而使得请求整体变慢。比如找10个国外的资源...
    99+
    2023-01-31
    爬虫 性能 Python
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作