前言: 今晚写一篇关于学员/讲师/销售员CRM系统。这个小项目是27号开始做的,大概搞了一星期不到。我把一些知识点总结下,还写下当时克服的BUG。
Django练习小项目:学员管理系统设计开发
带着项目需求学习是最有趣和效率最高的,今天就来基于下面的需求来继续学习Django
项目需求:
分讲师\学员\课程顾问角色
学员可以属于多个班级,学员成绩按课程分别统计
每个班级至少包含一个或多个讲师
一个学员要有状态转化的过程 ,比如未报名前,报名后,毕业老学员
客户要有咨询纪录, 后续的定期跟踪纪录也要保存
每个学员的所有上课出勤情况\学习成绩都要保存
学校可以有分校区,默认每个校区的员工只能查看和管理自己校区的学员
客户咨询要区分来源
拿到需求后,先要分析,再设计表结构: 超级重要!!
View Code
先来张图看看效果: 下图是销售员Alex登陆后看到的界面
点击右上方Alex已招学员,出现下图界面:
一、前端界面实现
界面看着我感觉是蛮漂亮的,登陆界面和信息界面都是搞bootstrap模版的。只要将bootstrap模版修改下,就变成所需要的界面啦。不会修改的可以看看如何使用bootstrap。
二、字数显示限制
如果备注过多,会使界面不好看,要想使备注只显示一定的字数,可用下列方法: 只显示13个字节
1 | <td>{{ customer.consult_memo|truncatechars: 13 }}< / td> |
三、报名状态加色
第一种方法,比较麻烦,有兴趣可看django进阶-modelform&admin action
第二种方法更简单
1. 在bootstrap添加自定义的css样式文件,custom.css
2. 在基础模版(我定义的是base.html,其它html模块是继承它的)导入custom.css文件:
1 | <link href = "/static/bootstrap-3.3.7-dist/css/custom.css" rel = "stylesheet" > |
3. 你随意在custom.css定义样式
View Code
4. 在对应的customer.html的标签加入样式; customer.status是后台传给前端的,是学生的报名状态。
1 | < td class="{{ customer.status }}">{{ customer.get_status_display }}</ td > |
四、分页功能
其实Alex销售员登陆后看到的界面只有两条客户的信息,这是我在后台写的。注意看左下角有个分页,类似与百度搜索的分页。其实分页实现起来还是有点难度的。
先看django官方文档。官方文档写得很详细!!
View Code
后台实现:
1 def customers(request): 2 print(">>>>request:",request) 3 # 查找所有客户,获取所有信息的结果集,但并不是所有信息都已经取出来了(如果有上万条数据,不能一次性取出来,先取一部分), 4 customer_list = models.Customer.objects.all() 5 print(">>>>customers:", customer_list) 6 paginator = Paginator(customer_list, 2) # 生成分页实例: 每一页有两条数据 7 page = request.GET.get("page") # 获取前端点击的页数,参数page可自定义 8 try: 9 customer_objs = paginator.page(page) # 生成第page页的对象10 except PageNotAnInteger:11 # If page is not an integer, deliver first page12 # 如果输入的页码不是下标,则返回第一页13 customer_objs = paginator.page(1)14 except EmptyPage:15 # If page is out of range (e.g. 9999), deliver last page of results.16 # 如果输入的页码超出,则跳转到最后的页码17 customer_objs = paginator.page(paginator.num_pages)18 19 return render(request, "crm/customer.html", {"customer_list": customer_objs})
前端实现:
1 <div class="pagination"> 2 3 <nav> 4 <ul class="pagination"> 5 {% if customer_list.has_previous %} 6 <li class=""><a href="?page={{ customer_list.previous_page_number }}" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 7 {% endif %} 8 9 {% for page_num in customer_list.paginator.page_range %}10 <!-- abs_page为函数名,后两个为参数 -->11 {% abs_page customer_list.number page_num %}12 13 {% endfor %}14 15 {% if customer_list.has_next %}16 <li class=""><a href="?page={{ customer_list.next_page_number }}" aria-label="Next"><span aria-hidden="true">»</span></a></li>17 {% endif %}18 </ul>19 </nav>20 21 </div>
第一次进入http://127.0.0.1:8000/crm/customer/页面时,请求为get方式,后台接收到的page参数为空,故会出PagenotAnInterger异常,故会返回到第一页!!
注意前端的第九行代码: customer_list.paginator.page_range是页数的范围。customer_list只是一个第几页的实例而已,是无法获取到页数的范围的。
但是问题来了,如果,你有100条数据,每页只放两条数据,意味着界面得有50个button,基本上页面是放不下的。
如果页面过多,看下百度怎么处理:
可用abs绝对值,若当前页面为第6页,想让3、4、5和7、8、9也显示出来,可在循环判断页面时,利用abs, 当|循环的页面值-当前的页面值|<=3 ,则显示。
但问题又来了,前端的templates可没有abs取绝对值这种后台才有的方法,怎么办??
自定义template tags
https://docs.djangoproject.com/es/1.9/howto/custom-template-tags/
效果图:
后台是如何自定义模版??
首先自定义templates模版,我随便建了个文件custom_tags.py,必须放在新建包templatetags下:
custom_tags.py: 当页码绝对值之差小于3时,则返回页码按钮的html给前端,反之不返回。
1 from django import template 2 from django.utils.html import format_html 3 4 register = template.Library() #django的语法库 5 6 7 @register.simple_tag 8 def abs_page(current_page, loop_page): 9 offset = abs(current_page - loop_page)10 if offset < 3:11 if current_page == loop_page:12 page_ele = "<li class='active'><a href='?page=%s'>%s</a></li>" % (current_page, current_page)13 else:14 page_ele = "<li class=''><a href='?page=%s'>%s</a></li>" % (loop_page, loop_page)15 return format_html(page_ele) #将字符串转化为html,返回给前端16 else:17 return ""
五、modelform进阶
modelform之前有写过,django进阶-modelform&admin action, 但主要是写django自带的admin。
现在我有个需求,销售员Alex想查看客户的详细信息。只需只击客户的ID号,便可查看,当然也可以修改。
前端:
1 | < td >< a href="/crm/customers/{{customer.id}}/">{{customer.id}}</ a ></ td > |
urls:
1 2 | # 当学员id当作参数,传给customer_detail方法 url(r '^customers/(\d+)/$' , views.customer_detail), |
后台:
1 2 3 4 | def customer_detail(request,customer_id): customer_obj = models.Customer.objects.get( id = customer_id) form = forms.CustomerModelForm() return render(request, "crm/customer_detail.html" ,{ "customer_form" :form}) |
看前端界面显示: 虽然能显示出表单,但无法显示出学员的信息,而且太丑了!!
如何显示出学员的信息:
1 2 | customer_obj = models.Customer.objects.get( id = customer_id) form = forms.CustomerModelForm(instance = customer_obj) # 将数据对象当作参数传入 |
如何使前端界面更漂亮:
forms.py表单文件:
View Code
前端: 样式是从bootstrap参考来的
View Code
效果图:
修改后保存信息
View Code
六、必填与非必填字段
效果图: 必填字段有加粗,且左上角有红色*号
只需修改下前端代码即可:
1 {% if field.field.required %} <!--若是必填字段 -->2 <label class="col-sm-2 control-label">3 <span style="color: red;font-size: larger">*</span>{{ field.label }}4 </label>5 {% else %} <!-- label在django默认为加粗 -->6 <label style="font-weight: normal" class="col-sm-2 control-label">{{ field.label }}</label>7 {% endif %}
权限分配, 这个改天再写博客整理下: 三个角色的权限是不同的。对销售员来讲,无法修改非本人招收客户的信息。
七、url别名
啥是url别名??
1 #当学员id当作参数,传给customer_detail方法,2 #给该url起别名,一调用别名customer_detail,就关联上url3 url(r'^customers/(\d+)/$',views.customer_detail,name="customer_detail"),
现在销售员想查看客户的详细信息,只需一点击客户的ID号便可查看。so, ID号必须是个a标签,下面来看看前端实现:
1 <!-- 这里查看学员的详细信息不应该写列,否则当url一改变,得来这里改代码 -->2 <!-- <td><a href = " /crm/customers/ {{customer.id}} / "> {{ customer.id }} </a></td> -->3 4 <td><a href = "{% url 'customer_detail' customer.id %}"> {{ customer.id }} </a></td>
注意了,如果不用url别名的话,就用第2行代码。但是,这样项目的可维护性大大降低了。当你需改动url时,必须到前端修改对应的a标签。如果用了url别名,就不用再来前端修改了。
看到没,我用浏览器审查元素,浏览器已经自动将ID号的a标签,转化为一条url. 神奇!!
1.非系统的学习也是在浪费时间 2.做一个会欣赏美,懂艺术,会艺术的技术人