四、对数据记录删除
1、首先是配置整个流程框架,使整个流程运转顺利:
路由项添加:path('<str:app_name>/<str:table_name>/<int:id_num>/delete/',views.rec_obj_delete,name='rec_delete'),
主要是匹配记录的id和delete字符串。
视图函数:
def rec_obj_delete(req,app_name,table_name,id_num):
print('========++++++++++++',app_name,table_name,id_num)
return render(req,"mytestapp/rec_delete.html",{})
前端页面rec_deleta.html
在修改页面中增加删除按钮,<div class="col-sm-2"><button type="button" class="btn btn-danger pull-left"><a href="/mytestapp/plcrm/customer/{{ form_obj.instance.id }}/delete/">Delete</a></button></div>
关键点是其href
点击删除按钮,匹配路由项,调用视图函数rec_obj_delete,跳转到rec_delete.html页面。
2、确定视图函数的处理逻辑,确定前端页面的显示内容
对于Django Admin提供的删除功能,当点击删除时,提示如下:
列示出当前记录关联的记录,给删除者以提示,确定后,再进行删除。
rec_delete.html网页和后端视图函数就是合作完成关联记录的取得和展示
前端:rec_delete.html
{% extends 'base.html' %}
{% load tags %}
{% block mybody %}
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">我的客户管理系统</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="#">{{ request.user.userprofile.name }}</a>
</li>
</ul>
</nav>
<div class="container-fluid" style="margin-top: 20px;">
<div class="flex-column bd-highlight">
{% display_obj_related model_obj %}
<form method="post">{% csrf_token %}
<input type="submit" class="btn btn-danger" value="Yes,I'm Sure">
<input type="hidden" value="yes" name="del_confirm">
<input type="hidden" value="{{ model_obj.id }}" name="del_id">
<input type=button class="btn btn-info" value="No,Take Me Back" onclick="window.history.go(-1);">
</form>
</div>
</div>
</body>
{% endblock %}
视图函数:
def rec_obj_delete(req,app_name,table_name,id_num):
admin_class = mytestapp_admin.enable_admins[app_name][table_name]
model_obj = admin_class.model.objects.get(id=id_num)
if req.method == "POST":
model_obj.delete()
return redirect("/mytestapp/%s/%s/" %(app_name,table_name))
return render(req,"mytestapp/rec_delete.html",{"model_obj":model_obj,
"admin_class":admin_class})
关键点在display_obj_related自定义标签的编写,对model_obj这个记录的关联数据进行查找,并形成列表,组装成html代码,返回给前端。
@register.simple_tag
def display_obj_related(model_obj):
# 把对象及其所有相关联的数据取出来
return mark_safe(recursive_related_objs_lookup(model_obj))
def recursive_related_objs_lookup(model_objs):
'''组装对象及关联数据为列表标签'''
ul_ele = "<ul>"
for model_obj in model_objs:
li_ele = '<li>%s:%s</li>' % (model_obj._meta.verbose_name,model_obj.__str__().strip("<>"))
# 上面是先将自身信息输出
ul_ele += li_ele
for m2m_field in model_obj._meta.local_many_to_many:
# 把所有跟这个对象直接关联的m2m字段选出,是models中ManyToManyField字段,即tags字段
sub_ul_ele = "<ul>"
m2m_field_obj = getattr(model_obj,m2m_field.name)
for obj in m2m_field_obj.select_related():
li_ele = '<li>%s:%s' % (m2m_field.verbose_name,obj.__str__().strip("<>"))
sub_ul_ele += li_ele
sub_ul_ele += "</ul>"
ul_ele += sub_ul_ele
for related_obj in model_obj._meta.related_objects:
# 这个循环是要把与对象关联的related_objects,即所有ManyToOneRel的数据列出,即customerfollowup和enrollment字段
if 'ManyToManyRel' in related_obj.__repr__():
if hasattr(model_obj,related_obj.get_accessor_name()):
accessor_obj = getattr(model_obj,related_obj.get_accessor_name())
# accessor_obj相当于customer.enrollment_set
if hasattr(accessor_obj,'select_related'): # select_related()== all()
target_objs = accessor_obj.select_related() # .filter(**filter_conditions)
# target_obj相当于customer.enrollment_set.all()
sub_ul_ele = "<ul style='color:red'>"
for o in target_objs:
li_ele = '<li>%s:%s</li>' % (o._meta.verbose_name,o.__str__().strip('<>'))
sub_ul_ele += li_ele
sub_ul_ele += "</ul>"
ul_ele += sub_ul_ele
elif hasattr(model_obj,related_obj.get_accessor_name()):
accessor_obj = getattr(model_obj,related_obj.get_accessor_name())
if hasattr(accessor_obj,'select_related'):
target_objs = accessor_obj.select_related()
else:
target_objs = accessor_obj
if len(target_objs)>0:
nodes = recursive_related_objs_lookup(target_objs)
ul_ele += nodes
ul_ele += "</ul>"
return ul_ele
找关联的数据,主要是找以此表为外键的表,如对于客户表,要删除一条记录时,要判断以客户表为外键的其他表的影响,如果其他表以客户表为外键,并且,在其他表中存在了以这条记录作为外键的记录,那么删除记录,对其他表是由影响的。如删除客户表中id为1的记录,而报名表中有一条或几条记录是以这个客户为外键的,删除这条记录,报名表中对应的这几条记录如何处理?这就是关联影响,在创建models时,有一个on_deleted,表明在本表记录删除时,援引此表为外键的其他表的处理方式。相应的还有多对多和一对一关系,上面的程序主要就是找这些记录进行显示。
五、自定义动作
实现如上图的功能,定制动作,选中后,批量执行这个动作。
在DjangoAdmin中,在AdminClass中定义actions:
actions = ['test_actions',]
def test_actions(self,arg1,arg2):
print("actions:::::=>",self,'|',arg1,'|',arg2)
结果:
打印:
actions:::::=> plcrm.CustomerAdmin | <WSGIRequest: POST '/admin/plcrm/customer/'> | <QuerySet [<Customer: 123654789>, <Customer: 1234352354352>, <Customer: 8899009898>]>
在test_actions中可以对选中的各条记录进行任意的处理。
模拟上述功能,先在前端页面增加action下拉列表框,实现每条记录前增加复选框,实现复选框的全选和全不选:
在table_objts.html中:
<hr style="width: 100%">
<div class="row" style="padding-left: 10px;">
<div style="float:left;margin-left:20px;">
<select id="action_list" name="myaction" style="width: 200px;height: 25px;">
<option value="">----------------</option>
{% for op in admin_class.actions %}
<option value="{{ op }}">{{ op }}</option>
{% endfor %}
</select>
</div>
<div class="col-sm">
<input type="button" value="Go" onclick="ActionSubmit();">
</div>
<thead> <!-- 表头显示要显示表的各字段名称-->
<tr class="text-danger" style="background-color: #9fcdff;">
<th colspan="6"><input type="checkbox" onclick="CheckAllToggle(this);"></th>
{{ header_order_tag | safe }}
</tr>
</thead>
在build_table_row自定义标签中,每行数据前加上复选框:
row_ele = row_ele +'<tr><td colspan="6"><input my_id="obj_checkbox" type="checkbox" value="%s"</td>'%(row_data.id)
前端实现全选与全取消功能:即CheckAllToggle(this):
function CheckAllToggle(ele) {
if ($(ele).prop("checked")){
$("input[my_id='obj_checkbox']").prop("checked",true);
} else {
$("input[my_id='obj_checkbox']").prop("checked",false);
}
}
效果如下:
点击Go按钮,将下列列表框的值和下面记录的值传递到后端,后端通过下拉列表框的值,找到对应的函数,有此函数处理传递的记录。
使用form提交,对下列列表框和Go按钮改造,做成form,这样,下拉列表框数据可自动提交,要想办法将记录数据添加到form表单中,进行提交。
<div class="row" style="padding-left: 10px;">
<form method="post" onsubmit="ActionSubmit(this)">
<div style="float:left;margin-left:20px;">
<select id="action_list" name="myaction" style="width: 200px;height: 25px;">
<option value="">----------------</option>
{% for op in admin_class.actions %}
<option value="{{ op }}">{{ op }}</option>
{% endfor %}
</select>
</div>
{% csrf_token %}
<input type="hidden" name="del_confirm" value="yes"/>
<div style="float: left;margin-left: 10px">
<input type="submit" value="Go">
</div>
</form>
。。。
<script>
function CheckAllToggle(ele) {
if ($(ele).prop("checked")){
$("input[my_id='obj_checkbox']").prop("checked",true);
} else {
$("input[my_id='obj_checkbox']").prop("checked",false);
}
}
function ActionSubmit(form_ele) {
var selected_ids = [];
$("input[my_id='obj_checkbox']:checked").each(function () {
selected_ids.push($(this).val());
});
var selected_action = $('#action_list').val();
console.log(selected_ids);
console.log(selected_action);
if(selected_ids.length == 0){
alert("no object got selected!");
}
if(!selected_action){
alert("no action got selected!");
}
var selected_ids_ele = "<input name='selected_ids' type='hidden' value=" +selected_ids.toString() +">"
$(form_ele).append(selected_ids_ele)
}
</script>
后台处理还借助display_table_objs视图函数:
def display_table_objs(req,app_name,table_name):
print("====>",req.POST)
# models_module = importlib.import_module('%s.models' %(app_name))
# model_obj = getattr(models_module,"Customer")
# print("------->>>",model_obj)
url_path = req.path
current_order_mark = req.GET.get('order_mark','0')
current_page = int(req.GET.get('p',1))
current_search_mark = req.GET.get('search_mark',"")
admin_class = mytestapp_admin.enable_admins[app_name][table_name]
if req.method == "POST":
select_ids = req.POST.get('selected_ids')
select_ids = select_ids.split(',')
actions = req.POST.get('myaction')
action_obj = getattr(admin_class,actions)
models_objs = admin_class.model.objects.filter(id__in=select_ids)
action_obj(admin_class,req,models_objs)
# models_data = admin_class.model.objects.values_list(*admin_class.list_display)
filter_f_a = {} # 保存传递过来的顾虑项键值对
for filter_f in admin_class.list_filter:
f_temp = req.GET.get(filter_f)
if f_temp:
filter_f_a[filter_f] = f_temp
filter_url = ""
# 过滤条件形成请求地址的参数串
if filter_f_a.__len__() != 0:
for k,v in filter_f_a.items():
filter_url = filter_url + k + "=" + str(v) +"&"
# 有查询,查询条件增加到请求地址的参数中
if current_search_mark.__len__()>0:
filter_url = filter_url +'search_mark=' + current_search_mark +'&'
print(filter_url)
order_url = ""
if current_order_mark == '0':
order_url = filter_url
else:
order_url = order_url + "order_mark=" + current_order_mark +"&"
per_page = admin_class.list_per_page
if current_search_mark.__len__() == 0:
total_count = admin_class.model.objects.filter(**filter_f_a).count()
else:
q_and = Q()
q_or = Q()
q_or.connector = 'OR'
for column in admin_class.list_search:
q_or.children.append(("%s__contains"%column,current_search_mark))
q_and.connector = "AND"
total_count = admin_class.model.objects.filter(**filter_f_a).filter(q_or).count()
print('url_path:',url_path)
obj_page = PageHelper(total_count,current_page,url_path,page_rec_count=per_page,filter_url=filter_url,order_url=order_url)
page_tag = obj_page.pager_tag_str()
filter_tag = myutils.built_filter_tag(admin_class,filter_f_a,url_path,current_search_mark)
header_order_tag = myutils.order_tag(admin_class,url_path,filter_url,current_order_mark)
# models_data_page = models_data[obj_page.page_rec_start:obj_page.page_rec_end]
# print('>>>>>>>>',models_data_page)
return render(req,"mytestapp/table_objs.html",{"admin_class":admin_class,"model_class_name":admin_class.model.__name__,
'page_tag':page_tag,'obj_page':obj_page,'total_count':total_count,
'filter_tag':filter_tag,'filter_f_a':filter_f_a,'header_order_tag':header_order_tag,
'order_mark':current_order_mark,'search_mark':current_search_mark,'url_path':url_path
关键点看req.method == ‘POST’段:
在这里接收POST提交过来的数据,获得动作的对象,获得models对象,调用动作对象函数,执行自定义的函数。
class CustomerAdmin(BaseAdmin):
list_display = ['qq','name','phone','source','referral_from','consult_course','tags','status']
list_per_page = 4
list_filter = ['qq','source','status','consult_course','tags']
list_search = ['qq','name']
filter_horizontal = ['tags']
actions = ['delete_action',]
def delete_action(self,req,model_objs):
print("运行delete_action",self,req,model_objs)
model_objs.delete()
这样就完成了自定义动作的执行。
实际上,在前面我们已经做过删除的功能了,这里可以借助这个功能,就是在自定义的delete_action中获取数据后,跳转到rec_delete.html,给出这些记录的关联数据,然后就衔接到上面的删除功能上。
def delete_action(self,req,model_objs):
print("运行delete_action",self,req,model_objs)
# 跳转到rec_delete.html,借助已经实现的功能,调用rec_obj_delete(req,app_name,table_name,id_num):
# return render(req,'mytestapp/rec_delete.html',{}) #需要改造,匹配rec_obj_delete的参数
model_objs.delete()
print("删除执行完毕")
关键点就是页面之间跳转时,参数要保证也传递过去。