【CRM客户关系管理】18.多选字段filter_horizon
js实现多选框数据移动
js实现左右多选框移动数据
为左边的select增加一个id="id_{{ field.name }}_from"
,为右边的select增加一个id="id_{{ field.name }}_to"
。
为左边的option增加一个双击事件ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_to')"
,为右边的option增加一个双击事件ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_from')"
。
查看编辑表单的客户姓名这一栏
<input type="text" name="name" value="测试" maxlength="50" class="form-control" required="" id="id_name">
以上{{ field }}
自动生成了id和name等
所以需要在右边的多选框增加name
字段:name="{{ field.name }}">
<div class="col-sm-10">
{% if field.name in admin_class.filter_horizontal %}
<!--字段名在多选的字段中,使用多选框-->
<div class="col-md-5">
<select multiple class="form-control" id="id_{{ field.name }}_from">
{% get_available_m2m_data field.name form_obj admin_class as vailable_m2m_data %}
{% for obj in vailable_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_to')">{{ obj }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-5">
<select multiple class="form-control" id="id_{{ field.name }}_to" name="{{ field.name }}">
{% get_selected_m2m_data field.name form_obj admin_class as selected_m2m_data %}
{% for obj in selected_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_from')">{{ obj }}</option>
{% endfor %}
</select>
</div>
{% else %}
{{ field }}
{% endif %}
<span style="color: red">{{ field.errors.0 }}</span>
</div>
然后再{% endblock %}
块前增加js用于多选框移除移入
<script>
function MoveSelectedOption(ele, target_id) {
let new_target_id = $(ele).parent().attr('id');
let option = "<option value='" + $(ele).val() +"'ondblclick=MoveSelectedOption(this,'"+ new_target_id +"') >" + $(ele).text() +"</option>";
//将双击的元素添加到另一方
$("#"+ target_id).append(option);
//移除本方的元素
$(ele).remove();
}
</script>
当双击咨询课程左边多选框内的数据,就会移动到右边多选框,当双击右边多选框内的数据,就会移动到左边多选框。
访问 http://127.0.0.1:8000/djadmin/crm/customerinfo/1/change/ ,修改咨询课程,然后提交保存,但会出现以下提示

说明name="{{ field.name }}">
所在的select中的option没有一个为选中状态
js实现提交表单selected已选择数据
修改form表单提交的验证函数调用<form class="form-horizontal" method="post" onsubmit="VerificationBeforeFormSubmit();">。。。</form
定义一个提交选中右方所有选项的js函数
<script>
function VerificationBeforeFormSubmit() {
$("select[myflag] option").prop('selected', true);
}
</script>
由于这个全选没有一个id或者是class找到右方的多选框,所以自定义一个myflag="selected_m2m"
<div class="col-md-5">
<select myflag="selected_m2m" multiple class="form-control" id="id_{{ field.name }}_to" name="{{ field.name }}">
{% get_selected_m2m_data field.name form_obj admin_class as selected_m2m_data %}
{% for obj in selected_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_from')">{{ obj }}</option>
{% endfor %}
</select>
</div>
现在提交时就会把右方的多选框全选,进行提交。
如果不自定义,如下
<script>
function VerificationBeforeFormSubmit() {
$("select option").prop('selected', true);
}
</script>
当表单提交时,所有的选框select--option都会被选中!这就回造成如果有单选框是不能保存。
最终table_edit.html内容是
{% extends 'djadmin/base.html' %}
{% load djadmin_tags %}
{% block title %}
数据表编辑 - 后台管理
{% endblock %}
{% block content %}
<h1 class="page-header">{{ app_name }} - {{ model_name }} - 编辑 {{ obj }} </h1>
<!--<div>{{ form_obj }}</div>-->
<form class="form-horizontal" method="post" onsubmit="VerificationBeforeFormSubmit();">
{{ form_obj.errors }}
{% for field in form_obj %}
<div class="form-group">
<label class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-10">
{% if field.name in admin_class.filter_horizontal %}
<!--字段名在多选的字段中,使用多选框-->
<div class="col-md-5">
<select multiple class="form-control" id="id_{{ field.name }}_from">
{% get_available_m2m_data field.name form_obj admin_class as vailable_m2m_data %}
{% for obj in vailable_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_to')">{{ obj }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-5">
<select myflag="selected_m2m" multiple class="form-control" id="id_{{ field.name }}_to" name="{{ field.name }}">
{% get_selected_m2m_data field.name form_obj admin_class as selected_m2m_data %}
{% for obj in selected_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_from')">{{ obj }}</option>
{% endfor %}
</select>
</div>
{% else %}
{{ field }}
{% endif %}
<span style="color: red">{{ field.errors.0 }}</span>
</div>
</div>
{% endfor %}
{% csrf_token %}
{% if not admin_class.form_add %}
{# 当修改数据时,才显示下方只读字段,当增加数据时,下方则不会显示 #}
{% for field in admin_class.readonly_fields %}
<label class="col-sm-2 control-label">{{ field }}</label>
<div class="col-sm-10">
<p>{% get_obj_field_val form_obj field %}</p>
</div>
{% endfor %}
{% endif %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</div>
</form>
<script>
function MoveSelectedOption(ele, target_id) {
let new_target_id = $(ele).parent().attr('id');
let option = "<option value='" + $(ele).val() +"'ondblclick=MoveSelectedOption(this,'"+ new_target_id +"') >" + $(ele).text() +"</option>";
//将双击的元素添加到另一方
$("#"+ target_id).append(option);
//移除本方的元素
$(ele).remove();
}
function VerificationBeforeFormSubmit() {
//提交表单时选中所有的select
$("select[myflag] option").prop('selected', true);
}
</script>
{% endblock %}

解决添加数据报错问题
现在更新数据就可以用了。但添加数据会报错。
错误提示是ValueError: "<CustomerInfo: >" needs to have a value for field "id" before this many-to-many relationship can be used.
查看报错位置是模板标签中的selected_data = getattr(form_obj.instance, field_name).all()
,当修改数据,获取所有的已选择数据,当添加数据时,已选择为空,这儿就会报错,暂时使用try
来捕获该异常,修改下面的模板标签
@register.simple_tag
def get_available_m2m_data(field_name, form_obj, admin_class):
"""获取多对多字段关联表的所有数据"""
field_obj = admin_class.model._meta.get_field(field_name) # 获取字段对象
# consult_courses = models.ManyToManyField(Course, verbose_name='咨询课程') # 多对多关联课程
# 这是一个多对多字段,通过consult_courses对象获取到Course,也就是获取到所有咨询的课程
obj_list = field_obj.related_model.objects.all()
obj_list = set(obj_list) # 所有咨询课程的集合
try:
selected_data = set(getattr(form_obj.instance, field_name).all()) # 所以已选中课程的即可
except:
selected_data = set([])
# 通过集合求差集,得到未选中的自选课程,填充到左边多选框中
return obj_list - selected_data
@register.simple_tag
def get_selected_m2m_data(field_name, form_obj, admin_class):
"""获取已选中的多对多数据"""
try:
selected_data = getattr(form_obj.instance, field_name).all()
except:
selected_data = set([])
# print(selected_data)
return selected_data
现在访问 http://127.0.0.1:8000/djadmin/crm/customerinfo/add/ 不会报错,并且可以正常添加数据

添加全选和删除全部功能

修改table_edit.html增加a标签和js函数,用于选择全部,以及移除选择功能
{% if field.name in admin_class.filter_horizontal %}
<!--字段名在多选的字段中,使用多选框-->
<div class="col-md-5">
<select multiple class="form-control" id="id_{{ field.name }}_from">
{% get_available_m2m_data field.name form_obj admin_class as vailable_m2m_data %}
{% for obj in vailable_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_to')">{{ obj }}</option>
{% endfor %}
</select>
<p><a onclick="MoveAllElements('id_{{ field.name }}_from', 'id_{{ field.name }}_to')">全选></a></p>
</div>
<div class="col-md-5">
<select myflag="selected_m2m" multiple class="form-control" id="id_{{ field.name }}_to" name="{{ field.name }}">
{% get_selected_m2m_data field.name form_obj admin_class as selected_m2m_data %}
{% for obj in selected_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_from')">{{ obj }}</option>
{% endfor %}
</select>
<p><a onclick="MoveAllElements('id_{{ field.name }}_to', 'id_{{ field.name }}_from')"><删除全部</a></p>
</div>
{% else %}
{{ field }}
{% endif %}
添加js函数实现该功能
<script>
function MoveAllElements(from_id, to_id) {
console.log($("#" + from_id).children())
$("#" + from_id).children().each(function () {
MoveSelectedOption(this, to_id);
})
}
</script>
访问 http://127.0.0.1:8000/djadmin/crm/customerinfo/1/change/
点击删除全部,就会把所有已到左方

点击全选就会把所有移动到右方

添加数据页面功能也正常
多框框过滤功能
类似于Django Admin的选择框所有
创建搜索框
在左方多选框上增加一个type="search"
的input标签
<div class="col-md-5">
<input type="search" class="form-control" oninput="FuzzySearch(this)" placeholder="模糊搜索">
<select multiple class="form-control" id="id_{{ field.name }}_from">
{% get_available_m2m_data field.name form_obj admin_class as vailable_m2m_data %}
{% for obj in vailable_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_to')">{{ obj }}</option>
{% endfor %}
</select>
<p><a onclick="MoveAllElements('id_{{ field.name }}_from', 'id_{{ field.name }}_to')">全选></a></p>
</div>
js实现搜索过滤
<script>
function FuzzySearch(ele) {
//过滤多选框中的数据
console.log($(ele).val());
let search_text = $(ele).val().toUpperCase();
$(ele).next().children().each(function () {
if ($(this).text().toUpperCase().search(search_text) !== -1) {
$(this).show();
} else {
$(this).hide();
}
})
}
</script>
现在在输入框中输入值就会过滤下方的多选框内容。


最终table_edit.html代码如下
{% extends 'djadmin/base.html' %}
{% load djadmin_tags %}
{% block title %}
数据表编辑 - 后台管理
{% endblock %}
{% block content %}
<h1 class="page-header">{{ app_name }} - {{ model_name }} - 编辑 {{ obj }} </h1>
<!--<div>{{ form_obj }}</div>-->
<form class="form-horizontal" method="post" onsubmit="VerificationBeforeFormSubmit();">
{{ form_obj.errors }}
{% for field in form_obj %}
<div class="form-group">
<label class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-10">
{% if field.name in admin_class.filter_horizontal %}
<!--字段名在多选的字段中,使用多选框-->
<div class="col-md-5">
<input type="search" class="form-control" oninput="FuzzySearch(this)" placeholder="模糊搜索">
<select multiple class="form-control" id="id_{{ field.name }}_from">
{% get_available_m2m_data field.name form_obj admin_class as vailable_m2m_data %}
{% for obj in vailable_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_to')">{{ obj }}</option>
{% endfor %}
</select>
<p><a onclick="MoveAllElements('id_{{ field.name }}_from', 'id_{{ field.name }}_to')">全选></a></p>
</div>
<div class="col-md-5">
<select myflag="selected_m2m" multiple class="form-control" id="id_{{ field.name }}_to" name="{{ field.name }}">
{% get_selected_m2m_data field.name form_obj admin_class as selected_m2m_data %}
{% for obj in selected_m2m_data %}
<option value="{{ obj.id }}" ondblclick="MoveSelectedOption(this,'id_{{ field.name }}_from')">{{ obj }}</option>
{% endfor %}
</select>
<p><a onclick="MoveAllElements('id_{{ field.name }}_to', 'id_{{ field.name }}_from')">
<删除全部
</a></p>
</div>
{% else %}
{{ field }}
{% endif %}
<span style="color: red">{{ field.errors.0 }}</span>
</div>
</div>
{% endfor %}
{% csrf_token %}
{% if not admin_class.form_add %}
{# 当修改数据时,才显示下方只读字段,当增加数据时,下方则不会显示 #}
{% for field in admin_class.readonly_fields %}
<label class="col-sm-2 control-label">{{ field }}</label>
<div class="col-sm-10">
<p>{% get_obj_field_val form_obj field %}</p>
</div>
{% endfor %}
{% endif %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</div>
</form>
<script>
function MoveSelectedOption(ele, target_id) {
let new_target_id = $(ele).parent().attr('id');
let option = "<option value='" + $(ele).val() + "'ondblclick=MoveSelectedOption(this,'" + new_target_id + "') >" + $(ele).text() + "</option>";
//将双击的元素添加到另一方
$("#" + target_id).append(option);
//移除本方的元素
$(ele).remove();
}
function VerificationBeforeFormSubmit() {
//提交表单时选中所有的select
$("select option").prop('selected', true);
}
function MoveAllElements(from_id, to_id) {
console.log($("#" + from_id).children())
$("#" + from_id).children().each(function () {
MoveSelectedOption(this, to_id);
})
}
function FuzzySearch(ele) {
//过滤多选框中的数据
console.log($(ele).val());
let search_text = $(ele).val().toUpperCase();
$(ele).next().children().each(function () {
if ($(this).text().toUpperCase().search(search_text) !== -1) {
$(this).show();
} else {
$(this).hide();
}
})
}
</script>
{% endblock %}