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
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: #2 self.page_start = 1 self.page_end = page_number #5 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
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})