CRM项目(六)
|
文章目录
1.1. 原生admin路由分析 1.2. 构建编辑页面路由 1.3. 添加视图函数 1.4. 添加模板文件 1.5.添加编辑页面的入口 2.1. 编写动态生成ModelForm派生类的功能函数 2.2 编写视图函数 2.3 编写模板文件 2.3.1 基础字段内容显示 2.3.2 必填字段显示设置 2.3.3 添加保存按钮 2.3.4 编辑测试 2.3.5 完善保存按钮,添加跳转 2.4 双向复选框 2.4.1 原生admin的双向复选框 2.4.2 添加双向复选框 2.5 添加返回按钮 |
CRM项目开发(六)
功能是永远加不完的!重头戏的Action放在后面作为压轴,接下来该添加三级页面啦!
1.添加编辑页面轮廓
有的朋友可能会问:为何直接写编辑页面而没有写添加页面?那是因为二者是相互继承的关系,个人觉得先写编辑比较好,然后添加继承编辑页面,改动基本上后台数据的更新与存储的问题。
1.1原生admin路由分析
如图:

1.2构建编辑页面路由
在king_admin应用下的urls.py文件中添加:
from django.conf.urls import url
from king_admin import views
urlpatterns = [
url(r'^$', views.index, name='table_index'),
url(r'^(\w+)/(\w+)/$', views.display_objects, name='display_objects'),
url(r'^(\w+)/(\w+)/(\d+)/edit/$', views.table_object_edit,name="table_object_edit"), #添加该行数据
]
1.3添加视图函数
在king_admin应用下的views.py文件中添加:
def table_object_edit(request, app_name, table_name, object_id):
return render(request, 'king_admin/table_object_edit.html')
1.4添加模板文件
在template/king_admin/目录下的table_object_edit.html文件,并在文件中添加如下内容:
{% extends 'king_admin/table_index.html' %}
{% block extra-css-resources %}
{% endblock %}
{% block container %}
{% endblock %}
1.5 添加编辑页面的入口
上面我们已经写完基本的页面流程,现在来添加编辑页面入口来打通整体流程,二级显示页面我们通过在templates中进行编写,修改这里即可,只需要添加一个参数(request),索引值和判断:
...
<-----------------------创建表格行数据-----------------------------
@register.simple_tag
def create_row(request, query_set_obj, admin_class):
#创建标签元素--空,None不行
element = ''
#遍历要显示的models字段
for number, row in enumerate(admin_class.list_display):
#获取显示字段对应的字段对象
field_obj = admin_class.model._meta.get_field(row)
#获取数据
#判断choice
if field_obj.choices:
#通过反射获取对象里面的值,并执行该方法get_字段_display()获取choices里面的数值
row_data = getattr(query_set_obj, 'get_{0}_display'.format(row))()
else:
row_data = getattr(query_set_obj, row)
#时间格式转换
if type(row_data).__name__ == 'datetime':
row_data = row_data.strftime('%Y-%m-%d %H-%M-%S')
#添加编辑页面入口
if number == 0: #add a tag, 可以跳转到修改页
row_data = "<a href='{request_path}{obj_id}/edit/'>{data}</a>".format(request_path=request.path,
obj_id=query_set_obj.id,
data=row_data)
#标签元素的拼接
element += "<td>{0}</td>".format(row_data)
return mark_safe(element)
...
上述内容改完后,不要忘记在模板文件中还要添加一个参数:request。
访问看看:

2.填充编辑页面详情
2.1编写动态生成ModelForm派生类的功能函数
由于数据量很大,每条数据都要进入到第三级页面中,生成新的页面进行操作:添加、修改、删除等。那么,我是不是要针对每条数据都要写一套代码,很显然是不可能的!作为程序员,重复代码就是最大的BUG,尤其是大量的同样代码。
在三级页面中,不论是编辑、添加还是删除都有共通的地方。那么该怎么实现呢?好在Django为我们提供了便捷的方式:动态生成Form表单。
在Django中,原生的admin常结合models使用的Form表单功能是ModelForm,它能够配合数据库动态生成数据表单。
我们知道如何动态的实现表单功能了,那它同样的问题就是代码的重复问题,那么多的数据,我们要写那么多的ModelForm吗?回答是肯定的:不可能! 不想写重复代码其实也很简单的,我们只需要动态生成ModelForm不就行了嘛!
通常我们使用ModelForm会这样:
from django.forms import ModelForm
from crm import models
#要动态生成的目标类
class CustomerModelForm(ModelForm):
class Meta:
model = models.Customer
fields = "__all__"
现在,我们要让它自动创建:在king_admin项目的目录下,创建forms.py文件。
from django.forms import ModelForm
def create_model_form(request,admin_class):
'''
动态生成ModelForm类
:param request:
:param admin_class:
:return:
'''
<-----------------------类成员构造-----------------------------
class Meta:
model = admin_class.model
fields = "__all__"
#类的成员
attrs = {'Meta':Meta}
<-----------------------动态创建类-----------------------------
#type函数创建类--->type('类名',(基类,),以字典形式的类的成员)
_model_form_class = type("DynamicModelForm",(ModelForm,),attrs)
<-----------------定义创建对象的方法-----------------------------
# 定义__new__方法,用于创建类,cls类名
def __new__(cls, *args, **kwargs):
# 遍历数据库的所有字段和字段对应的对象
for field_name, field_obj in cls.base_fields.items():
# 为字段对象的组件添加class属性
field_obj.widget.attrs['class'] = 'form-control'
# 创建当前类的实例--->即创建子类
return ModelForm.__new__(cls)
# 定义元数据
<-------------------为对象添加属性--------------------------------
#为该类添加__new__静态方法,当调用该类时,会先执行__new__方法,创建对象
# 这里会覆盖父类的__new__
setattr(_model_form_class,'__new__',__new__)
return _model_form_class
2.2 编写视图函数
在二级显示页面中,我们通过点击id值跳转到编辑页面,然后经过路由触发视图函数获取到相关数据,修改会经过上面创建的表单验证类进行验证:
...
from king_admin.forms import create_model_form
...
def table_object_edit(request, app_name, table_name, object_id):
admin_class = site.enabled_admins[app_name][table_name]
#创建ModelForm类
model_form = create_model_form(request, admin_class)
#通过id获取数据库内容
object_list = admin_class.model.objects.get(id=object_id)
if request.method == 'POST':
#表单进行验证,更新数据
form_object = model_form(request.POST, instance=object_list)
if form_object.is_valid():
form_object.save()
else:
form_object = model_form(instance=object_list)
return render(request, 'king_admin/table_object_edit.html', {"form_object": form_object,
"admin_class": admin_class,
"app_name": app_name,
"table_name": table_name})
2.3 编写模板文件
在新建的table_object_edit.html中我们添加的应该会比较多,因此,会拆分成几大部分编写。
2.3.1 基础字段内容显示
先将数据内容显示和字段名称显示出来:
{% extends 'king_admin/table_index.html' %}
{% block extra-css-resources %}
{% endblock %}
{% block container %}
<form method="post" class="form-horizontal">
{% csrf_token %}
{% for field in form_object %}
<div class="form-group" >
{# 显示名称 #}
<label class="col-sm-3 control-label" style="font-weight: normal">
{{ field.label }}
</label>
{# 显示数据输入框 #}
<div class="col-lg-6" style="width: auto">
{{ field }}
</div>
</div>
{% endfor %}
</form>
{% endblock %}
效果如下:

2.3.2 必填字段显示设置
效果我们看到了,已经达到预期。在回过头看看form_object到底是什么?
在视图函数中,我们通过打印form_object(自行添加print(form_object))能够看到如下内容:
<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> <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> <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> <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> <tr><th><label for="id_source">Source:</label></th><td><select class="form-control" id="id_source" name="source" required> <option value="">---------</option> <option value="0" selected="selected">转介绍</option> <option value="1">QQ群</option> <option value="2">官网</option> <option value="3">百度推广</option> <option value="4">51CTO</option> <option value="5">知乎</option> <option value="6">市场推广</option> </select></td></tr> <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> <tr><th><label for="id_consult_course">咨询课程:</label></th><td><select class="form-control" id="id_consult_course" name="consult_course" required> <option value="">---------</option> <option value="4">成功学</option> <option value="5">搜索引擎</option> <option value="6" selected="selected">内核开发</option> <option value="7">相亲</option> </select></td></tr> <tr><th><label for="id_content">咨询详情:</label></th><td><textarea class="form-control" cols="40" id="id_content" name="content" rows="10" required> 如图</textarea></td></tr> <tr><th><label for="id_tags">Tags:</label></th><td><select multiple="multiple" class="form-control" id="id_tags" name="tags"> <option value="3" selected="selected">大牛</option> </select></td></tr> <tr><th><label for="id_status">Status:</label></th><td><select class="form-control" id="id_status" name="status" required> <option value="0">已报名</option> <option value="1" selected="selected">未报名</option> </select></td></tr> <tr><th><label for="id_consultant">Consultant:</label></th><td><select class="form-control" id="id_consultant" name="consultant" required> <option value="">---------</option> <option value="1" selected="selected">三弗</option> </select></td></tr> <tr><th><label for="id_memo">Memo:</label></th><td><textarea class="form-control" cols="40" id="id_memo" name="memo" rows="10"> 如图用户</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是怎么来的了吧!好了,开始该处理它了,只需要我们在模板文件中添加一个简单的判断和样式修饰:
{# 显示名称 #}
{% if field.field.required %}
<label class="col-sm-3 control-label" style="font-weight: normal">
<span style="color: red">*</span>{{ field.label }}
</label>
{% else %}
<label class="col-sm-3 control-label" style="font-weight: normal">
{{ field.label }}
</label>
{% endif %}
渲染后的效果:

2.3.3 添加保存按钮
基本的显示字段和样式添加的差不多了,接下来就是添加提交的按钮(保存)。在同一个form表单中添加如下:
...
{# 显示数据输入框 #}
<div class="col-lg-6" style="width: auto">
{{ field }}
</div>
</div>
{% endfor %}
{# 添加保存按钮 #}
<div class="form-group">
<div class="col-sm-10 ">
<button type="submit" class="btn btn-success pull-right">保存</button>
</div>
</div>
渲染效果:

2.3.4 编辑测试
修改表格中一些内容:

保存后到上级页面中查看是否已经更改:

已经修改成功!
2.3.5 完善保存按钮,添加跳转
保存的功能已经实现,但是有一点还是比较苦逼的:保存后,没有自动跳转到上一页。那就给它添加一个跳转链接,但是为了用户体验良好,还要考虑一个问题:考虑到分页,返回后直接到用户点击时的页面。这里你有没有想起之前我们所做的过滤、排序功能啥的都是基于分页来的, 同样我们在这里基于分页来做。
在入口处,我们添加的是一个<a></a>标签,并在里面添加了动态参数拼接成url,这是因为这样,通过这个url来传递当前页码的参数:***/**/?get_page=get_page。
- 修改
table_objs.html文件中的内容,只需要为下面标签添加一个get_page参数:{#创建列表行数据#}
{% create_row request item admin_class get_page %}
- 修改该标签对应的
templatetags中对应的标签函数,只需要添加形参和修改<a>标签:<-----------------------创建表格行数据----------------------------- @register.simple_tag def create_row(request, query_set_obj, admin_class, get_page): #创建标签元素--空,None不行 element = '' #遍历要显示的models字段 for number, row in enumerate(admin_class.list_display): #获取显示字段对应的字段对象 field_obj = admin_class.model._meta.get_field(row) #获取数据 #判断choice if field_obj.choices: #通过反射获取对象里面的值,并执行该方法get_字段_display()获取choices里面的数值 row_data = getattr(query_set_obj, 'get_{0}_display'.format(row))() else: row_data = getattr(query_set_obj, row) #时间格式转换 if type(row_data).__name__ == 'datetime': row_data = row_data.strftime('%Y-%m-%d %H-%M-%S') #添加编辑页面入口 if number == 0: #add a tag, 可以跳转到修改页 row_data = "<a href='{request_path}{obj_id}/edit/?get_page={get_page}'>{data}</a>".format(request_path=request.path, obj_id=query_set_obj.id, get_page=get_page, data=row_data) #标签元素的拼接 element += "<td>{0}</td>".format(row_data) return mark_safe(element)
- 渲染测试:



2.4 双向复选框
为何要在最后来改这个复选框?因为咱们这个在添加或其他页面也会出现,甚至是继承下面即将编写的双向复选框。
2.4.1 原生admin的双向复选框
先来看看原生admin的双向复选框:
需要在admin.py文件中的自定义类中添加字段:filter_horizontal = ('tags',)

做的非常的不错。
2.4.2 添加双向复选框
跟上面的方式一样,我们要在之前自己编写的注册那里添加类似的字段:
king_admin.py中需要现实的类中添加:filter_horizontal = ['tags']King_admin_base.py中的ModelAdmin中添加:filter_horizontal = ['tags']
基本的配置操作就搞定了!
接下来就是实现功能的阶段:
-
判断配置中是否存在设置的字段
在
table_object_edit.html中,我们已经显示出tags复选框,在这里要额外的加一个简单的判断,这里作为演示简单的添加一个双击的事件,作为选择方式。比较不错的推荐开源的多重复选框。
需要添加的标签如下:
{% load tags %}
{% block extra-css-resources %}
<style type="text/css">
.selector{
float: left;
text-align: left;
}
h5{
border: 1px solid #ccc;
border-radius: 4px 4px 0 0;
background: #f8f8f8;
color: #666;
padding: 8px;
font-weight: 400;
font-size: 13px;
height: 30px;
width: 300px;
margin-left: 15px;
margin-bottom: 0;
}
.right,.left{
border: 1px solid #ccc;
border-top: 0;
margin-left: 15px;
}
ul{
-webkit-padding-start: 30px;
}
.selector-add{
background: url(/static/imgs/selector-icons.svg) 0 -96px no-repeat;
list-style: none;
}
.selector-remove{
background: url(/static/imgs/selector-icons.svg) 0 -64px no-repeat;
}
.selector-add, .selector-remove{
width: 16px;
height: 16px;
display: block;
text-indent: -3000px;
overflow: hidden;
cursor: default;
opacity: 0.3;
}
.selector-chooser {
float: left;
width: 16px;
background-color: #eee;
border-radius: 10px;
margin: 70px 5px 0 20px;
padding: 0;
}
</style>
{% endblock %}
...
<form method="post" class="form-horizontal" onsubmit="return SelectAllChosenData()">
{% csrf_token %}
...
{# 双向复选框的判断 #}
{% if field.name in admin_class.filter_horizontal %}
{# 左复选框 #}
<div class="selector ">
<h5>Available tags</h5>
<div class="selector">
{# 获取多对多的被选中数据 #}
{% m2m_get_object_list admin_class form_object field as select_object_list %}
<select class="left" style="width: 300px;height: 200px" multiple name="{{ field.name }}" id="id_{{ field.name }}_from">
{% for item in select_object_list %}
<option ondblclick="MoveElementTo(this,'id_{{ field.name }}_to','id_{{ field.name }}_from')" value="{{ item.id }}">{{ item }}</option>
{% endfor %}
</select>
</div>
</div>
{# 中间箭头 #}
<div style="float: left;margin-top: 50px">
<ul class="selector-chooser">
<li><a title="Choose" href="#" id="id_tags_add_link" class="selector-add">Choose</a></li>
<li><a title="Remove" href="#" id="id_tags_remove_link" class="selector-remove">Remove</a></li></ul>
</div>
{# 右复选框 #}
<div class="selector ">
<h5 style="background-color: #79aec8; color:white">Choosen tags</h5>
<div class="selector">
{% get_selected_object_list form_object field as get_selected_list %}
<select class="right" style="width: 300px;height: 200px" multiple id="id_{{ field.name }}_to" name="{{ field.name }}">
{% for item in get_selected_list %}
<option ondblclick="MoveElementTo(this,'id_{{ field.name }}_from','id_{{ field.name }}_to')" value="{{ item.id }}">{{ item }}</option>
{% endfor %}
</select>
</div>
</div>
{% else %}
<div class="col-lg-6" style="width: auto">
{{ field }}<span style="color: red">{{ field.errors.as_text }}</span>
</div>
{% endif %}
...
<script>
function MoveElementTo(self,target_id,source_id) {
var opt_ele = "<option value='" + $(self).val() + "' ondblclick=MoveElementTo(this,'" + source_id +"','"+ target_id +"')>" + $(self).text() + "</option>";
$("#" +target_id).append(opt_ele);
$(self).remove();
}
//提交所有数据
function SelectAllChosenData() {
$("select[class='right'] option").each(function () {
$(this).prop("selected",true);
});
return true;
}
</script>
...
上述代码实现的基本的复选功能,如图:

这是修改过后的内容,双方的内容转换了,就是表名已经成功。
2.5 添加返回按钮
这个就更简单了,但往往在数量少的时候我们会进行硬编码来写路径,要是动态生成该怎么搞呢?在这里,我们要引入Django的URL逆向解析。 请看这里:Django路由。
在table_object_edit.html文件中添加这一段代码:
...
{% block container %}
{# 添加下面的块,便于继续 #}
{% block top %}
<div class="panel-heading">
<button class="btn btn-success pull-right" ><a href="{% url 'king_admin:display_objects' app_name table_name %}" style="color: white">返回</a></button>
</div>
{% endblock %}
<form method="post" class="form-horizontal" onsubmit="return SelectAllChosenData()">
...
效果:

到此,编辑页面就算是基本完成了。
未完待续。。。。

浙公网安备 33010602011771号