Django-CRM后台管理系统
crm整体流程
表结构
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.contrib import auth
from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager, User
from django.utils.translation import ugettext_lazy as _
from multiselectfield import MultiSelectField
#安装:pip install django-multiselectfield,针对choices多选用的 from django.utils.safestring import mark_safe course_choices = (('LinuxL', 'Linux中高级'),
('PythonFullStack', 'Python高级全栈开发'),) class_type_choices = (('fulltime', '脱产班',),
('online', '网络班'),
('weekend', '周末班',),) source_type = (('qq', "qq群"),
('referral', "内部转介绍"),
('website', "官方网站"),
('baidu_ads', "百度推广"),
('office_direct', "直接上门"),
('WoM', "口碑"),
('public_class', "公开课"),
('website_luffy', "路飞官网"),
('others', "其它"),) enroll_status_choices = (('signed', "已报名"),
('unregistered', "未报名"),
('studying', '学习中'),
('paid_in_full', "学费已交齐")) seek_status_choices = (('A', '近期无报名计划'), ('B', '1个月内报名'), ('C', '2周内报名'), ('D', '1周内报名'),
('E', '定金'), ('F', '到班'), ('G', '全款'), ('H', '无效'),)
pay_type_choices = (('deposit', "订金/报名费"),
('tuition', "学费"),
('transfer', "转班"),
('dropout', "退学"),
('refund', "退款"),) attendance_choices = (('checked', "已签到"),
('vacate', "请假"),
('late', "迟到"),
('absence', "缺勤"),
('leave_early', "早退"),) score_choices = ((100, 'A+'),
(90, 'A'),
(85, 'B+'),
(80, 'B'),
(70, 'B-'),
(60, 'C+'),
(50, 'C'),
(40, 'C-'),
(0, ' D'),
(-1, 'N/A'),
(-100, 'COPY'),
(-1000, 'FAIL'),)
# 用户表
class UserInfo(AbstractUser):
#销售,班主任,讲师,三哥
telephone = models.CharField(max_length=32,null=True)
roles = models.ManyToManyField('Role')
def __str__(self):
return self.username
# 角色表
class Role(models.Model):
title = models.CharField(max_length=32)
permissions = models.ManyToManyField('Permission')
def __str__(self):
return self.title
# 系统所有的url
class Permission(models.Model):
title = models.CharField(max_length=32)
url = models.CharField(max_length=128)
def __str__(self):
return self.title class Customer(models.Model):
"""
客户表(最开始的时候大家都是客户,销售就不停的撩你,你还没交钱就是个客户)
"""
qq = models.CharField(verbose_name='QQ', max_length=64, unique=True, help_text='QQ号必须唯一')
qq_name = models.CharField('QQ昵称', max_length=64, blank=True, null=True)
name = models.CharField('姓名', max_length=32, blank=True, null=True, help_text='学员报名后,请改为真实姓名')
sex_type = (('male', '男'), ('female', '女'))
sex = models.CharField("性别", choices=sex_type, max_length=16, default='male', blank=True, null=True) #存的是male或者female,字符串
birthday = models.DateField('出生日期', default=None, help_text="格式yyyy-mm-dd", blank=True, null=True) phone = models.CharField('手机号', blank=True, null=True,max_length=32)
# phone = models.CharField('手机号', blank=True, null=True)
source = models.CharField('客户来源', max_length=64, choices=source_type, default='qq') introduce_from = models.ForeignKey('self', verbose_name="转介绍自学员", blank=True, null=True) #self指的就是自己这个表,和下面写法是一样的效果 # introduce_from = models.ForeignKey('Customer', verbose_name="转介绍自学员", blank=True, null=True,on_delete=models.CASCADE)
course = MultiSelectField("咨询课程", choices=course_choices) #多选,并且存成一个列表的格式
# course = models.CharField("咨询课程", choices=course_choices) #如果你不想用上面的多选功能,可以使用Charfield来存
class_type = models.CharField("班级类型", max_length=64, choices=class_type_choices, default='fulltime')
customer_note = models.TextField("客户备注", blank=True, null=True, )
status = models.CharField("状态", choices=enroll_status_choices, max_length=64, default="unregistered",help_text="选择客户此时的状态") #help_text这种参数基本都是针对admin应用里面用的 date = models.DateTimeField("咨询日期", auto_now_add=True) last_consult_date = models.DateField("最后跟进日期", auto_now_add=True) #考核销售的跟进情况,如果多天没有跟进,会影响销售的绩效等
next_date = models.DateField("预计再次跟进时间", blank=True, null=True) #销售自己大概记录一下自己下一次会什么时候跟进,也没啥用 #用户表中存放的是自己公司的所有员工。
consultant = models.ForeignKey('UserInfo', verbose_name="销售", blank=True, null=True) #一个客户可以报多个班,报个脱产班,再报个周末班等,所以是多对多。
class_list = models.ManyToManyField('ClassList', verbose_name="已报班级", ) def __str__(self):
return "%s:%s"%(self.name,self.qq) #主要__str__最好是个字符串昂,不然你会遇到很多的坑,还有我们返回的这两个字段填写数据的时候必须写上数据,必然相加会报错,null类型和str类型不能相加等错误信息。 def get_classlist(self): #当我们通过self.get_classlist的时候,就拿到了所有的班级信息,前端显示的时候用 l=[]
for cls in self.class_list.all():
l.append(str(cls))
# return mark_safe(",".join(l)) #纯文本,不用mark_safe也可以昂
return ",".join(l) #纯文本,不用mark_safe也可以昂 class Campuses(models.Model):
"""
校区表
"""
name = models.CharField(verbose_name='校区', max_length=64)
address = models.CharField(verbose_name='详细地址', max_length=512, blank=True, null=True) def __str__(self):
return self.name class ClassList(models.Model):
"""
班级表
"""
course = models.CharField("课程名称", max_length=64, choices=course_choices)
semester = models.IntegerField("学期") #python20期等
campuses = models.ForeignKey('Campuses', verbose_name="校区",on_delete=models.CASCADE)
price = models.IntegerField("学费", default=10000)
memo = models.CharField('说明', blank=True, null=True, max_length=100)
start_date = models.DateField("开班日期")
graduate_date = models.DateField("结业日期", blank=True, null=True) #不一定什么时候结业,哈哈,所以可为空 #contract = models.ForeignKey('ContractTemplate', verbose_name="选择合同模版", blank=True, null=True,on_delete=models.CASCADE)
teachers = models.ManyToManyField('UserInfo', verbose_name="老师") #对了,还有一点,如果你用的django2版本的,那么外键字段都需要自行写上on_delete=models.CASCADE class_type = models.CharField(choices=class_type_choices, max_length=64, verbose_name='班额及类型', blank=True,null=True) class Meta:
unique_together = ("course", "semester", 'campuses') def __str__(self):
return "{}{}({})".format(self.get_course_display(), self.semester, self.campuses) class ConsultRecord(models.Model):
"""
跟进记录表
"""
customer = models.ForeignKey('Customer', verbose_name="所咨询客户") note = models.TextField(verbose_name="跟进内容...") status = models.CharField("跟进状态", max_length=8, choices=seek_status_choices, help_text="选择客户此时的状态") consultant = models.ForeignKey("UserInfo", verbose_name="跟进人", related_name='records') date = models.DateTimeField("跟进日期", auto_now_add=True) delete_status = models.BooleanField(verbose_name='删除状态', default=False)
# def __str__(self):
# return self.consultant
model
1、继承django内置的user表并扩展字段
django内置的user表中给我们提供了username,password
from django.contrib.auth.models import AbstractUser
# 用户表
class UserInfo(AbstractUser):
telephone = models.CharField(max_length=32,null=True) # 该字段不可以为空
2、基于form组件实现注册
定义form组件的验证规则并生成标签
from django import forms # form组件
from app01 import models
from django.core.exceptions import ValidationError # 错误信息
from django.core.validators import RegexValidator # django内置的正则 # 用户注册form
class Myform(forms.Form):
username=forms.CharField(
max_length=32,
min_length=6,
label="用户名",
error_messages={
"required": "用户名不能为空",
"min_length": "用户名不能少于6位",
"max_length":"用户名不能超过32位",
},
widget=forms.widgets.TextInput(attrs={"placeholder": "请输入用户名"})
)
password=forms.CharField(
max_length=10,
min_length=6,
label="密码",
error_messages={
"required": "密码不能为空",
"min_length": "密码不能少于6位",
"max_length": "密码不能超过32位",
},
widget=forms.widgets.PasswordInput(attrs={"placeholder":"请输入密码"})
)
r_password = forms.CharField(
max_length=10,
min_length=6,
label="确认密码",
error_messages={
"required": "确认密码不能为空",
"min_length": "确认密码不能少于6位",
"max_length": "确认密码不能超过32位",
},
widget=forms.widgets.PasswordInput(attrs={"placeholder":"请确认密码"})
)
# 给手机号定义校验规则
telephone = forms.CharField(
max_length=11,
label="电话",
error_messages={
"required": "电话不能为空",
"max_length": "电话不能超过11位",
},
widget=forms.widgets.TextInput(attrs={"placeholder": "请输入11位的手机号"}),
validators=[RegexValidator(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$', '电话必须以13/15/17/18/14/开头,且必须满足11位')],
)
# 批量添加样式
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
for field in self.fields:
# 取每个字段,给每个字段添加样式
self.fields[field].widget.attrs.update({"class": "form-control"}) # 局部钩子判断用户名是否存在
def clean_username(self):
username=self.cleaned_data.get("username")
# 判断用户名是否存在
if models.UserInfo.objects.filter(username=username).exists():
raise ValidationError("用户名已经存在")
else:
# 将验证通过的信息返回
return username # 局部钩子判断手机号是否已经注册
def clean_phone(self):
phone=self.cleaned_data.get("phone")
# 判断手机号是否已经注册
if models.UserInfo.objects.filter(phone=phone).exists():
raise ValidationError("该手机号已经注册!!!")
else:
# 将验证通过的信息返回
return phone # 全局钩子判断两次输入的密码是否一致
def clean(self):
password_value = self.cleaned_data.get('password')
r_password_value = self.cleaned_data.get('r_password')
if password_value == r_password_value:
return self.cleaned_data # 全局钩子要返回所有的数据
else:
self.add_error('r_password', '两次密码不一致')
实例化Myform对象传给前端进行渲染
# 注册
def register(request):
# 实例化Myform对象
form_obj = Myform()
if request.method=="GET":
return render(request,"register.html",{"form_obj":form_obj})
else:
form_obj=Myform(data=request.POST)
if form_obj.is_valid():
# 所有信息都验证完,通过的认证信息都在cleaned_data里面
dic = form_obj.cleaned_data
# 将不需要写入数据库的信息删除
dic.pop("r_password")
# 用户注册信息写入数据库,(这种创建是密文密码)
models.UserInfo.objects.create_user(**dic)
return redirect("login")
else:
return render(request,"register.html",{"form_obj":form_obj})
前端页面
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static "bootstrap-3.3.7-dist/css/bootstrap.min.css" %}">
</head>
<body>
<div class="container">
<div class="row"> <div class="col-md-6 col-md-offset-3" style="margin-top: 150px">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">用户注册</h3>
</div>
<div class="panel-body">
<div>
<form action="{% url "register" %}" method="post" novalidate>
{% csrf_token %}
{% for foo in form_obj %}
<div class="form-group {% if foo.errors.0 %}
has-error
{% endif %}">
<label for="{{ foo.id_for_label }}">{{ foo.label }}</label>
{{ foo }}{{ foo.errors.0 }}
</div> {% endfor %}
<input type="submit" value="注册" class="btn btn-success pull-right">
</form> </div>
</div>
</div> </div> </div> </div>
</body>
</html>
3、登录认证基于ajax请求发送|附加验证码
验证码:生成的验证码保存在session中,方便校验
from io import BytesIO # 在内存中开辟空间
from PIL import Image,ImageDraw,ImageFont # 验证码模块
# 生成验证码
def code(request):
f = BytesIO()
# 创建图片
img = Image.new(mode="RGB", size=(120, 30),
color=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
# 创建画笔
draw = ImageDraw.Draw(img, mode='RGB')
draw.arc((0, 0, 30, 30), 0, 360, fill="red") # 随机生成4位验证码
str_code = ''
for i in range(4):
a1 = chr(random.randint(65, 90))
a2 = chr(random.randint(97, 122))
a3 = random.randint(0, 9)
str_num = random.choice([a1, a2, str(a3)])
# 每次生成的随机数保存在列表中
str_code+=str_num
# 选择字体
font = ImageFont.truetype("Monaco.ttf", 28)
# 写字
draw.text([i * 35, 0], str_num, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
font=font)
print(str_code)
# 将验证码存在session中,方便后面取出来与用户输入的进行验证
request.session["str_code"] = str_code
# 将图片保存在内存中
img.save(f, format="png")
# 读取内存中的图片
data = f.getvalue()
return HttpResponse(data)
登录认证
# 登录认证
def login(request):
if request.method=="GET":
return render(request, "login.html")
else:
username = request.POST.get("name")
password = request.POST.get("pwd")
str_code = request.POST.get('code')
# 从session中取出事先我们保存好的验证码
code = request.session['str_code']
# 验证码错误
if str_code.upper()!=code.upper():
dic = {"status":"code"}
# 验证码错误直接给ajax回复一个json格式的字符串
return JsonResponse(dic)
else:
# 判断用户输入的用户名和密码与数据库中的是否匹配
user_obj = auth.authenticate(username=username, password=password)
if user_obj and str_code.upper()==code.upper():
# 登录成功设置session
auth.login(request, user_obj)
# 获取该用户的所有权限并保存在session中,并去重
permissions = models.Permission.objects.filter(role__userinfo__username=user_obj.username).distinct()
# 将该用户可以访问的所有url都添加到session中
permisson_list = [i.url for i in permissions]
request.session["permisson_list"] = permisson_list
# print(permisson_list)
# ['/home/', '/customers/', '/my_customers/', '/add_customers/', '/edit_customers/', '/delete_customers/(\\d+)/', '/show_file/', '/update_file/(\\d+)/', '/update_add/(\\d+)/', '/update_edit/(\\d+)/(\\d+)/']
dic = {"status":"correct","url":reverse("home")}
return JsonResponse(dic)
else:
# 账号密码错误
dic = {"status":"userinfo"}
return JsonResponse(dic)
ajax请求的页面
验证码显示是一个img标签,我们让它单独去请求一个视图函数,并将图片返回
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static "css/css.css" %}">
<link rel="stylesheet" href="{% static "bootstrap-3.3.7-dist/css/bootstrap.min.css" %}">
</head>
<body>
<div class="change">
<div class="container ">
<div class="row">
<div class="col-md-6 col-md-offset-3"> <div class="panel panel-primary" style="margin-top: 150px"> <div class="panel-heading change_color">客户关系管理系统</div>
<div class="panel-body ">
<form class="form-horizontal"> {% csrf_token %}
<div class="form-group">
<h3 id="h3" style="color: red"></h3>
<label for="name" class="col-sm-3 control-label change_color">账号</label> <div class="col-sm-8">
<input type="text" class="form-control" id="name" placeholder="账号" name="username">
<span id="in_span" style="color: red"></span> </div>
<span class="col-sm-3 s1"></span>
</div>
<div class="form-group">
<label for="pwd" class="col-sm-3 control-label change_color">密码</label> <div class="col-sm-8">
<input type="password" class="form-control" id="pwd" placeholder="密码" name="password">
</div> </div> <div class="form-group">
<label for="yzm" class="col-sm-3 control-label change_color">验证码</label> <div class="col-sm-5">
<input type="text" class="form-control" id="yzm" placeholder="验证码" name="code">
<span id="in_yzm" style="color: red"></span>
</div>
<div >
<img src="{% url "code" %}"
id="auth_code" alt="">
</div>
</div> <div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<div class="checkbox">
<label class="change_color">
<input type="checkbox"> 是否记住密码
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-8 col-sm-offset-3">
<button type="button" class="btn btn-success change_color" id="login_in">登录</button>
<a href="{% url "register" %}" class="btn btn-danger pull-right change_color"
>注册</a>
</div>
</div> </form> </div>
</div> </div> </div> </div>
</div> </body>
<script src="{% static "jquery-3.4.1.js" %}"></script>
<script src="{% static "bootstrap-3.3.7-dist/js/bootstrap.min.js" %}"></script>
<script>
// 判断用户名密码是否为空
$("#login_in").click(function () {
var name = $("#name").val();
var pwd = $("#pwd").val();
var code = $("#yzm").val();
var csrf_data=$("[name=csrfmiddlewaretoken]").val();
if (!name.trim()){
$("#in_span").text("用户名不能位空!");
return false;
} $.ajax({
url:"{% url "login" %}",
type:"post",
data:{
name:name,
pwd:pwd,
code:code,
csrfmiddlewaretoken:csrf_data,
},
success:function (response) {
if (response["status"]==="code"){
$("#in_yzm").text("验证码有误!");
return false
}else {
if(response['status']==="correct"){
var href = location.search.slice(6);
if (href){
location.href=href
}else {
location.href={% url "home" %}
} }else {
$("#h3").text("账号或密码有误!")
}
}
}
})
}); // 点击刷新验证码
$("#auth_code").click(function () {
$("#auth_code")[0].src+='?'
}) </script>
</html>
登录成功后显示登录用户的信息:
从request.user中取出先要展示的信息
{{ request.user.username }}
4、退出和修改密码
退出登录:
我们为了保存登录状态,所以我们在登陆的时候通过django内置的anth模块设置了session;
所以我们点击退出按钮就需要清除用户的登录状态
from django.contrib import auth # django认证
# 退出
def logout(request):
# 清空session
auth.logout(request)
return redirect("login")
修改密码:
1、判断旧密码是否正确;
2、判断新密码与确认密码是否一致;
3、手机号码我们在form组件时已经做了认证,前两项都判断正确就需要我们将数据库中的旧密码替换成我们的修改密码;
request中的user对象给我们提供了校验旧密码和修改密码以及保存密码的方法
def set_password(request):
if request.method == "GET":
return render(request, "set_password.html")
else:
dic = request.POST.dict()
dic.pop("csrfmiddlewaretoken")
old_password = dic["old_password"]
new_password = dic['new_password']
r_new_password = dic['r_new_password']
# 判断用户输入的旧密码是否与数据库中的一致
if request.user.check_password(old_password):
if new_password != r_new_password:
return render(request,"set_password.html",{"new_password":"两次输入的密码不一致!"})
else:
# 修改密码
request.user.set_password(new_password)
request.user.save()
return redirect("login")
else:
return render(request,"set_password.html",{"old_password":"旧密码不正确!"})
5、自定义分页器
可以保存搜索条件
#自定义分页
#官方推荐,页码数为奇数
class PageNation:
def __init__(self,base_url,current_page_num,total_counts,request,per_page_counts=10,page_number=5,):
'''
:param base_url: 分页展示信息的基础路径
:param current_page_num: 当前页页码
:param total_counts: 总的数据量
:param per_page_counts: 每页展示的数据量
:param page_number: 显示页码数
'''
self.base_url = base_url
self.current_page_num = current_page_num
self.total_counts = total_counts
self.per_page_counts = per_page_counts
self.page_number = page_number
self.request = request
try:
self.current_page_num = int(self.current_page_num) except Exception:
self.current_page_num = 1
if self.current_page_num < 1:
self.current_page_num = 1 half_page_range = self.page_number // 2
# 计算总页数
self.page_number_count, a = divmod(self.total_counts, self.per_page_counts) if a:
self.page_number_count += 1 if self.current_page_num > self.page_number_count:
self.current_page_num = self.page_number_count if self.page_number_count <= self.page_number:
self.page_start = 1
self.page_end = self.page_number_count
else:
if self.current_page_num <= half_page_range: #
self.page_start = 1
self.page_end = page_number #
elif self.current_page_num + half_page_range >= self.page_number_count:
self.page_start = self.page_number_count - self.page_number + 1
self.page_end = self.page_number_count
else:
self.page_start = self.current_page_num - half_page_range
self.page_end = self.current_page_num + half_page_range import copy
from django.http.request import QueryDict self.params = copy.deepcopy(request.GET) # ?condition = qq & wd = 1 & page = 3
# params['page'] = current_page_num
# query_str = params.urlencode()
#数据切片依据,起始位置
@property
def start_num(self):
start_num = (self.current_page_num - 1) * self.per_page_counts
return start_num #数据切片依据,终止位置
@property
def end_num(self):
end_num = self.current_page_num * self.per_page_counts
return end_num # 拼接HTMl标签
def page_html(self):
tab_html = ''
tab_html += '<nav aria-label="Page navigation" class="pull-right"><ul class="pagination">'
#首页
self.params['page'] = 1
showye = '<li><a href="{0}?{1}" aria-label="Previous" ><span aria-hidden="true">首页</span></a></li>'.format(self.base_url,self.params.urlencode())
tab_html += showye
# 上一页
if self.current_page_num == 1:
previous_page = '<li disabled><a href="#" aria-label="Previous" ><span aria-hidden="true">«</span></a></li>'
else:
self.params['page'] = self.current_page_num - 1
previous_page = '<li><a href="{0}?{1}" aria-label="Previous" ><span aria-hidden="true">«</span></a></li>'.format(
self.base_url,self.params.urlencode())
tab_html += previous_page #循环生成页码标签
for i in range(self.page_start, self.page_end + 1):
# request.GET {condition: qq, wd: 1,'page':1} request.GET.urlencode() condition=qq&wd=1&page=4 self.params['page'] = i # {condition: qq, wd: 1,'page':1} urlencode() -- condition=qq&wd=1&page=4 if self.current_page_num == i: one_tag = '<li class="active"><a href="{0}?{2}">{1}</a></li>'.format(self.base_url, i,self.params.urlencode()) #?condition=qq&wd=1&page=3
else:
one_tag = '<li><a href="{0}?{2}">{1}</a></li>'.format(self.base_url, i,self.params.urlencode())
tab_html += one_tag # 下一页
if self.current_page_num == self.page_number_count:
next_page = '<li disabled><a href="#" aria-label="Next"><span aria-hidden="true">»</span></a></li>'
else:
self.params['page'] = self.current_page_num + 1
next_page = '<li><a href="{0}?{1}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(self.base_url, self.params.urlencode())
tab_html += next_page # 尾页
self.params['page'] = self.page_number_count
weiye = '<li><a href="{0}?{1}" aria-label="Previous" ><span aria-hidden="true">尾页</span></a></li>'.format(
self.base_url, self.params.urlencode()) tab_html += weiye
tab_html += '</ul></nav>' return tab_html
page
6、公有|私有客户信息展示|搜索|批量处理
样式:
数据展示分公有和私有客户的信息展示:
公有客户销售字段为空;
私有客户销售字段为当前登录用户;
因为数据展示和搜索都走get请求,所以我们用同一个视图函数即可;
搜索:
1、获取用户选择的搜索条件及用户输入的搜索内容
2、去数据库中查找匹配项,返回给前端页面
select提交时我们通过choice来获取用户选择的内容:
搜索时记得做成模糊匹配__contains
choice = request.GET.get("choice",'') # 用户选择的搜索条件
wd = request.GET.get("wd",'') # 用户输入的搜索内容
print(choice,wd) # qq 362 通过choice获取用户选择的搜索条件
这里注意一个Q查询的高级用法:
from django.db.models import F,Q,Max,Min,Avg
# 实例化一个Q对象
q = Q()
# 必须是元组,因为只接收一个参数
q.children.append((choice,wd)) # choice是选择条件,wd是搜索内容 customers_obj = models.Customer.objects.filter(consultant__isnull=True).customers_obj.filter(q)
批量处理:
1、提取用户发来的动作和批量选择的id
2、提前定义好这些动作的函数,利用反射执行对应的操作
批量操作的js代码:
$("#choice").click(function () {
// 获取当前选中的状态true或false
var state = $(this).prop("checked");
$("[name=selected_id]").prop("checked",state)
})
整体代码:
# 公有和私有客户信息展示
class CustomerView(View):
# get请求
def get(self,request):
# 获取用户选择的和用户输入的搜索内容,默认为空,不选择和不输入get的返回结果为None
choice = request.GET.get("choice",'')
wd = request.GET.get("wd",'')
print(choice,wd) # qq 362
# 在查询时设置成包含的情况contains--->关键字
choice = choice + '__contains'
# 默认get第一次请求第一页
current_page_num = request.GET.get('page', 1)
# 判断请求的路径是哪个
if request.path == reverse('customers'):
# 公有客户 销售为空
customers_obj = models.Customer.objects.filter(consultant__isnull=True)
else:
# 私有客户 销售是当前登录的用户,字段名=对象--->字段_id=具体id
customers_obj = models.Customer.objects.filter(consultant=request.user)
# 判断用户是否输入了搜索条件
if wd:
# 实例化一个Q对象
q = Q()
# 指定连接条件,默认是and
# q.connector = "or"
# 必须是元组,因为只接收一个参数
q.children.append((choice,wd))
# q.children.append(('name__contains', '小'))
# 名字中包含wd字段的所有信息 qq__contains 66 name__contains 小
# models.Customer.objects.filter(name__contains=wd)
# if request.path != reverse('customers'):
# customers_obj = models.Customer.objects.filter(consultant=request.user)
customers_obj = customers_obj.filter(q)
# customers_obj = models.Customer.objects.filter(consultant__isnull=True).filter(q)
else:
# 所有销售为空的,就是公共客户
# customers_obj = models.Customer.objects.filter(consultant__isnull=True)
customers_obj = customers_obj
# 每页展示的数据量
page_counts = 5
# 页码数
page_number = 7
# 总数据
total_count = customers_obj.count()
# 判断查询数据是否为空
if total_count:
# 实列化分页的类
page_obj = page.PageNation(request.path, current_page_num, total_count, request,page_counts, page_number)
# 切片:从总数据中每次切出多少条数据展示在页面上
customers_obj = customers_obj.order_by('-pk')[page_obj.start_num:page_obj.end_num]
ret_html = page_obj.page_html()
# 根据请求的路径返回对应的页面
path = request.path
if path == reverse("customers"):
return render(request,"customers.html",{"customers_obj":customers_obj,"ret_html":ret_html,'path':path})
else:
return render(request,'my_customers.html',{"my_customers_obj":customers_obj,"ret_html":ret_html,'path':path}) else:
return render(request,"404.html")
def post(self,request):
# 提取用户发来的动作和批量选择的id
action = request.POST.get("action")
self.data = request.POST.getlist("selected_id") # 用getlist获取用户选择的多条数据
print(action,self.data) # batch_delete ['107', '103']
# 判断这个动作是否在这个对象中
if hasattr(self,action):
func = getattr(self,action)
if callable(func):
func(request)
# 判断当前页面是公户还是私护
if request.path == reverse("customers"):
return redirect("customers")
else:
return redirect("my_customers")
else:
return render(request, "404.html")
else:
return render(request, "404.html")
# 批量删除
def batch_delete(self,request):
# self.data值是一个范围,pk值在这个范围内的都删除
models.Customer.objects.filter(pk__in=self.data).delete()
# 批量更新
def batch_update(self,request):
models.Customer.objects.filter(pk__in=self.data).update(status="已报名")
# 批量公户转私护
def batch_reverse_gs(self,request):
batch_customer = models.Customer.objects.filter(pk__in=self.data)
ret = []
for i in batch_customer:
if i.consultant:
ret.append(i)
else:
# 通过model对象更新数据
i.consultant=request.user
# 调用save才会保存
i.save()
batch_customer.update(consultant=request.user)
# 批量私护转公户
def batch_reverse_sg(self,request):
models.Customer.objects.filter(pk__in=self.data).update(consultant=None)
7、基于modelform客户信息的增删改
增:
1、获取用户输入的信息
2、将用户输入的信息request.POST交给modelform做校验
3、校验成功通过save方法直接保存到数据库
生成标签
# modelform生成添加页面
class MyModelForm(forms.ModelForm):
class Meta:
model = models.Customer # 指定哪张表
fields="__all__"
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
from multiselectfield.forms.fields import MultiSelectFormField
for field in self.fields.values():
# 不给哪个字段添加样式
if not isinstance(field, MultiSelectFormField):
field.widget.attrs.update({
'class': 'form-control',
})
视图函数
# 添加客户信息
def add_customers(request):
model_obj = MyModelForm()
if request.method=="GET":
return render(request,"add_customers.html",{"model_obj":model_obj})
else:
# 将用户输入的信息交给Mymodel对象做校验
model_obj = MyModelForm(request.POST)
# 如果校验成功
if model_obj.is_valid():
# 将用户输入的所有信息保存到数据库,数据会一一对应保存
model_obj.save()
return redirect("customers")
else:
return render(request, "add_customers.html", {"model_obj": model_obj})
前端页面
{% extends "template.html" %}
{% load static %}
{% block countent %}
<div class="container">
<div class="row"> <div class="col-md-6 col-md-offset-3">
<div class="panel-heading">
<h3 class="panel-title">添加信息</h3>
</div>
<div class="panel-body">
<form action="{% url "add_customers" %}" method="post" novalidate>
{% csrf_token %}
{% for filed in model_obj %}
<label for="{{ filed.id_for_label }}">{{ filed.label }}</label>
{{ filed }}{{ filed.errors.0 }}
{% endfor %} <input type="submit" value="提交" class="btn btn-primary pull-right">
</form>
</div>
</div>
</div>
</div>
{% endblock %}
删:
1、前端发送post请求时在请求的url后面携带要删除数据的id
2、对应的视图函数到数据库中查到指定的id删除即可
<a href="{% url "delete_customers" customers.pk %}"class="btn btn-danger btn-sm">删除</a>
# 删除客户信息
def delete_customers(request,pk):
models.Customer.objects.filter(pk=pk).delete()
return redirect("customers")
改:
1、前端发送post请求时在请求的url后面携带要删除数据的id
2、对应的视图函数查找该id的对像,返回给前端页面,
3、在前端修改完数据后走的post请求,直接将用户输入的传给modelform并指定instance=要编辑的对象,如果不写instance代表添加,
4、modelform校验完数据通过save保存到数据库
<a href="{% url "edit_customers" customers.pk %}"class="btn btn-primary btn-sm">编辑</a>
# 编辑客户信息
def edit_customers(request,pk):
if request.method=="GET":
edit_obj = models.Customer.objects.filter(pk=pk).first()
model_obj = MyModelForm(instance=edit_obj) # 指定编辑哪个信息
return render(request,"edit_customers.html",{"model_obj":model_obj})
else:
edit_obj = models.Customer.objects.filter(pk=pk).first()
# instance指定该字段是更新而不是添加,并将数据进行验证
model_obj = MyModelForm(request.POST,instance=edit_obj)
if model_obj.is_valid():
# 更新数据
model_obj.save()
return redirect("customers")
else:
# 检验不通过
return render(request,"edit_customers.html",{"model_obj":model_obj})
Django-CRM后台管理系统的更多相关文章
- Django的后台管理系统Admin(5)
Django的后台管理系统就是为了方便管理员管理网站,所以django自带了一个后台管理系统,接下来记录一下如何使用这个后台的管理系统. 首先我们要进入后台管理系统,就要有一个管理员的账号,先来创建有 ...
- 【tips】xadmin - django第三方后台管理系统
Django 为大家提供了一个完善的后台管理系统—admin,但是这个后台管理系统总体来说不太适合国人的习惯,所以有大神就使用 bootstrap 和 jQuery,为我们开发了一个第三 方的 Dja ...
- admin快速搭建后台管理系统
一.基于admin后台管理系统的特点: 权限管理:权限管理是后台管理系统必不可少的部分,拥有权限管理,可以赋予用户增删改查表权限(可以分别赋予用户对不同的表有不同的操作权限): 前端样式少:后台管理主 ...
- Django学习(四) Django提供的后台管理系统以及如何定义URL路由
一旦你建立了模型Models,那么Django就可以为你创建一个专业的,可以提供给生成用的后台管理站点.这个站点可以提供给有权限的人进行已有模型Models数据的增删改查. 将新建的模型Models是 ...
- Django后台管理系统讲解及使用
大家在创建Django项目后,在根路由urls.py文件中,会看到一行代码 from django.contrib import admin urlpatterns = [ url(r'^admin/ ...
- 4、Django实战第4天:xadmin快速搭建后台管理系统
Django默认为我们提供了后台管理系统admin, urls.py中配置的第一条就是访问后台管理系统admin的 urlpatterns = [ url(r'^admin/', admin.site ...
- django后台管理系统(admin)的简单使用
目录 django后台管理系统的使用 检查配置文件 检查根urls.py文件 启动项目,浏览器输入ip端口/admin 如: 127.0.0.1/8000/admin 回车 注册后台管理系统超级管理 ...
- 可插拔式后台管理系统(Django)
1.实现效果 研究了下django admin的功能,自己实现了一个简单的可插拔式后台管理系统,方便自定义特殊的功能,而且作为一个独立单独的django app,可以整体拷贝到其他项目中作为后台数据管 ...
- Django后台管理系统的使用
目录 django后台管理系统的使用 检查配置文件 检查根urls.py文件 启动项目,浏览器输入ip端口/admin 如: 127.0.0.1/8000/admin 回车 注册后台管理系统超级管理 ...
- 分享基于EF+MVC+Bootstrap的通用后台管理系统及架构
基于EF+MVC+Bootstrap构建通用后台管理系统,集成轻量级的缓存模块.日志模块.上传缩略图模块.通用配置及服务调用, 提供了OA.CRM.CMS的原型实例,适合快速构建中小型互联网及行业 ...
随机推荐
- 《深入理解 Java 虚拟机》学习 -- Java 内存模型
<深入理解 Java 虚拟机>学习 -- Java 内存模型 1. 区别 这里要和 JVM 内存模型区分开来: JVM 内存模型是指 JVM 内存分区 Java 内存模型(JMM)是指一种 ...
- 怎样监听xhr.readyState值的变化
可以使用 xhr.onreadystatechange 属性指向的函数去监听 xhr.readyState 值的变化. 示例如下: var xhr = new XMLHttpRequest(); xh ...
- Android 官方下拉刷新 SwipeRefreshLayout
0.build.gradle compile 'com.android.support:support-v4:23+' 1.布局文件 <android.support.v4.widget.Swi ...
- VS显示方法引用
菜单栏 工具->选项->文本编辑器->所有语言->CodeLens 勾上即可
- 关于MQ的几件小事(七)如果让你设计一个MQ,你怎么设计
其实回答这类问题,说白了,起码不求你看过那技术的源码,起码你大概知道那个技术的基本原理,核心组成部分,基本架构构成,然后参照一些开源的技术把一个系统设计出来的思路说一下就好 比如说这个消息队列系统,我 ...
- boost交叉编译
运行bootstrap.sh # ./bootstrap.sh 生成b2.bjam和project-config.jam文件 修改project-config.jam using gcc : arm ...
- 4.Servlet(动态web资源)
Servlet (动态web资源) 开发一个动态web资源(即开发一个Java程序向浏览器输出数据) 需完成以下两个步骤 1.编写一个Java类,实现servelet接口 2.把开发好的Java类部署 ...
- UCOSII 之 任务统计
UCOSII 使用空闲任务的计数值(OSIdleCtr)来实现CPU使用率的统计,首先统计一个固定时间内的计数值保存下来为 (MAX),然后再开启一个固定的时间段,当时间到达时得到另外一个(OSIdl ...
- Ubuntu系统---安装QQ
使用Ubuntu很不方便,如果有什么消息的话,还要回到windows中查看.预想在Ubuntu上直接安装一个QQ,有网友说使用WebQQ发现老是掉线,于是这里安装QQ国际版. 首先,下载安装包. 这里 ...
- python 插件 Flask-装饰器 惰性载入视图
Flask 通常使用装饰器.装饰器简单易用,只要把 URL 放在相应的函数的前面就可以了. 但是这种方式有一个缺点:使用装饰器的代码必须预先导入,否则 Flask 就无法真正找到 你的函数. 当你必须 ...