亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python自動化開發學習-RESTful API

發布時間:2020-07-30 02:20:51 來源:網絡 閱讀:12016 作者:騎士救兵 欄目:編程語言

RESTful API

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

JsonResponse

使用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>
<h2>主機列表</h2>
<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>

測試一下,應該只能看到h2標簽里的內容。頁面初始化之后會彈一個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>
<h2>主機列表</h2>
<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');
        $('h2').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');
        $('h2').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>
<h2>主機列表</h2>
<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');
        $('h2').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>
<h2>主機列表</h2>
<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');
        $('h2').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);
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

云南省| 白沙| 呼图壁县| 阿克陶县| 碌曲县| 长汀县| 枣庄市| 马关县| 蒲城县| 平昌县| 华宁县| 张家口市| 奉贤区| 红桥区| 策勒县| 申扎县| 安义县| 南和县| 沭阳县| 景东| 湘西| 巴林左旗| 新丰县| 芮城县| 紫阳县| 昌黎县| 新巴尔虎右旗| 子长县| 精河县| 永靖县| 泾源县| 观塘区| 吴忠市| 沅江市| 怀集县| 阜宁县| 化德县| 昭通市| 五常市| 江川县| 安徽省|