您好,登錄后才能下訂單哦!
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 用一個url代指一個資源,既然是資源,這個詞要用名詞。那么這個url就是 www.example.com/asset
。增刪改查都是通過這個url實現的,通過不同的method實現不同的方法,常用的是下面幾個方法:
在django中,推薦使用CBV。當然FBV也不是不行。
這篇貌似講的很好,值得參考: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>
<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。
寫一下前端的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)
這里換個方法來實現上面的處理函數。返回的數據不用字典記錄,而是用類來記錄。沒啥差別,就是原來是用中括號來操作的,現在可以用點來操作。最后返回的時候還是要返回字典的,可以用 .__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張之后可以測試一下對跨表的支持,先一起建好。然后去數據庫了隨便加幾條數據。
到這里,已經可以通過后端返回的字段名在前端動態的生成表頭了。接下來把表的內容也顯示出來,接著完善后端的處理函數,給前端返回更多的數據。下面是處理函數,根據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種特殊的情況:
錯誤信息的優化
處理函數里加了個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);
這里要在Sting對象的原型里添加一個format()方法,讓前端的字符串也可以像python那樣,對字符串進行格式化輸出。代碼就下面簡單的幾行,正則匹配然后用replace做替換。不過替換的內容又是一個function,邏輯有點復雜了,總之先拿著現成的用把,稍微改改大概也行。暫時沒有完全理解:
// 為字符串創建format方法,用于字符串格式化
String.prototype.format = function (args) {
return this.replace(/\{(\w+)\}/g, function (substring, args2) {
return args[args2];
})
};
首先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);
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。