四、对数据记录删除

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

基于django的数据可视化网站设计 django数据可视化实战项目_基于django的数据可视化网站设计

点击删除按钮,匹配路由项,调用视图函数rec_obj_delete,跳转到rec_delete.html页面。 

2、确定视图函数的处理逻辑,确定前端页面的显示内容

对于Django Admin提供的删除功能,当点击删除时,提示如下:

基于django的数据可视化网站设计 django数据可视化实战项目_python_02

 

 列示出当前记录关联的记录,给删除者以提示,确定后,再进行删除。

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,表明在本表记录删除时,援引此表为外键的其他表的处理方式。相应的还有多对多和一对一关系,上面的程序主要就是找这些记录进行显示。

五、自定义动作

基于django的数据可视化网站设计 django数据可视化实战项目_数据_03

 实现如上图的功能,定制动作,选中后,批量执行这个动作。

在DjangoAdmin中,在AdminClass中定义actions:

actions = ['test_actions',]
def test_actions(self,arg1,arg2):
     print("actions:::::=>",self,'|',arg1,'|',arg2)

结果:

基于django的数据可视化网站设计 django数据可视化实战项目_html_04

打印:

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);
            }
        }

效果如下:

基于django的数据可视化网站设计 django数据可视化实战项目_django_05

 点击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("删除执行完毕")

关键点就是页面之间跳转时,参数要保证也传递过去。