项目一:CRM(客户关系管理系统)--6-编辑页面、动态modelform、双向复选框
功能是永远加不完的!重头戏的Action
放在后面作为压轴,接下来该添加三级页面啦!
1. 添加编辑页面轮廓
有的朋友可能会问:为何直接写编辑页面而没有写添加页面?那是因为二者是相互继承的关系,个人觉得先写编辑比较好,然后添加继承编辑页面,改动基本上后台数据的更新与存储的问题。
1.1. 原生admin路由分析
如图:
1.2. 构建编辑页面路由
在king_admin
应用下的urls.py
文件中添加:
1 from django.conf.urls import url 2 from king_admin import views 3 urlpatterns = [ 4 url(r'^$', views.index, name='table_index'), 5 url(r'^(\w+)/(\w+)/$', views.display_objects, name='display_objects'), 6 url(r'^(\w+)/(\w+)/(\d+)/edit/$', views.table_object_edit,name="table_object_edit"), #添加该行数据 7 ]
1.3. 添加视图函数
在king_admin
应用下的views.py
文件中添加:
1 def table_object_edit(request, app_name, table_name, object_id): 2 return render(request, 'king_admin/table_object_edit.html')
1.4. 添加模板文件
在template/king_admin/
目录下的table_object_edit.html
文件,并在文件中添加如下内容:
1 {% extends 'king_admin/table_index.html' %} 2 {% block extra-css-resources %} 3 4 {% endblock %} 5 {% block container %} 6 7 {% endblock %}
1.5.添加编辑页面的入口
上面我们已经写完基本的页面流程,现在来添加编辑页面入口来打通整体流程,二级显示页面我们通过在templates
中进行编写,修改这里即可,只需要添加一个参数(request
),索引值和判断:
1 ... 2 3 <-----------------------创建表格行数据----------------------------- 4 @register.simple_tag 5 def create_row(request, query_set_obj, admin_class): 6 #创建标签元素--空,None不行 7 element = '' 8 9 #遍历要显示的models字段 10 for number, row in enumerate(admin_class.list_display): 11 #获取显示字段对应的字段对象 12 field_obj = admin_class.model._meta.get_field(row) 13 #获取数据 14 #判断choice 15 if field_obj.choices: 16 #通过反射获取对象里面的值,并执行该方法get_字段_display()获取choices里面的数值 17 row_data = getattr(query_set_obj, 'get_{0}_display'.format(row))() 18 else: 19 row_data = getattr(query_set_obj, row) 20 21 #时间格式转换 22 if type(row_data).__name__ == 'datetime': 23 row_data = row_data.strftime('%Y-%m-%d %H-%M-%S') 24 25 #添加编辑页面入口 26 if number == 0: #add a tag, 可以跳转到修改页 27 row_data = "<a href='{request_path}{obj_id}/edit/'>{data}</a>".format(request_path=request.path, 28 obj_id=query_set_obj.id, 29 data=row_data) 30 31 #标签元素的拼接 32 element += "<td>{0}</td>".format(row_data) 33 return mark_safe(element) 34 35 ...
上述内容改完后,不要忘记在模板文件中还要添加一个参数:request
。
访问看看:
2. 填充编辑页面详情
2.1. 编写动态生成ModelForm派生类的功能函数
由于数据量很大,每条数据都要进入到第三级页面中,生成新的页面进行操作:添加、修改、删除等。那么,我是不是要针对每条数据都要写一套代码,很显然是不可能的!作为程序员,重复代码就是最大的BUG
,尤其是大量的同样代码。
在三级页面中,不论是编辑、添加还是删除都有共通的地方。那么该怎么实现呢?好在Django
为我们提供了便捷的方式:动态生成Form
表单。
在Django
中,原生的admin
常结合models
使用的Form
表单功能是ModelForm
,它能够配合数据库动态生成数据表单。
我们知道如何动态的实现表单功能了,那它同样的问题就是代码的重复问题,那么多的数据,我们要写那么多的ModelForm
吗?回答是肯定的:不可能! 不想写重复代码其实也很简单的,我们只需要动态生成ModelForm
不就行了嘛!
通常我们使用ModelForm
会这样:
1 from django.forms import ModelForm 2 from crm import models 3 #要动态生成的目标类 4 class CustomerModelForm(ModelForm): 5 class Meta: 6 model = models.Customer 7 fields = "__all__"
现在,我们要让它自动创建:在king_admin
项目的目录下,创建forms.py
文件。
1 from django.forms import ModelForm 2 3 def create_model_form(request,admin_class): 4 ''' 5 动态生成ModelForm类 6 :param request: 7 :param admin_class: 8 :return: 9 ''' 10 <-----------------------类成员构造----------------------------- 11 class Meta: 12 model = admin_class.model 13 fields = "__all__" 14 #类的成员 15 attrs = {'Meta':Meta} 16 17 <-----------------------动态创建类----------------------------- 18 #type函数创建类--->type('类名',(基类,),以字典形式的类的成员) 19 _model_form_class = type("DynamicModelForm",(ModelForm,),attrs) 20 21 <-----------------定义创建对象的方法----------------------------- 22 # 定义__new__方法,用于创建类,cls类名 23 def __new__(cls, *args, **kwargs): 24 # 遍历数据库的所有字段和字段对应的对象 25 for field_name, field_obj in cls.base_fields.items(): 26 # 为字段对象的组件添加class属性 27 field_obj.widget.attrs['class'] = 'form-control' 28 # 创建当前类的实例--->即创建子类 29 return ModelForm.__new__(cls) 30 # 定义元数据 31 32 <-------------------为对象添加属性-------------------------------- 33 #为该类添加__new__静态方法,当调用该类时,会先执行__new__方法,创建对象 34 # 这里会覆盖父类的__new__ 35 setattr(_model_form_class,'__new__',__new__) 36 37 return _model_form_class
2.2 编写视图函数
在二级显示页面中,我们通过点击id
值跳转到编辑页面,然后经过路由触发视图函数获取到相关数据,修改会经过上面创建的表单验证类进行验证:
1 ... 2 from king_admin.forms import create_model_form 3 4 ... 5 def table_object_edit(request, app_name, table_name, object_id): 6 admin_class = site.enabled_admins[app_name][table_name] 7 #创建ModelForm类 8 model_form = create_model_form(request, admin_class) 9 #通过id获取数据库内容 10 object_list = admin_class.model.objects.get(id=object_id) 11 12 if request.method == 'POST': 13 #表单进行验证,更新数据 14 form_object = model_form(request.POST, instance=object_list) 15 if form_object.is_valid(): 16 form_object.save() 17 else: 18 form_object = model_form(instance=object_list) 19 20 return render(request, 'king_admin/table_object_edit.html', {"form_object": form_object, 21 "admin_class": admin_class, 22 "app_name": app_name, 23 "table_name": table_name})
2.3 编写模板文件
在新建的table_object_edit.html
中我们添加的应该会比较多,因此,会拆分成几大部分编写。
2.3.1 基础字段内容显示
先将数据内容显示和字段名称显示出来:
1 {% extends 'king_admin/table_index.html' %} 2 3 {% block extra-css-resources %} 4 5 {% endblock %} 6 7 8 {% block container %} 9 <form method="post" class="form-horizontal"> 10 {% csrf_token %} 11 {% for field in form_object %} 12 <div class="form-group" > 13 {# 显示名称 #} 14 <label class="col-sm-3 control-label" style="font-weight: normal"> 15 {{ field.label }} 16 </label> 17 {# 显示数据输入框 #} 18 <div class="col-lg-6" style="width: auto"> 19 {{ field }} 20 </div> 21 </div> 22 {% endfor %} 23 </form> 24 {% endblock %}
效果如下:
2.3.2 必填字段显示设置
效果我们看到了,已经达到预期。在回过头看看form_object
到底是什么?
在视图函数中,我们通过打印form_object
(自行添加print(form_object)
)能够看到如下内容:
1 <tr><th><label for="id_name">Name:</label></th><td><input class="form-control" id="id_name" maxlength="32" name="name" type="text" value="dfgh" /></td></tr> 2 <tr><th><label for="id_qq">Qq:</label></th><td><input class="form-control" id="id_qq" maxlength="64" name="qq" type="text" value="0002" required /></td></tr> 3 <tr><th><label for="id_qq_name">Qq name:</label></th><td><input class="form-control" id="id_qq_name" maxlength="64" name="qq_name" type="text" value="发电规划" /></td></tr> 4 <tr><th><label for="id_phone">Phone:</label></th><td><input class="form-control" id="id_phone" maxlength="64" name="phone" type="text" value="234蚊346" /></td></tr> 5 <tr><th><label for="id_source">Source:</label></th><td><select class="form-control" id="id_source" name="source" required> 6 <option value="">---------</option> 7 <option value="0" selected="selected">转介绍</option> 8 <option value="1">QQ群</option> 9 <option value="2">官网</option> 10 <option value="3">百度推广</option> 11 <option value="4">51CTO</option> 12 <option value="5">知乎</option> 13 <option value="6">市场推广</option> 14 </select></td></tr> 15 <tr><th><label for="id_referral_from">转介绍人qq:</label></th><td><input class="form-control" id="id_referral_from" maxlength="64" name="referral_from" type="text" value="567456" /></td></tr> 16 <tr><th><label for="id_consult_course">咨询课程:</label></th><td><select class="form-control" id="id_consult_course" name="consult_course" required> 17 <option value="">---------</option> 18 <option value="4">成功学</option> 19 <option value="5">搜索引擎</option> 20 <option value="6" selected="selected">内核开发</option> 21 <option value="7">相亲</option> 22 </select></td></tr> 23 <tr><th><label for="id_content">咨询详情:</label></th><td><textarea class="form-control" cols="40" id="id_content" name="content" rows="10" required> 24 如图</textarea></td></tr> 25 <tr><th><label for="id_tags">Tags:</label></th><td><select multiple="multiple" class="form-control" id="id_tags" name="tags"> 26 <option value="3" selected="selected">大牛</option> 27 </select></td></tr> 28 <tr><th><label for="id_status">Status:</label></th><td><select class="form-control" id="id_status" name="status" required> 29 <option value="0">已报名</option> 30 <option value="1" selected="selected">未报名</option> 31 </select></td></tr> 32 <tr><th><label for="id_consultant">Consultant:</label></th><td><select class="form-control" id="id_consultant" name="consultant" required> 33 <option value="">---------</option> 34 <option value="1" selected="selected">三弗</option> 35 </select></td></tr> 36 <tr><th><label for="id_memo">Memo:</label></th><td><textarea class="form-control" cols="40" id="id_memo" name="memo" rows="10"> 37 如图用户</textarea></td></tr>
细心的朋友会发现一个required
,也会问这是什么鬼?哪来的?其实回头看看打印内容目的就是要看这个required
属性。
在独立使用Form
表单时,我们可以独立设置相关字段的参数,同样在MoldelForm
中也是可以的,但是有一点是值得注意的: If the model field has blank=True, then required is set to False on the form field. Otherwise, required=True.
这是官方的原话。意思是:如果在model中字段参数中设置了blank=True
,那么Form
中的required=False
;若没设置blank=True
,则默认为required=True
(具体看源码的初始化函数设定)。
现在,明白了required
是怎么来的了吧!好了,开始该处理它了,只需要我们在模板文件中添加一个简单的判断和样式修饰:
1 {# 显示名称 #} 2 {% if field.field.required %} 3 <label class="col-sm-3 control-label" style="font-weight: normal"> 4 <span style="color: red">*</span>{{ field.label }} 5 </label> 6 {% else %} 7 <label class="col-sm-3 control-label" style="font-weight: normal"> 8 {{ field.label }} 9 </label> 10 {% endif %}
渲染后的效果:
2.3.3 添加保存按钮
基本的显示字段和样式添加的差不多了,接下来就是添加提交的按钮(保存)。在同一个form
表单中添加如下:
1 ... 2 3 {# 显示数据输入框 #} 4 <div class="col-lg-6" style="width: auto"> 5 {{ field }} 6 </div> 7 </div> 8 {% endfor %} 9 {# 添加保存按钮 #} 10 <div class="form-group"> 11 <div class="col-sm-10 "> 12 <button type="submit" class="btn btn-success pull-right">保存</button> 13 </div> 14 </div>
渲染效果:
2.3.4 编辑测试
修改表格中一些内容:
保存后到上级页面中查看是否已经更改:
已经修改成功!
这里需要注意的是,前端展示数据的变化情况,由几个因素决定:1.检索。2.搜索。3.页码。4.排序...。这里只是做了页码的参数传递,如果修改前前端展示的是基于检索、搜索、页码等组合选出的数据,你在修改完后,其它过滤字段就丢失了,就不能返回修改前那条数据所在的页码。就如图上图展示的,排序字段丢失了,编辑后跳转回来的页面数据就不是id=4的数据了。
修改完成后的效果应该是这样:
2.3.5 完善保存按钮,添加跳转
保存的功能已经实现,但是有一点还是比较苦逼的:保存后,没有自动跳转到上一页。那就给它添加一个跳转链接,但是为了用户体验良好,还要考虑一个问题:考虑到分页,返回后直接到用户点击时的页面。这里你有没有想起之前我们所做的过滤、排序功能啥的都是基于分页来的, 同样我们在这里基于分页来做。
在入口处,我们添加的是一个<a></a>
标签,并在里面添加了动态参数拼接成url
,这是因为这样,通过这个url
来传递当前页码的参数:***/**/?get_page=get_page
。
1. 修改table_objs.html
文件中的内容,只需要为下面标签添加一个get_page
参数:
1 {#创建列表行数据#} 2 {% create_row request item admin_class get_page %}
2. 修改该标签对应的templatetags
中对应的标签函数,只需要添加形参和修改<a>标签
:
1 <-----------------------创建表格行数据----------------------------- 2 @register.simple_tag 3 def create_row(request, query_set_obj, admin_class, get_page): 4 #创建标签元素--空,None不行 5 element = '' 6 7 #遍历要显示的models字段 8 for number, row in enumerate(admin_class.list_display): 9 #获取显示字段对应的字段对象 10 field_obj = admin_class.model._meta.get_field(row) 11 #获取数据 12 #判断choice 13 if field_obj.choices: 14 #通过反射获取对象里面的值,并执行该方法get_字段_display()获取choices里面的数值 15 row_data = getattr(query_set_obj, 'get_{0}_display'.format(row))() 16 else: 17 row_data = getattr(query_set_obj, row) 18 19 #时间格式转换 20 if type(row_data).__name__ == 'datetime': 21 row_data = row_data.strftime('%Y-%m-%d %H-%M-%S') 22 23 #添加编辑页面入口 24 if number == 0: #add a tag, 可以跳转到修改页 25 row_data = "<a href='{request_path}{obj_id}/edit/?get_page={get_page}'>{data}</a>".format(request_path=request.path, 26 obj_id=query_set_obj.id, 27 get_page=get_page, 28 data=row_data) 29 30 #标签元素的拼接 31 element += "<td>{0}</td>".format(row_data) 32 return mark_safe(element)
3.视图函数kingadmin/views.py增加如下代码
1 def table_object_edit(request, app_name, table_name, object_id): 2 """ 3 编辑表中的一条数据 4 :param request: 5 :param app_name: 6 :param table_name: 7 :param id: 8 :return: 9 """ 10 admin_class = kingadmin.enabled_admins[app_name][table_name] 11 12 model_form = create_model_form(request, admin_class) 13 14 object_list = admin_class.model.objects.get(id=object_id) 15 page = request.GET.get('page') 16 if request.method == 'POST': 17 form_object = model_form(request.POST, instance=object_list) 18 if form_object.is_valid(): 19 form_object.save() 20 return redirect('/kingadmin/{0}/{1}?page={2}'.format( 21 app_name, 22 table_name, 23 page, 24 )) 25 else: # 目前可以删除(无论检验成功与否都是要返回table_object_edit.html) 26 return render(request, 'kingadmin/table_object_edit.html', {'form_object': form_object, 27 'admin_class': admin_class, 28 'app_name': app_name, 29 'table_name': table_name, 30 }) # 目前可以删除 31 else: 32 form_object = model_form(instance=object_list) 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 })
同时,在模板文件中table_object_edit.html中,需要添加错误提示:
1 {% extends 'project/head.html' %} 2 {% load tags %} 3 4 {% block extra_css_resource %} 5 6 {% endblock %} 7 8 {% block main %} 9 <form class="form-horizontal" method="POST"> 10 {% csrf_token %} 11 {% for field in form_object %} 12 <div class="form-group"> 13 {# <label for="inputEmail3" class="col-sm-2 control-label">Email</label>#} 14 {% if field.field.required %} 15 <label class="col-sm-3 control-label" style="font-weight:bold"><span 16 style="color:red;">*</span>{{ field.label }}</label> 17 {% else %} 18 <label class="col-sm-3 control-label" style="font-weight:normal;">{{ field.label }}</label> 19 {% endif %} 20 <div class="col-sm-6"> 21 {{ field }}<span style="color:red;">{{ field.errors }}</span> 22 </div> 23 </div> 24 {% endfor %} 25 <div class="form-group"> 26 <div class="col-sm-10"> 27 <button type="submit" class="btn btn-success pull-right">保存</button> 28 </div> 29 </div> 30 </form> 31 {% endblock %}
渲染测试:
2.4 双向复选框
为何要在最后来改这个复选框?因为咱们这个在添加或其他页面也会出现,甚至是继承下面即将编写的双向复选框。
2.4.1 原生admin的双向复选框
先来看看原生admin
的双向复选框:
需要在admin.py
文件中的自定义类中添加字段:filter_horizontal = ('tags',)
做的非常的不错。
原生版的效果图如下:
我们目前的前端展示的效果如下:
相距甚远,我们下面就主要实现这个效果。
2.4.2 添加双向复选框
跟上面的方式一样,我们要在之前自己编写的注册那里添加类似的字段:
1 king_admin.py中需要现实的类中添加:filter_horizontal = ['tags'] 2 King_admin_base.py中的ModelAdmin中添加:filter_horizontal = ['tags']
基本的配置操作就搞定了!
接下来就是实现功能的阶段:
1. 判断配置中是否存在设置的字段
1 在table_object_edit.html中,我们已经显示出tags复选框,在这里要额外的加一个简单的判断,
这里作为演示简单的添加一个双击的事件,作为选择方式。比较不错的推荐开源的多重复选框。
需要添加的标签如下:
1 {% load tags %} 2 {% block extra-css-resources %} 3 <style type="text/css"> 4 .selector{ 5 float: left; 6 text-align: left; 7 } 8 h5{ 9 border: 1px solid #ccc; 10 border-radius: 4px 4px 0 0; 11 background: #f8f8f8; 12 color: #666; 13 padding: 8px; 14 font-weight: 400; 15 font-size: 13px; 16 height: 30px; 17 width: 300px; 18 margin-left: 15px; 19 margin-bottom: 0; 20 } 21 .right,.left{ 22 border: 1px solid #ccc; 23 border-top: 0; 24 margin-left: 15px; 25 } 26 ul{ 27 -webkit-padding-start: 30px; 28 } 29 .selector-add{ 30 background: url(/static/imgs/selector-icons.svg) 0 -96px no-repeat; 31 list-style: none; 32 } 33 .selector-remove{ 34 background: url(/static/imgs/selector-icons.svg) 0 -64px no-repeat; 35 } 36 .selector-add, .selector-remove{ 37 width: 16px; 38 height: 16px; 39 display: block; 40 text-indent: -3000px; 41 overflow: hidden; 42 cursor: default; 43 opacity: 0.3; 44 } 45 .selector-chooser { 46 float: left; 47 width: 16px; 48 background-color: #eee; 49 border-radius: 10px; 50 margin: 70px 5px 0 20px; 51 padding: 0; 52 } 53 </style> 54 {% endblock %} 55 ... 56 57 <form method="post" class="form-horizontal" onsubmit="return SelectAllChosenData()"> 58 {% csrf_token %} 59 60 ... 61 62 {# 双向复选框的判断 #} 63 {% if field.name in admin_class.filter_horizontal %} 64 {# 左复选框 #} 65 <div class="selector "> 66 <h5>Available tags</h5> 67 <div class="selector"> 68 {# 获取多对多的被选中数据 #} 69 {% m2m_get_object_list admin_class form_object field as select_object_list %} 70 <select class="left" style="width: 300px;height: 200px" multiple name="{{ field.name }}" id="id_{{ field.name }}_from"> 71 {% for item in select_object_list %} 72 <option ondblclick="MoveElementTo(this,'id_{{ field.name }}_to','id_{{ field.name }}_from')" value="{{ item.id }}">{{ item }}</option> 73 {% endfor %} 74 </select> 75 </div> 76 </div> 77 {# 中间箭头 #} 78 <div style="float: left;margin-top: 50px"> 79 <ul class="selector-chooser"> 80 <li><a title="Choose" href="#" id="id_tags_add_link" class="selector-add">Choose</a></li> 81 <li><a title="Remove" href="#" id="id_tags_remove_link" class="selector-remove">Remove</a></li></ul> 82 </div> 83 {# 右复选框 #} 84 <div class="selector "> 85 <h5 style="background-color: #79aec8; color:white">Choosen tags</h5> 86 <div class="selector"> 87 {% get_selected_object_list form_object field as get_selected_list %} 88 <select class="right" style="width: 300px;height: 200px" multiple id="id_{{ field.name }}_to" name="{{ field.name }}"> 89 {% for item in get_selected_list %} 90 <option ondblclick="MoveElementTo(this,'id_{{ field.name }}_from','id_{{ field.name }}_to')" value="{{ item.id }}">{{ item }}</option> 91 {% endfor %} 92 </select> 93 </div> 94 </div> 95 96 {% else %} 97 <div class="col-lg-6" style="width: auto"> 98 {{ field }}<span style="color: red">{{ field.errors.as_text }}</span> 99 </div> 100 {% endif %} 101 ... 102 103 <script> 104 function MoveElementTo(self,target_id,source_id) { 105 var opt_ele = "<option value='" + $(self).val() + "' ondblclick=MoveElementTo(this,'" + source_id +"','"+ target_id +"')>" + $(self).text() + "</option>"; 106 $("#" +target_id).append(opt_ele); 107 $(self).remove(); 108 109 } 110 //提交所有数据 111 function SelectAllChosenData() { 112 113 $("select[class='right'] option").each(function () { 114 $(this).prop("selected",true); 115 }); 116 return true; 117 } 118 </script> 119 ...
上述代码实现的基本的复选功能,tags.py需要添加两个函数,一个用于返回所有对应manytomany字段的所有数据,另外一个是将该条记录对应的manytomany字段的数据查询出来,如下:
1 from django import template 2 from django.utils.safestring import mark_safe 3 from kingadmin.utils import filters_to_text 4 5 register = template.Library() 6 7 8 @register.simple_tag 9 def table_verbose_name(admin_class): 10 return admin_class.model._meta.verbose_name 11 12 13 @register.simple_tag 14 def create_page_num(contacts, ordering, _q, filter_text): 15 """ 16 返回分页按钮的html方式 17 :param contacts: Paginator.page(num) 18 :return: 19 """ 20 page_num_html = '' 21 dot_sign = False 22 for number in contacts.paginator.page_range: 23 btn_element = """<li class="{0}"><a href="?page={1}&o={2}&_q={3}&{4}">{5}</a></li>""" 24 li_class = '' 25 if number < 3 \ 26 or number > contacts.paginator.num_pages - 2 \ 27 or abs(number - contacts.number) <= 2: # 前三页,后2页,当前页的前后2页 28 if number == contacts.number: # 页码等于当前页 29 li_class = "active" 30 dot_sign = False 31 page_num_html += btn_element.format(li_class, number, ordering, _q, filter_text, number) 32 else: 33 if not dot_sign: 34 page_num_html += '<li><a>...</a></li>' 35 dot_sign = True 36 return mark_safe(page_num_html) 37 # for number in contacts.paginator.page_range: 38 # if number < 3 or number > contacts.paginator.num_pages - 2 or abs(contacts.number - number) <= 2: # 前两页或最后两页 #} 39 # if number == contacts.number: 40 # page_num_html += """<li class ="active"><a href="?page={0}">{1}</a></li>""".format(number, number) 41 # dot_sign = False 42 # else: 43 # dot_sign = False 44 # page_num_html += """<li><a href="?page={0}">{1}</a></li>""".format(number, number) 45 # elif contacts.paginator.num_pages < 7: # 总页数为6,直接显示页码 46 # dot_sign = False 47 # page_num_html += """<li><a href="?page={0}">{1}</a></li>""".format(number, number) 48 # elif contacts.paginator.num_pages >= 7: # 7页以上 49 # temp = contacts.paginator.num_pages / 2 50 # if type(temp) is int: # 能整除,偶数页,显示中间四页 51 # if temp - 1 <= number <= temp + 2: # 中间四页 52 # if number == contacts.number: # 判断当前页 53 # page_num_html += """<li class ="active"><a href="?page={0}">{1}</a></li>""".format(number, 54 # number) 55 # dot_sign = False 56 # else: 57 # dot_sign = False 58 # page_num_html += """<li><a href="?page={0}">{1}</a></li>""".format(number, number) 59 # else: # 非中间四页 60 # page_num_html += """<li><a href="#">...</a></li>""" 61 # else: # 不能整除,奇数页,显示中间三页 62 # if int(temp) <= number <= temp + 2: # 中间三页 63 # if number == contacts.number: 64 # page_num_html += """<li class ="active"><a href="?page={0}">{1}</a></li>""".format(number, 65 # number) 66 # dot_sign = False 67 # else: 68 # dot_sign = False 69 # page_num_html += """<li><a href="?page={0}">{1}</a></li>""".format(number, number) 70 # else: # 非中间三页 71 # if not dot_sign: 72 # page_num_html += """<li><a href="#">...</a></li>""" 73 # dot_sign = True 74 # return mark_safe(page_num_html) 75 76 77 @register.simple_tag 78 def field_verbose_name(filter_condition, admin_class, filter_conditions_customer): 79 """ 80 返回表中的字段的verbose_name,取代英文 81 :param filter_condition: string 82 :param admin_class: class 83 :return: 84 """ 85 if hasattr(admin_class.model, filter_condition): 86 field_class = admin_class.model._meta.get_field(filter_condition) 87 name = field_class.verbose_name 88 choices = field_class.choices 89 selected_value = '' 90 label_html = """<label for={0}>{1}</label>""".format(field_class.name, name) # label content 91 select_html = """<select class="form-control" id="{0}" name="{1}">""".format(field_class.name, field_class.name) 92 # '<option>1</option></select>' # select content 93 94 # selected 95 if field_class.name in filter_conditions_customer: 96 selected_value = filter_conditions_customer[field_class.name] 97 98 if choices or type(field_class).__name__ == 'ForeignKey': # 外键 99 for item in field_class.get_choices(): 100 if str(item[0]) == selected_value: 101 select_html += """<option value={0} selected>{1}</option>""".format(item[0], item[1]) 102 else: 103 select_html += """<option value={0}>{1}</option>""".format(item[0], item[1]) 104 select_html += '</select>' 105 group_html = label_html + select_html 106 107 return mark_safe(group_html) 108 else: 109 return 'there is no %s' % filter_condition 110 111 112 @register.simple_tag 113 def ret_field_value(request, query_set, fields_name, page, order, filter_conditions_customer, search_text): 114 """返回admin_class中list_display包含字段对应的一行数据,并组装成html格式""" 115 row_data = '' 116 filter_text = filters_to_text(filter_conditions_customer) 117 for k, field_name in enumerate(fields_name): 118 # options的数据类型如下: 119 # ((0, '转介绍'), 120 # (1, 'QQ群'), 121 # (2, '官网'), 122 # (3, '百度推广'), 123 # (4, '51CTO'), 124 # (5, '知乎'), 125 # (6, '市场部推广')) 126 # """<th><span><input type="checkbox"></span></th>""" 127 options = query_set._meta.get_field(field_name).choices 128 option_value = getattr(query_set, field_name) 129 if options: # 处理多选字段,即choices属性不为空 130 option_means = [item for item in options if item[0] == option_value][0][1] 131 unit_data = '<td>{0}</td>'.format(option_means) 132 elif type(option_value).__name__ == 'datetime': # 处理时间格式 133 option_value = option_value.strftime("%Y-%m-%d %H:%M:%S") 134 unit_data = '<td>{0}</td>'.format(option_value) 135 elif k == 0: 136 unit_data = '<td><a href="{0}/{1}/edit?page={2}&o={3}&_q={4}&{5}">{6}</a></td>'.format( 137 request.path, 138 query_set.id, 139 page, 140 order, 141 search_text, 142 filter_text, 143 option_value, 144 ) 145 else: 146 unit_data = '<td>{0}</td>'.format(option_value) 147 row_data += unit_data 148 row_data = '<tr><td><span><input type="checkbox" tag="object_checkbox" value="{0}"></span></td>{1}</tr>'.format( 149 query_set.id, 150 row_data 151 ) 152 return mark_safe(row_data) 153 154 155 @register.simple_tag 156 def ret_getattr(query_set, field_name): 157 return hasattr(query_set, field_name) 158 159 160 @register.simple_tag 161 def create_row(query_set_obj, admin_class): 162 element = '' 163 for row in admin_class.list_display: 164 165 field_obj = admin_class.model._meta.get_field(row) 166 if field_obj.choices: 167 row_data = getattr(query_set_obj, 'get_{0}_display'.format(row))() 168 else: 169 row_data = getattr(query_set_obj, row) 170 if type(row_data).__name__ == 'datetime': 171 row_data = row_data.strftime('%Y-%m-%d %H:%M:%S') 172 173 element += "<td>{0}</td>".format(row_data) 174 return mark_safe(element) 175 176 177 @register.simple_tag 178 def create_page_element(page, filter_conditions): 179 page_btns = '' 180 filters = '' 181 for k, v in filter_conditions.items(): 182 filters += '&{0}={1}'.format(k, v) 183 add_dot_ele = False # 标志符 184 for page_num in page.paginator.page_range: # query_set.paginator.page_range: range(1,n),分的总页数, 185 # page_num:每页的页码 186 # query_set.paginator.num_pages: 一共分了多少页 187 # query_set.number: 页对应的页码 188 if page_num < 3 or \ 189 page_num > page.paginator.num_pages - 2 or \ 190 abs(page.number - page_num) <= 2: # 代表最前2页或最后2页 #abs判断前后1页 191 element_class = "" 192 if page.number == page_num: 193 add_dot_ele = False 194 element_class = "active" 195 page_btns += '''<li class="%s"><a href="?page=%s%s">%s</a></li>''' % (element_class, 196 page_num, 197 filters, 198 page_num) 199 else: 200 if not add_dot_ele: 201 page_btns += '<li><a href="#">...</a></li>' 202 add_dot_ele = True 203 204 return mark_safe(page_btns) 205 206 207 @register.simple_tag 208 def render_filter_element(condition, admin_class, filter_conditions): 209 ''' 210 211 :param condition: 字符串, list_filter中的一个 212 :param admin_class: 数据库中的数据表类 213 :param filter_conditions: 字典,key=list_filter中的一个, value=前端传回的对应的option的value值 214 :return: 215 ''' 216 # 初始化下拉框 217 select_element = """<select class='form-control' name={0}><option value=''>------ 218 </option>""".format(condition) 219 field_object = admin_class.model._meta.get_field(condition) # 获取字段, admin_class中的字段 220 # 字段处理, 默认不选中 221 selected = '' # choice处理 222 if field_object.choices: # 遍历choices值 223 for choice_item in field_object.get_choices()[1:]: 224 # print(choice_item) 225 # 判断选择条件是否和choice值相等, 226 if filter_conditions.get(condition) == str(choice_item[0]): 227 # 被选中 228 selected = 'selected' 229 select_element += """<option value='{0}' {1}>{2}</option>""".format(choice_item[0], 230 selected, choice_item[1]) 231 selected = '' 232 # 外键处理 233 if type(field_object).__name__ == 'ForeignKey': 234 for choice_item in field_object.get_choices()[1:]: 235 # 判断选择条件是否和choice值相等 236 if filter_conditions.get(condition) == str(choice_item[0]): 237 # 被选中 238 selected = 'selected' 239 select_element += """<option value='{0}' {1}>{2}</option>""".format(choice_item[0], 240 selected, choice_item[1]) 241 selected = '' 242 select_element += '</select>' 243 return mark_safe(select_element) 244 245 246 @register.simple_tag 247 def ret_search_help(admin_class): 248 help_info = '' 249 if admin_class.search_fields: 250 # help_info += '搜索:' 251 for item in admin_class.search_fields: 252 help_info += item + '、' 253 else: 254 help_info += '未定义搜索字段' 255 256 return help_info 257 258 259 @register.simple_tag 260 def create_table_title(fields, order_after, filter_conditions_customer, search_text): 261 """ 262 263 :param fields: 264 :param order_after: 265 :param filter_conditions_customer: 266 :return: 267 """ 268 # """<th><span><input type="checkbox"></span>""" 269 # """<a href="?o={{ field.name }} style="display:block;font-size:14px;">{{ field.verbose_name }}</a>""" 270 # 添加一列checkbox 271 ele_checkbox = """<th><span><input type="checkbox" id="test" onclick="select_all_checkbox(this);"></span></th>""" 272 # filter_text 273 filter_text = '' 274 for k, v in filter_conditions_customer.items(): 275 temp = k + '=' + v 276 filter_text += temp + '&' 277 filter_text = filter_text.rstrip('&') 278 # 添加title 279 ele_title = '' 280 # 设置标题的href 281 for field in fields: 282 temp = order_after.lstrip('-') 283 if temp == field.name: 284 ele_title += """<th><a href="?{0}&o={1}&_q={2}">{3}</a></th>""".format( 285 filter_text, 286 order_after, 287 search_text, 288 field.verbose_name 289 ) 290 else: 291 ele_title += """<th><a href="?{0}&o={1}&_q={2}">{3}</a></th>""".format( 292 filter_text, 293 field.name, 294 search_text, 295 field.verbose_name 296 ) 297 table_title = ele_checkbox + ele_title 298 return mark_safe(table_title) 299 300 301 @register.simple_tag 302 def get_horizontal_tag_values(field, admin_class, form_object): 303 field_obj = getattr(admin_class.model, field.name) 304 query_sets_all = field_obj.rel.to.objects.all() 305 # print('===>query_sets_all', query_sets_all) 306 307 instance_field = getattr(form_object.instance, field.name) 308 query_sets_select = instance_field.all() 309 # print('====>query_sets_select', dir(query_sets_select.union())) 310 311 diff_query_sets = query_sets_all.difference(query_sets_select) 312 # print('====>different_query_sets', diff_query_sets) 313 return diff_query_sets 314 315 316 @register.simple_tag 317 def get_horizontal_field_value(field, form_object): 318 print(form_object.instance.tags) 319 field_obj = getattr(form_object.instance, field.name) 320 selected_list = field_obj.all() 321 return selected_list
这是修改过后的内容,双方的内容转换了,就是表名已经成功。
2.5 添加返回按钮
这个就更简单了,但往往在数量少的时候我们会进行硬编码来写路径,要是动态生成该怎么搞呢?在这里,我们要引入Django
的URL
逆向解析。 请看这里:Django路由。
在table_object_edit.html
文件中添加这一段代码:
1 ... 2 {% block container %} 3 {# 添加下面的块,便于继续 #} 4 {% block top %} 5 <div class="panel-heading"> 6 <button class="btn btn-success pull-right" ><a href="{% url 'king_admin:display_objects' app_name table_name %}" style="color: white">返回</a></button> 7 </div> 8 {% endblock %} 9 10 <form method="post" class="form-horizontal" onsubmit="return SelectAllChosenData()"> 11 ...
效果:
这里需要注意的是:
在前端展示页面table_object_edit.html中,客户类型的对应html标签中:
1 <select name="tags" class="form-control left" id="id_tags_from" multiple="multiple"> 2 <option value="4" ondblclick="MoveElementTo(this, 'id_tags_to', 'id_tags_from')"> 3 type4 4 </option> 5 <option value="5" ondblclick="MoveElementTo(this, 'id_tags_to', 'id_tags_from')"> 6 type5 7 </option> 8 </select>
只有当option设置成selected属性时,点击保存的时候,数据才能保存到后台,不然的话是无法保存数据的,因此需要编写js脚本将所有class="form-control left"的所有option选项都设置selected属性:
1 <script> 2 function MoveElementTo(self, target_id, source_id) { 3 var opt_ele = "<option value='" + $(self).val() + "' ondblclick=MoveElementTo(this,'" + source_id + "','" + target_id + "')>" + $(self).text() + "</option>"; 4 $('#' + target_id).append(opt_ele); 5 $(self).remove(); 6 } 7 8 function SelectAllChosenData() { 9 $("select[class='form-control right'] option").each(function () { 10 $(this).prop('selected', true); 11 console.log('select write'); 12 }); 13 return true; 14 } 15 </script>
需要注意的是,jquery class选择器
$("select[class='form-control right'] option")
必须是class='form-control right'不能写成class='dright',不然找不到数据。
到此,编辑页面就算是基本完成了。
但是发现有一个bug:
经过排查发现前端渲染中没有处理多对多字段的数据:
因此增加处理多对多字段的功能:
1 from django import template 2 from django.utils.safestring import mark_safe 3 from kingadmin.utils import filters_to_text 4 5 register = template.Library() 6 7 8 @register.simple_tag 9 def table_verbose_name(admin_class): 10 return admin_class.model._meta.verbose_name 11 12 13 @register.simple_tag 14 def create_page_num(contacts, ordering, _q, filter_text): 15 """ 16 返回分页按钮的html方式 17 :param contacts: Paginator.page(num) 18 :return: 19 """ 20 page_num_html = '' 21 dot_sign = False 22 for number in contacts.paginator.page_range: 23 btn_element = """<li class="{0}"><a href="?page={1}&o={2}&_q={3}&{4}">{5}</a></li>""" 24 li_class = '' 25 if number < 3 \ 26 or number > contacts.paginator.num_pages - 2 \ 27 or abs(number - contacts.number) <= 2: # 前三页,后2页,当前页的前后2页 28 if number == contacts.number: # 页码等于当前页 29 li_class = "active" 30 dot_sign = False 31 page_num_html += btn_element.format(li_class, number, ordering, _q, filter_text, number) 32 else: 33 if not dot_sign: 34 page_num_html += '<li><a>...</a></li>' 35 dot_sign = True 36 return mark_safe(page_num_html) 37 # for number in contacts.paginator.page_range: 38 # if number < 3 or number > contacts.paginator.num_pages - 2 or abs(contacts.number - number) <= 2: # 前两页或最后两页 #} 39 # if number == contacts.number: 40 # page_num_html += """<li class ="active"><a href="?page={0}">{1}</a></li>""".format(number, number) 41 # dot_sign = False 42 # else: 43 # dot_sign = False 44 # page_num_html += """<li><a href="?page={0}">{1}</a></li>""".format(number, number) 45 # elif contacts.paginator.num_pages < 7: # 总页数为6,直接显示页码 46 # dot_sign = False 47 # page_num_html += """<li><a href="?page={0}">{1}</a></li>""".format(number, number) 48 # elif contacts.paginator.num_pages >= 7: # 7页以上 49 # temp = contacts.paginator.num_pages / 2 50 # if type(temp) is int: # 能整除,偶数页,显示中间四页 51 # if temp - 1 <= number <= temp + 2: # 中间四页 52 # if number == contacts.number: # 判断当前页 53 # page_num_html += """<li class ="active"><a href="?page={0}">{1}</a></li>""".format(number, 54 # number) 55 # dot_sign = False 56 # else: 57 # dot_sign = False 58 # page_num_html += """<li><a href="?page={0}">{1}</a></li>""".format(number, number) 59 # else: # 非中间四页 60 # page_num_html += """<li><a href="#">...</a></li>""" 61 # else: # 不能整除,奇数页,显示中间三页 62 # if int(temp) <= number <= temp + 2: # 中间三页 63 # if number == contacts.number: 64 # page_num_html += """<li class ="active"><a href="?page={0}">{1}</a></li>""".format(number, 65 # number) 66 # dot_sign = False 67 # else: 68 # dot_sign = False 69 # page_num_html += """<li><a href="?page={0}">{1}</a></li>""".format(number, number) 70 # else: # 非中间三页 71 # if not dot_sign: 72 # page_num_html += """<li><a href="#">...</a></li>""" 73 # dot_sign = True 74 # return mark_safe(page_num_html) 75 76 77 @register.simple_tag 78 def field_verbose_name(filter_condition, admin_class, filter_conditions_customer): 79 """ 80 返回表中的字段的verbose_name,取代英文 81 :param filter_condition: string 82 :param admin_class: class 83 :return: 84 """ 85 if hasattr(admin_class.model, filter_condition): 86 field_class = admin_class.model._meta.get_field(filter_condition) 87 name = field_class.verbose_name 88 choices = field_class.choices 89 selected_value = '' 90 label_html = """<label for={0}>{1}</label>""".format(field_class.name, name) # label content 91 select_html = """<select class="form-control" id="{0}" name="{1}">""".format(field_class.name, field_class.name) 92 # '<option>1</option></select>' # select content 93 94 # selected 95 if field_class.name in filter_conditions_customer: 96 selected_value = filter_conditions_customer[field_class.name] 97 98 if choices or type(field_class).__name__ == 'ForeignKey': # 外键 99 for item in field_class.get_choices(): 100 if str(item[0]) == selected_value: 101 select_html += """<option value={0} selected>{1}</option>""".format(item[0], item[1]) 102 else: 103 select_html += """<option value={0}>{1}</option>""".format(item[0], item[1]) 104 select_html += '</select>' 105 group_html = label_html + select_html 106 107 return mark_safe(group_html) 108 else: 109 return 'there is no %s' % filter_condition 110 111 112 @register.simple_tag 113 def ret_field_value(request, query_set, fields_name, page, order, filter_conditions_customer, search_text): 114 """ 115 返回admin_class中list_display包含字段对应的一行数据,并组装成html格式 116 :param request: 117 :param query_set: class instanceme 118 :param fields_name: 119 :param page: 120 :param order: 121 :param filter_conditions_customer: 122 :param search_text: 123 :return: 124 """ 125 row_data = '' 126 filter_text = filters_to_text(filter_conditions_customer) 127 for k, field_name in enumerate(fields_name): 128 # options的数据类型如下: 129 # ((0, '转介绍'), 130 # (1, 'QQ群'), 131 # (2, '官网'), 132 # (3, '百度推广'), 133 # (4, '51CTO'), 134 # (5, '知乎'), 135 # (6, '市场部推广')) 136 # """<th><span><input type="checkbox"></span></th>""" 137 field_type = query_set._meta.get_field(field_name) 138 options = field_type.choices 139 option_value = getattr(query_set, field_name) 140 if options: # 处理多选字段,即choices属性不为空 141 option_means = [item for item in options if item[0] == option_value][0][1] 142 unit_data = '<td>{0}</td>'.format(option_means) 143 elif type(option_value).__name__ == 'datetime': # 处理时间格式 144 option_value = option_value.strftime("%Y-%m-%d %H:%M:%S") 145 unit_data = '<td>{0}</td>'.format(option_value) 146 elif k == 0: 147 unit_data = '<td><a href="{0}/{1}/edit?page={2}&o={3}&_q={4}&{5}">{6}</a></td>'.format( 148 request.path, 149 query_set.id, 150 page, 151 order, 152 search_text, 153 filter_text, 154 option_value, 155 ) 156 elif type(field_type).__name__ == 'ManyToManyField': # 处理多对多字段 157 field_values = getattr(query_set, field_name).all() 158 temp = '' 159 for k, v in enumerate(field_values): 160 temp += v.name + ',' 161 temp = temp.rstrip(',') 162 unit_data = '<td>{0}</td>'.format(temp) 163 else: 164 unit_data = '<td>{0}</td>'.format(option_value) 165 row_data += unit_data 166 row_data = '<tr><td><span><input type="checkbox" tag="object_checkbox" value="{0}"></span></td>{1}</tr>'.format( 167 query_set.id, 168 row_data 169 ) 170 return mark_safe(row_data) 171 172 173 @register.simple_tag 174 def ret_getattr(query_set, field_name): 175 return hasattr(query_set, field_name) 176 177 178 @register.simple_tag 179 def create_row(query_set_obj, admin_class): 180 element = '' 181 for row in admin_class.list_display: 182 183 field_obj = admin_class.model._meta.get_field(row) 184 if field_obj.choices: 185 row_data = getattr(query_set_obj, 'get_{0}_display'.format(row))() 186 else: 187 row_data = getattr(query_set_obj, row) 188 if type(row_data).__name__ == 'datetime': 189 row_data = row_data.strftime('%Y-%m-%d %H:%M:%S') 190 191 element += "<td>{0}</td>".format(row_data) 192 return mark_safe(element) 193 194 195 @register.simple_tag 196 def create_page_element(page, filter_conditions): 197 page_btns = '' 198 filters = '' 199 for k, v in filter_conditions.items(): 200 filters += '&{0}={1}'.format(k, v) 201 add_dot_ele = False # 标志符 202 for page_num in page.paginator.page_range: # query_set.paginator.page_range: range(1,n),分的总页数, 203 # page_num:每页的页码 204 # query_set.paginator.num_pages: 一共分了多少页 205 # query_set.number: 页对应的页码 206 if page_num < 3 or \ 207 page_num > page.paginator.num_pages - 2 or \ 208 abs(page.number - page_num) <= 2: # 代表最前2页或最后2页 #abs判断前后1页 209 element_class = "" 210 if page.number == page_num: 211 add_dot_ele = False 212 element_class = "active" 213 page_btns += '''<li class="%s"><a href="?page=%s%s">%s</a></li>''' % (element_class, 214 page_num, 215 filters, 216 page_num) 217 else: 218 if not add_dot_ele: 219 page_btns += '<li><a href="#">...</a></li>' 220 add_dot_ele = True 221 222 return mark_safe(page_btns) 223 224 225 @register.simple_tag 226 def render_filter_element(condition, admin_class, filter_conditions): 227 ''' 228 229 :param condition: 字符串, list_filter中的一个 230 :param admin_class: 数据库中的数据表类 231 :param filter_conditions: 字典,key=list_filter中的一个, value=前端传回的对应的option的value值 232 :return: 233 ''' 234 # 初始化下拉框 235 select_element = """<select class='form-control' name={0}><option value=''>------ 236 </option>""".format(condition) 237 field_object = admin_class.model._meta.get_field(condition) # 获取字段, admin_class中的字段 238 # 字段处理, 默认不选中 239 selected = '' # choice处理 240 if field_object.choices: # 遍历choices值 241 for choice_item in field_object.get_choices()[1:]: 242 # print(choice_item) 243 # 判断选择条件是否和choice值相等, 244 if filter_conditions.get(condition) == str(choice_item[0]): 245 # 被选中 246 selected = 'selected' 247 select_element += """<option value='{0}' {1}>{2}</option>""".format(choice_item[0], 248 selected, choice_item[1]) 249 selected = '' 250 # 外键处理 251 if type(field_object).__name__ == 'ForeignKey': 252 for choice_item in field_object.get_choices()[1:]: 253 # 判断选择条件是否和choice值相等 254 if filter_conditions.get(condition) == str(choice_item[0]): 255 # 被选中 256 selected = 'selected' 257 select_element += """<option value='{0}' {1}>{2}</option>""".format(choice_item[0], 258 selected, choice_item[1]) 259 selected = '' 260 select_element += '</select>' 261 return mark_safe(select_element) 262 263 264 @register.simple_tag 265 def ret_search_help(admin_class): 266 help_info = '' 267 if admin_class.search_fields: 268 # help_info += '搜索:' 269 for item in admin_class.search_fields: 270 help_info += item + '、' 271 else: 272 help_info += '未定义搜索字段' 273 274 return help_info 275 276 277 @register.simple_tag 278 def create_table_title(fields, order_after, filter_conditions_customer, search_text): 279 """ 280 281 :param fields: 282 :param order_after: 283 :param filter_conditions_customer: 284 :return: 285 """ 286 # """<th><span><input type="checkbox"></span>""" 287 # """<a href="?o={{ field.name }} style="display:block;font-size:14px;">{{ field.verbose_name }}</a>""" 288 # 添加一列checkbox 289 ele_checkbox = """<th><span><input type="checkbox" id="test" onclick="select_all_checkbox(this);"></span></th>""" 290 # filter_text 291 filter_text = '' 292 for k, v in filter_conditions_customer.items(): 293 temp = k + '=' + v 294 filter_text += temp + '&' 295 filter_text = filter_text.rstrip('&') 296 # 添加title 297 ele_title = '' 298 # 设置标题的href 299 for field in fields: 300 temp = order_after.lstrip('-') 301 if temp == field.name: 302 ele_title += """<th><a href="?{0}&o={1}&_q={2}">{3}</a></th>""".format( 303 filter_text, 304 order_after, 305 search_text, 306 field.verbose_name 307 ) 308 else: 309 ele_title += """<th><a href="?{0}&o={1}&_q={2}">{3}</a></th>""".format( 310 filter_text, 311 field.name, 312 search_text, 313 field.verbose_name 314 ) 315 table_title = ele_checkbox + ele_title 316 return mark_safe(table_title) 317 318 319 @register.simple_tag 320 def get_horizontal_tag_values(field, admin_class, form_object): 321 field_obj = getattr(admin_class.model, field.name) 322 query_sets_all = field_obj.rel.to.objects.all() 323 # print('===>query_sets_all', query_sets_all) 324 325 instance_field = getattr(form_object.instance, field.name) 326 query_sets_select = instance_field.all() 327 # print('====>query_sets_select', dir(query_sets_select.union())) 328 329 diff_query_sets = query_sets_all.difference(query_sets_select) 330 # print('====>different_query_sets', diff_query_sets) 331 return diff_query_sets 332 333 334 @register.simple_tag 335 def get_horizontal_field_value(field, form_object): 336 # print(form_object.instance.tags) 337 field_obj = getattr(form_object.instance, field.name) 338 selected_list = field_obj.all() 339 return selected_list
效果如下: