关于客户的操作
主页(被继承)
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content=""> <meta name="author" content=""> <title>CRM管理系统</title> <!-- Bootstrap core CSS --> <link href="{% static '/plugins/bootstrap.min.css' %}" rel="stylesheet"> <link href="{% static '/plugins/dashboard.css' %}" rel="stylesheet"> <link href="{% static '/fonts/font-awesome.min.css' %}" rel="stylesheet"> <link href="{% static '/fonts/fontawesome-webfont.ttf' %}" rel="stylesheet"> <link href="{% static '/fonts/fontawesome-webfont.woff' %}" rel="stylesheet"> <link href="{% static '/fonts/fontawesome-webfont.woff2' %}" rel="stylesheet"> <link rel="icon" href="{% static '/layout/luffy-logo.png' %}"> <script src="{% static 'js/jquery.min.js' %}"></script> <script src="{% static 'plugins/bootstrap.js' %}"></script> {% block css %} {% endblock %} </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <img src="{% static 'layout/logo.svg' %}" alt="" style="float: left;margin-top: 5px;margin-right: 5px"> <a class="navbar-brand" href="#">CRM管理系统</a> </div> <div id="navbar" class="navbar-collapse collapse"> <div class="navbar-right"> <img class="img-circle" height="45px" style="margin-top: 2.5px;margin-right: 5px" src="{% static '/layout/default.png' %}" alt="" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <ul class="dropdown-menu"> <li><a href="#">个人中心</a></li> <li><a href="#">修改密码</a></li> <li role="separator" class="divider"></li> <li><a href="#">注销</a></li> </ul> </div> <ul class="nav navbar-nav navbar-right"> <li><a href="#">任务 <i class="fa fa-bell-o" aria-hidden="true"></i> <span class="badge">4</span> </a></li> <li><a href="#">通知 <i class="fa fa-envelope-o" aria-hidden="true"></i> <span class="badge">2</span></a> </li> <li><a href="#">消息 <i class="fa fa-commenting-o" aria-hidden="true"></i> <span class="badge">6</span></a></li> </ul> <form class="navbar-form navbar-right"> <input class="form-control" placeholder="Search..." type="text"> </form> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li><a href="{% url 'consumer' %}">客户列表</a></li> <li><a href="{% url 'my_consumer' %}">我的客户</a></li> <li><a href="{% url 'consult_record' 0 %}">我的跟进记录</a></li> <li><a href="{% url 'enrollment' 0 %}">报名记录</a></li> <li><a href="#">Overview</a></li> <li><a href="#">Export</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> {% block content %} {% endblock %} </div> </div> </div> </body> </html>
所有表结构
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 from django.utils.safestring import mark_safe import datetime # Create your models here. 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, 'FALL'),) class Customer(models.Model): """ 客户表 """ qq = models.CharField('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) birthday = models.DateField('出生日期', default=None, help_text='格式yyyy-mm-dd', blank=True, null=True) phone = models.BigIntegerField('手机号', 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) course = MultiSelectField('咨询课程', choices=course_choices) 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='选择客户此时的状态') network_consult_note = models.TextField(blank=True, null=True, verbose_name='网络咨询师咨询内容') date = models.DateTimeField('咨询日期', auto_now_add=True, null=True) last_consult_date = models.DateField('最后跟进时间', auto_now_add=True, blank=True, null=True) next_date = models.DateField('预计再次跟进时间', blank=True, null=True, ) network_consultant = models.ForeignKey('UserProfile', blank=True, null=True, verbose_name='咨询师', related_name='network_consultant') consultant = models.ForeignKey('UserProfile', verbose_name='销售', related_name='customers', blank=True, null=True, ) class_list = models.ManyToManyField('ClassList', verbose_name='已报班级') 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())) def show_classes(self): return ' | '.join([str(i) for i in self.class_list.all()]) def __str__(self): return '{}<{}>'.format(self.name, self.qq) class Meta: verbose_name = '客户列表' verbose_name_plural = '客户列表' 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 ContractTemplate(models.Model): """ 合同模板表 """ name = models.CharField('合同名称', max_length=128, unique=True) content = models.TextField('合同内容') date = models.DateField(auto_now=True) class ClassList(models.Model): """ 班级表 """ course = models.CharField('课程名称', max_length=64, choices=course_choices) semester = models.IntegerField('学期') campuses = models.ForeignKey('Campuses', verbose_name='校区') price = models.IntegerField('学费', default=10000) memo = models.CharField('说明', blank=True, null=True, max_length=100) start_date = models.DateField('开班日期') graduate = models.DateField('结业日期', blank=True, null=True) contract = models.ForeignKey('ContractTemplate', verbose_name='选择合同模板', blank=True, null=True) def __str__(self): return '{}{}{}'.format(self.get_course_display(), self.semester, self.campuses) class Meta: unique_together = ('course', 'semester', '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('UserProfile', max_length='跟进人', related_name='records') date = models.DateTimeField('跟进日期', auto_now_add=True) delete_status = models.BooleanField(verbose_name='删除状态', default=False) class Enrollment(models.Model): """ 报名表 """ why_us = models.TextField('为什么报名', max_length=1024, default=None, blank=True, null=True) your_expectation = models.TextField('学完想达到的具体期望', max_length=1024, blank=True, null=True) contract_agreed = models.BooleanField('我已认真阅读完培训协议并同意全部协议内容', default=False) contract_approved = models.BooleanField('审批通过', help_text='在审阅完全部学员的资料无误后勾选此项,合同即生效', default=False) enrolled_date = models.DateTimeField(auto_now_add=True, verbose_name='报名日期') memo = models.TextField('备注', blank=True, null=True) delete_status = models.BooleanField(verbose_name='删除状态', default=False) customer = models.ForeignKey('Customer', verbose_name='客户名称') school = models.ForeignKey('Campuses',verbose_name='校区') enrollment_class = models.ForeignKey('ClassList', verbose_name='所报班级') class Meta: unique_together = ('enrollment_class', 'customer') class PaymentRecord(models.Model): """ 缴费记录表 """ pay_type = models.CharField('费用类型', choices=pay_type_choices, max_length=64, default='deposit') paid_fee = models.IntegerField('费用数额', default=0) note = models.TextField('备注', blank=True, null=True) date = models.DateTimeField('交款日期', auto_now_add=True) course = models.CharField('课程名', choices=course_choices, max_length=64, blank=True, null=True, default='N/A') class_type = models.CharField('班级类型', choices=class_type_choices, max_length=64, blank=True, null=True, default='N/A') enrollment_class = models.ForeignKey('ClassList', verbose_name='所报班级', blank=True, null=True) customer = models.ForeignKey('Customer', verbose_name='客户') consultant = models.ForeignKey('UserProfile', verbose_name='销售') delete_status = models.BooleanField(verbose_name='删除状态', default=False) status_choices = ( (1, '未审核'), (2, '已审核'), ) status = models.IntegerField(verbose_name='审核', default=1, choices=status_choices) confirm_date = models.DateTimeField(verbose_name='确认日期', null=True, blank=True) confirm_user = models.ForeignKey(verbose_name='确认人', to='UserProfile', related_name='confirms', null=True, blank=True) class CourseRecord(models.Model): """ 课程记录表 """ day_num = models.IntegerField('节次', help_text='此处填写第几节课或第几天课程。。。,必须为数字') date = models.DateField(auto_now_add=True, verbose_name='上课日期') course_title = models.CharField('本节课程标题', max_length=64, blank=True, null=True) course_memo = models.TextField('本节课程内容', max_length=300, blank=True, null=True) has_homework = models.BooleanField(default=True, verbose_name='本节有作业') homework_title = models.CharField('本节作业标题', max_length=64, blank=True, null=True) homework_memo = models.TextField('做也描述', max_length=500, blank=True, null=True) scoring_point = models.TextField('得分点', max_length=300, blank=True, null=True) re_class = models.ForeignKey('ClassList', verbose_name='班级') teacher = models.ForeignKey('UserProfile', verbose_name='讲师') class Meta: unique_together = ('re_class', 'day_num') class StudyRecord(models.Model): """ 学习记录 """ attendance = models.CharField('考勤', choices=attendance_choices, default='checked', max_length=64) score = models.IntegerField('本节成绩', choices=score_choices, default=-1) homework_note = models.CharField(max_length=255, verbose_name='作业批语', blank=True, null=True) date = models.DateTimeField(auto_now_add=True) note = models.CharField('备注', max_length=255, blank=True, null=True) homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None) course_record = models.ForeignKey('CourseRecord', verbose_name='某节课程') student = models.ForeignKey('Customer', verbose_name='学员') class Meta: unique_together = ('course_record', 'student') class UserManager(BaseUserManager): use_in_migrations = True def _create_user(self, username, password, **extra_fields): """ Creates and saves a User with the given username, email and password. """ if not username: raise ValueError('The given username must be set') username = self.normalize_email(username) username = self.model.normalize_username(username) user = self.model(username=username, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_user(self, username, password=None, **extra_fields): extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) return self._create_user(username, password, **extra_fields) def create_superuser(self, username, password, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_staff') is not True: raise ValueError('Superuser must have is_staff=True.') if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.') return self._create_user(username, password, **extra_fields) # A few helper functions for common logic between User and AnonymousUser. def _user_get_all_permissions(user, obj): permissions = set() for backend in auth.get_backends(): if hasattr(backend, "get_all_permissions"): permissions.update(backend.get_all_permissions(user, obj)) return permissions def _user_has_perm(user, perm, obj): """ A backend can raise `PermissionDenied` to short-circuit permission checking. """ for backend in auth.get_backends(): if not hasattr(backend, 'has_perm'): continue try: if backend.has_perm(user, perm, obj): return True except PermissionDenied: return False return False def _user_has_module_perms(user, app_label): """ A backend can raise `PermissionDenied` to short-circuit permission checking. """ for backend in auth.get_backends(): if not hasattr(backend, 'has_module_perms'): continue try: if backend.has_module_perms(user, app_label): return True except PermissionDenied: return False return False class Department(models.Model): name = models.CharField(max_length=32, verbose_name='部门名称') count = models.IntegerField(verbose_name='人数', default=0) class Meta: verbose_name = '部门' verbose_name_plural = '部门' def __str__(self): return self.name class UserProfile(AbstractBaseUser, PermissionsMixin): username = models.EmailField( unique=True, max_length=255 ) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.') ) is_admin = models.BooleanField(default=False) name = models.CharField('名字', max_length=32) department = models.ForeignKey('Department', default=None, blank=True, null=True) mobile = models.CharField('手机', max_length=32, default=None, blank=True, null=True) memo = models.TextField('备注', blank=True, null=True, default=None) date_joined = models.DateTimeField(auto_now_add=True) USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['name'] class Meta: verbose_name = '账户信息' verbose_name_plural = '账户信息' def get_full_name(self): # The user is identified by their email address return self.name def get_short_name(self): # The user is identified by their email address return self.username def __str__(self): # __unicode__ on Python 2 return self.username def has_perm(self, perm, obj=None): # "Does the user have a specific permission?" # Simplest possible answer: Yes, always if self.is_active and self.is_superuser: return True return _user_has_perm(self, perm, obj) def has_perms(self, perm_list, obj=None): # "Does the user have a specific permission?" # Simplest possible answer: Yes, always for perm in perm_list: if not self.has_perm(perm, obj): return False return True def has_module_perms(self, app_label): # "Does the user have permissions to view the app `app_label`?" # Simplest possible answer: Yes, always if self.is_active and self.is_superuser: return True return _user_has_module_perms(self, app_label) objects = UserManager()
分页器
""" 分页器 """ from django.utils.safestring import mark_safe from django.http import QueryDict class Pagination: def __init__(self, request, all_count, query_params=QueryDict(), per_num=2, max_show=11): # 基本的URL self.base_url = request.path_info # 查询条件 self.query_params = query_params # _mutable = True就可以修改,让urlencode()以后为# query=alex&page=1 query_params._mutable = True # 获取当前页码 try: self.current_page = int(request.GET.get('page', 1)) if self.current_page <= 0: self.current_page = 1 except Exception as e: self.current_page = 1 # 总共拥有的数据量 self.all_count = all_count # 每页显示的数据条数 self.per_num = per_num # 总页数 self.total_num, more = divmod(all_count, per_num) if more: self.total_num += 1 # 最多显示的页数 self.max_show = max_show self.half_page = max_show // 2 # 总页码数小于最大显示的页码数:显示总的页码 if self.total_num <= max_show: self.page_start = 1 self.page_end = self.total_num # 总页数大于最大显示的页数:显示最多可显示页码数 else: # 当前页码小于最多可显示页码的一半时,从1显示到最多可显示的页码 if self.current_page <= self.half_page: self.page_start = 1 self.page_end = max_show # 当前页码加上最多可显示页码的一半大于总页码时,最多可显示到总页码数 elif self.current_page + self.half_page >= self.total_num: self.page_start = self.total_num - max_show + 1 self.page_end = self.total_num # 当当前页面减去(加上)最多可显示页码的一半大于(小于总页码数时)等于1时,正常执行(按照最多可显示的页码数走) else: self.page_start = self.current_page - self.half_page self.page_end = self.current_page + self.half_page @property def start(self): # 显示数据的起始值 return (self.current_page - 1) * self.per_num @property def end(self): # 显示数据的结束值 return self.current_page * self.per_num @property def show_li(self): # 标签列表 html_list = [] self.query_params['page'] = 1 # query=alex&page=1 # 首页标签 first_li = '<li><a href="{0}?{1}">首页</a></li>'.format(self.base_url, self.query_params.urlencode()) html_list.append(first_li) # 当前页是否为首页 if self.current_page == 1: pre_li = '<li class="disabled"><a aria-label="Previous"><span aria-hidden="true">«</span></a></li>' else: self.query_params['page'] = self.current_page - 1 # pre_li = '<li><a href="{1}?page={0}" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'.format( pre_li = '<li><a href="{1}?{0}" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'.format( self.query_params.urlencode(), self.base_url) # 把上一页这个标签加到标签列表中 html_list.append(pre_li) # 页面显示的页码标签 for num in range(self.page_start, self.page_end + 1): self.query_params['page'] = num if self.current_page == num: num_li = '<li class="active"><a href="{0}?{1}">{2}</a></li>'.format(self.base_url,self.query_params.urlencode(), num) else: num_li = '<li><a href="{0}?{1}">{2}</a></li>'.format(self.base_url, self.query_params.urlencode(), num) html_list.append(num_li) # 判断当前页是否为尾页 if self.current_page == self.total_num: next_li = '<li class="disabled"><a aria-label="Next"><span aria-hidden="true">»</span></a></li>' else: self.query_params['page'] = self.current_page + 1 next_li = '<li><a href="{1}?{0}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format( self.query_params.urlencode(), self.base_url) # 把写一页这个标签加到标签列表中 html_list.append(next_li) # 尾页标签 self.query_params['page'] = self.total_num last_li = '<li><a href="{1}?{0}">尾页</a></li>'.format(self.query_params.urlencode(), self.base_url) html_list.append(last_li) # 把列表转化成字符串 return mark_safe(''.join(html_list))
views.py中的注册登录以及客户类|方法等
from django.shortcuts import render, redirect, reverse, HttpResponse from django.contrib import auth from django.utils.safestring import mark_safe from utils.pagination import Pagination from crm import models from crm.forms import RegForm, ConsumerForm, ConsultRecordForm, EnrollmentForm from django.views import View from django.db.models import Q from django.http import QueryDict # Create your views here. def login(request): err_msg = '' if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') # 做校验 obj = auth.authenticate(request, username=username, password=password) # 如果通过校验,则跳转 if obj: auth.login(request, obj) return redirect(reverse('consumer')) err_msg = '用户或密码错误' return render(request, 'login.html') # 注册 def register(request): form_obj = RegForm() if request.method == 'POST': form_obj = RegForm(request.POST) if form_obj.is_valid(): # 创建新用户 # 方法一 # form_obj.cleaned_data.pop('re_password') # models.UserProfile.objects.create_user(**form_obj.cleaned_data) # 方法二 obj = form_obj.save() obj.set_password(obj.password) obj.save() return redirect('/login/') return render(request, 'register.html', {'form_obj': form_obj}) # # 展示客户 # def consumer_list(request): # if request.path_info == reverse('consumer'): # all_customer = models.Customer.objects.filter(consultant__isnull=True) # else: # all_customer = models.Customer.objects.filter(consultant=request.user) # page = Pagination(request, all_customer.count()) # return render(request, 'crm/consumer_list.html', # {'all_customer': all_customer[page.start:page.end], # 'pagination': page.show_li}) # 展示客户列表CBV class ConsumerList(View): def get(self, request): q = self.get_search_contion(['qq', 'name', 'last_consult_date']) if request.path_info == reverse('consumer'): # all_customer = models.Customer.objects.filter(consultant__isnull=True) all_customer = models.Customer.objects.filter(q, consultant__isnull=True) else: # all_customer = models.Customer.objects.filter(consultant=request.user) all_customer = models.Customer.objects.filter(q, consultant=request.user) # query_params = copy.deepcopy(request.GET) # <QueryDict: {'query': ['alex']}> query_params = request.GET.copy() # <QueryDict: {'query': ['alex']}> # query_params = request.GET # <QueryDict: {'query': ['alex']}> # # query_params['page'] = 1 # <QueryDict: {'query': ['alex'],'page': ['1']}> # query=alex&page=1 page = Pagination(request, all_customer.count(), query_params) add_btn, query_params = self.get_add_btn() return render(request, 'crm/consumer_list.html', { 'all_customer': all_customer[page.start:page.end], 'pagination': page.show_li, 'add_btn': add_btn, 'query_params': query_params, }) def post(self, request): # 处理post提交的action的动作 action = request.POST.get('action') # 判断是否存在这个操作 if not hasattr(self, action): return HttpResponse('非法操作') ret = getattr(self, action)() if ret: return ret return self.get(request) # 公户变私户 def multi_pri(self): # 获取选择的数据的ID ids = self.request.POST.getlist('id') # 方法一 # models.Customer.objects.filter(id__in=ids).update(consultant=self.request.user) # 方法二 self.request.user.customers.add(*models.Customer.objects.filter(id__in=ids)) # 私户变公户 def multi_pub(self): # 获取选择数据的ID ids = self.request.POST.getlist('id') # 方法一 # models.Customer.objects.filter(id__in=ids).update(consultant=None) # 方法二 self.request.user.customers.remove(*models.Customer.objects.filter(id__in=ids)) def get_search_contion(self, query_list): query = self.request.GET.get('query', '') q = Q() q.connector = 'OR' # q.children.append(Q(('qq__contains', query))) # q.children.append(Q(('name__contains', query))) for i in query_list: q.children.append(Q(('{}__contains'.format(i), query))) return q # Q(Q(qq__contains=query) | Q(name__contains=query)) def get_add_btn(self): # 获取添加按钮 url = self.request.get_full_path() qd = QueryDict() qd._mutable = True qd['next'] = url query_params = qd.urlencode() add_btn = '<a href="{}?{}" class="btn btn-info">添加</a>'.format(reverse('add_consumer'), query_params) return mark_safe(add_btn), query_params # 增加客户 def add_consumer(request): # 实例化一个空的form对象 form_obj = ConsumerForm() if request.method == 'POST': # 实例化一个带提交数据的form对象 form_obj = ConsumerForm(request.POST) # 对提交的数据进行校验 if form_obj.is_valid(): # 创建对象 form_obj.save() return redirect(reverse('consumer')) return render(request, 'crm/add_consumer.html', {'form_obj': form_obj}) # 编辑客户 def edit_consumer(request, edit_id): # 根据ID查出需要编辑的客户对象 obj = models.Customer.objects.filter(id=edit_id).first() # 默认为None实例化一个对象 form_obj = ConsumerForm(instance=obj) if request.method == 'POST': # 将提交的数据和要修改的实例交给form对象 form_obj = ConsumerForm(request.POST, instance=obj) # 如果通过验证则进行保存到数据库 if form_obj.is_valid(): form_obj.save() # 反向解析跳转到客户可列表 return redirect(reverse('consumer')) return render(request, 'crm/edit_consumer.html', {'form_obj': form_obj}) # 新增和编辑客户 def consumer(request, edit_id=None): obj = models.Customer.objects.filter(id=edit_id).first() form_obj = ConsumerForm(instance=obj) if request.method == 'POST': form_obj = ConsumerForm(request.POST, instance=obj) if form_obj.is_valid(): form_obj.save() # 获取到下一个跳转的地址next next = request.GET.get('next') if next: return redirect(next) return redirect(reverse('consumer')) return render(request, 'crm/consumer.html', {'form_obj': form_obj, 'edit_id': edit_id})
url.py中的路由匹配等
from django.conf.urls import url from django.contrib import admin from crm.views import ConsumerList, ConsultRecordList,EnrollmentList from crm import views urlpatterns = [ # 公户 url(r'^consumer_list/', ConsumerList.as_view(), name='consumer'), # 私户 url(r'^my_consumer/', ConsumerList.as_view(), name='my_consumer'), # # 增加客户 # url(r'^add_consumer/', views.add_consumer, name='add_consumer'), # # 编辑客户 # url(r'^edit_consumer/(d+)', views.edit_consumer, name='edit_consumer'), # 增加客户 url(r'^add_consumer/', views.consumer, name='add_consumer'), # 编辑客户 url(r'^edit_consumer/(d+)', views.consumer, name='edit_consumer'), # name的意义在于用来反向解析时用到
forms.py中的内容
from django import forms from crm import models from django.core.exceptions import ValidationError class BaseForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): # 给所有的字段增加class这个属性 field.widget.attrs.update({'class': 'form-control'}) # 注册form class RegForm(BaseForm): password = forms.CharField( label='密码', widget=forms.widgets.PasswordInput(), min_length=6, error_messages={'min_length': '最小长度为6'} ) re_password = forms.CharField( label='确认密码', widget=forms.widgets.PasswordInput() ) class Meta: model = models.UserProfile # fields = '__all__' # 所有字段 fields = ['username', 'password', 're_password', 'name', 'department'] # 指定字段 # exclude = [''] widgets = { 'username': forms.widgets.EmailInput(attrs={'class': 'form-control'}), 'password': forms.widgets.PasswordInput, } labels = { 'username': '用户名', 'password': '密码', 'name': '姓名', 'department': '部门', } error_messages = { 'password': { 'required': '密码不能为空', } } # def __init__(self, *args, **kwargs): # super().__init__(*args, **kwargs) # for filed in self.fields.values(): # filed.widget.attrs.update({'class': 'form-control'}) def clean(self): pwd = self.cleaned_data.get('password') re_pwd = self.cleaned_data.get('re_password') if pwd == re_pwd: return self.cleaned_data self.add_error('re_password', '两次密码不一致') raise ValidationError('两次密码不一致') # 客户form class ConsumerForm(BaseForm): class Meta: model = models.Customer fields = '__all__' widgets = { 'course': forms.widgets.SelectMultiple } # def __init__(self, *args, **kwargs): # super(ConsumerForm, self).__init__(*args, **kwargs) # for field in self.fields.values(): # field.widget.attrs.update({'class': 'form-control'})
展示客户列表HTML
{% extends 'layout.html' %} {% block css %} <style> th, tr { text-align: center; } </style> {% endblock %} {% block content %} <div class="panel panel-primary"> <div class="panel-heading">客户列表</div> <div class="panel-body"> <div class="form-group"> {# <a href="{% url 'add_consumer' %}" class="btn btn-info">添加</a>#} <a href="{% url 'add_consumer' %}?{{ query_params }}" class="btn btn-primary">添加</a> {# {{ add_btn }}#} </div> <div> <form action="" class="form-inline pull-right"> <input type="text" name="query" class="form-control"> <button class="btn btn-primary">搜索 <i class="fa fa-search"></i></button> </form> </div> <div class="form-group"> <form action="" class="form-inline" method="post"> {% csrf_token %} <select name="action" class="form-control" style="margin-bottom: 10px;"> <option value="">请选择</option> <option value="multi_delete">删除</option> <option value="multi_pri">放入私户</option> <option value="multi_pub">放入公户</option> </select> <button class="btn btn-success" style="margin-bottom: 10px;">提交</button> <table class="table table-bordered table-condensed table-hover "> <thead> <tr> <th>选择</th> <th>序号</th> <th>QQ</th> {# <th>QQ昵称</th>#} <th>姓名</th> <th>性别</th> {# <th>出生日期</th>#} <th>手机号</th> {# <th>客户来源</th>#} <th>咨询课程</th> <th>班级类型</th> <th>状态</th> {# <th>咨询日期</th>#} <th>最后跟进时间</th> <th>销售</th> <th>已报班级</th> <th>操作</th> </tr> </thead> <tbody> {% 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.birthday }}</td>#} <td>{{ customer.phone|default:'暂无' }}</td> {# <td>{{ customer.get_source_display }}</td>#} <td>{{ customer.course }}</td> <td>{{ customer.get_class_type_display }}</td> <td>{{ customer.show_status }}</td> {# <td>{{ customer.date }}</td>#} <td>{{ customer.last_consult_date }}</td> <td>{{ customer.consultant }}</td> <td>{{ customer.show_classes }}</td> <td><a href="{% url 'edit_consumer' customer.id %}?{{ query_params }}"> <i class="fa fa-edit fa-fw"></i></a></td> </tr> {% endfor %} </tbody> </table> </form> </div> <div style="text-align: center"> <nav aria-label="Page navigation"> <ul class="pagination"> {{ pagination }} </ul> </nav> </div> </div> </div> {% endblock %}
增加和编辑客户合在一起的写法HTML
{% extends 'layout.html' %} {% block content %} <div class="panel panel-primary"> <div class="panel-heading"> {% if edit_id %} 编辑客户 {% else %} 新增客户 {% endif %} </div> <div class="panel-body"> <div class="col-sm-8 col-sm-offset-2"> <form action="" method="post" novalidate> {% csrf_token %} {% for field in form_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> </div> </div> </div> {% endblock %}
关于客户操作的一些知识点
url中需知
# 公户 url(r'^consumer_list/', ConsumerList.as_view(), name='consumer'), # 私户 url(r'^my_consumer/', ConsumerList.as_view(), name='my_consumer'), # name存在的意义在于反向解析时使用可以定位到 ConsumerList.as_view()这里,再去调用 ConsumerList.as_view()指向的函数。 # ConsumerList.as_view()是当需要调用的函数封装到类中的时候这样写,正常为views.consumer_list # 这里consumer_list为views中的函数名
veiws中需知
request.path_info # 获取路径信息 当路径为http://127.0.0.1:8001/crm/enrollment_list/?page=1时,只会获取/crm/enrollment_list/这个部分不包含脚本前缀也不包含参数 filter(q, consultant__isnull=True) # 支持多个条件的筛选、过滤 query_params = request.GET.copy() # 修改时不会改变源码中的数值 query_params['page'] = 1 # <QueryDict: {'query': ['alex'],'page': ['1']}>这是固定的方法 qd = QueryDict() qd._mutable = True # 改为True时对QueryDict进行修改 qd['next'] = url query_params = qd.urlencode() # 将<QueryDict: {'query': ['alex'],'page': ['1']}>变为query=alex&page=1 models.Customer.objects.filter(id__in=ids).update(consultant=None) # 把筛选出来的数据中的consultant字段更新为consultant=None request.get_full_path() -- 获取当前url,(包含参数) 请求一个http://127.0.0.1:8000/200/?type=10 request.get_full_path()返回的是【/200/?type=10】 request.path -- 获取当前url,(但不含参数) request.path返回的是 【/200/】 from django.utils.safestring import mark_safe mark_safe(add_btn) # 不让add_btn中的内容进行转义正常当做标签来显示 # 实例化一个带提交数据的form对象 form_obj = ConsumerForm(request.POST) # 与数据库做校验(登录) obj = auth.authenticate(request, username=username, password=password) # 记录当前用户的登录状态 auth.login(request, obj) # 设置密码 obj.set_password(obj.password) # 将公户变为私户(add中放的必须是对象) self.request.user.customers.add(*models.Customer.objects.filter(id__in=ids))
class Meta: # 写在类的后面(内部) verbose_name = '客户' # 修改admin中的显示名称会在后面加s verbose_name_plural = '客户' # 彻底修改admin中的显示名称
def __str__(self): return '{}{}{}'.format(self.get_course_display(),self.semester,self.campuses) # 让admin中显示的内容为中文
def __init__(self, *args, **kwargs): super(Consumer, self).__init__(*args, **kwargs) for field in self.fields.values(): field.widgets.attrs.update({'class': 'form-control'}) # 给所有的字段(也就是标签)添加属性class= form-control