使用Django完成CRM管理系统

时间:2024-11-05 20:33:56

CRM介绍:

  CRM即客户关系管理,是指企业用CRM技术来管理与客户之间的关系。在不同场合下,CRM可能是一个管理学术语,可能是一个软件系统。通常所指的CRM,指用计算机自动化分析销售、市场营销、客户服务以及应用等流程的软件系统。它的目标是通过提高客户的价值、满意度、赢利性和忠实度来缩减销售周期和销售成本、增加收入、寻找扩展业务所需的新的市场和渠道。CRM是选择和管理有价值客户及其关系的一种商业策略,CRM要求以客户为中心的企业文化来支持有效的市场营销、销售与服务流程。

本次CRM项目的需求以及特点:

  本次项目的特点基于教学系统,在此基础上进行的开发,不仅有传统意义CRM的功能,还具备了一些扩展的功能,目的旨在提高效率,解决办公上的一些痛点问题.

  需求:

    1, 不同的用户使用该系统, 显示不同的展示页面.

    2, 解决销售在联系客户时产生的冲突.

    3, 客户(学员)可以实时查看自己的有关信息,以及课程进度

    4, 老师可以布置作业,给自己所在的班级的同学评分.

    ...

  掌握的知识:

    1, python

    2, Django框架的使用

    3, bootstrap的使用

    4, jQuery的使用

    5, ajax

    6, HTML/CSS/JS

    ...

花里胡哨的说这么多,下面一步一步来吧!!!

第一部分: 创建crm_system的Django项目, 并完成基础的配置

第一步:

下载安装Django

pip install django==1.11.15

第二步:

打开pycharm创建一个项目

使用Django完成CRM管理系统

第三步:

配置settings.py文件

  1, 手动创建static文件,用于存放静态文件

使用Django完成CRM管理系统

  2, settings文件的配置

"""
Django settings for blog_crm project. Generated by 'django-admin startproject' using Django 1.11.15. For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/ For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
""" import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'xx%re+j4h-@mwr_%u8c@46im%m==e877jadvqz@4lszx*fl!33' # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'crm_manage.apps.CrmManageConfig', # 注册的app
] MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # csrf中间件
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ROOT_URLCONF = 'blog_crm.urls' TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
] WSGI_APPLICATION = 'blog_crm.wsgi.application' # Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "blog",
'USER': 'root',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': 3306, }
} # Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
] # Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' # 静态文件的别名
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static")
] AUTH_USER_MODEL = "crm_manage.UserProfile" # 不是用admin提供的表,自己对auth的表进行扩展后的表名.规则: app.表名 LOGIN_URL = " login" # 不使用Django中自己跳转的/account/login,重新配置为login(自己创建的)

settings.py的配置

  3, 使用MySQL,使用pymysql

# settings文件中配置完成后.
# 1, 在settings同级目录下的__init__文件中导入pymysql模块
import pymysql
pymysql.install_as_MySQLdb()

  4,创建库,建表

# Django不能创建库
# 1, 打开CMD,进入mysql
mysql -uroot -p # 2, 创建数据库
create database crm_data;

  5, 在app下的models中创建项目用的表(表结构复杂,不贴出来了)

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 multiselectfield import MultiSelectField
from django.utils.translation import ugettext_lazy as _ course_choices = (('LinuxL', "Linux中高级"),
("PythonFullStack", "Python高级全栈开发"),
) class_type_choices = (("fulltime", "脱产班",),
("online", "网络班"),
("weekend", "周末班"),
) source_type = (("qq", "qq群"),
("referral", "内部转介绍"),
("website", "官方网站"),
("baidu_ads", "百度推广"),
("WoM", "口碑"),
("public_class", "公开课"),
("website_luffy", "路飞官网"),
("others", "其他"),
) enroll_status_choices = (("signed", "已报名"),
("unregistered", "未报名"),
("studying", "学习中"),
("paid_in_full", "学费已交齐")
) seek_status_choices = (('A', '近期无报名计划'), ('B', '一个月内包名'),
('C', '2周内报名'), ('D', '1周内报名'),
('E', '定金'), ('F', '到班'), ('G', '全款'), ('H', '无效')) pay_type_choices = (('deposit', "订金/报名费"),
("tuition", "学费"),
("transfer", "转班"),
("dropout", "退学"),
("refund", "退款"),
) attendance_choice = (("checked", "已签到"),
("vacate", "请假"),
("late", "迟到"),
("absence", "缺勤"),
("leave_early", "早退")
) score_choices = ((100, "A+"), (90, "A"), (85, "B+"),
(80, "B"), (70, "B-"), (60, "C+"),
(50, "C"), (40, "C-"), (0, "D"),
(-1, "N/A"), (-100, "COPY"), (-1000, "FAIL")) class 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, help_text=True)
status = models.CharField("状态", choices=enroll_status_choices, max_length=64, default="unregistered",
help_text="选择客户此时的状态")
network_cosult_note = models.TextField(blank=True, null=True, verbose_name="网络咨询师咨询内容")
date = models.DateTimeField("咨询日期", auto_now_add=True)
last_consult_date = models.DateField("最后跟进日期", blank=True, null=True)
next_date = models.DateField("预计再次跟进事件", blank=True, null=True)
private = models.BooleanField(verbose_name="私人客户", default=True)
# 关联对象
network_consultant = models.ForeignKey("UserProfile", blank=True, null=True, verbose_name="咨询师")
consultant = models.ForeignKey("UserProfile", verbose_name="销售", related_name="consultant")
class_list = models.ManyToManyField("ClassList", verbose_name="已报班级") def __str__(self):
return "{}+{}".format(self.name, self.qq) 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_date = models.DateField("结业日期", blank=True, null=True)
contract = models.ForeignKey("ContractTemplate", verbose_name="选择合同模板", blank=True, null=True)
teachers = models.ManyToManyField("UserProfile", verbose_name="讲师")
class_type = models.CharField(choices=class_type_choices, max_length=64, verbose_name="班级及类型", blank=True,
null=True) class Meta:
unique_together = ("course", "semester", "campuses") def __str__(self):
return self.course class ConsultRecord(models.Model):
"""
跟进记录
"""
consultant = models.ForeignKey("Customer", verbose_name="所咨询客户")
note = models.TextField(verbose_name="跟进内容...")
status = models.CharField("跟进状态", max_length=8, choices=seek_status_choices, help_text="选择客户此时的状态")
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("我已经认真阅读完培训协议并同意全部协议内容")
contract_approved = models.BooleanField("审批通过", help_text="在审批完学员的资料无误后勾选此项,合同即生效")
enrolled_date = models.DateTimeField(auto_now_add=True, verbose_name="报名日期")
memo = models.TextField("备注", blank=True, null=True)
delete_status = models.ForeignKey("Customer", verbose_name="客户名称")
school = models.ForeignKey('Campuses')
enrolment_class = models.ForeignKey("ClassList", verbose_name="所报班级") 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)
delete_status = models.BooleanField(verbose_name="删除状态", default=False)
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="销售") 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)
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_choice, 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 UserManage(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 UserProfile(AbstractBaseUser, PermissionsMixin):
username = models.EmailField(
max_length=255,
unique=True,
)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(default=True)
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 = UserManage()

models下创建表

  6, 执行数据迁移的命令

# 记录数据表有哪些改变
python manage.py makemigrations
# 在数据库中真正写入数据
python manage.py migrate

第二部分:完成登陆注册功能

第一步:设计url,创建html页面

使用Django完成CRM管理系统

第二步:

在app中创建myforms.py文件,创建自定义的form表单

使用Django完成CRM管理系统

第三步:自定义form表单

#  myforms.py

# ! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
# 2018/9/25 20:28 from crm_manage import models
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError def check(value):
if "alex" in value:
raise ValidationError("含有敏感字符") class LoginForm(forms.Form):
username = forms.CharField(
label="用户名",
min_length=5,
max_length=20,
# initial="张三",
required=True,
validators=[check, ],
widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "Email address"}),
error_messages={"min_length": "用户名最少是5位", "max_length": "用户名最长不能超过20位"}
)
pwd = forms.CharField(
label="密码",
min_length=8,
required=True,
widget=widgets.PasswordInput(attrs={"class": "form-control", "placeholder": "Password"}),
error_messages={"min_length": "密码最少需要8位"}
) class RegForm(forms.ModelForm):
# 还可以添加ModelForm中没有的字段
re_password = forms.CharField(label="确认密码", widget=forms.PasswordInput(attrs={"class": "form-control"})) class Meta:
model = models.UserProfile
# 使用所有的字段
fields = "__all__"
# fields = ["username", "password"]
# 派出列表中的字段
exclude = ["is_active"]
labels = {
"username": "用户名",
"name": "真实姓名",
"password": "密码",
"department": "部门",
}
widgets = {
"username": forms.widgets.TextInput(attrs={"class": "form-control"}),
"password": forms.widgets.PasswordInput(attrs={"class": "form-control"}),
} # labels = {
# "username": "用户名",
# "password": "密码",
# } # 对每个字段添加属性, self.fields是一个有序的字典, self.fields.values()获取出每个字段的values值,即每个对象,
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs.update({"class": "form-control"}) # 校验两次密码是否一致
def clean(self):
if self.cleaned_data.get("password") != self.cleaned_data.get("re_password"):
self.add_error("re_password", "两次密码不一致")
raise ValidationError("两次密码不一致")
return self.cleaned_data

自定义forms

第四步:视图函数中使用自定义的form

# views.py

from django.shortcuts import render, redirect
from . import forms
from django.contrib import auth
from django.contrib.auth.decorators import login_required
from crm_manage.forms import RegForm
from . import models def login(request):
msg = ''
loginForm = forms.LoginForm()
if request.method == "POST":
loginForm = forms.LoginForm(request.POST)
username = request.POST.get('username')
pwd = request.POST.get("pwd")
obj = auth.authenticate(request, username=username, password=pwd)
if obj:
auth.login(request, obj)
return redirect("/index/")
else:
msg = "用户名密码错误"
return render(request, "login.html", {"loginForm": loginForm, "msg": msg}) def regForm(request):
reg_obj = RegForm()
if request.method == "POST":
reg_obj = RegForm(request.POST)
if reg_obj.is_valid():
# 数据库中写入数据
# 第一种方法
# reg_obj.cleaned_data.pop("groups")
# reg_obj.cleaned_data.pop("user_permissions")
# reg_obj.cleaned_data.pop("re_password")
# models.UserProfile.objects.create_user(**reg_obj.cleaned_data) # 第二种方法(此方法写入数据库中的密码是明文,所以多了一步设置密码的操作)
password = reg_obj.cleaned_data.get("password")
user = reg_obj.save()
user.set_password(password)
user.save() return redirect("/login/")
return render(request, "reg.html", {"reg_obj": reg_obj}) @login_required
def index(request):
return render(request, "index.html") def logout(request):
auth.logout(request)
return redirect("/login/") @login_required
def control(request):
customers = models.Customer.objects.all()
return render(request, "control.html", {"customers": customers})

视图函数

第五步:

前端中展示

<!DOCTYPE html>
<html lang="zh-CN">
<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标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Bootstrap 101 Template</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css"> </head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<form class="form-signin" method="post" novalidate>
{% csrf_token %}
<h2 class="form-signin-heading">欢迎登陆</h2>
<label for="inputEmail" class="sr-only">Email address</label>
{{ loginForm.username }}
<br>
<label for="inputPassword" class="sr-only">{{ loginForm.pwd.label }}</label>
{{ loginForm.pwd }}
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">登陆</button>
<br>
<a href="/reg/">
<button class="btn btn-lg btn-primary btn-block" type="button">注册</button>
</a>
</form>
</div>
</div>
</div>
</body>
</html>

登陆页面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css"> </head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form action="" class="form-horizontal" novalidate method="post">
{% csrf_token %}
<h2 class="form-signin-heading">注册</h2>
<div class="form-group {% if reg_obj.username.errors.0 %}has-error{% endif %}">
<label for="{{ reg_obj.username.id_for_label }}" class="col-sm-2 control-label">
{{ reg_obj.username.label }}
</label>
<div class="col-sm-10">
{{ reg_obj.username }}
</div>
<span id="helpBlock2" class="help-block">{{ reg_obj.username.errors.0 }}</span>
</div> <div class="form-group {% if reg_obj.name.errors.0 %}has-error{% endif %}">
<label for="{{ reg_obj.name.id_for_label }}" class="col-sm-2 control-label">
{{ reg_obj.name.label }}
</label>
<div class="col-sm-10">
{{ reg_obj.name }}
</div>
<span id="helpBlock2" class="help-block">{{ reg_obj.name.errors.0 }}</span>
</div> <div class="form-group {% if reg_obj.password.errors %}has-error{% endif %}">
<label for="{{ reg_obj.password.id_for_label }}" class="col-sm-2 control-label">
{{ reg_obj.password.label }}
</label>
<div class="col-sm-10">
{{ reg_obj.password }}
</div>
<span id="helpBlock2" class="help-block">{{ reg_obj.password.errors.0 }}</span>
</div> <div class="form-group {% if reg_obj.re_password.errors %}has-error{% endif %}">
<label for="{{ reg_obj.password.id_for_label }}" class="col-sm-2 control-label">
{{ reg_obj.re_password.label }}
</label>
<div class="col-sm-10">
{{ reg_obj.re_password }}
</div>
<span id="helpBlock2" class="help-block">{{ reg_obj.re_password.errors.0 }}</span>
</div> <div class="form-group">
<label for="{{ reg_obj.department.id_for_label }}" class="col-sm-2 control-label">
{{ reg_obj.department.label }}
</label>
<div class="col-sm-10">
{{ reg_obj.department }}
</div>
</div> <button class="btn btn-lg btn-primary btn-block" type="submit">提交</button>
</form>
</div>
</div>
</div>
</body>
</html>

注册页面

<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no">
<title>实名认证</title>
<link href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css" title="" rel="stylesheet">
<link title="" href="/static/css/style.css" rel="stylesheet" type="text/css">
<link title="blue" href="/static/css/dermadefault.css" rel="stylesheet" type="text/css">
<link href="/static/css/templatecss.css" rel="stylesheet" title="" type="text/css">
<script src="/static/jquery/jquery-1.10.2.js"></script>
<script src="/static/js/jquery.cookie.js" type="text/javascript"></script>
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js" type="text/javascript"></script>
</head>
<body style="">
<nav class="nav navbar-default navbar-mystyle navbar-fixed-top">
<div class="navbar-header">
<button class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand mystyle-brand"><span class="glyphicon glyphicon-home"></span></a></div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="li-border"><a class="mystyle-color" href="#">管理控制台</a></li>
</ul>
<ul class="nav navbar-nav pull-right">
<li class="li-border">
<a href="#" class="mystyle-color">
<span class="glyphicon glyphicon-bell"></span>
<span class="topbar-num">0</span>
</a>
</li>
<li class="li-border dropdown"><a href="#" class="mystyle-color" data-toggle="dropdown">
<span class="glyphicon glyphicon-search"></span> 搜索</a>
<div class="dropdown-menu search-dropdown">
<div class="input-group">
<input type="text" class="form-control">
<span class="input-group-btn">
<button type="button" class="btn btn-default">搜索</button>
</span>
</div>
</div>
</li>
<li class="dropdown li-border"><a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">帮助与文档<span
class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">帮助与文档</a></li>
<li class="divider"></li>
<li><a href="#">论坛</a></li>
<li class="divider"></li>
<li><a href="#">博客</a></li>
</ul>
</li>
{# <li class="dropdown li-border"><a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">605875855@qq.com<span#}
{# class="caret"></span></a>#}
{# <ul class="dropdown-menu">#}
{# <li><a href="#">退出</a></li>#}
{# </ul>#}
{# </li>#}
<li class="li-border"><a href="/login/" class="mystyle-color">登陆</a></li>
</ul>
</div>
</nav>
<div class="down-main">
<div class="left-main left-off">
<div class="sidebar-fold"><span class="glyphicon glyphicon-menu-hamburger"></span></div>
<div class="subNavBox">
<div class="sBox">
<div class="subNav"><span class="title-icon glyphicon glyphicon-chevron-up"></span><span
class="sublist-title">用户中心</span>
</div>
<ul class="navContent" style="display: block;">
<li>
<div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">账号管理
</div>
<a href=""><span class="sublist-icon glyphicon glyphicon-user"></span><span
class="sub-title">账号管理</span></a></li>
<li>
<div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">消息中心
</div>
<a href=""><span class="sublist-icon glyphicon glyphicon-envelope"></span><span
class="sub-title">消息中心</span></a></li>
<li>
<div class="showtitle" style="width:100px;"><img src="/static/img/leftimg.png">短信</div>
<a href=""><span class="sublist-icon glyphicon glyphicon-bullhorn"></span><span
class="sub-title">短信</span></a></li>
<li class="active">
<div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">实名认证
</div>
<a href=""><span class="sublist-icon glyphicon glyphicon-credit-card"></span><span
class="sub-title">实名认证</span></a></li>
</ul>
</div>
</div>
</div>
<div class="right-product view-product right-off">
<div class="table-responsive">
{% block main_info %}
<table class="table table-striped">
<thead>
<tr>
{# <th>序号</th>#}
<th>ID</th>
<th>姓名</th>
<th>qq</th>
<th>性别</th>
<th>客户来源</th>
<th>咨询课程</th>
<th>最后一次咨询时间</th>
</tr>
</thead>
<tbody>
{% for customer in customers %}
{% if customer.private == 0 %}
<tr>
{# <td>{{ forloop.counter }}</td>#}
<td>{{ customer.id }}</td>
<td>{{ customer.name }}</td>
<td>{{ customer.qq }}</td>
<td>{{ customer.sex }}</td>
<td>{{ customer.source }}</td>
<td>{{ customer.course }}</td>
<td>{{ customer.last_consult_date }}</td>
</tr>
{% endif %} {% endfor %} </tbody>
</table>
<a href="/logout/"><button class="btn btn-block">注销</button></a>
{% endblock %} </div>
</div>
</div>
<script type="text/javascript">
$(function () {
/*左侧导航栏显示隐藏功能*/
$(".subNav").click(function () {
/*显示*/
if ($(this).find("span:first-child").attr('class') == "title-icon glyphicon glyphicon-chevron-down") {
$(this).find("span:first-child").removeClass("glyphicon-chevron-down");
$(this).find("span:first-child").addClass("glyphicon-chevron-up");
$(this).removeClass("sublist-down");
$(this).addClass("sublist-up");
}
/*隐藏*/
else {
$(this).find("span:first-child").removeClass("glyphicon-chevron-up");
$(this).find("span:first-child").addClass("glyphicon-chevron-down");
$(this).removeClass("sublist-up");
$(this).addClass("sublist-down");
}
// 修改数字控制速度, slideUp(500)控制卷起速度
$(this).next(".navContent").slideToggle(300).siblings(".navContent").slideUp(300);
});
/*左侧导航栏缩进功能*/
$(".left-main .sidebar-fold").click(function () { if ($(this).parent().attr('class') == "left-main left-full") {
$(this).parent().removeClass("left-full");
$(this).parent().addClass("left-off"); $(this).parent().parent().find(".right-product").removeClass("right-full");
$(this).parent().parent().find(".right-product").addClass("right-off"); }
else {
$(this).parent().removeClass("left-off");
$(this).parent().addClass("left-full"); $(this).parent().parent().find(".right-product").removeClass("right-off");
$(this).parent().parent().find(".right-product").addClass("right-full"); }
});
})
</script> </body>
</html>

主页面

部分页面效果展示:

登陆页面:

使用Django完成CRM管理系统

注册页面:

使用Django完成CRM管理系统

第三部分:完成主页面展示信息功能和分页功能

主页面代码:

主模板:

<!DOCTYPE html>
<html lang="en">
<head>
{% load static %}
<meta charset="UTF-8">
<title>Title</title>
<link rel="icon" href="{% static "img/luffy-logo.png" %}">
<link rel="stylesheet" href="{% static "bootstrap-3.3.7-dist/css/bootstrap.min.css" %}">
<link rel="stylesheet" href="{% static "css/layout.css" %}">
<link rel="stylesheet" href="{% static "font-awesome-4.7.0/css/font-awesome.min.css" %}"> </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> <a class="navbar-brand" href="#"><i class="fa fa-tripadvisor fa-fw" aria-hidden="true"
style="margin-right: 6px;"></i>CRM管理系统</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<div class="nav navbar-nav navbar-right">
<img src="{% static "img/default.png" %}" alt="" class="dropdown-toggle img-circle" width="46px" id="dropdownMenu1" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
<img src="" alt="">
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</div>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#">任务<i class="fa fa-bell-o fa-fw" aria-hidden="true"></i>
<span class="badge">4</span>
</a>
</li>
<li>
<a href="#">通知<i class="fa fa-envelope-o fa-fw" aria-hidden="true"></i>
<span class="badge">2</span>
</a>
</li>
<li>
<a href="#">消息<i class="fa fa-comment-o fa-fw" aria-hidden="true"></i>
<span class="badge">3</span>
</a>
</li>
<li>
<a href="#">更多<i class="fa fa-ellipsis-v fa-fw" aria-hidden="true"></i></a>
</li>
</ul>
</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 class="active"><a href="#">信息广场 <span class="sr-only">(current)</span></a></li>
<li><a href="#">个人中心</a></li>
<li><a href="#">帮助</a></li>
<li><a href="#">更多</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> {% block content %}
<h1 class="page-header">Dashboard</h1> <h2 class="sub-header">Section title</h2> <div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Header</th>
<th>Header</th>
<th>Header</th>
<th>Header</th>
</tr>
</thead>
<tbody>
<tr>
<td>1,001</td>
<td>Lorem</td>
<td>ipsum</td>
<td>dolor</td>
<td>sit</td>
</tr>
<tr>
<td>1,002</td>
<td>amet</td>
<td>consectetur</td>
<td>adipiscing</td>
<td>elit</td>
</tr>
<tr>
<td>1,003</td>
<td>Integer</td>
<td>nec</td>
<td>odio</td>
<td>Praesent</td>
</tr>
</tbody>
</table>
</div>
{% endblock %} </div>
</div>
</div> <script src="{% static "jquery/jquery-1.10.2.js" %}"></script>
<script src="{% static "bootstrap-3.3.7-dist/js/bootstrap.js" %}"></script>
</body>
</html>

主页面

子模板:继承主模板

{% extends "layout.html" %}
{% block content %}
<h2 class="sub-header" style="display: inline-block">公户信息</h2> <a href="/add/">
<button type="button" class="btn btn-success" style="float: right; margin-top: 30px; margin-right: 50px;">添加
</button>
</a>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th style="text-align: center">序号</th>
<th style="text-align: center">ID</th>
<th style="text-align: center">QQ</th>
<th style="text-align: center">QQ昵称</th>
<th style="text-align: center">姓名</th>
<th style="text-align: center">客户来源</th>
<th style="text-align: center">班级类型</th>
<th style="text-align: center">销售</th>
<th style="text-align: center">状态</th>
<th style="text-align: center">日期</th>
<th style="text-align: center">咨询日期</th>
<th style="text-align: center">已报班级</th>
<th style="text-align: center">
操作
</th>
</tr>
</thead>
<tbody>
{% for customer in customers %}
{% if customer.private == 0 %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ customer.id }}</td>
<td>{{ customer.qq }}</td>
<td>{{ customer.qq_name|default:"暂无" }}</td>
<td>{{ customer.name|default:"暂无" }}</td>
<td>{{ customer.get_source_display }}</td>
<td>{{ customer.get_class_type_display }}</td>
<td>{{ customer.consultant }}</td>
<td>
{{ customer.show_status }}
</td>
<td>{{ customer.date }}</td>
<td>{{ customer.last_consult_date }}</td>
<td>{{ customer.show_class }}</td>
<td>
<a href="/edit/">
<button type="button" class="btn btn-info">编辑</button>
</a> <a href="/remove/">
<button type="button" class="btn btn-danger">删除</button>
</a>
</td>
</tr>
{% endif %} {% endfor %} </tbody>
</table>
</div>
{% endblock %}

主页面HTML

form表单的代码:

# ! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
# 2018/9/25 20:28 from crm_manage import models
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError def check(value):
if "alex" in value:
raise ValidationError("含有敏感字符") def checkio(s):
fs = "".join(filter(str.isalnum, s))
return (not fs.isalpha() and not fs.isdigit() and not fs.islower() and not fs.isupper()) class LoginForm(forms.Form):
username = forms.CharField(
label="用户名",
min_length=5,
max_length=20,
# initial="张三",
required=True,
validators=[check, ],
widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "Email address"}),
error_messages={"min_length": "用户名最少是5位", "max_length": "用户名最长不能超过20位"}
)
pwd = forms.CharField(
label="密码",
min_length=8,
required=True,
widget=widgets.PasswordInput(attrs={"class": "form-control", "placeholder": "Password"}),
error_messages={"min_length": "密码最少需要8位"}
) class AddForm(forms.ModelForm):
class Meta:
model = models.Customer
fields = "__all__" # 给每个input标签添加form-control.
def __init__(self, *args, **kwargs):
super(AddForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs.update({"class": "form-control"}) class RegForm(forms.ModelForm):
# 还可以添加ModelForm中没有的字段
re_password = forms.CharField(label="确认密码", widget=forms.PasswordInput(attrs={"class": "form-control"})) class Meta:
model = models.UserProfile
# 使用所有的字段
# fields = "__all__"
fields = ["username", "name", "password", "re_password", "department"]
# 派出列表中的字段
exclude = ["is_active"]
labels = {
"username": "用户名",
"name": "真实姓名",
"password": "密码",
"department": "部门",
}
widgets = {
"username": forms.widgets.TextInput(attrs={"class": "form-control"}),
"password": forms.widgets.PasswordInput(attrs={"class": "form-control"}),
} # 对每个字段添加属性, self.fields是一个有序的字典, self.fields.values()获取出每个字段的values值,即每个对象,
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs.update({"class": "form-control"}) # 校验两次密码是否一致
def clean(self):
if self.cleaned_data.get("password") != self.cleaned_data.get("re_password"):
self.add_error("re_password", "两次密码不一致")
raise ValidationError("两次密码不一致")
return self.cleaned_data def clean_password(self):
password = self.cleaned_data.get("password")
status = checkio(password)
if status:
return password
else:
self.add_error("password", "密码太简单了")
raise ValidationError("密码不合格")

主要是ModelForm的使用

分页功能的实现代码:

# ! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
# 2018/9/27 21:36 from django.utils.html import mark_safe class Pagination(object):
def __init__(self, request, all_count,base_url, per_num=10, max_show=11, ):
try:
current_page = int(request.GET.get("page"))
if current_page <= 0: # 判断页码是否为负数
raise Exception()
except Exception as e:
current_page = 1
self.base_url = base_url
self.current_page = current_page
self.max_show = max_show
self.half_show = max_show // 2
self.all_count = all_count
self.per_num = per_num # 每页显示的数量
self.total_page, more = divmod(self.all_count, self.per_num) # 计算显示的总页数
if more:
self.total_page += 1 def start(self):
return (self.current_page - 1) * self.per_num def end(self):
return self.current_page * self.per_num def html_str(self): # 总页码数小于最大显示
if self.total_page < self.max_show:
page_start = 1
page_end = self.total_page
# 总页码大于显示页码
else:
if self.current_page < self.half_show: # 当前页面小于显示的一半,防止有负数
page_start = 1
page_end = self.max_show elif self.current_page + self.half_show > self.total_page: # 限制当前+一半大于总页数
page_start = self.total_page - self.max_show + 1
page_end = self.total_page
else:
page_start = self.current_page - self.half_show
page_end = self.current_page + self.half_show html_list = [] if self.current_page <= 1:
prev_li = '<li class="disabled"><a>上一页</a></li>'
else:
prev_li = '<li><a href="{1}?page={0}">上一页</a></li>'.format(self.current_page - 1, self.base_url)
html_list.append(prev_li) for i in range(page_start, page_end + 1):
if i == self.current_page:
li_html = '<li class="active"><a href="{1}?page={0}">{0}</a></li>'.format(i, self.base_url)
else:
li_html = '<li><a href="{1}?page={0}">{0}</a></li>'.format(i, self.base_url)
html_list.append(li_html)
if self.current_page >= self.total_page:
last_li = '<li class="disabled"><a>下一页</a></li>'
else:
last_li = '<li><a href="{1}?page={0}">下一页</a></li>'.format(self.current_page + 1, self.base_url)
html_list.append(last_li) html_str = mark_safe("".join(html_list)) return html_str

将分页功能封装为类

使用封装好的类实现分页功能

视图函数的使用:

def user_list(request):
# 实例化一个对象
p = Pagination(request, len(users), request.path_info)
return render(request, "user_list.html", {"user": users[p.start(): p.end()], "html_str": p.html_str()})

views.py视图函数的使用

前端模板的使用:

{% extends "layout.html" %}
{% block content %}
<h2 class="sub-header" style="display: inline-block">公户信息</h2> <a href="/add/">
<button type="button" class="btn btn-success" style="float: right; margin-top: 30px; margin-right: 50px;">添加
</button>
</a>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th style="text-align: center">用户名</th>
<th style="text-align: center">密码</th>
</tr>
</thead>
<tbody>
{% for ret in user %}
<tr>
<td>{{ ret.name }}</td>
<td>{{ ret.password }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="text-center">
<nav aria-label="Page navigation">
<ul class="pagination"> {{ html_str }} {# {% for page in total_page %}#}
{# <li><a href="/user_list/?page={{ page }}">{{ page }}</a></li>#}
{# {% endfor %}#} </ul>
</nav>
</div> </div>
{% endblock %}

前端模板的使用

第四部分:很多!!!

完成的主要内容:

1, 完成私户和公户的区分,以及互相转换的功能

  基于以上代码的修改:models.py中的Customer中的consultant字段的related_name="customers", null=True, blank="True"; private注释掉,因为可以通过销售来判断是否为公户.

url设计:

使用Django完成CRM管理系统

前端页面的展示:

{% extends "layout.html" %}
{% block content %}
{% if request.path_info == "/index/" %}
<h2 class="sub-header" style="display: inline-block">公户信息</h2>
{% else %}
<h2 class="sub-header" style="display: inline-block">拥有的客户信息</h2>
{% endif %} <form action="" class="form-inline" method="post">
{% csrf_token %}
<div class="table-responsive">
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<select name="actions" id="" class="form-control">
<option value="">请选择</option>
<option value="">删除</option>
{% if request.path_info == "/index/" %}
<option value="mutil_apply">转为私户</option>
{% else %}
<option value="mutil_pub">转为公户</option>
{% endif %} </select>
<button type="submit" class="btn btn-info">执行
</button>
</div> <div class="col-md-1 col-md-offset-9">
<a href="/add/">
<button type="button" class="btn btn-success">添加
</button>
</a>
</div>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th style="text-align: center">选择</th>
<th style="text-align: center">序号</th>
{# <th style="text-align: center">ID</th>#}
<th style="text-align: center">QQ</th>
<th style="text-align: center">QQ昵称</th>
<th style="text-align: center">姓名</th>
<th style="text-align: center">客户来源</th>
<th style="text-align: center">班级类型</th>
<th style="text-align: center">销售</th>
<th style="text-align: center">状态</th>
<th style="text-align: center">日期</th>
<th style="text-align: center">咨询日期</th>
<th style="text-align: center">已报班级</th>
<th style="text-align: center">
操作
</th>
</tr>
</thead>
<tbody>
{% for customer in customers %}
<tr>
<td style="text-align: center"><input type="checkbox" value="{{ customer.id }}" name="id"></td>
<td style="text-align: center">{{ forloop.counter }}</td>
{# <td>{{ customer.id }}</td>#}
<td style="text-align: center">{{ customer.qq }}</td>
<td style="text-align: center">{{ customer.qq_name|default:"暂无" }}</td>
<td style="text-align: center">{{ customer.name|default:"暂无" }}</td>
<td style="text-align: center">{{ customer.get_source_display }}</td>
<td style="text-align: center">{{ customer.get_class_type_display }}</td>
<td style="text-align: center">{{ customer.consultant }}</td>
<td style="text-align: center">
{{ customer.show_status }}
</td>
<td style="text-align: center">{{ customer.date }}</td>
<td style="text-align: center">{{ customer.last_consult_date }}</td>
<td style="text-align: center">{{ customer.show_class }}</td>
<td style="text-align: center">
<a href="/edit/{{ customer.id }}/">
<button type="button" class="btn btn-info">编辑</button>
</a> <a href="/remove/{{ customer.id }}">
<button type="button" class="btn btn-danger">删除</button>
</a>
</td>
</tr> {% endfor %} </tbody>
</table>
</div>
</form>
<div class="container-fluid">
<div class="row">
<div class="row">
<div class="col-md-6 col-md-offset-6">
<div class="pull-right">
<form action="" class="form-inline">
<input type="text" placeholder="请输入内容" class="form-control" name="query">
<button type="submit" class="btn btn-info">搜索<i class="fa fa-search"></i>
</button>
</form>
</div>
</div>
<div class="col-md-12">
<div class="text-center">
<nav aria-label="Page navigation">
<ul class="pagination">
{{ html_str }}
</ul>
</nav>
</div>
</div>
</div>
</div>
</div> {% endblock %}

主页面的HTML

使用Django完成CRM管理系统

使用Django完成CRM管理系统

后端使用类去写:

class UserInex(View):

    @method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
ret = super(UserInex, self).dispatch(request, *args, **kwargs)
return ret def get(self, request): # 获取所有字段
field_obj = forms.Customer()
field_list = [i for i in field_obj.fields]
print(field_list)
q = self.get_search(field_list) if request.path_info == "/user_index/":
user_obj = models.Customer.objects.filter(q, consultant=request.user)
else:
user_obj = models.Customer.objects.filter(q, consultant__isnull=True) query_params = deepcopy(request.GET)
query_params._mutable = True
# query_params["page"] = 2
# # 需要修改配置
# print(query_params.urlencode()) # 实例化一个分页
pagination = Pagination(request, len(user_obj), request.path_info, query_params, per_num=3, max_show=5)
html_str = pagination.html_str return render(request, "user_index.html",
{"customers": user_obj[pagination.start: pagination.end], "html_str": html_str}) def post(self, request):
action = request.POST.get("actions")
if not hasattr(self, action):
return HttpResponse("非法操作")
getattr(self, action)()
return self.get(request) def mutil_pub(self):
obj_ids = self.request.POST.getlist("id")
self.request.user.customers.remove(*models.Customer.objects.filter(id__in=obj_ids)) def mutil_apply(self):
obj_ids = self.request.POST.getlist("id")
self.request.user.customers.add(*models.Customer.objects.filter(id__in=obj_ids)) def get_search(self, search_list):
query = self.request.GET.get("query", "")
q = Q()
q.connector = "OR"
for field in search_list:
q.children.append(Q(("{}__contains".format(field), query)))
return q

后端代码

使用Django完成CRM管理系统

使用Django完成CRM管理系统

使用Django完成CRM管理系统

2, 完成批量操作

使用Django完成CRM管理系统

使用Django完成CRM管理系统

3, 完成搜索功能

使用Django完成CRM管理系统

使用Django完成CRM管理系统

4, 加入分页功能

修改之后的分页类;

# ! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
# 2018/9/27 21:36 from django.utils.html import mark_safe class Pagination(object):
def __init__(self, request, all_count, base_url,query_params, per_num=10, max_show=11, ):
try:
current_page = int(request.GET.get("page"))
if current_page <= 0: # 判断页码是否为负数
raise Exception()
except Exception as e:
current_page = 1
self.base_url = base_url
self.current_page = current_page
self.max_show = max_show
self.half_show = max_show // 2
self.all_count = all_count
self.per_num = per_num # 每页显示的数量
self.total_page, more = divmod(self.all_count, self.per_num) # 计算显示的总页数
self.query_params = query_params
if more:
self.total_page += 1 @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 html_str(self): # 总页码数小于最大显示
if self.total_page < self.max_show:
page_start = 1
page_end = self.total_page
# 总页码大于显示页码
else:
if self.current_page < self.half_show: # 当前页面小于显示的一半,防止有负数
page_start = 1
page_end = self.max_show elif self.current_page + self.half_show > self.total_page: # 限制当前+一半大于总页数
page_start = self.total_page - self.max_show + 1
page_end = self.total_page
else:
page_start = self.current_page - self.half_show
page_end = self.current_page + self.half_show html_list = [] if self.current_page <= 1:
prev_li = '<li class="disabled"><a>上一页</a></li>'
else:
self.query_params["page"] = self.current_page - 1
prev_li = '<li><a href="{1}?{0}">上一页</a></li>'.format(self.query_params.urlencode(), self.base_url)
html_list.append(prev_li) for i in range(page_start, page_end + 1):
self.query_params["page"] = i
if i == self.current_page:
li_html = '<li class="active"><a href="{1}?{0}">{2}</a></li>'.format(self.query_params.urlencode(), self.base_url, i)
else:
li_html = '<li><a href="{1}?{2}">{0}</a></li>'.format(i, self.base_url, self.query_params.urlencode())
html_list.append(li_html)
if self.current_page >= self.total_page:
last_li = '<li class="disabled"><a>下一页</a></li>'
else:
self.query_params["page"] = self.current_page + 1
last_li = '<li><a href="{1}?{0}">下一页</a></li>'.format(self.query_params.urlencode(), self.base_url)
html_list.append(last_li) html_str = mark_safe("".join(html_list)) return html_str

分页功能代码示例

使用:

使用Django完成CRM管理系统

5, 添加编辑

from . import forms
from . import models
from django.views import View
from django.db.models import Q
from django.contrib import auth
from crm_manage.forms import RegForm
from django.utils.html import mark_safe
from utils.pagination import Pagination
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect, reverse, HttpResponse
from utils.pagination import Pagination
from django.http import QueryDict
from copy import deepcopy class UserInex(View): @method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
ret = super(UserInex, self).dispatch(request, *args, **kwargs)
return ret def get(self, request): # 获取所有字段
field_obj = forms.Customer()
field_list = [i for i in field_obj.fields]
print(field_list)
q = self.get_search(field_list) if request.path_info == "/user_index/":
user_obj = models.Customer.objects.filter(q, consultant=request.user)
else:
user_obj = models.Customer.objects.filter(q, consultant__isnull=True) query_params = deepcopy(request.GET)
query_params._mutable = True
# query_params["page"] = 2
# # 需要修改配置
# print(query_params.urlencode()) # 实例化一个分页
pagination = Pagination(request, len(user_obj), request.path_info, query_params, per_num=3, max_show=5)
html_str = pagination.html_str return render(request, "user_index.html",
{"customers": user_obj[pagination.start: pagination.end], "html_str": html_str}) def post(self, request):
action = request.POST.get("actions")
if not hasattr(self, action):
return HttpResponse("非法操作")
getattr(self, action)()
return self.get(request) def mutil_pub(self):
obj_ids = self.request.POST.getlist("id")
self.request.user.customers.remove(*models.Customer.objects.filter(id__in=obj_ids)) def mutil_apply(self):
obj_ids = self.request.POST.getlist("id")
self.request.user.customers.add(*models.Customer.objects.filter(id__in=obj_ids)) def get_search(self, search_list):
query = self.request.GET.get("query", "")
q = Q()
q.connector = "OR"
for field in search_list:
q.children.append(Q(("{}__contains".format(field), query)))
return q def login(request):
msg = ''
loginForm = forms.LoginForm()
if request.method == "POST":
loginForm = forms.LoginForm(request.POST)
username = request.POST.get('username')
pwd = request.POST.get("pwd")
obj = auth.authenticate(request, username=username, password=pwd)
if obj:
auth.login(request, obj)
return redirect("/index/")
else:
msg = "用户名密码错误"
return render(request, "login.html", {"loginForm": loginForm, "msg": msg}) def regForm(request):
reg_obj = RegForm()
if request.method == "POST":
reg_obj = RegForm(request.POST)
if reg_obj.is_valid():
# 数据库中写入数据
# 第一种方法
# reg_obj.cleaned_data.pop("groups")
# reg_obj.cleaned_data.pop("user_permissions")
# reg_obj.cleaned_data.pop("re_password")
# models.UserProfile.objects.create_user(**reg_obj.cleaned_data) # 第二种方法(此方法写入数据库中的密码是明文,所以多了一步设置密码的操作)
password = reg_obj.cleaned_data.get("password")
user = reg_obj.save()
user.set_password(password)
user.save() return redirect("/login/")
return render(request, "reg.html", {"reg_obj": reg_obj}) # @login_required
# def index(request):
# customers = models.Customer.objects.filter(consultant__isnull=True)
# return render(request, "index.html", {"customers": customers}) def logout(request):
auth.logout(request)
return redirect("/login/") # @login_required
# def control(request):
# customers = models.Customer.objects.all()
# return render(request, "control.html", {"customers": customers}) # 增加和添加
def add_edit(request, edit_id=None):
edit_obj = models.Customer.objects.filter(id=edit_id).first()
form_obj = forms.AddForm(instance=edit_obj)
if request.method == "POST":
form_obj = forms.AddForm(request.POST, instance=edit_obj)
if form_obj.is_valid():
form_obj.save()
return redirect("/index/")
return render(request, "add.html", {"form_obj": form_obj}) def remove(request):
return render(request, "index.html") # 分页功能 users = [{"name": "chenrun{}".format(i), "password": "chenrunasb{}".format(i)} for i in range(1, 302)] # def user_list(request):
# """
# :param current_page: 当前页码
# :param all_count: 总数据条数
# :param per_num: 每页显示数据条数
# :param max_show: 最多显示页码数
# :param total_page: 总页码数
# :param start: 数据切片起始索引
# :param end: 数据切片终止索引
# :return:
# """
# max_show = 11
# half_show = max_show//2
#
# all_count = len(users) # 所有的数据数
#
# per_num = 10 # 每页显示的数量
#
# total_page, more = divmod(all_count, per_num) # 计算显示的总页数
#
# # 获取用户点击的那一页
# current_page = 1
# try:
# current_page = int(request.GET.get("page"))
# if current_page <= 0: # 判断页码是否为负数
# raise Exception()
# except Exception as e:
# current_page = 1
#
# # 分割数据并显示
# """
# 1 1 10 0 10
# 2 11 20 10 20
# """
# start = (current_page - 1) * 10
# end = current_page * 10
#
# # 判断more时候有值,如果有余数,需要在总页数上加1
# if more:
# total_page += 1
#
# # 总页码数小于最大显示
# if total_page < max_show:
# page_start = 1
# page_end = total_page
# # 总页码大于显示页码
# else:
# if current_page < half_show: # 当前页面小于显示的一半,防止有负数
# page_start = 1
# page_end = max_show
#
# elif current_page + half_show > total_page: # 限制当前+一半大于总页数
# page_start = total_page - max_show + 1
# page_end = total_page
# else:
# page_start = current_page - half_show
# page_end = current_page + half_show
#
#
# html_list = []
#
# if current_page <= 1:
# prev_li = '<li class="disabled"><a>上一页</a></li>'
# else:
# prev_li = '<li><a href="/user_list/?page={0}">上一页</a></li>'.format(current_page - 1)
# html_list.append(prev_li)
#
# for i in range(page_start, page_end+1):
# if i == current_page:
# li_html = '<li class="active"><a href="/user_list/?page={0}">{0}</a></li>'.format(i)
# else:
# li_html = '<li><a href="/user_list/?page={0}">{0}</a></li>'.format(i)
# html_list.append(li_html)
# if current_page >= total_page:
# last_li = '<li class="disabled"><a>下一页</a></li>'
# else:
# last_li = '<li><a href="/user_list/?page={0}">下一页</a></li>'.format(current_page+1)
# html_list.append(last_li)
#
# html_str = mark_safe("".join(html_list))
#
# return render(request, "user_list.html", {
# "user": users[start:end],
# "html_str": html_str,
# }) # return render(request, "user_list.html",
# {
# "user": users[start: end],
# "total_page": range(page_start, page_end+1), # 因为range顾头不顾尾,所以要加一
#
# }
# ) # def user_list(request):
# # 实例化一个对象
# p = Pagination(request, len(users), request.path_info)
# return render(request, "user_list.html", {"user": users[p.start(): p.end()], "html_str": p.html_str()})

目前所有的后端代码(包含添加)

使用Django完成CRM管理系统

第五部分: 遇到一些问题;并加以解决

问题一: 在个人用户添加或编辑完成之后跳转的公户信息.

解决思路: 在访问添加或编辑的时候,将url的信息添加到next=...后边,提交过去,添加或者修改之后,拿到提交的next的url地址返回即.

第一步: 记录删一条的搜索地址和查询条件; 将地址拼接到添加的buttun的按钮上.

修改之前的添加按钮:

<a href="/add/">
<button type="button" class="btn btn-success">添加</button>
</a>

在后端的cbv中定义方法:获取url的路径以及查询条件

    def get_add_btn(self, request):
"""
生成按钮的标签
:param request:
:return:
"""
url = request.path_info
param = request.GET.copy() # 得到的是一个querydict对象
qd = QueryDict()
qd._mutable = True qd["next"] = url
qd["_query"] = param.urlencode() # 通过urlencode得到字符串
query = qd.urlencode()
add_btn = '<a href="{}?{}"><button type="button" class="btn btn-success">添加</button></a>'.format(reverse('add'), query)
return mark_safe(add_btn)

生成add_btn按钮

使用Django完成CRM管理系统

使用Django完成CRM管理系统

然后再添加的函数中添加:

使用Django完成CRM管理系统

同样的编辑也是需要添加筛选条件的

使用Django完成CRM管理系统

# 接受query并传递到前端页面
add_btn, query = self.get_add_btn(request)

前端接收:

使用Django完成CRM管理系统

问题二:

需要添加客户的跟进记录和展示客户的跟进记录

先定义跟进记录表:

class BaseForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BaseForm, self).__init__(*args, **kwargs)
for filed in self.fields.values():
filed.widget.attrs.update({"class": "form-control"}) # 定义跟进记录
class ConsultRecord(BaseForm):
class Meta:
model = models.ConsultRecord
fields = "__all__" widgets = { }

再forms中定义跟进记录表

第六部分:继续完善

问题一:

当两个销售同时将同一个公户转为私户时,理应时先到先得,而现在是后来的可以转换成功。

使用Django完成CRM管理系统

解决:使用数据库中的锁。

# 1, 开始使用锁
$ begin;
# 2, 使用锁
$ select * from table where id = 11 for update;
# 此时另外一个终端去开启mysql使用同一张表修改这个字段的时候就会夯住。只有释放掉锁另一边才可以修改成功
# 3,结束事物
$ commit;

views.py中如何加锁

from django.db import transaction

使用Django完成CRM管理系统

此时应该判断这两个用户是否是私户, 进而判断是否销售能否进行修改

    def mutil_apply(self):
flag = False
obj_ids = self.request.POST.getlist("id")
# self.request.user.customers.add(*models.Customer.objects.filter(id__in=obj_ids))
with transaction.atomic():
old = models.Customer.objects.filter(id__in=obj_ids, consultant__isnull=True).select_for_update()
if len(obj_ids) == len(old):
models.Customer.objects.filter(id__in=obj_ids).update(consultant=self.request.user)
flag = True
if not flag:
return HttpResponse("下手满了,已经被别人抢走了")

问题2: 销售不能无限制将公户添加到自己的私户中

解决:第一步:在settings.py中配置最大的私户限制

MAX_CUSTOMER_NUM = 3

第二步:

倒入settins文件

from django.conf import settings

在views.py中的转私户的函数中添加判断限制最大人数

obj_ids = self.request.POST.getlist("id")
count = models.Customer.objects.filter(consultant=self.request.user).count()
if count + len(obj_ids) > settings.MAX_CUSTOMER_NUM:
  return HttpResponse("你的私户人数太多了")

相关文章