CRM项目
- 业务
- 权限
CRM
客户关系管理系统(customer relationship management)
使用人员:
销售 财务 班主任 讲师 助教
业务:
- 登录
- 注册
销售:
- 客户信息管理
展示 新增 编辑
- 跟进记录的管理
展示 新增 编辑
- 报名记录的管理
展示 新增 编辑
- 缴费记录的管理
展示 新增 编辑
班主任
- 班级的管理
展示 新增 编辑 - 课程记录的管理
展示 新增 编辑 - 学习记录的管理
表结构
- 用户表
- 客户表
关联销售 可以为空 (为空 是公户 ,不为空是私户) - 跟进记录表
- 报名记录表
- 缴费记录表
- 校区
- 班级表
- 课程记录表
1、django创建虚拟环境:
show All:
2、 +:
3、 创建中:
4、 干净的虚拟环境:
5、 下载django:
6、创建数据库:
7、pycharm配置git:
git init
8、导入忽略文件:
9、不要提交到git上并忽略再次出现:
10、crm\__init__.py:
#默认使用mysqldb的模块 要使用pymysql替换mysqldb
import pymysql
pymysql.install_as_MySQLdb()
11、crm\models.py:
from django.db import models
#导入多选功能:
from multiselectfield import MultiSelectField
#定义哪些课程:
course_choices = (('Linux', 'Linux中高级'),
('PythonFullStack', 'Python高级全栈开发'),)
#定义哪些班级:
class_type_choices = (('fulltime', '脱产班',),
('online', '网络班'),
('weekend', '周末班',),)
#定义哪些来源:
source_type = (('qq', "qq群"),
('referral', "内部转介绍"),
('website', "官方网站"),
('baidu_ads', "百度推广"),
('office_direct', "直接上门"),
('WoM', "口碑"),
('public_class', "公开课"),
('website_luffy', "路飞官网"),
('others', "其它"),)
#定义状态:
enroll_status_choices = (('signed', "已报名"),
('unregistered', "未报名"),
('studying', '学习中'),
('paid_in_full', "学费已交齐"))
#定义跟进情况:
seek_status_choices = (('A', '近期无报名计划'), ('B', '1个月内报名'), ('C', '2周内报名'), ('D', '1周内报名'),
('E', '定金'), ('F', '到班'), ('G', '全款'), ('H', '无效'),)
#定义支付类型:
pay_type_choices = (('deposit', "订金/报名费"),
('tuition', "学费"),
('transfer', "转班"),
('dropout', "退学"),
('refund', "退款"),)
#定义上课情况:
attendance_choices = (('checked', "已签到"),
('vacate', "请假"),
('late', "迟到"),
('absence', "缺勤"),
('leave_early', "早退"),)
#定义哪些成绩选择:
score_choices = ((100, 'A+'),
(90, 'A'),
(85, 'B+'),
(80, 'B'),
(70, 'B-'),
(60, 'C+'),
(50, 'C'),
(40, 'C-'),
(0, ' D'),
(-1, 'N/A'),
(-100, 'COPY'),
(-1000, 'FAIL'),)
class Department(models.Model):
"""
部门表
"""
name = models.CharField(max_length=32, verbose_name="部门名称")
count = models.IntegerField(verbose_name="人数", default=0) #IntegerField、一个整数类型。数值的范围是 -2147483648 ~ 2147483647。
#定义返回页面字符串:
def __str__(self):
return self.name
class UserProfile(models.Model):
"""
用户表
"""
username = models.EmailField(max_length=255, unique=True, ) #EmailField字符串类型,Django Admin以及ModelForm中提供验证机制、unique数据库中字段是否可以建立唯一索引
password = models.CharField(max_length=128)
name = models.CharField('名字', max_length=32) #name真实姓名
department = models.ForeignKey('Department', default=None, blank=True, null=True) #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) #TextField长文本
date_joined = models.DateTimeField(auto_now_add=True) #date_joined加入日期、DateTimeField日期字段、auto_now_add新创建对象时自动添加当前日期时间。
is_active = models.BooleanField(default=True) #is_active是否激活、BooleanField布尔值类型
def __str__(self):
return self.name
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) #qq_nameQQ昵称、
name = models.CharField('姓名', max_length=32, blank=True, null=True, help_text='学员报名后,请改为真实姓名') #name真实姓名
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) #help_textAdmin中该字段的提示信息
phone = models.BigIntegerField('手机号', blank=True, null=True) #BigIntegerField长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
source = models.CharField('客户来源', max_length=64, choices=source_type, default='qq') #source来源
introduce_from = models.ForeignKey('self', verbose_name="转介绍自学员", blank=True, null=True) #introduce_from介绍
course = MultiSelectField("咨询课程", choices=course_choices) #course课程
class_type = models.CharField("班级类型", max_length=64, choices=class_type_choices, default='fulltime')
customer_note = models.TextField("客户备注", blank=True, null=True, ) #customer_note客户备注
status = models.CharField("状态", choices=enroll_status_choices, max_length=64, default="unregistered",
help_text="选择客户此时的状态")
last_consult_date = models.DateField("最后跟进日期", auto_now_add=True) #last_consult_date最后跟进日期、DateField日期类型,日期格式为YYYY-MM-DD,相当于Python中的datetime.date的实例。
next_date = models.DateField("预计再次跟进时间", blank=True, null=True) #next_date预计再次跟进时间
consultant = models.ForeignKey('UserProfile', verbose_name="销售", related_name='customers', blank=True, null=True, ) #consultant课程顾问、verbose_nameAdmin中显示的字段名称、related_name反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
class_list = models.ManyToManyField('ClassList', verbose_name="已报班级", blank=True) #class_list班级表多对多
def __str__(self):
return "{} - {}".format(self.qq,self.name)
class Campuses(models.Model):
"""
校区表
"""
name = models.CharField(verbose_name='校区', max_length=64)
address = models.CharField(verbose_name='详细地址', max_length=512, blank=True, null=True)
class ClassList(models.Model):
"""
班级表
"""
course = models.CharField("课程名称", max_length=64, choices=course_choices)
semester = models.IntegerField("学期") #semester学期
campuses = models.ForeignKey('Campuses', verbose_name="校区") #campuses校区
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)
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: #Meta数据库中生成的表名称 默认 app名称 + 下划线 + 类名
unique_together = ("course", "semester", 'campuses') #unique_together联合唯一索引
class ConsultRecord(models.Model):
"""
跟进记录表
"""
customer = models.ForeignKey('Customer', verbose_name="所咨询客户") #customer客户
note = models.TextField(verbose_name="跟进内容...")
status = models.CharField("跟进状态", max_length=8, choices=seek_status_choices, help_text="选择客户此时的状态")
consultant = models.ForeignKey("UserProfile", verbose_name="跟进人", related_name='records') #consultant跟进的人
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="报名日期") #enrolled_date报名日期
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')
enrolment_class = models.ForeignKey("ClassList", verbose_name="所报班级") #enrolment_class注册班级
class Meta:
unique_together = ('enrolment_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')
enrolment_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) #to="UserProfile"关联用户信息表
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',related_name="course_records", verbose_name="讲师")
recorder = 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')
12、old_crm\local_settings.py:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "old_crm",
'HOST': "127.0.0.1",
'PORT': 3306,
'USER': "root",
'PASSWORD': "123",
}
}
13、old_crm\settings.py:
"""
Django settings for old_crm project.
Generated by 'django-admin startproject' using Django 1.11.27.
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 = 'g15q%*@$fgj%t9-7)c7v#rg**latgcz7_7#p(f7+u)h0&%dob6'
# 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.apps.CrmConfig', #检测注册的app
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', #不需要注释
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'old_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 = 'old_crm.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
#配置数据库信息
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "old_crm",
'HOST': "127.0.0.1",
'PORT': 3306,
'USER': "root",
'PASSWORD': "123",
}
}
# 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 = 'zh-hans' #更改语言
TIME_ZONE = 'Asia/Shanghai' #更改时区
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') #配置文件路径
]
#导入本地设置文件的所有功能并捕获异常以防止不报错:
try:
from .local_settings import *
except ModuleNotFoundError:
pass
14、忽略文件.gitignore:
# Byte-compiled / optimized / DLL files
.idea/
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
15、结构图:
16、数据库迁移命令:
python manage.py makemigrations
python manage.py migrate
17、创建超级用户命令:
python manage.py createsuperuser
用户名:root
邮箱:可以不填
密码(不能纯数字和纯英文):root1234
18、注册admin:
from django.contrib import admin
#导入crm下面的models模块:
from crm import models
# Register your models here.
# 注册model类到admin
admin.site.register(models.UserProfile)
19、运行django打开页面:
20、进入管理后台:
21、admin登录:用户名:root、密码:root1234
22、路由分发old_crm\urls.py:
"""old_crm URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r"^",include("crm.urls")),
]
23、crm\urls:
from django.conf.urls import url
#导入视图函数:
from django.conf.urls import url
#导入视图函数:
from crm import views
urlpatterns = [
url(r'^login/',views.Login.as_view(),name="login"), #使用反向解析
url(r'^reg/',views.reg,name="reg"), #使用反向解析
]
24、设置登录视图函数crm\views:
from django.shortcuts import render,HttpResponse
#导入视图:
from django.views import View
#导入models:
from crm import models
# Create your views here.
#定义登录类:
class Login(View):
#定义get的获取方式功能:
def get(self,request,*args,**kwargs):
return render(request,"login.html")
#定义post获取方式功能:
def post(self,request,*args,**kwargs):
username = request.POST.get("username")
password = request.POST.get("password")
#判断用户名、密码、激活状态、
obj = models.UserProfile.objects.filter(username=username,password=password,is_active=True).first()
if obj:
return HttpResponse("ok")
return render(request,"login.html",{"error":"用户名或密码错误"})
25、old_crm\templates\login.html:
<!DOCTYPE html>
<html lang="zh-CN">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>登陆丨Sharelink</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<body>
<div class="login-container">
<h1>ShareLink</h1>
<div class="connect">
<p>Link the world. Share to world.</p>
</div>
<form action="" method="post" novalidate>
{# 重定向csrf_token:#}
{% csrf_token %}
<div>
{# autocomplete自动进行填充、#}
<input type="text" name="username" class="username" placeholder="用户名" autocomplete="off" value="admin"/>
</div>
<div>
{# oncontextmenu是否可以右键和复制:#}
<input type="password" name="password" class="password" placeholder="密码" oncontextmenu="return false"
onpaste="return false"/>
</div>
{# 定义登录错误提示:#}
<div>{{ error }}</div>
<button type="submit">登 陆</button>
</form>
<a href="register.html">
<button type="button" class="register-tis">还有没有账号?</button>
</a>
</div>
</body>
<script src="http://www.jq22.com/jquery/1.11.1/jquery.min.js"></script>
<script src="{% static 'js/common.js' %}"></script>
<script src="{% static 'js/supersized.3.2.7.min.js' %}"></script>
<script src="{% static 'js/supersized-init.js' %}"></script>
<script src="{% static 'js/jquery.validate.min.js' %}"></script>
</html>
26、old_crm\templates\reg.html:
<!DOCTYPE html>
<html lang="zh-CN">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>登陆丨Sharelink</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<style>
select {
width: 302px;
height: 42px;
line-height: 42px;
margin-top: 25px;
padding: 0 15px;
background: #2d2d2d;
background: rgba(45, 45, 45, .15);
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, .15);
-moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, .1) inset;
-webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, .1) inset;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, .1) inset;
font-family: microsoft yahei, Helvetica, Arial, sans-serif;
font-size: 14px;
color: #fff;
text-shadow: 0 1px 2px rgba(0, 0, 0, .1);
-o-transition: all .2s;
-moz-transition: all .2s;
-webkit-transition: all .2s;
}
{#定义背景颜色:#}
option {
background-color: #ff8adf;
}
</style>
<body>
<div class="register-container">
<h1>ShareLink</h1>
<div class="connect">
<p>Link the world. Share to world.</p>
</div>
{# novalidate是控制页面字段校验:#}
<form action="" method="post" novalidate>
{# 定义保护机制:#}
{% csrf_token %}
<div>
{# <input type="text" name="username" class="username" placeholder="您的用户名" autocomplete="off"/>#}
{{ form_obj.username }}
{{ form_obj.username.errors.0 }}
</div>
<div>
{# <input type="password" name="password" class="password" placeholder="输入密码" oncontextmenu="return false"#}
{# onpaste="return false"/>#}
{{ form_obj.password }}
{{ form_obj.password.errors.0 }}
</div>
<div>
{# <input type="password" name="confirm_password" class="confirm_password" placeholder="再次输入密码"#}
{# oncontextmenu="return false" onpaste="return false"/>#}
{{ form_obj.re_password }}
{{ form_obj.re_password.errors.0 }}
</div>
<div>
{# <input type="text" name="phone_number" class="phone_number" placeholder="输入手机号码" autocomplete="off"#}
{# />#}
{{ form_obj.mobile }}
{{ form_obj.mobile.errors.0 }}
</div>
<div>
{{ form_obj.name }}
{{ form_obj.name.errors.0 }}
</div>
<div>
{{ form_obj.department }}
{{ form_obj.department.errors.0 }}
</div>
<button type="submit">注 册</button>
</form>
<a href="{% url 'login' %}">
<button type="button" class="register-tis">已经有账号?</button>
</a>
</div>
</body>
<script src="http://www.jq22.com/jquery/1.11.1/jquery.min.js"></script>
<script src="{% static 'js/common.js' %}"></script>
<script src="{% static 'js/supersized.3.2.7.min.js' %}"></script>
<script src="{% static 'js/supersized-init.js' %}"></script>
<script src="{% static 'js/jquery.validate.min.js' %}"></script>
</html>
27、定义form组件、注册功能、抛出异常:
#导入forms:
from django import forms
#导入抛出异常:
from django.core.exceptions import ValidationError
#导入hashlib模块:
import hashlib
#定义form组件:
class RegForm(forms.ModelForm):
#重新定义密码:validators自定义错误验证(列表类型),从而定制想要的验证规则、min_length长度、label提示信息
re_password = forms.CharField(min_length=6,widget=forms.PasswordInput(attrs={"placeholder":"确认密码"}),label="确认密码")
password = forms.CharField(min_length=6,widget=forms.PasswordInput(attrs={"placeholder":"输入密码"}),label="密码")
#定义def clean方法:
def clean(self):
#校验字段的唯一性:
self._validate_unique = True
#默认密码:
password = self.cleaned_data.get("password")
#确认密码:
re_password = self.cleaned_data.get("re_password")
#两次密码进行判断:
if password == re_password:
#保存之前加密:
md5 = hashlib.md5()
#注意编码bytes类型:
md5.update(password.encode("utf-8"))
#加密结果:
self.cleaned_data["password"] = md5.hexdigest()
return self.cleaned_data
#抛出异常:
#自己制定:
self.add_error("re_password","两次密码不一致!!")
raise ValidationError("两次密码不一致")
#form做配置:
class Meta:
#根据UserProfile表生成一些字段:
model = models.UserProfile
#__all__是使用所有的字段:
# fields = "__all__"
fields = ["username","password"]
#定义排除的字段:
exclude = ["memo","is_active"]
#设置全局所有字段:
widgets = {
"username":forms.TextInput(attrs={"placeholder":"请输入邮箱"}),
# "password":forms.PasswordInput(attrs={"placeholder":"请输入密码"}),
"mobile":forms.TextInput(attrs={"placeholder":"请输入手机号"}),
"name":forms.TextInput(attrs={"placeholder":"请输入姓名"}),
}
#自定义修改错误信息:
error_messages = {
"username":{}
}
#定义提示:
# labels = {
# "username":"xxx"
# }
#定义注册功能:
def reg(request):
#实例化form:
form_obj = RegForm()
if request.method == "POST":
form_obj = RegForm(request.POST)
#校验数据:
if form_obj.is_valid():
# print(form_obj.cleaned_data)
form_obj.save()
return redirect("login")
return render(request,"reg.html",{"form_obj":form_obj})
28、定义展示客户列表功能:
from django.conf.urls import url
#导入视图函数:
from crm import views
urlpatterns = [
url(r'^login/',views.Login.as_view(),name="login"), #使用反向解析
url(r'^reg/',views.reg,name="reg"), #使用反向解析
url(r'^customers/',views.customers,name="customers"), #使用反向解析
]
29、定义展示页面模板layout:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>路飞学城</title>
<link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
<link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
<link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
<link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
<link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
<style>
body {
margin: 0;
}
.no-radius {
border-radius: 0;
}
.no-margin {
margin: 0;
}
.pg-body > .left-menu {
background-color: #EAEDF1;
position: absolute;
left: 1px;
top: 48px;
bottom: 0;
width: 220px;
border: 1px solid #EAEDF1;
overflow: auto;
}
.pg-body > .right-body {
position: absolute;
left: 225px;
right: 0;
top: 48px;
bottom: 0;
overflow: scroll;
border: 1px solid #ddd;
border-top: 0;
font-size: 13px;
min-width: 755px;
}
.navbar-right {
float: right !important;
margin-right: -15px;
}
.luffy-container {
padding: 15px;
}
.left-menu .menu-body .static-menu {
}
.left-menu .menu-body .static-menu .icon-wrap {
width: 20px;
display: inline-block;
text-align: center;
}
.left-menu .menu-body .static-menu a {
text-decoration: none;
padding: 8px 15px;
border-bottom: 1px solid #ccc;
color: #333;
display: block;
background: #efefef;
background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
background: -ms-linear-gradient(bottom, #efefef, #fafafa);
background: -o-linear-gradient(bottom, #efefef, #fafafa);
filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
box-shadow: inset 0 1px 1px white;
}
.left-menu .menu-body .static-menu a:hover {
color: #2F72AB;
border-left: 2px solid #2F72AB;
}
.left-menu .menu-body .static-menu a.active {
color: #2F72AB;
border-left: 2px solid #2F72AB;
}
</style>
{% block css %} {% endblock %}
</head>
<body>
<div class="pg-header">
<div class="nav">
<div class="logo-area left">
<a href="#">
<img class="logo" src="{% static 'imgs/logo.svg' %}">
<span style="font-size: 18px;">路飞学城 </span>
</a>
</div>
<div class="left-menu left">
<a class="menu-item">资产管理</a>
<a class="menu-item">用户信息</a>
<a class="menu-item">路飞管理</a>
<div class="menu-item">
<span>使用说明</span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
<div class="more-info">
<a href="#" class="more-item">管他什么菜单</a>
<a href="#" class="more-item">实在是编不了</a>
</div>
</div>
</div>
<div class="right-menu right clearfix">
<div class="user-info right">
<a href="#" class="avatar">
<img class="img-circle" src="{% static 'imgs/default.png' %}">
</a>
<div class="more-info">
<a href="#" class="more-item">个人信息</a>
<a href="#" class="more-item">注销</a>
</div>
</div>
<a class="user-menu right">
消息
<i class="fa fa-commenting-o" aria-hidden="true"></i>
<span class="badge bg-success">2</span>
</a>
<a class="user-menu right">
通知
<i class="fa fa-envelope-o" aria-hidden="true"></i>
<span class="badge bg-success">2</span>
</a>
<a class="user-menu right">
任务
<i class="fa fa-bell-o" aria-hidden="true"></i>
<span class="badge bg-danger">4</span>
</a>
</div>
</div>
</div>
<div class="pg-body">
<div class="left-menu">
<div class="menu-body">
<div class="static-menu">
<a href="/crm/depart/list/"><span class="icon-wrap"><i class="fa fa-map-o" aria-hidden="true"></i></span> 部门管理</a>
<a href="/crm/user/list/"><span class="icon-wrap"><i class="fa fa-map-o" aria-hidden="true"></i></span> 用户管理</a>
</div>
</div>
</div>
<div class="right-body">
<div>
<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
<li><a href="#">首页</a></li>
<li class="active">客户管理</li>
</ol>
</div>
{% block content %} {% endblock %}
</div>
</div>
<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
{% block js %} {% endblock %}
</body>
</html>
30、定义展示功能函数customers:
#定义展示功能:
def customers(request):
#拿到所有的客户:
all_customers = models.Customer.objects.all()
return render(request,"customers.html",{"customers":customers})
31、客户列表页面customers.html
{#继承layout模板:#}
{% extends "layout.html" %}
{% block content %}
<tabke class="table table-hover table-bordered">
<thead>
<tr>
<th>序号</th>
<th>QQ</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_customers %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ customer.qq }}</td>
<td>{{ customer.name }}</td>
<td>{{ customer.sex }}</td>
<td>{{ customer.phone }}</td>
<td>{{ customer.source }}</td>
<td>{{ customer.course }}</td>
<td>{{ customer.class_type }}</td>
<td>{{ customer.status }}</td>
<td>{{ customer.last_consult_date }}</td>
<td>{{ customer.consultant }}</td>
<td>{{ customer.class_list }}</td>
</tr>
{% endfor %}
</tbody>
</tabke>
{% endblock %}
32、admin下注册Customer、已报班级、校区:
from django.contrib import admin
#导入crm下面的models模块:
from crm import models
# Register your models here.
# 注册model类到admin
admin.site.register(models.UserProfile)
#注册Customer:
admin.site.register(models.Customer)
#注册已报班级ClassList:
admin.site.register(models.ClassList)
#注册校区Campuses:
admin.site.register(models.Campuses)
33、http://127.0.0.1:8000/admin/、
34、保存: