目录
urls.py
from django.conf.urls import url
from king_admin import views
urlpatterns = [
url(r'^(\w+)/(\w+)/(\d+)/change$', views.change_obj, name='change_obj'),
]
form_handles.py
若当前项目有众多app和models,需要手写modelform也是比较大的工作量。
动态生成modelform:根据传入的类参数动态生成对应的modelform。
from django.forms import ModelForm
def create_model_form(admin_class,add=False):
class Meta:
model = admin_class.model # 指定类
fields = '__all__' # 指定字段
exclude = admin_class.readonly_fields # 排除指定的字段,也不会生成form对象
# 若为新增对象,则移除所有exclude字段
if add:
exclude=[]
def __new__(cls, *args, **kwargs):
"""在dynamic_form实例化的时候会调用会先将form传入作为第一参数"""
print(cls.base_fields) # 当前记录的所有input标签{'name':name_field_obj,...}
# 1、循环当前form对象的所有字段名和字段对象
for field_name, field_obj in cls.base_fields.items():
# 2、给当前字段对象 增加样式
field_obj.widget.attrs.update({'class':'form-control'})
if field_obj.label=='Content':
# 给字段单独设置textarea样式
field_obj.widget.attrs.update({'cols':'100','rows':'10','class':'form-control'})
# 3、筛选出教师角色为教师和顾问的对象,以此修改显示的内容
if field_name == 'teachers':
field_obj._queryset = field_obj._queryset.filter(role__title='讲师')
if field_name == 'consultant':
field_obj._queryset = field_obj._queryset.filter(role__title='销售')
pass
# 4、给只读字段单独设置disabled样式
if field_name in admin_class.readonly_fields:
# 注意设置为disabled后,form表单不会提交数据
if add:
pass
else:
field_obj.widget.attrs.update({'disabled': 'true'})
return ModelForm.__new__(cls)
# 根据传入的admin_class创建form对象
dynamic_form = type("DynamicModelForm", (ModelForm,), {'Meta': Meta,'__new__':__new__})
return dynamic_form
views.py
def change_obj(request, app_name, model_name, id):
"""动态生成并返回modelform"""
admin_class = site.enable_admins[app_name][model_name]
# 1、根据id取出对象
obj = admin_class.model.objects.filter(id=int(id)).first()
# 2、动态创建modelform
dynamic_form = create_model_form(admin_class)
if request.method == 'POST':
# 4、用户提交post数据,暂存到form实例中
form_obj = dynamic_form(request.POST, instance=obj)
# 5、验证提交的数据的合法性
if form_obj.is_valid():
# 6、输入的数据符合规则才能保存到数据库
form_obj.save()
return redirect('/kingadmin/%s/%s' % (app_name, model_name))
errors = form_obj.errors
return render(request, 'kingadmin/change_info.html', locals())
# 3、向form中填充对象的数据,并返回到前端页面展示
form_obj = dynamic_form(instance=obj)
return render(request, 'kingadmin/change_info.html', locals())
change_info.html
<form class="form-horizontal" method="post" novalidate onsubmit="add_select()" STYLE="margin-left: 2%">
{% csrf_token %}
{# 在此处循环生成form的字段对象 #}
{% for input_obj in form_obj %}
<div class="form-group">
<label for="inputEmail3" class="control-label">{{ input_obj.label }}</label>
<div class="multiple_select_box_container">
{% if input_obj.name in admin_class.filter_horizontal %}
<!--特殊字段,开发多选框-->
{% else %}
{{ input_obj }}{% show_error_tips input_obj errors%}
{% endif %}
</div>
</div>
{% endfor %}
</form>
错误信息提示自定义标签
@register.simple_tag
def show_error_tips(input_obj, errors):
"""根据当前input对象和errors取出当前的input对应的错误信息"""
# 1、取出字段名
name_attr = input_obj.name
# 2、判断错误信息中是否有该字段
if name_attr in errors:
# 3、取出该字段的错误信息
error_msg = errors[name_attr][0]
# 4、制作错误信息标签,返回到页面
return mark_safe('<span class="error_msg">%s</span>' % error_msg)
else:
# 5、错误信息中没有当字段,则返回空
return ''
filter_horizontal多选框功能开发
左侧为所有可选择课程,右侧为已选择课程,要实现的功能是双击课程则转移到对面的列表中**。效果见下图:
kingadmin.py
filter_horizontal = ['consult_course'] # ManyToMany字段
前端页面
<div class='multiple_select_box_container'>
{% if input_obj.name in admin_class.filter_horizontal %}
<!--特殊字段,开发多选框-->
<div class="col-md-3" id="left_select_container">
<h5>可选课程</h5>
<select class="form-control" id="left_select_box" multiple="multiple">
<!--获取所有可选课程-->
{% get_available_fields admin_class input_obj.name id 1 %}
</select>
<input type="button" class="btn btn-success" id="add_all_btn" value="add all" style="margin-top: 15px">
</div>
<div class="col-md-3" id="right_select_container">
<h5 style="background: #79aec8;color: #fff;">已选择课程</h5>
<select multiple="multiple" class="form-control" name="{{ input_obj.name }}"
id="right_select_box" autocomplete="off">
<!--获取已选择的课程-->
{% get_available_fields admin_class input_obj.name id 0 %}
</select>
<input class="btn btn-danger" id="remove_all_btn" value="remove all" type="button" style="margin-top: 15px">
</div>
<span style='margin-left: -490px'>
{% show_error_tips input_obj errors%}
</span>
{% else %}
</div>
自定义标签渲染option标签
@register.simple_tag
def get_available_fields(admin_class, field_name, id, left=True):
"""获取客户的咨询课程"""
model_class = admin_class.model
html_str = ''
# 1、查看是否是新增对象页面
if id:
# 2、获取客户对象
obj = admin_class.model.objects.filter(id=int(id)).first()
# 3、取出 客户咨询课程 的集合
right_set = set(getattr(obj, field_name).all())
else:
right_set = set()
# 4、获取consult_courses字段对象
f_obj = model_class._meta.get_field(field_name)
# 5、取出字段对象关联的表的所有对象信息,此处为所有的Course对象集合
total_obj_set = set(f_obj.rel.to.objects.all())
# 6、通过集合运算取出没有选择的课程对象
left_set = total_obj_set - right_set
# 7、根据传入的参数确定当前请求是返回的是 左侧 还是 右侧 多选菜单标签
if left:
for course_obj in left_set:
html_str += '<option value=%s >%s</option>' % (course_obj.id, course_obj)
else:
for course_obj in right_set:
html_str += '<option value=%s >%s</option>' % (course_obj.id, course_obj)
return mark_safe(html_str)
js动态效果
功能:
- 1、双击某个课程则移动至对面的多选框;
- 2、左侧的add all 按钮 添加所有的课程到右侧;
- 3、右侧的remove all 按钮 移除所有课程
<script>
// 功能1、采用事件委派监听所有的option的双击事件
$('.multiple_select_box_container').on('dblclick','option',function () {
// 2、取出左侧 或 右侧 多选框 整个标签
var grand_parent_node = $(this).parent().parent();
// 3、判断当前的点击事件是发生在左侧还是右侧
if(grand_parent_node.attr('id')=='left_select_container'){
var click_node = $(this);
# 4、确定双击的是左侧标签中的option,则移除当前option
$(this).remove();
# 5、在右侧多选框中的子标签中添加此option标签,实现动态显示
grand_parent_node.next().children('[id=right_select_box]').append(click_node)
}
else if(grand_parent_node.attr('id')=='right_select_container'){
# 同4、5
var click_node = $(this);
$(this).remove();
grand_parent_node.prev().children('[id=left_select_box]').append(click_node);
}
});
// 功能2:点击添加所有课程
$('#add_all_btn').click(function () {
$('#right_select_box').append( $('#left_select_box option').remove() )
});
// 功能3:点击移除所有课程
$('#remove_all_btn').click(function () {
$('#left_select_box').append( $('#right_select_box option').remove() )
});
// 表单提交时,将右侧待提交的所有option的selected属性设置为True,因为由于页面点击操作可能修改右侧option的selected属性
function add_select() {
$('#right_select_box option').prop('selected',true)
}
// 过滤对象功能
// 1、监听input框的value改变状态
$('#id_consult_course_input').on('input propertychange',function () {
# 2、取出输入值
search_str = $(this).val();
# 3、循环遍历左侧多选框中的option标签
$('#left_select_box option').each(function (i,ele) {
# 4、找到符合条件的option则显示,否则隐藏
if(ele.text.search(search_str)==-1){
ele.hidden=true
}
else{
ele.hidden=false
}
})
});
</script>
只读字段设置
1、在设置form对象的时候排除只读字段:
def create_model_form(admin_class,add=False):
class Meta:
model = admin_class.model # 指定类
fields = '__all__' # 指定字段
exclude = admin_class.readonly_fields # 排除指定的字段,也不会生成form对象
2、前端渲染
{% block readonly_fields_display %}
{% readonly_fields_display admin_class id %}
{% endblock %}
3、自定义标签手动生成只读字段标签
@register.simple_tag
def readonly_fields_display(admin_class, id):
readonly_fields = admin_class.readonly_fields
ele = ''
obj = admin_class.model.objects.filter(id=int(id)).first()
for field_name in readonly_fields:
ele += ''' <div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">%s</label>
<div class="col-sm-10">
<p style="height: 34px;line-height: 34px">%s</p>
</div>
</div>''' % (field_name, obj.__dict__.get(field_name, '')) # 获取当前对象的字段值
return mark_safe(ele)