RESTful API 是一種面向資源編程,也叫表征狀態(tài)轉(zhuǎn)移(英文:Representational State Transfer,簡稱REST)。
認(rèn)為網(wǎng)絡(luò)上所有的東西都是資源,對資源的操作無非就是增刪改查。
10年積累的成都網(wǎng)站建設(shè)、網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先建設(shè)網(wǎng)站后付款的網(wǎng)站建設(shè)流程,更有睢寧縣免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
比如有個資產(chǎn)的頁面,URL是 www.example.com/asset
。要對它進(jìn)行增刪改查,可能使用不同的url來區(qū)分:
www.example.com/addAsset
:增加資產(chǎn),一般是POST方法。www.example.com/delAsset
:刪除資產(chǎn),一般是POST方法。www.example.com/editAsset
:修改資產(chǎn),一般是POST方法。www.example.com/showAsset
:顯示資產(chǎn),一般是GET方法。也可能使用 www.example.com/asset
作為url這里的url一般使用的都是動詞,表示是一個動作。
RESTful API 用一個url代指一個資源,既然是資源,這個詞要用名詞。那么這個url就是 www.example.com/asset
。增刪改查都是通過這個url實(shí)現(xiàn)的,通過不同的method實(shí)現(xiàn)不同的方法,常用的是下面幾個方法:
在django中,推薦使用CBV。當(dāng)然FBV也不是不行。
這篇貌似講的很好,值得參考:http://www.ruanyifeng.com/blog/2014/05/restful_api.html
使用API就會有很多序列化數(shù)據(jù)返回的操作。
之前當(dāng)我們需要給前端返回序列化后的字符串時,往往都是先調(diào)用json.dumps()這個方法,然后再用HttpResponse()把字符串返回給前端。既然每次都要這么搞,于是django給我么封裝了一個新方法,直接完成序列化和返回字符串。
JsonResponse這個類是HttpRespon的子類,通過它直接就可以把字典進(jìn)行序列化并返回給前端。
>>> from django.http import JsonResponse
>>> response = JsonResponse({'foo': 'bar'})
>>> response.content
'{"foo": "bar"}'
默認(rèn)只能傳入一個字典,并且API要返回的數(shù)據(jù)應(yīng)該也就是字典。但是如果一定要序列化一個其他的類型,比如列表,可以設(shè)置safe參數(shù):
>>> response = JsonResponse([1, 2, 3], safe=False)
如果要自定義編碼器,和json方法一樣,通過下面的參數(shù)指定:
>>> response = JsonResponse(data, encoder=MyJSONEncoder)
這里的 encoder 參數(shù)就是原生的 json.dumps 的cls參數(shù)。源碼里最后也是調(diào)用原生的 json.dumps 把 encoder 傳給cls 的。
另外,也可以只定義類中的 default 方法,但是 JsonRespons 沒有專門的參數(shù)來接收,不過調(diào)用原生的 json.dumps 時,會把 json_dumps_params 參數(shù)傳遞過去。也就是在 JsonRespons 里,可以把所有的 json.dumps 的參數(shù)先傳給 json_dumps_params 。調(diào)用原生的 json.dumps 方法的源碼是這樣的:
data = json.dumps(data, cls=encoder, **json_dumps_params)
所以,可以這么用:
return JsonResponse(
data={'obj': obj},
json_dumps_params={'default': fn}, # 這個參數(shù)是傳給原生的 json.dumps 執(zhí)行的參數(shù)
)
# 上面自然是要先定義好一個fn函數(shù)的,比如下面這樣
def fn(obj):
if hasattr(obj, 'isoformat'):
return obj.strftime("%Y-%m-%d %T")
這段代碼用來從數(shù)據(jù)庫獲取數(shù)據(jù),然后在前端動態(tài)的生成表格。
完整的代碼在最后,前面是一步一步把這個功能給做出來。
處理函數(shù)主要負(fù)責(zé)兩件事情:
在 urls.py 里寫好對應(yīng)關(guān)系:
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()),
]
寫一個處理函數(shù) views.py,這里用CBV,直接返回頁面
from django.views import View
class HostView(View):
def get(self, request, *args, **kwargs):
return render(request, 'host.html')
前端的頁面先返回一個空的表格,之后再填充表格內(nèi)容:
主機(jī)列表
測試一下,應(yīng)該只能看到h2標(biāo)簽里的內(nèi)容。頁面初始化之后會彈一個alert。
寫一下前端的init()方法,發(fā)送一個AJAX請求到一個新的url,然后接收到返回的數(shù)據(jù)后,后臺看一下:
在 url.py 里再加一個api接口的對應(yīng)關(guān)系:
urlpatterns = [
path('admin/', admin.site.urls),
path('host/', views.HostView.as_view()),
path('api/host/', views.HostApi.as_view()),
]
處理函數(shù)直接返回字典:
class HostApi(View):
def get(self, request, *args, **kwargs):
ret = {'status': True,
'message': None,
'data': None,
'error': None,
}
ret['message'] = 'API接口測試'
return JsonResponse(ret)
這里換個方法來實(shí)現(xiàn)上面的處理函數(shù)。返回的數(shù)據(jù)不用字典記錄,而是用類來記錄。沒啥差別,就是原來是用中括號來操作的,現(xiàn)在可以用點(diǎn)來操作。最后返回的時候還是要返回字典的,可以用 .__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() # 先實(shí)例化
table_config = [
{
'title': "主機(jī)名", # 表格的列名
'display': 1, # 是否顯示該列,1是顯示,0是不顯示
},
{
'title': "端口號",
'display': 1,
}
]
response.data = {'table_config': table_config} # 用點(diǎn)來操作,就是給類的屬性賦值
return JsonResponse(response.__dict__)
把之前前端頁面里AJAX請求的success的回調(diào)函數(shù)寫完整。如果返回status是True,則把參數(shù)傳遞給接下來的處理的函數(shù)。否則彈一個alert():
如此AJAX請求也完成了:發(fā)送了請求,接收了返回結(jié)果,然后把返回的結(jié)果交給之后的函數(shù)進(jìn)行處理。接下來是就是完善createThead()這個函數(shù)了。這里要根據(jù)收到的title生成表格的thead的標(biāo)簽:
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);
}
到現(xiàn)在這步,可以在前端看到表格的表頭的內(nèi)容。并且表頭是根據(jù)后端返回的字典動態(tài)生成的。
到這里要后端返回數(shù)據(jù)了,表結(jié)構(gòu)都還沒建,我這里設(shè)計(jì)了三張表:
class UserInfo(models.Model):
"""用戶表"""
name = models.CharField(max_length=32)
age = models.IntegerField()
class BusinessUnit(models.Model):
"""業(yè)務(wù)線"""
name = models.CharField(max_length=32)
class Host(models.Model):
"""主機(jī)列表"""
host_type_choices = ((1, '服務(wù)器'),
(2, '防火墻'),
(3, '路由器'),
(4, '交換機(jī)'),
)
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)
主要用主機(jī)列表,其他2張之后可以測試一下對跨表的支持,先一起建好。然后去數(shù)據(jù)庫了隨便加幾條數(shù)據(jù)。
到這里,已經(jīng)可以通過后端返回的字段名在前端動態(tài)的生成表頭了。接下來把表的內(nèi)容也顯示出來,接著完善后端的處理函數(shù),給前端返回更多的數(shù)據(jù)。下面是處理函數(shù),根據(jù)table_config的配置,去數(shù)據(jù)庫里去對應(yīng)的字段,然后返回給前端。下面是目前處理函數(shù)完整的代碼:
class HostApi(View):
def get(self, request, *args, **kwargs):
response = BaseResponse() # 先實(shí)例化
table_config = [
{
'field': 'hostname', # 表中對應(yīng)的字段名,必須要和字段名一致,下面要用作查詢條件
'title': "主機(jī)名", # 表格的列名
'display': 1, # 是否顯示該列,1是顯示,0是不顯示
},
{
'field': 'id',
'title': "ID",
'display': 0, # 這一列不用顯示,但是前端能接收到數(shù)據(jù)
},
{
'field': 'port',
'title': "端口號",
'display': 1,
},
{
'field': None, # 允許添加額外的列,這個列的內(nèi)容沒有對應(yīng)的字段
'title': "操作",
'display': 1,
}
]
field_list = []
for item in table_config:
if item['field']:
field_list.append(item['field'])
# 寫一個try,也可以把上面的內(nèi)容都放進(jìn)來,
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) # 錯誤信息,用下面的模塊可以看到錯誤產(chǎn)生的位置
import traceback
response.error = traceback.format_exc() # 返回詳細(xì)的錯誤信息,包括哪個文件的哪一行
print(response.error)
return JsonResponse(response.__dict__)
這里主要就是去數(shù)據(jù)庫里獲取數(shù)據(jù),然后把獲取的QuerySet轉(zhuǎn)成列表也放到response對象里,方便最后返回。
這里注意table_config的配置里有2種特殊的情況:
錯誤信息的優(yōu)化
處理函數(shù)里加了個try,可以把處理函數(shù)的全部過程都寫到try里進(jìn)行捕獲。如果捕獲到異常,就會返回異常信息給前端。前端已經(jīng)用arg.status來確認(rèn)是否有異常返回了,下面會再優(yōu)化一下前端異常顯示的效果。
另外這里用了一個traceback模塊,traceback對象中包含出錯的行數(shù)、位置等數(shù)據(jù),貌似也很有用。用例子中的方法就可以拿到了。等下面的小節(jié)把前端顯示優(yōu)化之后,可以隨便哪句語句添加或者刪除個字符搞個語法錯誤,測試效果。
這里加了一個createTbody()方法,作用是把數(shù)據(jù)填充到表格里去。另外還有一個showError()方法,作用是如果收到的是后端捕獲的異常信息,在標(biāo)題下面顯示出來。下面也是目前前端的完整代碼:
主機(jī)列表
修改table_config的內(nèi)容,調(diào)整前端顯示的數(shù)據(jù)
前端的表格都是通過后端傳遞來的數(shù)據(jù)動態(tài)生成的。在上面模板的基礎(chǔ)上,現(xiàn)在要修改表格顯示的內(nèi)容,只需要去后端調(diào)整table_config就可以了,比如改成這樣,這里有跨表操作:
table_config = [
{
'field': 'hostname', # 表中對應(yīng)的字段名,必須要和字段名一致,下面要用作查詢條件
'title': "主機(jī)名", # 表格的列名
'display': 1, # 是否顯示該列,1是顯示,0是不顯示
},
{
'field': 'id',
'title': "ID",
'display': 0, # 這一列不用顯示,但是前端能接收到數(shù)據(jù)
},
{
'field': 'port',
'title': "端口號",
'display': 1,
},
{
'field': 'business_unit__name',
'title': "業(yè)務(wù)線",
'display': 1,
},
{
'field': 'host_type',
'title': "主機(jī)類型",
'display': 1,
},
{
'field': None, # 允許添加額外的列,這個列的內(nèi)容沒有對應(yīng)的字段
'title': "操作",
'display': 1,
}
]
主機(jī)類型暫時沒有辦法,因?yàn)閿?shù)據(jù)庫里記錄的值只是數(shù)值。而這個數(shù)值具體表示的內(nèi)容是在內(nèi)存里的。要顯示內(nèi)容首先要獲得 models.Host.host_type_choices 然后通過數(shù)值拿到對應(yīng)的文本內(nèi)容。后面繼續(xù)優(yōu)化后應(yīng)該會有解決的辦法。
先暫時寫到這里,現(xiàn)在要把前端的js代碼做一個封裝,做成一個通用的組件。封裝的知識點(diǎn)在之前學(xué)習(xí)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) {
// 循環(huán)數(shù)據(jù),每條數(shù)據(jù)有一行
$.each(list, function (k1, row) {
var tr = document.createElement('tr');
// 循環(huán)配置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);
現(xiàn)在前端頁面只要先引用這個js文件,然后調(diào)用一下extend里的show_table方法就和之前一樣了:
主機(jī)列表
封裝之后的js文件,其實(shí)就是一個插件了,可以靈活的運(yùn)用到其他要生成表格的場景里。
這里要進(jìn)一步定制輸出的內(nèi)容。之前只能輸出數(shù)據(jù)庫里的內(nèi)容。現(xiàn)在是把數(shù)據(jù)庫的內(nèi)容作為原始數(shù)據(jù),但是輸出到頁面的內(nèi)容可以通過format方法格式化后再最終展示出來。table_config里再加一個text屬性。text內(nèi)部有content屬性,這個是最終要輸出的內(nèi)容,可以像format那樣使用{}把需要格式化的內(nèi)容標(biāo)記出來。然后再在text內(nèi)部的kwargs里,指定前面的這些占位符所對應(yīng)的具體內(nèi)容,這里面又用了@來標(biāo)記這不是一個字符串,而是要取對應(yīng)的字段的值。
所有的{}和@標(biāo)記都是等到前端再處理的,后端只是進(jìn)行設(shè)置,現(xiàn)在的table_config如下:
table_config = [
{
'field': 'hostname', # 表中對應(yīng)的字段名,必須要和字段名一致,下面要用作查詢條件
'title': "主機(jī)名", # 表格的列名
'display': 1, # 是否顯示該列,1是顯示,0是不顯示
},
{
'field': 'id',
'title': "ID",
'display': 0, # 這一列不用顯示,但是前端能接收到數(shù)據(jù)
'text': None, # 上面不顯示,所以這里text有沒有都沒關(guān)系
},
{
'field': 'port',
'title': "端口號",
'display': 1,
'text': {'content': '端口:{port}', 'kwargs': {'port': '@port'}}
},
{
'field': 'business_unit__id',
'title': "業(yè)務(wù)線ID",
'display': 0,
},
{
'field': 'business_unit__name',
'title': "業(yè)務(wù)線",
'display': 1,
'text': {'content': '{n}(id:{id})', 'kwargs': {'n': '@business_unit__name', 'id': '@business_unit__id'}}
},
{
'field': 'host_type',
'title': "主機(jī)類型",
'display': 1,
'text': {'content': '{type}', 'kwargs': {'type': '@host_type'}}
},
{
'field': None, # 允許添加額外的列,這個列的內(nèi)容沒有對應(yīng)的字段
'title': "操作",
'display': 1,
'text': {'content': '查看詳細(xì)', 'kwargs': {'id': '@id'}}
},
]
不顯示的字段,display設(shè)置為0,那么就不顯示了,所以text屬性是用不到的。但是其他字段里可以通過@取到這個字段的值了。
有的顯示的字段,我也沒設(shè)置text,那么等下前端處理的時候,還是按照之前的方法來進(jìn)行展示
最后的操作字段,現(xiàn)在可以加上任意內(nèi)容了。這里寫了一個a標(biāo)簽,并且href里加上了主機(jī)id。
前端代碼
之前已經(jīng)完成了封裝,所以這里就是修改js文件里的內(nèi)容。
之前是通過 td.innerHTML = row[configItem.field]
顯示內(nèi)容的?,F(xiàn)在這個方法保留,在沒有text屬性的時候繼續(xù)按這個來顯示。否則,顯示content的內(nèi)容并且根據(jù)kwargs的內(nèi)容進(jìn)行格式化。前端是沒有格式化方法的,這里自己寫了一個(下一節(jié)展開),完整的代碼如下:
(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) {
// 循環(huán)數(shù)據(jù),每條數(shù)據(jù)有一行
$.each(list, function (k1, row) {
var tr = document.createElement('tr');
// 循環(huán)配置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的內(nèi)容存到上面的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)
})
}
// 為字符串創(chuàng)建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);
這里要在Sting對象的原型里添加一個format()方法,讓前端的字符串也可以像python那樣,對字符串進(jìn)行格式化輸出。代碼就下面簡單的幾行,正則匹配然后用replace做替換。不過替換的內(nèi)容又是一個function,邏輯有點(diǎn)復(fù)雜了,總之先拿著現(xiàn)成的用把,稍微改改大概也行。暫時沒有完全理解:
// 為字符串創(chuàng)建format方法,用于字符串格式化
String.prototype.format = function (args) {
return this.replace(/\{(\w+)\}/g, function (substring, args2) {
return args[args2];
})
};
首先table_config里再加一個屬性attr,用來定制td標(biāo)簽的屬性:
table_config = [
{
'field': 'hostname', # 表中對應(yīng)的字段名,必須要和字段名一致,下面要用作查詢條件
'title': "主機(jī)名", # 表格的列名
'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標(biāo)簽里之前,插入下面這段,為td標(biāo)簽設(shè)置屬性:
// 為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)
這里添加屬性的時候,也支持@符號。
把單元格的原始數(shù)據(jù)保留一份在td的某個屬性里,這樣做的好處是,如果你支持在表格里做數(shù)據(jù)修改。當(dāng)你要保存修改的時候,先通過js代碼檢查單元格里現(xiàn)在的內(nèi)容和之前留在td屬性里的原始內(nèi)容是否一致。不一致才提交給后臺進(jìn)行更新,如果一致,那么這個單元格不需要更新。
用什么表情都無所謂,但是這里需要一個新的標(biāo)記,標(biāo)記一個新的數(shù)據(jù)顯示的方法。
這里解決之前顯示 models.Host.host_type_choices 的問題了。后端返回的response.data里開辟一個key(global_dict),用來存放這類數(shù)據(jù)
# 獲取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,
}
這樣的數(shù)據(jù)格式不但放在內(nèi)存里的choices可以用,F(xiàn)oreignKey使用 .values_list()方法也能生成一樣的數(shù)據(jù),所以也能用。這種方法是不跨表的,適合條目比較少的情況。如果表里行數(shù)很多的話就不適合了,一方面所有的條目都會傳遞給客戶端,另一方面前端是遍歷查找。
這里需要一個新的標(biāo)記,標(biāo)記是去global_dict里去查找對應(yīng)的內(nèi)容。所以用了兩個@。那么table_config現(xiàn)在要這么寫:
table_config = [
{
'field': 'hostname', # 表中對應(yīng)的字段名,必須要和字段名一致,下面要用作查詢條件
'title': "主機(jī)名", # 表格的列名
'display': 1, # 是否顯示該列,1是顯示,0是不顯示
'attr': {'k1': 'v1', 'k2': 'v2'}
},
{
'field': 'business_unit',
'title': "業(yè)務(wù)線_不跨表",
'display': 1,
'text': {'content': '{n}', 'kwargs': {'n': '@@business_unit'}}
},
{
'field': 'host_type',
'title': "主機(jī)類型",
'display': 1,
'text': {'content': '{type}', 'kwargs': {'type': '@@host_type'}}
},
]
前端的實(shí)現(xiàn)
先處理response.data.global_dict數(shù)據(jù)的接收。所有的數(shù)據(jù)都是在AJAX的success方法里在參數(shù)arg里,原先已經(jīng)有2個方法了,這里再增加一個方法,保存global_dict數(shù)據(jù):
initGlobal(arg.data.global_dict); // AJAX的success函數(shù)里新加這個方法
createThead(arg.data.table_config);
createTbody(arg.data.table_config, arg.data.data_list)
調(diào)用的方法,就是把這個數(shù)據(jù)暫存到一個在插件內(nèi)部是全局有效的變量GLOBAL_DICT里,這樣做應(yīng)該是方便在插件內(nèi)部的其他方法里調(diào)用:
// 用戶保存當(dāng)前作用域內(nèi)的“全局變量”
var GLOBAL_DICT = {};
function initGlobal(globalDict) {
$.each(globalDict, function (k, v) {
GLOBAL_DICT[k] = v;
})
}
然后來處理@@的解析,在原來的@的解析的if里再增加一個分支:
var kwargs = {};
// 把configItem.text.kwargs的內(nèi)容存到上面的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
}
});
這里用的是遍歷的方式來查找的,所以如果列表太長就不太適合了。放在內(nèi)存中的choices應(yīng)該都不會很長。如果是ForeignKey,現(xiàn)在有2個方法可以顯示了。這個方法不跨表,但是數(shù)據(jù)太多就不適合了。
路由的對應(yīng)關(guān)系,urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('host/', views.HostView.as_view()),
path('api/host/', views.HostApi.as_view()),
]
表結(jié)構(gòu),models.py:
class UserInfo(models.Model):
"""用戶表"""
name = models.CharField(max_length=32)
age = models.IntegerField()
class BusinessUnit(models.Model):
"""業(yè)務(wù)線"""
name = models.CharField(max_length=32)
class Host(models.Model):
"""主機(jī)列表"""
host_type_choices = ((1, '服務(wù)器'),
(2, '防火墻'),
(3, '路由器'),
(4, '交換機(jī)'),
)
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)
處理函數(shù),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() # 先實(shí)例化
table_config = [
{
'field': 'hostname', # 表中對應(yīng)的字段名,必須要和字段名一致,下面要用作查詢條件
'title': "主機(jī)名", # 表格的列名
'display': 1, # 是否顯示該列,1是顯示,0是不顯示
'attr': {'k1': 'v1', 'k2': 'v2'}
},
{
'field': 'id',
'title': "ID",
'display': 0, # 這一列不用顯示,但是前端能接收到數(shù)據(jù)
'text': None, # 上面不顯示,所以這里text有沒有都沒關(guān)系
},
{
'field': 'port',
'title': "端口號",
'display': 1,
'text': {'content': '端口:{port}', 'kwargs': {'port': '@port'}},
'attr': {'original': '@port'}
},
{
'field': 'business_unit__id',
'title': "業(yè)務(wù)線ID",
'display': 0,
},
{
'field': 'business_unit__name',
'title': "業(yè)務(wù)線",
'display': 1,
'text': {'content': '{n}(id:{id})', 'kwargs': {'n': '@business_unit__name', 'id': '@business_unit__id'}}
},
{
'field': 'business_unit',
'title': "業(yè)務(wù)線_不跨表",
'display': 1,
'text': {'content': '{n}', 'kwargs': {'n': '@@business_unit'}}
},
{
'field': 'host_type',
'title': "主機(jī)類型",
'display': 1,
'text': {'content': '{type}', 'kwargs': {'type': '@@host_type'}}
},
{
'field': None, # 允許添加額外的列,這個列的內(nèi)容沒有對應(yīng)的字段
'title': "操作",
'display': 1,
'text': {'content': '查看詳細(xì)', 'kwargs': {'id': '@id'}}
},
]
field_list = []
for item in table_config:
if item['field']:
field_list.append(item['field'])
# 寫一個try,也可以把上面的內(nèi)容都放進(jìn)來,
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) # 錯誤信息,用下面的模塊可以看到錯誤產(chǎn)生的位置
import traceback
response.error = traceback.format_exc() # 返回詳細(xì)的錯誤信息,包括哪個文件的哪一行
print(response.error)
return JsonResponse(response.__dict__)
前端主頁,host.html:
主機(jī)列表
前端插件,show-table.js:
(function ($) {
// 用戶保存當(dāng)前作用域內(nèi)的“全局變量”
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) {
// 循環(huán)數(shù)據(jù),每條數(shù)據(jù)有一行
$.each(list, function (k1, row) {
var tr = document.createElement('tr');
// 循環(huán)配置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的內(nèi)容存到上面的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)
})
}
// 為字符串創(chuàng)建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);