项目一:CRM(客户关系管理系统)--9---自定制action
现在该来添加前面提到的Action
功能了,强大的它能干什么,先来体验一下原生admin
中的效果。
1. 原生admin体验
默认的删除功能是可以进行批量删除的!
可以在后台进行自定义功能
在CRM
应用目录下的admin.py
中添加:
1 from django.contrib import admin 2 from CRM import models 3 from django.shortcuts import render 4 # Register your models here. 5 6 #自定义操作 7 class CustomerAdmin(admin.ModelAdmin): 8 list_display = (\'name\', \'id\',\'qq\',\'source\',\'consultant\',\'content\',\'status\',\'date\') 9 list_filter = (\'source\',\'consultant\',\'date\') 10 search_fields = (\'qq\',\'name\') 11 raw_id_fields = (\'consult_course\',) 12 filter_horizontal = (\'tags\',) 13 list_editable = (\'status\',) 14 #添加如下内容 15 actions = ("test_action", ) 16 17 def test_action(self,request,arg2): 18 print(\'test action:\',self,request,arg2) 19 return render(request,"king_admin/table_index.html")
效果图:
跳转到指定的页面:
2. 重写action
2.1基类中添加字段
第一件事还是跟以前一样,需要在基类中添加对应的字段选项:
king_admin_base.py
文件中:
1 class ModelAdmin(object): 2 list_display = [] 3 list_filter = [] 4 search_fields = [] 5 ordering = None 6 filter_horizontal = [] 7 actions = [] #添加该字段 8 list_per_page = 10
2.2 添加默认的批量删除
我还要在基类中添加默认情况下的批量删除功能,这个同原生admin
中定义的方式一样。需要在基类下面定义个批量删除功能的函数。
在king_admin_base.py
文件中添加要定义的功能函数:
1 from django.shortcuts import render 2 3 #创建基类 4 class ModelAdmin(object): 5 list_display = [] 6 list_filter = [] 7 search_fields = [] 8 ordering = None 9 filter_horizontal = [] 10 actions = [\'delete_many_objects\'] 11 list_per_page = 10 12 13 14 def delete_many_objects(self, request, selected_objects): 15 return render(request, \'king_admin/table_object_delete.html\') 16 17 ...
2.3 模板文件编写
2.3.1 模板文件中显示功能
要在模板文件中显示这个功能函数,我们必须在table_objs.html
文件中添加,只需要简单的添加一些标签:
1 ... 2 3 <button type="SUBMIT" class="btn btn-success">search</button> 4 </div> 5 </form> 6 </div> 7 {# 添加action #} 8 <div class="row" style="margin-top: 10px"> 9 <div class="col-lg-2"> 10 <select id="action_list" name="action" class="form-control" style="margin-left:15px"> 11 <option value="">---------</option> 12 {% for action in admin_class.actions %} 13 <option value="{{ action }}">{{ action }}</option> 14 {% endfor %} 15 </select> 16 </div> 17 <div class="col-lg-1"> 18 <button type="submit" class="btn " >Go</button> 19 </div> 20 </div> 21 {#具体的表格内容展示 #} 22 23 ...
显示效果如下:
有的朋友会问:你没有传admin_class
变量?是的,这里没有必要再传了,因为在该模板文件中已经存在了!
2.3.2 模板中添加表单功能
上面只是简单的显示了功能,并没有实现具体的功能。然而实现具体的功能我们需要进行传递一些必要的值,传输这些值就需要表单组件来提供功能。
还是上面的代码,套一层表单标签即可:
1 </div> 2 </form> 3 </div> 4 {# 添加action #} 5 <div class="row" style="margin-top: 10px"> 6 <form onsubmit="return ActionSubmit(this)" method="POST"> 7 {% csrf_token %} 8 <div class="col-lg-2"> 9 <select id="action_list" name="action" class="form-control" style="margin-left:15px"> 10 <option value="">---------</option> 11 {% for action in admin_class.actions %} 12 <option value="{{ action }}">{{ action }}</option> 13 {% endfor %} 14 </select> 15 </div> 16 <div class="col-lg-1"> 17 <button type="submit" class="btn">Go</button> 18 </div> 19 </form> 20 </div> 21 {#具体的表格内容展示 #}
2.3.3 中文显示功能
说到中文显示,我们就不得不用到自定义标签了。但在使用之前先来看看原生的admin
是怎么实现中文显示的吧:
1 ... 2 3 def test_action(self,request,arg2): 4 print(\'test action:\',self,request,arg2) 5 return render(request,"king_admin/table_index.html") 6 test_action.short_description = \'测试\' #添加这行代码 7 8 ...
显示效果:
开始我们自己的中文显示吧,就为我们刚刚写到默认批量删除功能添加显示中文:
1 ... 2 class ModelAdmin(object): 3 list_display = [] 4 list_filter = [] 5 search_fields = [] 6 ordering = None 7 filter_horizontal = [] 8 actions = [\'delete_many_objects\'] 9 list_per_page = 10 10 11 12 def delete_many_objects(self, request, queryset): 13 return render(request, \'king_admin/table_object_delete.html\') 14 delete_many_objects.short_description = \'批量删除\' 15 ...
模板文件修改如下:
1 {# 添加action #} 2 <div class="row" style="margin-top: 10px"> 3 <form onsubmit="return ActionSubmit(this)" method="POST"> 4 {% csrf_token %} 5 <div class="col-lg-2"> 6 <select id="action_list" name="action" class="form-control" style="margin-left:15px"> 7 <option value="">---------</option> 8 {% for action in admin_class.actions %} 9 <option value="{{ action }}">{% get_action_name admin_class action %}</option> 10 {% endfor %} 11 </select> 12 </div>
在templates/tags.py
文件中,添加的内容如下:
1 ... 2 3 <------------------------显示默认action中文-------------------------- 4 @register.simple_tag 5 def get_action_name(admin_class, action): 6 action_func = getattr(admin_class, action) 7 if hasattr(action_func, \'short_description\'): 8 return action_func.short_description 9 else: 10 return action
渲染后的效果:
2.4 视图函数
上面我们已经查不到将模板文件所需要的都已经编写完毕,接下来就是数据的收发,涉及到数据的收发我们必须要用到视图。这里是在table_objs.html
文件基础上添加的功能,视图中,我们同样要使用它来接收来自该模板文件的数据请求。
2.4.1 模板文件的数据发送
前面刚刚建立了表单功能,向后台提交数据就需要使用POST
方式进行,同时我们还要将那些选中的checkbox
中的行id
一同传送到后端。
在前面建立表单的时候,已经添加了传送方式和提交的函数:<form onsubmit="return ActionSubmit(this)" method="POST">
,这里将具体的提交函数编写一下:
1 ... 2 3 function ActionSubmit(self) { 4 var selected_ids = []; 5 $("input[tag=\'object_checkbox\']:checked").each(function () { 6 selected_ids.push($(this).val()); 7 }); 8 var selected_action = $("#action_list").val(); 9 if (selected_ids.length == 0){ 10 alert("No object got selected!"); 11 return; 12 } 13 if (!selected_action){ 14 alert("No action got selected!"); 15 } 16 //添加隐藏标签,用来存储提交的数据key/value 17 var selected_ids_ele = "<input name=\'selected_ids\' type=\'hidden\' value=\'" + selected_ids.toString() + "\'>"; 18 $(self).append(selected_ids_ele); 19 return true; 20 }
这些脚本同样放在该文件的最下面。
2.4.2 视图函数的数据接收
上面的数据已经发送,该接收数据了。接收数据的功能函数我们使用display_objects(request, app_name, table_name)
即可,我们只需要添加几行代码:
1 ... 2 3 def display_objects(request, app_name, table_name): 4 #获取自定义的admin_class 5 admin_class = site.enabled_admins[app_name][table_name] 6 #数据查询 7 #query_set = admin_class.model.objects.all() 8 9 <------------------接收action数据---------------------------- 10 if request.method == \'POST\': 11 #获取提交的数据 12 selected_ids = request.POST.get("selected_ids") 13 action = request.POST.get("action") 14 #后台判断 15 if selected_ids: 16 selected_objs = admin_class.model.objects.filter(id__in=selected_ids.split(\',\')) 17 else: 18 raise KeyError("No object selected.") 19 if hasattr(admin_class, action): 20 action_func = getattr(admin_class, action) 21 #将action存储在请求体中便于调用 22 request._admin_action = action 23 return action_func(admin_class, request, selected_objs) 24 25 ...
2.5 编写具体批量删除功能
还记得之前编写的删除功能吗?这里我们要复用一下,在此基础上简单的添加一标签用来提交数据。
2.5.1 批量删除功能编写
继续填补之前编写的批量删除功能的架子,默认情况的GET
请求页面获取,在king_admin_base.py
中:
1 ... 2 def delete_many_objects(self, request, selected_objects): 3 app_name = self.model._meta.app_label 4 table_name = self.model._meta.model_name 5 selected_ids = \',\'.join([str(i.id) for i in selected_objects]) 6 return render(request, \'king_admin/table_object_delete.html\',{\'selected_objects\': selected_objects, 7 \'action\': request._admin_action, 8 \'selected_ids\': selected_ids, 9 \'app_name\': app_name, 10 \'table_name\': table_name}) 11 delete_many_objects.short_description = \'批量删除\'
2.5.2 复用编辑页面的删除功能主页面
在table_object_delete.html
文件中修改如下:
1 {% extends \'king_admin/table_index.html\' %} 2 {% load tags %} 3 4 {% block body-content %} 5 6 {% if not action %} 7 {# 显示映射关系 #} 8 {% display_object_related object_list %} 9 {# 表单提交 #} 10 <form method="post"> 11 {% csrf_token %} 12 <input type="submit" class="btn btn-danger" value="Yes,I\'m sure"> 13 <input type="hidden" value="yes" name="delete_confirm"> 14 <a class="btn btn-info" href="{% url \'king_admin:table_object_edit\' app_name table_name object_id %}">No,Take me back</a> 15 </form> 16 {% else %} 17 {# 显示映射关系 #} 18 {% display_object_related selected_objects %} 19 {# 批量删除 #} 20 <form method="post"> 21 {% csrf_token %} 22 <input type="submit" class="btn btn-danger" value="Yes,I\'m sure"> 23 <input type="hidden" value="yes" name="delete_confirm"> 24 <input type="hidden" value="{{ action }}" name="action"/> 25 <input type="hidden" value="{{ selected_ids }}" name="selected_ids"/> 26 <a class="btn btn-info" href="/king_admin/{{ app_name }}/{{ table_name }}/?page={{ get_page }}">No,Take me back</a> 27 </form> 28 {% endif %} 29 {% endblock %}
2.5.3 删除数据
上面完成了数据的发送与接收,最后剩下的就是数据的删除:
king_admin_base.py
文件的函数修改:
1 ... 2 def delete_many_objects(self, request, selected_objects): 3 app_name = self.model._meta.app_label 4 table_name = self.model._meta.model_name 5 selected_ids = \',\'.join([str(i.id) for i in selected_objects]) 6 #添加如下判断 7 if request.method == \'POST\': 8 if request.POST.get(\'delete_confirm\') == \'yes\': 9 selected_objects.delete() 10 return redirect(\'/king_admin/{app_name}/{table_name}\'.format(app_name=app_name, 11 table_name=table_name)) 12 return render(request, \'king_admin/table_object_delete.html\',{\'selected_objects\': selected_objects, 13 \'action\': request._admin_action, 14 \'selected_ids\': selected_ids, 15 \'app_name\': app_name, 16 \'table_name\': table_name, 17 \'get_page\': request.GET.get(\'page\')}) 18 delete_many_objects.short_description = \'批量删除\' 19 20 ...
效果查看:
Good!批量删除功能搞定了,同时也意味着action
的告终。如果你想要实现其他功能,基本的流程就是这样的,仅供参考。
大致的思路如下:
1. modeladmin中需要定义自定义action,同时将该action的函数定义需要写在这里;
1 from CRM import models 2 from django.shortcuts import render, HttpResponse, redirect 3 4 5 class BaseAdmin(object): 6 """构建后台管理的基类,模仿dango的admin""" 7 list_display = [] 8 list_filter = [] 9 list_per_page = 10 10 search_fields = [] 11 filter_horizontal = [] 12 model = None 13 ordering = None 14 actions = [] 15 16 17 # 构建字典数据结构,存储的数据类型是: 18 # {\'CRM\': {\'customerinfo\': <class \'kingadmin.kingadmin.CustomerInfoAdmin\'>}} 19 # {\'app名称\': {\'数据表名\' : 管理界面需要显示的数据表类} 20 enabled_admins = {} 21 22 23 def register(model_class, admin_class=None): 24 """ 25 注册管理类(管理界面要显示的数据表类),主要是将数据表定义类与后台管理数据表定义类进行关联; 26 将所有的数据表与项目名称做一个关联,存储在全局变量中:enabled_admins; 27 :param model_class: 数据表的定义类 28 :param admin_class: 后台管理数据表定义类 29 :return: 30 """ 31 if model_class._meta.app_label not in enabled_admins: 32 enabled_admins.update({model_class._meta.app_label: {}}) 33 # 给后台管理数据表定义类添加一个model属性,该属性就是数据表的定义类, 34 # 方便以后通过后台管理数据表定义类取相关的数据表的定义类中的先关信息 35 admin_class.model = model_class 36 # model_class._meta.app_label 当前数据表所在的APP的名称 37 # model_class._meta.model_name 当前数据表的名称 38 enabled_admins[model_class._meta.app_label][model_class._meta.model_name] = admin_class 39 40 41 class CustomerInfoAdmin(BaseAdmin): 42 """客户信息表管理类""" 43 list_display = [\'id\', \'qq\', \'name\', \'refer_path\', \'consultant\', \'consult_course\', \'date\', \'status\', \'tags\'] 44 list_filter = [\'refer_path\', \'consultant\', \'consult_course\', \'status\'] 45 list_per_page = 10 46 search_fields = [\'qq\', \'name\', \'consult_course__name\'] 47 ordering = \'date\' 48 filter_horizontal = [\'tags\', ] 49 actions = [\'delete_many_objects\', ] 50 51 def delete_many_objects(self, request, selected_objects): 52 """ 53 54 :param request: 55 :param selected_objects: 56 :return: 57 """ 58 print(self, request, selected_objects) 59 app_name = self.model._meta.app_label 60 table_name = self.model._meta.model_name 61 object_ids = \',\'.join([str(i.id) for i in selected_objects]) 62 print(app_name, table_name, object_ids, selected_objects, request._admin_action) 63 # 添加如下判断 64 if request.method == \'POST\': 65 if request.POST.get(\'delete_confirm\') == \'yes\': 66 selected_objects.delete() 67 return redirect(\'/kingadmin/{0}/{1}\'.format(app_name, table_name)) 68 return render(request, \'kingadmin/table_object_delete.html\', {\'app_name\': app_name, 69 \'table_name\': table_name, 70 \'object_id\': object_ids, 71 \'object_list\': selected_objects, 72 \'action\': request._admin_action, 73 }) 74 delete_many_objects.short_description = \'批量删除\'
这其中需要注意的问题是在该函数定义中,如果需要返回一个模板文件,需要将对应的view处理函数需要的参数传递进去,如果不传递的话,系统会报错。
因此,这里是有可能引起urls.py的变动的,目前我的urls.py如下:
1 from django.conf.urls import url, include 2 from django.contrib import admin 3 from . import views 4 5 urlpatterns = [ 6 url(r\'^$\', views.default), 7 url(r\'^(\w+)/(\w+)$\', views.table_list, name=\'table_list\'), 8 url(r\'^(\w+)/(\w+)/((\d+\,*)+)/delete$\', views.table_object_delete, name=\'table_object_delete\'), 9 url(r\'^(\w+)/(\w+)/((\d+\,*)+)/edit$\', views.table_object_edit, name=\'table_object_edit\'), 10 url(r\'^(\w+)/(\w+)/add$\', views.table_object_add, name=\'table_object_add\'),
2. views.py需要做相应的变动,需要将POST和GET请求进行分开处理:
1 def table_object_edit(request, app_name, table_name, object_id, form_object): 2 """ 3 编辑表中的一条数据 4 :param request: 5 :param app_name: 6 :param table_name: 7 :param object_id: 8 :param form_object: 9 :return: 10 """ 11 admin_class = kingadmin.enabled_admins[app_name][table_name] 12 model_form = create_model_form(request, admin_class) 13 object_list = admin_class.model.objects.get(id=object_id) 14 query_sets, filter_conditions = table_filter(request, admin_class) 15 page = request.GET.get(\'page\', default=\'\') 16 order = request.GET.get(\'o\', \'\') 17 search_text = table_search(request, admin_class, query_sets)[1] 18 filter_conditions = custom_filter(request, admin_class)[1] 19 filter_text = filters_to_text(filter_conditions) 20 if request.method == \'POST\': 21 form_object = model_form(request.POST, instance=object_list) 22 if form_object.is_valid(): 23 form_object.save() 24 return redirect(\'/kingadmin/{0}/{1}?page={2}&o={3}&_q={4}&{5}\'.format( 25 app_name, 26 table_name, 27 page, 28 order, 29 search_text, 30 filter_text, 31 )) 32 # else: # 目前可以删除(无论检验成功与否都是要返回table_object_edit.html) 33 # return render(request, \'kingadmin/table_object_edit.html\', {\'form_object\': form_object, 34 # \'admin_class\': admin_class, 35 # \'app_name\': app_name, 36 # \'table_name\': table_name, 37 # }) # 目前可以删除 38 else: 39 form_object = model_form(instance=object_list) 40 return render(request, \'kingadmin/table_object_edit.html\', {\'app_name\': app_name, 41 \'table_name\': table_name, 42 \'object_id\': object_id, 43 \'form_object\': form_object, 44 \'admin_class\': admin_class, 45 }) 46 47 48 def table_object_add(request, app_name, table_name): 49 """ 50 添加页面 51 :param request: 52 :param app_name: 53 :param table_name: 54 :return: 55 """ 56 admin_class = kingadmin.enabled_admins[app_name][table_name] 57 model_form = create_model_form(request, admin_class) 58 59 query_string = request.META.get(\'QUERY_STRING\', \'\') 60 redirect_url = \'/kingadmin/{0}/{1}?{2}\'.format(app_name, table_name, query_string) 61 if request.method == \'POST\': 62 form_object = model_form(request.POST) 63 if form_object.is_valid(): 64 form_object.save() 65 return redirect(redirect_url) 66 else: 67 form_object = model_form() 68 return render(request, \'kingadmin/table_object_add.html\', {\'app_name\': app_name, 69 \'table_name\': table_name, 70 \'admin_class\': admin_class, 71 \'form_object\': form_object, 72 }) 73 74 75 def table_object_delete(request, app_name, table_name, object_id): 76 """ 77 78 :param request: 79 :param app_name: 80 :param table_name: 81 :param object_id: 82 :return: 83 """ 84 admin_class = kingadmin.enabled_admins[app_name][table_name] 85 object_list = admin_class.model.objects.filter(id=object_id) 86 if request.method == \'POST\': 87 # print(dir(request.POST)) 88 # print(request.POST) 89 if request.POST.get(\'delete_confirm\', \'\') == \'yes\': 90 object_list.delete() 91 return redirect(\'/kingadmin/{app_name}/{table_name}\'.format( 92 app_name=app_name, 93 table_name=table_name, 94 )) 95 return render(request, \'kingadmin/table_object_delete.html\', {\'app_name\': app_name, 96 \'table_name\': table_name, 97 \'object_id\': object_id, 98 \'object_list\': object_list, 99 })
需要特别注意的是:
1 def table_list(request, app_name, table_name): 2 """ 3 列出数据表中的所有数据 4 :param request: 5 :param app_name: app名称 6 :param table_name: 数据表名称 7 :return: 8 """ 9 # app_name & table_name都存在enabled_admins中,取出其中的数据 10 # for item in request.GET.items(): 11 # print(item) 12 if app_name in kingadmin.enabled_admins.keys() and \ 13 table_name in kingadmin.enabled_admins[app_name].keys(): 14 admin_class = kingadmin.enabled_admins[app_name][table_name] 15 16 # 检索过滤数据 17 # query_sets = admin_class.model.objects.all() 18 query_sets, filter_conditions_customer = custom_filter(request, admin_class) 19 # 搜索过滤数据 20 query_sets, search_text = table_search(request, admin_class, query_sets) 21 # query_sets 排序 22 query_sets, order_before, order_after = table_sort(request, admin_class, query_sets) 23 filter_conditions_preset = admin_class.list_filter 24 25 if request.method == \'POST\': 26 # 获取提交数据 27 selected_ids = request.POST.get("selected_ids") 28 action = request.POST.get("selected_action") 29 print(selected_ids, action) 30 if selected_ids: 31 selected_objs = admin_class.model.objects.filter(id__in=selected_ids.split(\',\')) 32 else: 33 raise KeyError(\'No object selected\') 34 if hasattr(admin_class, action): 35 action_func = getattr(admin_class, action) 36 # 将action存储在请求体中便于调用 37 request._admin_action = action 38 return action_func(admin_class, request, selected_objs) 39 else: 40 # list_display中的所有字段对应的verbose_name 41 field_verbose_names = [] 42 field_names = [] 43 fields = [] 44 for field_name in admin_class.list_display: 45 for field in admin_class.model._meta.get_fields(): 46 if field.name == field_name: 47 fields.append(field) 48 # field_names.append(field_name) 49 # field_verbose_names.append(field.verbose_name) 50 51 # 分页 52 page = request.GET.get(\'page\', \'\') 53 54 paginator = Paginator(query_sets, admin_class.list_per_page) 55 # 检索参数 56 filter_text = filters_to_text(filter_conditions_customer) 57 try: 58 contacts = paginator.page(page) 59 except PageNotAnInteger: 60 # If page is not an integer, deliver first page. 61 contacts = paginator.page(1) 62 except EmptyPage: 63 # If page is out of range (e.g. 9999), deliver last page of results. 64 contacts = paginator.page(paginator.num_pages) 65 contacts_count = len(contacts) 66 return render(request, \'kingadmin/table_list.html\', {\'app_name\': app_name, 67 \'table_name\': table_name, 68 \'query_sets\': query_sets, 69 \'admin_class\': admin_class, 70 # \'field_verbose_names\': field_verbose_names, 71 # \'field_names\': field_names, 72 \'order_before\': order_before, 73 \'order_after\': order_after, 74 \'fields\': fields, 75 \'contacts\': contacts, 76 \'contacts_count\': contacts_count, 77 \'filter_conditions\': filter_conditions_preset, 78 \'filter_conditions_customer\': filter_conditions_customer, 79 \'search_text\': search_text, 80 \'page\': page, 81 \'filter_text\': filter_text, 82 }) 83 # app_name | table_name不存在enabled_admin中,就返回错误信息 84 else: 85 error = \'应用名称或数据表名称不存在或未注册\' 86 return render(request, 87 \'kingadmin/table_list.html\', 88 {\'error\': error, 89 \'app_name\': app_name, 90 \'table_name\': table_name, 91 })
selected_ids与selected_action都是通过POST方法返回的,在模板文件中进行确认的时候,隐藏字段的值也需要一一对应,否则,获取到的selected_ids/selected_action都是None,系统就会出错。
3. 模板文件做相应的修改:
1 {% extends \'project/head.html\' %} 2 {% load tags %} 3 {% block main %} 4 {% if not action %} 5 {% get_relationships object_list %} 6 <form method="post"> 7 {% csrf_token %} 8 <input type="submit" class="btn btn-danger" value="Yes, I\'m sure"> 9 <input type="hidden" value="yes" name="delete_confirm"> 10 <a class="btn btn-info" href="{% url \'table_object_edit\' app_name table_name object_id %}">No, Take me 11 back</a> 12 </form> 13 {% else %} 14 {% get_relationships object_list %} 15 <form method="post"> 16 {% csrf_token %} 17 <input type="submit" class="btn btn-danger" value="Yes, I\'m sure"> 18 <input type="hidden" value="yes" name="delete_confirm"> 19 <input type="hidden" value="{{ action }}" name="selected_action"> 20 <input type="hidden" value="{{ object_id }}" name="selected_ids"> 21 <a class="btn btn-info" href="/kingadmin/{{ app_name }}/{{ table_name }}">No, Take me 22 back</a> 23 </form> 24 {% endif %} 25 26 {% endblock %}