码迷,mamicode.com
首页 > 其他好文 > 详细

项目一:CRM(客户关系管理系统)--6

时间:2017-12-24 18:44:33      阅读:195      评论:0      收藏:0      [点我收藏+]

标签:repeat   源码   忘记   button   str   分页   multiple   mat   图片   

功能是永远加不完的!重头戏的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(具体看源码的初始化函数设定)。

参考:Django官方文档和源码

现在,明白了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 编辑测试

修改表格中一些内容:

技术分享图片

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

技术分享图片

已经修改成功!

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. 渲染测试:

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 ...

上述代码实现的基本的复选功能,如图:

 

技术分享图片

这是修改过后的内容,双方的内容转换了,就是表名已经成功。

2.5 添加返回按钮

这个就更简单了,但往往在数量少的时候我们会进行硬编码来写路径,要是动态生成该怎么搞呢?在这里,我们要引入DjangoURL逆向解析。 请看这里: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 ...

效果:

 

技术分享图片

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

 

未完待续。。。。

项目一:CRM(客户关系管理系统)--6

标签:repeat   源码   忘记   button   str   分页   multiple   mat   图片   

原文地址:http://www.cnblogs.com/eaglesour/p/8098583.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!