关于客户的操作

主页(被继承)

{% 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. 转载:用Source Insight中看Python代码

    在Source Insight中看Python代码 http://blog.csdn.net/lvming404/archive/2009/03/18/4000394.aspx SI是个很强大的代码查 ...

  2. dubbo超时优先级设置

    调用超时配置的优先级 可以在多个配置项设置超时,由上至下覆盖(即上面的优先),示例如下: # 其它的参数(retries.loadbalance.actives等)的覆盖策略也一样. 提供者端特定方法 ...

  3. MVC基于角色权限控制--用户管理

    用户管理模块包括 新增用户.修改用户.展示用户列表.删除用户.用户角色分配.用户角色删除.用户权限分配 这里只介绍关于权限有关的 用户角色分配.用户角色删除.用户权限分配 新建控制器 UserInfo ...

  4. WPF按钮响应函数中执行操作耗时较长时,UI 界面不能实时更新——问题原因与解决方案

    原因: 一般来说,一个WPF窗口程序,只有一个UI线程, 如果这个线程停在某个函数,UI将会被阻塞,所有其他的界面操作都不能即时响应. 解决方案: 新开一个线程来执行耗时较长的操作,以不阻塞UI.

  5. WDA-Web Dynpro的POWL(个人对象工作清单)

    POWL(Personal Object Worklist) for Web Dynpro 转载地址:https://blogs.sap.com/2013/02/15/powlpersonal-obj ...

  6. C++ primer ch6 函数基础-1

    1.形参和实参:编译器并没有规定实参的求值顺序. 类似下面的代码,其行为是未定义的: ; printf("%d %d\n",++i,++i); 2.变量的初始化: 如果内置类型的变 ...

  7. cordova-config.xml 配置记录

    <?xml version='1.0' encoding='utf-8'?> <widget id="come.gs.webapp1" version=" ...

  8. Zabbix 3.0 LTS安装配置

    关于Zabbix监控项类型的官网介绍: https://www.zabbix.com/documentation/3.4/zh/manual/config/items/itemtypes zabbix ...

  9. 5分钟K线图压力线买点怎么看?

    某开盘后底开一直呈现形成了一个长时间的箱体振荡的走势,K线在底位振荡时,其波动底点总是在不断抬高的话,这种走势说明有资金在场中积极运作,正是由于资金悄然建仓导致了底点慢慢抬高的走势,在底点不断抬高时, ...

  10. Structs复习 访问web元素

    Structs帮我们在action和http里建立了联系 主要有四种方式 我们主要用第二种(IOC 依赖容器注入 ) Jar包 web.XML <?xml version="1.0&q ...