关于客户的操作

主页(被继承)

{% 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

Django项目之客户的更多相关文章

  1. 使用gunicorn将django项目部署到生产环境的子目录下,在nginx后端获取客户真实IP地址

    生产环境有时,并不是为了一个项目而存在的.毕竟,域名是比较稀有的. 今天遇到这个问题,解决了.作个记录. 并且,如果将django项目部署在Nginx后面,那如何获取用户真实的IP地址呢? 下面就来解 ...

  2. Django项目实践4 - Django网站管理(后台管理员)

    http://blog.csdn.net/pipisorry/article/details/45079751 上篇:Django项目实践3 - Django模型 Introduction 对于某一类 ...

  3. Django项目实践4 - Django站点管理(后台管理员)

    http://blog.csdn.net/pipisorry/article/details/45079751 上篇:Django项目实践3 - Django模型 Introduction 对于某一类 ...

  4. Docker部署Django项目+Nginx+Fluend日志收集 和redis、memcached、RabbitMQ、Celery

    前言 一.docker 1.docker是什么? Docker的英文本意是“搬运工”,Docker搬运的是集装箱(Container)可以成为容器,我可以把写的Django的WEB应用以及Python ...

  5. 2019/01/17 对django项目部署的学习

    前记:最近在学习django项目的部署. 开发环境:windows10,使用pycharm,python2.7.15,django1.11.本地测试使用nginx和前端交互. 生产环境:centos7 ...

  6. Django学习笔记之使用 Django项目开发框架

    Django 项目是一个定制框架,它源自一个在线新闻 Web 站点,于 2005 年以开源的形式被释放出来.Django 框架的核心组件有: 用于创建模型的对象关系映射 为最终用户设计的完美管理界面 ...

  7. centos7 apache httpd安装和配置django项目

    一.安装httpd服务 apache在centos7中是Apache HTTP server.如下对httpd的解释就是Apache HTTP Server.所以想安装apache其实是要安装http ...

  8. 终端指令操作创建Django项目

    需求:通过Django创建一个用户表和权限表. 用户表包括:用户名,邮箱,密码,管理权限. 权限表包括:普通用户,管理用户,超级用户. 权限表和用户表有一对多的关系,即用户表中的每条数据对应权限表中的 ...

  9. mac osx 上面部署Django项目 apache+mysql+mod_wsgi

    1.安装Xcode command line tools 首先,编译mysql和Homebrew需要用到Xcode command line tools,所以首先安装command line tool ...

随机推荐

  1. TypeError: 'range' object doesn't support item deletion

    python 是个逐步迭代开发的过程,他不是向下兼容的,更不是向上兼容,版本不一致,好端端的程序就是不能运行了. 下面是在python 2中能运行,在Python 3中不能运行的代码.其实也很简单.但 ...

  2. guava 对集合的支持

  3. 通过Sonar的代码质量报告学习【如何写安全高质量的代码】

    1.不要用.size(),改用isEmpty() Using Collection.size() to test for emptiness works, but using Collection.i ...

  4. WPF ListView即时更新

    1.ListView 的 ItemSource 使用 BindingList < T >: 注:由于 List < T > 没有实现 INotifyPropertyChange ...

  5. Centos7 Minimal 安装后 初始化配置

    安装完成后初始化配置 1:更新yum yum upgrade 2: 安装基础命令 #yum -y install vim* lrzsz gcc-c++ pcre pcre-devel zlib zli ...

  6. UI5-学习篇-9-本地Eclipse UI5应用发布到SAP前端服务器

    参考路径: https://blogs.sap.com/2017/11/19/sap-fiori-ui5-application-deployment/ 1.准备环境 2.上载SAP-FIORI前端服 ...

  7. hadoop-3

    结合https://blog.csdn.net/zhangjun5965/article/details/76796998,自己过一遍感受下 public class DFSZKFailoverCon ...

  8. sublime text3:下载代码格式化插件和汉化插件

    1.从官网下载sublime text3 2.下载插件工具 A.使用Ctrl+`(Esc键下方)快捷键或者通过View->Show Console菜单打开命令行 将以下代码复制后粘贴,然后按En ...

  9. MySQL大数据量分页查询方法及其优化

    MySQL大数据量分页查询方法及其优化   ---方法1: 直接使用数据库提供的SQL语句---语句样式: MySQL中,可用如下方法: SELECT * FROM 表名称 LIMIT M,N---适 ...

  10. Linux:写一个简单的服务器

    开始了新篇章:Linux网络编程. 基础知识: 套接字概念 Socket本身有"插座"的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型.本质为内核借助缓冲区形成的伪 ...