客户关系管理。
以admin项目为基础,扩展自己的项目。
一、创建项目
二、配置数据库,使用mysql数据库:
需要安全mysqlclient模块:pip install mysqlclient
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'crm',
- 'USER': 'testa',
- 'PASSWORD': '123456',
- 'HOST': '127.0.0.1',
- 'PORT': '3306',
- }
- }
三、Django中创建数据库,创建超级用户:
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
四、登录admin
启动项目,登录


可以看到,Authentication and Authorization项目下有两个表,组和用户,这是Django的用户及组管理,即权限管理。看Users表结构:
- create table auth_user
- (
- id int auto_increment
- primary key,
- password varchar(128) not null,
- last_login datetime(6) null,
- is_superuser tinyint(1) not null,
- username varchar(150) not null,
- first_name varchar(150) not null,
- last_name varchar(150) not null,
- email varchar(254) not null,
- is_staff tinyint(1) not null,
- is_active tinyint(1) not null,
- date_joined datetime(6) not null,
- constraint username
- unique (username)
- );
username就是登录的用户名,is_staff是否是正式职员,只有正式职员才能登录,is_active,只有激活才能使用。
自己的项目的登录验证需要借助这个用户模块。
五、创建models类,创建数据库表,在admin中注册以进行管理
1、创建model类
- from django.db import models
-
- # Create your models here.
- class Tag(models.Model):
- '''客户标签表'''
- name = models.CharField(unique=True,max_length=32)
-
- def __str__(self): # 在打印类对象时,返回这个函数的值,否则默认是对象类型加地址
- return self.name
- class Meta:
- verbose_name_plural = "客户标签表" # 用于在admin管理时显示的类名称,没有这个配置,默认就是类名
2、生成表:
python manage.py makemigrations
python manage.py migrate
3、注册到admin中:
打开对应应用目录下的admin.py文件,在这里进行注入
- from django.contrib import admin
- from plcrm import models
- # Register your models here.
- admin.site.register(models.Tag) # 这样就在admin中注册了
4、刷新admin:

可以看到,注册的model出现在对应应用的下面,名称默认就是类名称。
启用
class Meta:
verbose_name_plural = "客户标签表"
刷新页面显示:

5、全部model类:
- from django.db import models
- from django.contrib.auth.models import User
- # Create your models here.
- class Tag(models.Model):
- '''客户标签表'''
- name = models.CharField(unique=True,max_length=32)
-
- def __str__(self):
- # 在打印类对象时,返回这个函数的值,否则默认是对象类型加地址
- # 在admin中,点击对应的表,展开显示的表只显示一列,内容就是这个方法的返回值
- return self.name
- class Meta:
- verbose_name_plural = "客户标签表" # 用于在admin管理时显示的类名称,没有这个配置,默认就是类名
-
- class UserProfile(models.Model):
- '''账号表'''
- user = models.OneToOneField(User,on_delete=models.CASCADE)
- # 这里就是借助admin系统的用户系统,一对一引用User表中的用户。
- name = models.CharField(max_length=32)
- roles = models.ManyToManyField("Role",blank=True,null=True)
- # 用户与角色的对应关系,多对多,用户可以没有角色
- # blank用于表单的认证,被设为blank=False(默认为False)的字段在填写表单时不能为空。
- # null用于规定数据库中的列的非空性,被设为null=False(默认为False)的字段在数据库中对应的列不能为空(用SQL来说明就是为该列添加了NOT NULL的约束)。
- # 当存在两个参数时,总共会有四种设定组合:
- # blank = True、null = True。统一的表明了该字段(列)是可以为空的。
- # blank = False、null = False。统一的表面了该字段(列)不可以为空。
- # blank = True、null = False。这个设定的意义在于,某些字段并不希望用户在表单中创建(如slug),而是通过在save方法中根据其他字段生成。
- # blank = False、null = True。这个设定不允许表单中该字段为空,但是允许在更新时或者通过shell等非表单方式插入数据该字段为空。
- # 在只设定了blank=True而没有设定null=True的时候,通过表单创建模型实例并且表单在该字段上没有值时数据库不报错呢?
- # 原因在于,django在处理某些在数据库中实际的存储值为字符串的Field时(如CharField, TextField, ImageField(图片文件的路径)),永远不会向数据库中填入空值。如果表单中某个CharField或者TextField字段为空,那么django会在数据库中填入"",而不是null.
-
- def __str__(self):
- return self.name
- class Meta:
- verbose_name_plural ="账号表"
-
- class Role(models.Model):
- '''角色表'''
- name = models.CharField(max_length=32,unique=True)
- menus = models.ManyToManyField("Menu",blank=False,null=False)
- # 角色与菜单项的对应关系,多对多,菜单项是一个个url的别名,即定义资源,定义角色可以访问的资源
- # menus不能为null,即null = False,每个角色都有对应的资源
- # 当设置了null=时,不论False或True,在进行makemigrations时都提示plcrm.Role.menus: (fields.W340) null has no effect on ManyToManyField.
-
- def __str__(self):
- return self.name
- class Meta:
- verbose_name_plural = "角色"
-
- class Menu(models.Model):
- '''菜单表,即URL别名表'''
- name = models.CharField(max_length=32,null=False)
- url_name = models.CharField(max_length=64)
-
- def __str__(self):
- return self.name
- class Meta:
- verbose_name_plural = "菜单项表"
-
- class Course(models.Model):
- '''课程表'''
- name = models.CharField(max_length=64,unique=True)
- price = models.PositiveSmallIntegerField()
- period = models.PositiveSmallIntegerField(verbose_name="周期(月)")
- # 字段中的verbose_name,是在admin中管理这个表时显示的字段名称,如果没有配置,显示字段名
- outline = models.TextField()
-
- def __str__(self):
- return self.name
-
- class Meta:
- verbose_name = "课程表"
- verbose_name_plural = "课程表"
- # 用于在admin管理时显示的类名称,verbose_name是在显示时再加一个s,如这里应该显示课程表s
- # verbose_name_plural则是最终的显示结果,显示的就是课程表
-
- class Branch(models.Model):
- '''校区表'''
- name = models.CharField(max_length=128,unique=True)
- addr = models.CharField(max_length=128)
- def __str__(self):
- return self.name
-
- class Meta:
- verbose_name_plural = "校区表"
-
- class ClassList(models.Model):
- '''班级表'''
- branch = models.ForeignKey("Branch",verbose_name="校区",on_delete=models.SET_NULL,blank=False,null=True)
- course = models.ForeignKey("Course",on_delete=models.CASCADE)
- class_type_choices = ((0,'面授(脱产)'),
- (1,'面授(周末)'),
- (2,'网络班')
- )
- class_type = models.SmallIntegerField(choices=class_type_choices,verbose_name="班级类型")
- semester = models.PositiveSmallIntegerField(verbose_name="学期")
- teachers = models.ManyToManyField("UserProfile")
- start_date = models.DateField(verbose_name="开班日期")
- end_date = models.DateField(verbose_name="结业日期",blank=True,null=True)
-
- def __str__(self):
- return "%s %s %s" %(self.branch,self.course,self.semester)
-
- class Meta:
- unique_together = ('branch','course','semester')
- verbose_name_plural = "班级"
- # 对于OneToOneField、ForeignKey字段,必须设置on_delete=,否则在makemigrations时报错
- # on_delete的值:
- # CASCADE:级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。
- # PROTECT:受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。如果强行删除,Django就会报错。
- # SET_NULL:设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。
- # SET_DEFAULT:设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。
- # SET():如果外键的那条数据被删除了。那么将会获取SET函数中的值来作为这个外键的值。SET函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。可以不用指定默认值
- # DO_NOTHING:不采取任何行为。一切全看数据库级别的约束。
-
-
- class Customer(models.Model):
- '''客户信息表'''
- name = models.CharField(max_length=32,blank=True,null=True)
- qq = models.CharField(max_length=64,unique=True)
- qq_name = models.CharField(max_length=64,blank=True,null=True)
- phone = models.CharField(max_length=64,blank=True,null=True)
- source_choices = ((0,'转介绍'),
- (1,'QQ群'),
- (2,'官网'),
- (3,'百度推广'),
- (4,'51CTO'),
- (5,'知乎'),
- (6,'市场推广'),
- )
- source = models.SmallIntegerField(choices=source_choices)
- referral_from = models.CharField(verbose_name="转介绍人qq",max_length=64,blank=True,null=True)
- consult_course = models.ForeignKey("Course",verbose_name="咨询课程",on_delete=models.SET_NULL,null=True,blank=True)
- content = models.TextField(verbose_name="咨询详情")
- tags = models.ManyToManyField("Tag",blank=True,null=True)
- status_choices = ((0,'已报名'),
- (1,'未报名'),
- )
- status = models.SmallIntegerField(choices=status_choices,default=1)
- consultant = models.ForeignKey("UserProfile",on_delete=models.SET_NULL,null=True) # 咨询人,咨询的谁,谁录入,使用的是咨询人的账号
- memo = models.TextField(blank=True,null=True)
- date = models.DateTimeField(auto_now_add=True)
-
- def __str__(self):
- return self.qq
-
- class Meta:
- verbose_name_plural ="客户表"
-
- class CustomerFollowUp(models.Model):
- '''客户跟进表'''
- customer = models.ForeignKey("Customer",on_delete=models.CASCADE)
- content = models.TextField(verbose_name="跟进内容")
- consultant = models.ForeignKey("UserProfile",on_delete=models.SET_NULL,null=True)
- intention_choices = ((0,'2周内报名'),
- (1,'1个月内报名'),
- (2,'近期无报名计划'),
- (3,'已在其它机构报名'),
- (4,'已报名'),
- (5,'已拉黑'),
- )
- intention = models.SmallIntegerField(choices=intention_choices)
- date = models.DateTimeField(auto_now_add=True)
-
- def __str__(self):
- return "<%s : %s>" %(self.customer.qq,self.intention)
-
- class Meta:
- verbose_name_plural = "客户跟进记录"
-
- class CourseRecord(models.Model):
- '''上课记录'''
- from_class = models.ForeignKey("ClassList",verbose_name="班级",on_delete=models.CASCADE)
- day_num = models.PositiveSmallIntegerField(verbose_name="第几节(天)")
- teacher = models.ForeignKey("UserProfile",on_delete=models.SET_NULL,blank=False,null=True)
- # 教师可以删除,删除完毕这里设置为空,实际情况应该设置为SET,然后删除教师同时指定另一位教师
- has_homework = models.BooleanField(default=True)
- homework_title = models.CharField(max_length=128,blank=True,null=True)
- homework_content = models.TextField(blank=True,null=True)
- outline = models.TextField(verbose_name="本节课程大纲")
- date = models.DateField(auto_now_add=True)
-
- def __str__(self):
- return "%s %s" %(self.from_class,self.day_num)
-
- class Meta:
- unique_together = ("from_class", "day_num")
- verbose_name_plural = "上课记录"
-
- class StudyRecord(models.Model):
- '''学习记录'''
- student = models.ForeignKey("Enrollment",on_delete=models.CASCADE)
- course_record = models.ForeignKey("CourseRecord",on_delete=models.CASCADE)
- attendance_choices = ((0,'已签到'),
- (1,'迟到'),
- (2,'缺勤'),
- (3,'早退'),
- )
- attendance = models.SmallIntegerField(choices=attendance_choices,default=0)
- score_choices = ((100,"A+"),
- (90,"A"),
- (85,"B+"),
- (80,"B"),
- (75,"B-"),
- (70,"C+"),
- (60,"C"),
- (40,"C-"),
- (-50,"D"),
- (-100,"COPY"),
- (0,"N/A"),
- )
- score = models.SmallIntegerField(choices=score_choices,default=0)
- memo = models.TextField(blank=True,null=True)
- date = models.DateField(auto_now_add=True)
-
- def __str__(self):
- return "%s %s %s" %(self.student,self.course_record,self.score)
-
- class Meta:
- unique_together = ('student','course_record')
- verbose_name_plural = "学习记录"
-
- class Enrollment(models.Model):
- '''报名表'''
- customer = models.ForeignKey("Customer",on_delete=models.SET_NULL,null=True)
- name = models.CharField(max_length=32,blank=False,null=False)
- phone = models.CharField(max_length=64)
- enrolled_class = models.ForeignKey("ClassList",verbose_name="所报班级",on_delete=models.SET_NULL,null=True)
- consultant = models.ForeignKey("UserProfile",verbose_name="课程顾问",on_delete=models.SET_NULL,null=True)
- contract_agreed = models.BooleanField(default=False,verbose_name="学员已同意合同条款")
- contract_approved = models.BooleanField(default=False,verbose_name="合同已审核")
- date = models.DateTimeField(auto_now_add=True)
-
- def __str__(self):
- return "%s %s" %(self.customer,self.enrolled_class)
-
- class Meta:
- unique_together = ("customer","enrolled_class")
- verbose_name_plural = "报名表"
-
- class Payment(models.Model):
- '''缴费记录'''
- customer = models.ForeignKey("Enrollment",on_delete=models.CASCADE)
- course = models.ForeignKey("Course",verbose_name="所报课程",on_delete=models.SET_NULL,null=True)
- amount = models.PositiveIntegerField(verbose_name="数额",default=500)
- consultant = models.ForeignKey("UserProfile",on_delete=models.SET_NULL,null=True)
- date = models.DateTimeField(auto_now_add=True)
-
- def __str__(self):
- return "%s %s" %(self.customer,self.amount)
-
- class Meta:
- verbose_name_plural = "缴费记录"
注册类:
- from django.contrib import admin
- from plcrm import models
- # Register your models here.
-
- admin.site.register(models.Tag) # 这样就在admin中注册了
- admin.site.register(models.Role)
- admin.site.register(models.UserProfile)
- admin.site.register(models.Menu)
- admin.site.register(models.Course)
- admin.site.register(models.StudyRecord)
- admin.site.register(models.Payment)
- admin.site.register(models.Branch)
- admin.site.register(models.CourseRecord)
- admin.site.register(models.ClassList)
- admin.site.register(models.Enrollment)
- admin.site.register(models.CustomerFollowUp)
- admin.site.register(models.Customer)
此时:

字段的verbose_name:

此时点击客户表,显示:

只显示一列,显示的值是Customer的__str__()的返回值,即这里显示QQ号。
如果想要显示多列,即显示表中的多列,需要在admin.py中进行配置:
- class CustomerAdmin(admin.ModelAdmin):
- list_display = ('id','qq','source','consultant','consult_course','content','status','date')
- # 在客户表显示时显示的列
- list_filter = ('source','date','consultant','tags')
- # # 过滤器中使用的过滤字段
- search_fields = ('qq','name')
- # # 查询框使用的查询字段
- raw_id_fields = ('consult_course',)
- # # raw_id_fields属性的作用 显示外键的详细信息
- # filter_horizontal = ('tags',)
- filter_vertical = ('tags',)
- list_editable = ('status',)
admin.site.register(models.Customer,CustomerAdmin) # 对某个注册model类进行个性化定制






list_editable = ('status',)配置后,在记录列表时就可以对相应字段修改 
还有一个重点是字段的choices=选项。
makemigrations在使用时,可以加上应用的名称:python manage.py makemigrations crm