一、客户管理系统增删改查
1.业务回顾
大家还记得我们项目的三大块吧,第一个就是客户管理。
客户管理系统是提供给网咨、销售人员来查看客户信息、增加客户、修改客户信息的。当然客户在我们项目中还有公户和私户的区别,公户私户实际是由销售总监根据销售的业务能力来合理分配,从而使客户转化率最大化。
销售人员通过分配到的客户,存放在自己的私户中,通过不断的跟进,交流,让客户报名。期间的跟进记录我们应当都存入数据库,以便以后的继续跟进。
由于该博客仅用于知识点的分享实现,所以我们这里只实现一个完整的功能块,其他类似的就不展示了,所以我们这里只实现客户的信息增删改查,也就是customer应用下的客户数据。
2.公共客户数据的增删改查url
项目下urls路由分发对应的请求到customer应用下
url(r'^customer/', include("customer.urls")),
customer应用下的增删改查url
from django.conf.urls import url
from customer.views import customer
urlpatterns = [
# 公户数据展示url
url(r'^common/list', customer.CommonList.as_view(), name="common_list"),
# 公户信息添加url
url(r'^common/add/', customer.CommonAdd.as_view(), name="common_add"),
# 公户信息修改url
url(r'^common/edit/(\d+)/', customer.CommonEdit.as_view(), name="common_edit"),
# 公户信息删除
url(r'^common/del/(\d+)/', customer.CommonDel.as_view(), name="common_del"),
]
3.公户数据的展示视图和模板
关于客户信息的相关视图我们放在customer下的views文件夹下的customer.py中。
公户信息展示几点注意点:
- 在客户数据展示的视图中我们使用了django自带的auth认证装饰器,装饰器在验证失败后会跳转login页面,但是django默认配置的跳转login页面url并不是我们想要的,需要我们在settings中配置一下。
# 配置登录认证失败跳转的页面
LOGIN_URL = '/crmweb/login/'
- 在数据展示页面中,还使用了自定义分页,自定义分页博客中有,就不详述了,想要查看点这里:Django框架—分页器paginator 过滤器部分
公户展示视图写法
公户信息展示中实现了两个额外功能
- 查询功能
查询功能的实现,是基于form表单通过get提交select框和input框,select框用来提交查询的条件,input框提交查询的查询参数。
后端从get请求中获取前端参数,根据select提交的值来确定通过什么字段来查询,根据input的值来确定查询条件。
<form action="" method="get" class="navbar-form navbar-left">
<div class="input-group">
<div class="input-group-btn btn-info">
<select name="condition" id="search" class="btn input-group-sm btn-info"
style="border: 0">
<option value="" readonly>条件</option>
<option value="qq_name">昵称</option>
<option value="qq">QQ号</option>
</select>
</div>
<input type="text" name="q" class="form-control" placeholder="Search...">
<span class="input-group-btn">
<button type="submit" id="search-btn" class="btn btn-flat">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</form>
- 批量操作功能
批量操作功能的实现,也是通过form表单post请求提交数据,在类中定义一系列批量操作的方法。
根据前端select提交的option选项中的value值,也就是字符串来反射类中的批量操作方法,根据表格中提交的记录id来批量修改。
from django import views
from customer import models
from django.db.models import Q, Count
from django.shortcuts import (
render, redirect, reverse, HttpResponse
)
from customer.forms import formAuth
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from utils.customPaginator import Paginator
# 公户数据展示
class CommonList(views.View):
@method_decorator(login_required) # 装饰器函数验证是否登录
def dispatch(self, request, *args, **kwargs):
res = super().dispatch(request, *args, **kwargs)
return res
def get(self, request):
# 查询公户全部数据
all_customers = models.Customer.objects.filter(consultant__isnull=True, status="unregistered").order_by(
"-pk")
# 使用Q查询拼接查询条件
condition = request.GET.get("condition", "") # 获取搜索的条件分类
query = request.GET.get("q", "") # 获取搜索的条件
if condition and query: # 如果有查询调参数,两个参数都有,根据查询参数查询后找到数据
condition = condition + "__contains"
q = Q() # Q实例化生成q对象,q对象可以帮我们拼接字符串为 condition__contians= xx的关键字参数传到filter中。
q.children.append((condition, query))
all_customers = all_customers.filter(q)
# 开始分页展示
data_counts = all_customers.count() # 获取分页的总数据数量
# 生成一个分页对象
paginator = Paginator(request, data_counts, 10)
# 获取当前页展示数据的范围
try: # 异常是否查到了数据,查到了才切片,不然会报错
all_customers = all_customers[paginator.start:paginator.end]
except Exception:
pass
# 获取分页的标签
paginator_tag = paginator.paginate() # 调用定义好的分页方法
# 获取跳转页的标签
jump_tag = paginator.jump_page() # 调用定义好的跳转页方法获取跳转页标签
jump_js = paginator.jump_js() # 调用定义好的跳转页方法获取跳转页js代码
# fixme 这里我实现的用户被选走的提示方式有点low,暂时先这样吧,而且在实际业务中,公户转私户应该是由销售总监分配的,而不是比谁先抢到。
name_str = None
# 客户被选走的错误提示
if "*customer*" in request.path:
name_list = request.path.split("*customer*")[1:]
name_str = ','.join(name_list)
# 返回response对象,以及需要渲染的数据
return render(request, "common_list.html",{
"all_customers": all_customers, "paginator_tag": paginator_tag,"jump_tag": jump_tag, "jump_js": jump_js, "name_str": name_str})
def post(self, request):
operate = request.POST.get("operate") # 获取用户提交的批量操作类型
if operate:
# 如果有,去反射类中对应的批量操作方法
if hasattr(self, operate):
func = getattr(self, operate)
if callable(func):
ret = func(request) # 执行批量操作方法
if ret: # 函数有返回值,也就是有被别的销售提前选走的客户
info = ""
for obj in ret:
# fixme 客户被任选走,在前端提示哪些客户被选走,这里我放在路径中,并不太好,目前就这样实现,以后有更好的办法再更新
info = info + "*customer*" + obj.__str__()
url = request.path + info # 拼接url,携带提示信息
return redirect(url)
return redirect(request.path)
else:
return HttpResponse("访问连接有误!")
return HttpResponse("访问连接有误!")
return redirect("common")
def batch_delete(self, request, *args, **kwargs):
"""批量删除客户"""
# 实际工作场景中并不是真的删除,而是修改该条数据在数据库的修改状态
choose_list = request.POST.getlist("choose")
models.Customer.objects.filter(pk__in=choose_list).delete()
def batch_update(self, request, *args, **kwargs):
"""批量更新客户状态"""
choose_list = request.POST.getlist("choose")
models.Customer.objects.filter(pk__in=choose_list).update(status="studying")
def batch_c2p(self, request, *args, **kwargs):
"""批量公户转私户操作"""
choose_list = request.POST.getlist("choose") # 获取选中的客户id,注意通过getlist来获取,获取一个列表
customer_list = models.Customer.objects.filter(pk__in=choose_list) # 根据客户id查到客户
has_choosed = [] # 定义一个列表
for customer_obj in customer_list:
if customer_obj.consultant: # 如果客户被别人选了,放到已选列表
has_choosed.append(customer_obj)
else:
# 如果还没有备选则选择并保存
customer_obj.consultant = request.user
customer_obj.save() # 通过save方法保存到数据库
return has_choosed # 返回已经被选择的用户
公户展示及批量操作视图
base.html
我们之前说过项目模板是用的开源的模板,这里我们是使用一个base页面,其他页面结构相同的都来继承这个base页面。
话说还记得模板继承的用法把,这里简单回顾:
先提取公共页面到一个base.html文件中,对于需要根据具体页面内容个性化定制的地方,使用block块来包裹,block块尽量多。
在继承的页面中,通过extends来继承base页面,对于需要自己定制的部分,在block中写入自己的内容。
{% load static %}
<!DOCTYPE html>
<!--
This is a starter template page. Use this page to start your new project from
scratch. This page gets rid of all links and provides the needed markup only.
-->
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>AliCRM</title>
{% block head %}
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<link rel="stylesheet" href="{% static 'adminlte/bower_components/bootstrap/dist/css/bootstrap.min.css' %}">
<!-- Font Awesome -->
<link rel="stylesheet" href="{% static 'adminlte/bower_components/font-awesome/css/font-awesome.min.css' %}">
<!-- Ionicons -->
<link rel="stylesheet" href="{% static 'adminlte/bower_components/Ionicons/css/ionicons.min.css' %}">
<!-- Theme style -->
<link rel="stylesheet" href="{% static 'adminlte/dist/css/AdminLTE.min.css' %}">
<!-- AdminLTE Skins. We have chosen the skin-blue for this starter
page. However, you can choose any other skin. Make sure you
apply the skin class to the body tag so the changes take effect. -->
<link rel="stylesheet" href="{% static 'adminlte/dist/css/skins/skin-blue.min.css' %}">
{% endblock head %}
</head>
<!--
BODY TAG OPTIONS:
=================
Apply one or more of the following classes to get the
desired effect
|---------------------------------------------------------|
| SKINS | skin-blue |
| | skin-black |
| | skin-purple |
| | skin-yellow |
| | skin-red |
| | skin-green |
|---------------------------------------------------------|
|LAYOUT OPTIONS | fixed |
| | layout-boxed |
| | layout-top-nav |
| | sidebar-collapse |
| | sidebar-mini |
|---------------------------------------------------------|
-->
<body class="hold-transition skin-blue sidebar-mini">
<div class="wrapper">
<!-- Main Header -->
<header class="main-header">
<!-- Logo -->
<a href="" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"><b>A</b>li</span>
<!-- logo for regular state and mobile devices -->
<