一、CRM框架
二、展示信息
class Show_Customer(View):
def get(self, request):
if request.path_info == "/crm/":
all_customer = models.Customer.objects.filter(consultant__isnull=True)
else:
if request.path_info == "/crm/all_customer/": # 所有客户
all_customer = models.Customer.objects.all()
else:
all_customer = models.Customer.objects.filter(onsultant=request.user) # 我的客户
return render(request, 'crm/customer.html',
context={"all_customer": all_customer})
def post(self, request):
pass
将all_customer的参数传递给前端,前端拿出数据再进行显示
choice字段才有的显示,定义显示用户需要看到的字段,get_字段_display
,在python文件中需要用get_字段_display()
{% for customer in all_customer %}
<tr>
<td><input type="checkbox" name="id" value="{{ customer.id }}"></td>
<td>{{ forloop.counter }}</td>
<td>{{ customer.qq }}</td>
<td>{{ customer.qq_name|default:'暂无' }}</td> #定义默认值
<td>{{ customer.name|default:'暂无' }}</td>
<td>{{ customer.get_sex_display }}</td>
<td>{{ customer.phone|default:'暂无' }}</td>
<td>{{ customer.get_source_display }}</td>
<td>{{ customer.get_class_type_display }}</td>
<td>
{{ customer.show_status }}#也可以使用models中定义的函数
</td>
{% endfor %}
在models中定义函数,用于显示当前状态
from django.utils.safestring import mark_safe # 将html内容返回到前端时,用此方法
def show_status(self):
color_dict = {
"signed": 'green',
"unregistered": 'red',
"studying": 'pink',
"paid_in_full": 'blue',
}
return mark_safe(
'<span style="background-color: {};color: white;padding: 4px">{}</span>'.format(color_dict[self.status],
self.get_status_display()))
三、新增信息
class CustomerForm(forms.ModelForm):
class Meta:
model = models.Customer
fields = '__all__'
def add_customer(request):
customer_obj = CustomerForm()
if request.method == "POST":
customer_obj = CustomerForm(request.POST)
if customer_obj.is_valid():
customer_obj.save()
# save方法直接将清洗过的数据存放到数据库中
print(customer_obj.cleaned_data)
return redirect(reverse('customer'))
return render(request, 'crm/add_customer.html', context={"customer_obj": customer_obj})
前端循环
通过Form传递过来的字段可以传给前端页面,进行循环逐一获取
label: field.label
label-for : field.id_for_bale
errors: field.errors
字段: field
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in customer_obj %}
<div class="form-group row {% if field.errors %}has-error{% endif %} ">
<label for="{{ field.id_for_label }}"
class="col-sm-2 control-label"> {{ field.label }}</label>
<div class="col-sm-10">
{{ field }}
<span class="help-block">
{{ field.errors.0 }}
</span>
</div>
</div>
{% endfor %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</div>
</form>
四、编辑信息
与新增的代码相似,所以可以直接使用将新增和编辑代码合并,将传入的参数赋值为None就可以成功合并
# 编辑客户
def edit_customer(request, customer_id):
# 参数是通过前端编辑a标签传递customer_id,用id来获取当前信息,并通过form表单实例化,修改完之后再保存下来
obj = models.Customer.objects.filter(id=customer_id).first() #通过id获取客户对象
customer_obj = CustomerForm(instance=obj)
if request.method == "POST":
customer_obj = CustomerForm(request.POST, instance=obj)
if customer_obj.is_valid():
customer_obj.save()
next = request.GET.get('next')
if next:
return redirect(next)
return redirect(reverse('customer'))
return render(request, 'crm/edit_customer.html', context={"customer_obj": customer_obj})
五、分页功能的实现
- 分页功能的基本实现
# 分页练习
users = [{"name": "tian{0}".format(i), "pwd": "pwd{0}".format(i)} for i in range(302)]
def page(request):
#获取路径
path = request.path_info
# print(path,request)
# 获得当前页码数
current = request.GET.get('page', 1)
page = int(current)
per_pagination = 11 # 显示页码数
half = per_pagination // 2
per_page = 10 # 每页显示的数据个数
# 获取当前页应该对应的页面数据
pre = (page - 1) * per_page
next = page * per_page
print(users[pre:next])
# 1 0 9
# 2 10 19
# 3 20 29
# 4 30 39
# 5 40 49
# 通过当前页来传递页的数字
num_page, remainder = divmod(len(users), per_page)
if remainder:
num_page += 1
pagination = [str(i) for i in range(1, num_page + 1)] # 页码数据列表
if page > 0 and page < 6:
start = 1
end = per_pagination
elif page > 26:
start = num_page - per_pagination + 1
end = num_page
else:
start = page - half
end = page + half
# # 存放li标签的列表
html_list = []
#首页
first_li = '<li><a href="{}?page=1">首页</a></li>'.format(path)
html_list.append(first_li)
#上一页
if page == 1:
prev_li = '<li class="disabled"><a><<</a></li>'
else:
prev_li = '<li><a href="{1}?page={0}"><<</a></li>'.format((page - 1),path)
html_list.append(prev_li)
#中间显示页码数
for p in pagination[start - 1:end]:
page_li = '<li><a href="{1}?page={0}">{0}</a></li>'.format(p,path)
html_list.append(page_li)
#下一页
if page == num_page:
next_li = '<li class="disabled"><a>>></a></li>'
else:
next_li = '<li><a href="{1}?page={0}">>></a></li>'.format((page + 1),path)
html_list.append(next_li)
#尾页
last_li = '<li><a href="{1}?page={0}">尾页</a></li>'.format(num_page,path)
html_list.append(last_li)
html_str = mark_safe(''.join(html_list))
return render(request, 'crm/page.html',
context={"users": users[pre:next], "pagination": pagination[start - 1:end], "html_str": html_str}
- 分页功能写成类,调用起来会很方便
query_params参数为当前页面的request.GET请求,需要将GET中的路径信息,将其加入到分页中,实现了翻页中保留搜索条件
from django.utils.safestring import mark_safe # 安全传输html标签去前端
from django.http import QueryDict # request.GET返回的是QueryDict
class Page():
#SyntaxError: non-default argument follows default argument 非默认参数跟在默认参数后面
def __init__(self, request, data_count, query_params ,per_pagination=11, per_page=10):
self.path = request.path_info
self.page = int(request.GET.get('page', 1))
self.per_pagination = per_pagination
self.half = per_pagination // 2
self.per_page = per_page
self.data_count = data_count
self.query_params = query_params
self.query_params._mutable = True # _mutable改为True就可以修改字典
def pre(self):
return (self.page - 1) * self.per_page
def next(self):
return self.page * self.per_page
@property # 加入python 内置装饰器,使得方法调用转换为属性调用
def get_html(self):
num_page, remainder = divmod(self.data_count, self.per_page)
if remainder:
num_page += 1
pagination = [str(i) for i in range(1, num_page + 1)] # 页码数据列表
if self.page > 0 and self.page < self.half + 1:
start = 1
end = self.per_pagination
elif self.page > num_page - self.half:
start = num_page - self.per_pagination + 1
end = num_page
else:
start = self.page - self.half
end = self.page + self.half
# # 存放li标签的列表
html_list = []
# 首页
self.query_params['page'] = 1
first_li = '<li><a href="{0}?{1}">首页</a></li>'.format(self.path, self.query_params.urlencode())
html_list.append(first_li)
# 上一页
if self.page == 1:
prev_li = '<li class="disabled"><a><<</a></li>'
else:
self.query_params['page'] = self.page - 1
prev_li = '<li><a href="{0}?{1}"><<</a></li>'.format(self.path, self.query_params.urlencode())
html_list.append(prev_li)
# 中间显示页码数
for p in pagination[start - 1:end]:
self.query_params['page'] = p
page_li = '<li><a href="{0}?{1}">{2}</a></li>'.format(self.path, self.query_params.urlencode(), p)
html_list.append(page_li)
# 下一页
if self.page == num_page:
next_li = '<li class="disabled"><a>>></a></li>'
else:
self.query_params['page'] = self.page + 1
next_li = '<li><a href="{0}?{1}">>></a></li>'.format(self.path, self.query_params.urlencode())
html_list.append(next_li)
# 尾页
self.query_params['page'] = num_page
last_li = '<li><a href="{0}?{1}">尾页</a></li>'.format(self.path, self.query_params.urlencode())
html_list.append(last_li)
html_str = mark_safe(''.join(html_list))
return html_str
六、模糊搜索功能与分页结合实现
翻页时,保留搜索条件;通过for循环逐一搜索添加的字段,条件为并列关系
from django.db.models import Q
class Show_Customer(View):
def get(self, request, ret=None):
# print(request.GET)
q = self.get_search_contion(['qq', 'name', 'qq_name', 'last_consult_date'])
if request.path_info == "/crm/":
all_customer = models.Customer.objects.filter(q, consultant__isnull=True)
else:
if request.path_info == "/crm/all_customer/": # 所有客户
all_customer = models.Customer.objects.filter(q)
else:
all_customer = models.Customer.objects.filter(q, consultant=request.user) # 我的客户
query_params = copy.deepcopy(request.GET) # 深度copy会获取原来的对象再生成一个,不会影响原来的结果
# print(query_params.urlencode()) #<QueryDict: {'query': ['Laughing'], 'page': ['2']}> 会将QueryDict字典转化为url拼接的方式 query=Laughing&page=2
len_obj = len(all_customer)
page_obj = pagination.Page(request, len_obj, query_params)
pre = page_obj.pre()
next = page_obj.next()
html_str = page_obj.get_html # 使用了装饰器
return render(request, 'crm/customer.html',
context={"all_customer": all_customer[pre:next], "html_str": html_str, "len_obj": len_obj,
"query_params": query_params, "ret": ret})
def get_search_contion(self, query_list):
query = self.request.GET.get('query', '')
q = Q()
q.connector = 'OR'
for i in query_list:
q.children.append(Q(('{}__contains'.format(i), query)))
return q
七、保留路径
下次点击操作时,将前一次的路径通过?next=params
参数来进行传递,所以下一次点击的结果会包含上一次路径的信息,当点击提交按钮时,会返回至next路径下的页面
def add_record(self):
path = self.request.get_full_path() # 获得所有页面包括?之后的查询条件
# url = self.request.path_info #获得基地址页面
# print(path, url)
qd = QueryDict()
qd._mutable = True
qd['next'] = path
# next=%2Fcrm%2Fcustomer_list%2F%3Fquery%3Dalex%26page%3D2
query_params = qd.urlencode()
# add_btn = '<a href="{}?next={}" class="btn btn-primary btn-sm">添加</a>'.format(reverse('add_customer'), url)
add_btn = '<a href="{}?{}" class="btn btn-primary btn-sm">添加</a>'.format(reverse('add_customer'), query_params)
return mark_safe(add_btn), query_params
八、批量操作
html代码
form action="" method="post" class="form-inline">
{% csrf_token %}
<select name="action" class="form-control" style="margin: 5px 0">
<option value="">请选择</option>
<option value="multi_delte">删除</option>
{% if request.path_info == '/crm/' %}
<option value="multi_apply">放入私户</option>
{% elif request.path_info == '/crm/my_customer/' %}
<option value="multi_pub">放入公户</option>
{% else %}
<option value="multi_apply">放入私户</option>
<option value="multi_pub">放入公户</option>
{% endif %}
{#
<option value=""></option>
#}
</select>
<button class="btn btn-success btn-sm">提交</button>
</form>
hasattr(object, name)是否存在属性name
getattr(object, name[, default]) 如果存在则返回属性值,如果不存在分为两种情况,一种是没有default参数时,会直接报错;给定了default参数,若对象本身没有name属性,则会返回给定的default值,如果给定的属性name是对象的方法,则返回的是函数对象,需要调用函数对象来获得函数的返回值;调用的话就是函数对象后面加括号,如func之于func();
setattr(object, name, value)给object对象的name属性赋值value
当多人同时申请同一个客户的时候,需要增加事务功能,才能保证不会出现争抢资源出现
def post(self, request):
# print(request.POST)
action = request.POST.get('action')
query = request.POST.get('query')
if action:
print(action)
ret = getattr(self, action)(request)
print(ret)
return self.get(request, ret)
else:
return HttpResponse("非法操作")
def multi_apply(self, request):
id_list = request.POST.getlist('id')
# obj = models.Customer.objects.filter(id__in=id_list).update(consultant=request.user)
# print(settings.MAX_CUSTOMER_NUMBERS)
if request.user.customers.count() + len(id_list) <= settings.MAX_CUSTOMER_NUMBERS: # 控制添加客户数量
with transaction.atomic(): # 添加事务
obj = models.Customer.objects.filter(id__in=id_list, consultant__isnull=True).select_for_update() # 加锁,
if len(id_list) == len(obj): # 判断,当第一次数量和原本数据量相等时,同意请求
time.sleep(1) # 先申请的的人等待,后申请的人等之前的人
obj.update(consultant=request.user)
return "客户添加私有成功"
else:
return "客户添加失败"
else:
return "超过客户添加上限:5"
def multi_pub(self, request):
id_list = request.POST.getlist('id')
obj = models.Customer.objects.filter(id__in=id_list).update(consultant=None)
return "客户添加公共成功"
def multi_delte(self, request):
id_list = request.POST.getlist('id')
obj = models.Customer.objects.filter(id__in=id_list).delete()
return "删除成功"