Python CRUD配合jQuery进行当前行修改与自定义显示列
说明如下:
1. 页面显示数据采用Ajax形式进行加载,包括分页显示
2. 页面需要显示的列可在后台进行配置,可自由搭配
3. 进入编辑模式后可以进行在线编辑然后退出编辑模式后点击保存提交
========================================================================
view视图:
def unitInfoListByGet(request): return render(request,'app04/unitlist.html',locals()) def unitInfoListByAjax(request): """ :param request: :return: 配置说明: field: 为需要去数据库要取得数据,如数据库没有该字段,填写None即可 title:为页面Headr显示的对应数据库字段的中文 display:为页面数据需要显示的列,需要填写True,不需要填写False text: 为页面每个td里面的内容(js会进行正则匹配) 1. content为页面td里面要显示的内容,具体怎么显示会根据 kwargs里面的对应显示 2. @+数据库字段为正常显示数据库里面查询的数据 3. @@+其他全局变量名称为显示全局对应的数组(前提:必须要和传入到js前端的全局变量名保持一致) 4. 例如本例子的 unitInfo_status_choices=((1,'啓用',),(0,'停用',),) , 数据库会查出status的结果是0 还是 1, 我们会将unitInfo_status_choices存入到前端的全局变量里面 然后根据 @@来匹配结果 attrs:为编辑框的属性设置, edit-enable:为是否可以进行编辑 edit-type: 为编辑的状态是 input还是 select name:为编辑框的name属性要和数据库字段对应,后续提交到后台 origin:编辑框的原来数据,使用@+数据库字段来获取数据;最后提交数据的时候就是根据这个属性来进行对比看看到底有么有发生改变 global-name:为select下拉选属性,主要是根据这个属性来匹配前端全局变量,来填充下拉选 """ table_config=[ { 'field': None, 'title': '選項', 'display': True, 'text': {'content': '<input type="checkbox"/>', 'kwargs': {}}, 'attrs': {} }, { 'field':'id', 'title':'No', 'display':False, 'text': {'content': '{n}', 'kwargs': {'n': '@id'}}, 'attrs': {} }, { 'field': 'fab', 'title': '廠區', 'display':True, 'text':{'content':'{n}','kwargs':{'n':'@fab'}}, 'attrs': {'edit-enable': 'true', 'edit-type': 'input','name':'fab','origin':'@fab'} }, { 'field': 'device', 'title': '設備', 'display':True, 'text': {'content':'{n}', 'kwargs': {'n':'@device'}}, 'attrs': {'edit-enable': 'true', 'edit-type': 'input','name':'device','origin':'@device'} }, { 'field': 'unit', 'title': 'Unit', 'display': True, 'text': {'content': '{n}', 'kwargs':{'n':'@unit'}}, 'attrs': {'edit-enable': 'true', 'edit-type': 'input','name':'unit','origin':'@unit'} }, { 'field': 'code', 'title': '異常碼', 'display': True, 'text': {'content': '{n}', 'kwargs':{'n':'@code'}}, 'attrs': {'edit-enable': 'true', 'edit-type': 'input','name':'code','origin':'@code'} }, { 'field': 'title', 'title': '標題', 'display': True, 'text': {'content': '{n}', 'kwargs': {'n':'@title'}}, 'attrs': {'edit-enable': 'true', 'edit-type': 'input','name':'title','origin':'@title'} }, { 'field': 'status', 'title': '狀態', 'display': True, 'text': {'content': '{n}', 'kwargs': {'n': '@@unitInfo_status_choices'}}, 'attrs':{'edit-enable':'true','edit-type':'select','global-name':'unitInfo_status_choices','origin':'@status','name':'status'} }, { 'field': None, 'title': '操作', 'display':False, 'text': {'content': '<a href="/rbac4/editinfobyid?id={id}">查看詳細</a>', 'kwargs': {'id':'@id'}}, 'attrs': {} }, ] # 获取能查询的字段到数据库 field_list=[] for item in table_config: if item['field']: field_list.append(item['field']) page = Pagination(UnitInfo.objects.count(), request.POST.get('p', 1),None,2) table_data=list( UnitInfo.objects.filter().values(*field_list)[page.start():page.end()] ) unitInfo_status_choices=UnitInfo.unitInfo_status_choices # 需要到前端全局变量的字典, 前端会根据key开进行生成全局变量 global_dict={ 'unitInfo_status_choices':unitInfo_status_choices, 'current_page':page.current_page } json_result={'table_config':table_config,'table_data':table_data,'global_dict':global_dict,'page':page.page_str()} return HttpResponse(json.dumps(json_result)) def editInfoById(request): json_result={'status':True,'error':None} post_list=eval(request.POST.get('post_list')) print(post_list) for item in post_list: print(item) id=item['id'] del item['id'] print(item) UnitInfo.objects.filter(id=id).update(**item) return HttpResponse(json.dumps(json_result)) def deleteInfoById(request): json_result = {'status': True, 'error': None} ids = eval(request.POST.get('ids')) for id in ids: UnitInfo.objects.filter(id=id).delete() return HttpResponse(json.dumps(json_result))
url:
path('unitInfoListByGet.html', unitInfoListByGet, name='unitInfoListByGetAction'), path('unitInfoListByAjax.html', unitInfoListByAjax, name='unitInfoListByAjaxAction'), path('editInfoById.html', editInfoById, name='editInfoByIdAction'), path('deleteInfoById.html', deleteInfoById, name='deleteInfoByIdAction'),
model:
class UnitInfo(models.Model): unitInfo_status_choices=((1,'啓用',),(0,'停用',),) fab = models.CharField(max_length=32, null=True, blank=True, verbose_name='FAB') device = models.CharField(max_length=32, null=True, blank=True, verbose_name='Device') unit = models.CharField(max_length=32, null=True, blank=True, verbose_name='Unit') code = models.CharField(max_length=32, null=True, blank=True, verbose_name='Code') title = models.CharField(max_length=32, null=True, blank=True, verbose_name='title') status = models.IntegerField( null=True, blank=True,choices=unitInfo_status_choices, verbose_name='狀態') def __str__(self): return self.fab class Meta: verbose_name='異常信息' verbose_name_plural='異常信息'
分页插件:
class Pagination: def __init__(self,total_count,current_page, url,page_size,page_item_num=7): self.total_count=total_count # 总条数 try: v=int(current_page)# 当前页 if v<=0: v=1 self.current_page=v except Exception as e: self.current_page=1 self.page_size=page_size# 每页显示数量 self.page_item_num=page_item_num # 页码 self.url=url self.data=None def start(self): return (self.current_page-1)*self.page_size def end(self): return self.current_page*self.page_size @property def num_pages(self): a, b = divmod(self.total_count, self.page_size) if b == 0: return a return a + 1 def page_num_range(self): if self.num_pages <self.page_item_num: return range(1,self.num_pages+1) part=int(self.page_item_num/2) if self.current_page<=part: return range(1,self.page_item_num+1) if (self.current_page+part)>self.num_pages: return range(self.num_pages-self.page_item_num+1,self.num_pages+1) return range(self.current_page-part,self.current_page+part+1) def page_str(self): page_list=[] string='<nav aria-label="Page navigation"><ul class="pagination pagination-sm" id="pagination" style="margin:0" >' page_list.append(string) first="<li><a href='###' p='1'>首页</a></li>" page_list.append(first) if self.current_page==1: prev="<li><a href='###' p='1' aria-label='Previous'><span aria-hidden='true'>«</span></a></li>" else: prev = "<li><a href='###' p='%s' aria-label='Previous'><span aria-hidden='true'>«</span></a></li>"%(self.current_page-1) page_list.append(prev) for i in self.page_num_range(): if i ==self.current_page: temp = "<li class='active'><a href='###' p='%s'>%s</a></li>" % (i, i) else: temp="<li><a href='###' p='%s'>%s</a></li>"%(i,i) page_list.append(temp) if self.current_page ==self.num_pages: nex="<li><a href='####' p='%s' aria-label='Next'><span aria-hidden='true'>»</span></a></li>"%(self.current_page) else: nex="<li><a href='###' p='%s' aria-label='Next'><span aria-hidden='true'>»</span></a></li>"%(self.current_page+1) page_list.append(nex) last="<li><a href='###' p='%s'>尾页</a><li>"%self.num_pages page_list.append(last) string='</ul></nav>' page_list.append(string) return ''.join(page_list)
HTML
页面里面的 id属性命名和js里面的是对应的,如果要更改,则js里面的也要同步
<!DOCTYPE html> <html lang="zh-CN"> <head> {% load staticfiles %} <title>RBAC</title> <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet"> <script src="{% static 'bootstrap/js/jquery.min.js' %}"></script> <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script> <style> </style> </head> <body> {% csrf_token %} <div style="padding:10px;"> <button class="btn btn-primary btn-sm" id="checkall">全選 all</button> <button class="btn btn-primary btn-sm" id="checkcancelall">取消 all</button> <button class="btn btn-primary btn-sm" id="reverseChecked">反選</button> <button class="btn btn-primary btn-sm" id="editMode">進入編輯模式</button> <button class="btn btn-primary btn-sm" id="save" >保存</button> <button class="btn btn-primary btn-sm" id="delete">刪除</button> </div> <div style="padding:10px;"> <table class="table table-bordered"> <thead id="thead"></thead> <tbody id="tbody"></tbody> </table> </div> <div style="padding:5px;float:right;" id="pager"> </div> </body> </html> <script src="{% static 'bootstrap/js/tableload.js' %}"></script> <script> $(function(){ $.method('/rbac4/unitInfoListByAjax.html','/rbac4/editInfoById.html','/rbac4/deleteInfoById.html') }) </script>
<script>
jQuery(document).ready(function () {
if (window.history && window.history.pushState) {
$(window).on('popstate', function () {
// 当点击浏览器的 后退和前进按钮 时才会被触发,
window.history.pushState('forward', null, '');
window.history.forward(1);
});
}
window.history.pushState('forward', null, ''); //在IE中必须得有这两行
window.history.forward(1);
});
</script>
tableload.js JS:
// 頁面初始化后開始執行自執行函數 (function(){ //request_load_date_url='/rbac4/unitInfoListByAjax.html' //request_edit_date_url='/rbac4/editInfoById.html' request_load_date_url=null request_edit_date_url=null request_delete_date_url=null jQuery.extend({ 'method':function(load_url,edit_url,delete_url){ request_load_date_url=load_url request_edit_date_url=edit_url request_delete_date_url=delete_url init(1) bindChangePage() bindeditMode() bindCheckBox() bindCheckall() bindCheckcancelall() bindReversechecked() bindSave() bindDelete() } }) function bindChangePage(){ $('#pager').on('click','a',function(){ var num=$(this).attr('p') init(num) }) } function bindeditMode(){ $('#editMode').click(function(){ var editing=$(this).hasClass('btn-warning') if(editing){ $(this).removeClass('btn-warning') $(this).text('進入編輯模式') $('#tbody').find(':checked').each(function(){ var tr=$(this).parent().parent() trOutEdit(tr) }) }else{ $(this).addClass('btn-warning') $(this).text('退出編輯模式') $('#tbody').find(':checked').each(function(){ var tr=$(this).parent().parent() trIntoEdit(tr) }) } }) } function bindCheckBox(){ $('#tbody').on('click',':checkbox',function(){ if($('#editMode').hasClass('btn-warning')){ var ck=$(this).prop('checked') if(ck){ console.log('進入編輯模式') var tr=$(this).parent().parent() tr.addClass('success') trIntoEdit(tr) }else{ console.log('退出編輯模式') var tr=$(this).parent().parent() tr.removeClass('success') trOutEdit(tr) } } }) } function bindCheckall(){ $('#checkall').click(function(){ $('#tbody').find(':checkbox').each(function(){ var editing=$('#editMode').hasClass('btn-warning') if(editing){ if($(this).prop('checked')){ }else{ $(this).prop('checked',true) var tr=$(this).parent().parent() trIntoEdit(tr) } }else{ $(this).prop('checked',true) } }) }) } function bindCheckcancelall(){ $('#checkcancelall').click(function(){ $('#tbody').find(':checked').each(function(){ var editing=$('#editMode').hasClass('btn-warning') if(editing){ $(this).prop('checked',false) var tr=$(this).parent().parent() trOutEdit(tr) }else{ $(this).prop('checked',false) } }) }) } function bindReversechecked(){ $('#reverseChecked').click(function(){ $('#tbody').find(':checkbox').each(function(){ var editing=$('#editMode').hasClass('btn-warning') if (editing){ if($(this).prop('checked')){ $(this).prop('checked',false) var tr=$(this).parent().parent() trOutEdit(tr) }else{ $(this).prop('checked',true) var tr=$(this).parent().parent() trIntoEdit(tr) } }else{ if($(this).prop('checked')){ $(this).prop('checked',false) }else{ $(this).prop('checked',true) } } }) }) } function bindSave(){ var post_list=[] $('#save').click(function(){ $('#tbody').find('tr[has-edit="true"]').each(function(){ var temp={} var id=$(this).attr('row-id') id=Number(id) temp['id']=id $(this).children('[edit-enable="true"]').each(function(){ var name=$(this).attr('name') var origin=$(this).attr('origin') var name=$(this).attr('name') var new_val=$(this).attr('new-val') if(origin!=new_val){ temp[name]=new_val } }) post_list.push(temp) }) console.log(post_list) $.ajax({ url:request_edit_date_url, type:'post', dataType:'json', data:{ 'csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val(), 'post_list':JSON.stringify(post_list) }, success:function(result){ if(result.status){ init(window.current_page) alert('更新成功!') }else{ alert(result.error) } } }) }) } function bindDelete(){ $('#delete').click(function(){ var checklist=$('#tbody').find(':checked') if(checklist.length==0){ alert('請選擇!') }else{ var ids=[] $.each(checklist,function(){ var tr=$(this).parent().parent() var id=tr.attr('row-id') id=Number(id) ids.push(id) }) $.ajax({ url:request_delete_date_url, type:'post', dataType:'json', data:{ 'csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val(), 'ids':JSON.stringify(ids) }, success:function(result){ if(result.status){ init(window.current_page) alert('刪除成功!') }else{ alert(result.error) } } }) } }) } function trIntoEdit(tr){ tr.addClass('success') $(tr).attr('has-edit','true') tr.children().each(function(){ var editEnable=$(this).attr('edit-enable') var editType=$(this).attr('edit-type') if(editEnable=='true'){ if(editType=='select'){ var origin=$(this).attr('origin') var global_name=$(this).attr('global-name') var tupe=window[global_name] var select=document.createElement('select') $(select).css('height','26') $.each(tupe,function(k,v){ var option=document.createElement('option') option.setAttribute('value',v[0]) option.innerHTML=v[1] $(select).append(option) }) $(select).val(origin) $(this).html(select) }else if(editType=='input'){ var innerText=$(this).text() var input=document.createElement('input') input.value=innerText $(this).html(input) }else{ } } }) } function trOutEdit(tr){ tr.removeClass('success') tr.children().each(function(){ var editEnable=$(this).attr('edit-enable') var editType=$(this).attr('edit-type') if(editEnable=='true'){ if(editType=='select'){ var select=$(this).children().first() var newID=select.val() var newText=select[0].selectedOptions[0].innerHTML $(this).html(newText) $(this).attr('new-val',newID) }else if(editType=='input'){ var input=$(this).children().first() var newText=input.val() $(this).html(newText) $(this).attr('new-val',newText) } } }) } String.prototype.textformat=function(kwargs){ var val=this.replace(/\{(\w+)\}/g,function(km,m){ return kwargs[m] }) return val } function init(p){ $.ajax({ url:request_load_date_url, type:'post', dataType:'json', data:{ 'csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val(), 'p':p }, success:function(result){ var table_config=result.table_config var table_data=result.table_data var global_dict=result.global_dict init_global_data(global_dict) init_thead(table_config) init_tbody(table_data,table_config) init_page(result.page) } }) } function init_global_data(global_dict){ $.each(global_dict,function(k,v){ window[k]=v }) } function init_thead(table_config){ var tr=document.createElement('tr') $.each(table_config,function(i,item){ if(item.display){ var th=document.createElement('th') th.innerHTML=item.title $(tr).append(th) } }) $('#thead').empty() $('#thead').append(tr) } function init_tbody(table_data,table_config){ $('#tbody').empty() $.each(table_data,function(i,data){ var tr=document.createElement('tr') tr.setAttribute('row-id',data['id']) $.each(table_config,function(k,config){ if(config.display){ var kwargs={} $.each(config.text.kwargs,function(key,value){ if(value.substring(0,2)=='@@'){ var global_name=value.substring(2,value.length) var val=data[config.field] var res=getTextFormGlobalByKey(global_name,val) kwargs[key]=res }else if (value[0]=='@'){ kwargs[key]=data[value.substring(1,value.length)] }else{ kwargs[key]=value } }) var val=config.text.content.textformat(kwargs) var td=document.createElement('td') td.innerHTML=val $.each(config.attrs,function(key,value){ if(value[0]=='@'){ td.setAttribute(key,data[value.substring(1,value.length)]) }else{ td.setAttribute(key,value) } }) $(tr).append(td) } }) $('#tbody').append(tr) }) } function init_page(page){ $('#pager').html(page) } function getTextFormGlobalByKey(global_name,val){ var tupe=window[global_name] var res=null $.each(tupe,function(i,item){ if (item[0]==val){ res= item[1] return } }) return res } })();