因为stark用到了templates里面的html页面文件,所以要整合在一个app里,在stark里面创建名字为templates的Python Package,将之前的html页面拷贝在stark组件里面的templates里面,然后拷贝stark组件
二、实现crm逻辑
CRM即客户关系管理,是指企业用CRM技术来管理与客户之间的关系
1、创建数据库
在app01应用下的models.py文件:
from django.db import models
class Department(models.Model):
"""
部门表
市场部 1000
销售 1001
"""
title = models.CharField(verbose_name='部门名称', max_length=16)
code = models.IntegerField(verbose_name='部门编号', unique=True, null=False)
def __str__(self):
return self.title
class UserInfo(models.Model):
"""
员工表
"""
name = models.CharField(verbose_name='员工姓名', max_length=16)
email = models.EmailField(verbose_name='邮箱', max_length=64)
depart = models.ForeignKey(verbose_name='部门', to="Department", to_field="code")
def __str__(self):
return self.name
class Course(models.Model):
"""
课程表
如:
Linux基础
Linux架构师
Python自动化开发精英班
Python自动化开发架构师班
Python基础班
go基础班
"""
name = models.CharField(verbose_name='课程名称', max_length=32)
def __str__(self):
return self.name
class School(models.Model):
"""
校区表
如:
北京沙河校区
上海校区
"""
title = models.CharField(verbose_name='校区名称', max_length=32)
def __str__(self):
return self.title
class ClassList(models.Model):
"""
班级表
如:
Python全栈 面授班 5期 10000 2017-11-11 2018-5-11
"""
school = models.ForeignKey(verbose_name='校区', to='School')
course = models.ForeignKey(verbose_name='课程名称', to='Course')
semester = models.IntegerField(verbose_name="班级(期)")
price = models.IntegerField(verbose_name="学费")
start_date = models.DateField(verbose_name="开班日期")
graduate_date = models.DateField(verbose_name="结业日期", null=True, blank=True)
memo = models.CharField(verbose_name='说明', max_length=256, blank=True, null=True, )
# teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo',limit_choices_to={'depart_id__in':[1003,1004],})
teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo', related_name="abc",
limit_choices_to={"depart_id__in":[1002,1003]} )
tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo', related_name='classes',
limit_choices_to={"depart": 1005})
def __str__(self):
return "{0}({1}期)".format(self.course.name, self.semester)
class Customer(models.Model):
"""
客户表
"""
qq = models.CharField(verbose_name='qq', max_length=64, unique=True, help_text='QQ号必须唯一')
name = models.CharField(verbose_name='学生姓名', max_length=16)
gender_choices = ((1, '男'), (2, '女'))
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
education_choices = (
(1, '重点大学'),
(2, '普通本科'),
(3, '独立院校'),
(4, '民办本科'),
(5, '大专'),
(6, '民办专科'),
(7, '高中'),
(8, '其他')
)
education = models.IntegerField(verbose_name='学历', choices=education_choices, blank=True, null=True, )
graduation_school = models.CharField(verbose_name='毕业学校', max_length=64, blank=True, null=True)
major = models.CharField(verbose_name='所学专业', max_length=64, blank=True, null=True)
experience_choices = [
(1, '在校生'),
(2, '应届毕业'),
(3, '半年以内'),
(4, '半年至一年'),
(5, '一年至三年'),
(6, '三年至五年'),
(7, '五年以上'),
]
experience = models.IntegerField(verbose_name='工作经验', blank=True, null=True, choices=experience_choices)
work_status_choices = [
(1, '在职'),
(2, '无业')
]
work_status = models.IntegerField(verbose_name="职业状态", choices=work_status_choices, default=1, blank=True,
null=True)
company = models.CharField(verbose_name="目前就职公司", max_length=64, blank=True, null=True)
salary = models.CharField(verbose_name="当前薪资", max_length=64, blank=True, null=True)
source_choices = [
(1, "qq群"),
(2, "内部转介绍"),
(3, "官方网站"),
(4, "百度推广"),
(5, "360推广"),
(6, "搜狗推广"),
(7, "腾讯课堂"),
(8, "广点通"),
(9, "高校宣讲"),
(10, "渠道代理"),
(11, "51cto"),
(12, "智汇推"),
(13, "网盟"),
(14, "DSP"),
(15, "SEO"),
(16, "其它"),
]
source = models.SmallIntegerField('客户来源', choices=source_choices, default=1)
referral_from = models.ForeignKey(
'self',
blank=True,
null=True,
verbose_name="转介绍自学员",
help_text="若此客户是转介绍自内部学员,请在此处选择内部学员姓名",
related_name="internal_referral"
)
course = models.ManyToManyField(verbose_name="咨询课程", to="Course")
status_choices = [
(1, "已报名"),
(2, "未报名")
]
status = models.IntegerField(
verbose_name="状态",
choices=status_choices,
default=2,
help_text=u"选择客户此时的状态"
)
consultant = models.ForeignKey(verbose_name="课程顾问", to='UserInfo', related_name='consultanter',
limit_choices_to={'depart_id': 1001})
date = models.DateField(verbose_name="咨询日期", auto_now_add=True)
recv_date = models.DateField(verbose_name="当前课程顾问的接单日期", null=True)
last_consult_date = models.DateField(verbose_name="最后跟进日期", )
def __str__(self):
return self.name
class ConsultRecord(models.Model):
"""
客户跟进记录
"""
customer = models.ForeignKey(verbose_name="所咨询客户", to='Customer')
consultant = models.ForeignKey(verbose_name="跟踪人", to='UserInfo',limit_choices_to={"depart_id":1001})
date = models.DateField(verbose_name="跟进日期", auto_now_add=True)
note = models.TextField(verbose_name="跟进内容...")
def __str__(self):
return self.customer.name + ":" + self.consultant.name
class Student(models.Model):
"""
学生表(已报名)
"""
customer = models.OneToOneField(verbose_name='客户信息', to='Customer')
emergency_contract = models.CharField(max_length=32, blank=True, null=True, verbose_name='紧急联系人')
class_list = models.ManyToManyField(verbose_name="已报班级", to='ClassList', blank=True)
company = models.CharField(verbose_name='公司', max_length=128, blank=True, null=True)
location = models.CharField(max_length=64, verbose_name='所在区域', blank=True, null=True)
position = models.CharField(verbose_name='岗位', max_length=64, blank=True, null=True)
salary = models.IntegerField(verbose_name='薪资', blank=True, null=True)
welfare = models.CharField(verbose_name='福利', max_length=256, blank=True, null=True)
date = models.DateField(verbose_name='入职时间', help_text='格式yyyy-mm-dd', blank=True, null=True)
memo = models.CharField(verbose_name='备注', max_length=256, blank=True, null=True)
def __str__(self):
return str(self.customer)
class ClassStudyRecord(models.Model):
"""
上课记录表 (班级记录)
"""
class_obj = models.ForeignKey(verbose_name="班级", to="ClassList")
day_num = models.IntegerField(verbose_name="节次", help_text=u"此处填写第几节课或第几天课程...,必须为数字")
teacher = models.ForeignKey(verbose_name="讲师", to='UserInfo', limit_choices_to={"depart_id__in": [1002, 1003]}) #过滤id为1002和1003的部门
date = models.DateField(verbose_name="上课日期", auto_now_add=True)
course_title = models.CharField(verbose_name='本节课程标题', max_length=64, blank=True, null=True)
course_memo = models.TextField(verbose_name='本节课程内容概要', blank=True, null=True)
has_homework = models.BooleanField(default=True, verbose_name="本节有作业")
homework_title = models.CharField(verbose_name='本节作业标题', max_length=64, blank=True, null=True)
homework_memo = models.TextField(verbose_name='作业描述', max_length=500, blank=True, null=True)
exam = models.TextField(verbose_name='踩分点', max_length=300, blank=True, null=True)
def __str__(self):
return "{0} day{1}".format(self.class_obj, self.day_num)
class StudentStudyRecord(models.Model):
'''
学生记录
'''
class_study_record = models.ForeignKey(verbose_name="第几天课程", to="ClassStudyRecord")
student = models.ForeignKey(verbose_name="学员", to='Student')
record_choices = (('checked', "已签到"),
('vacate', "请假"),
('late', "迟到"),
('noshow', "缺勤"),
('leave_early', "早退"),
)
record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)
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'),
)
score = models.IntegerField("本节成绩", choices=score_choices, default=-1)
homework_note = models.CharField(verbose_name='作业评语', max_length=255, blank=True, null=True)
note = models.CharField(verbose_name="备注", max_length=255, blank=True, null=True)
homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None)
stu_memo = models.TextField(verbose_name='学员备注', blank=True, null=True)
date = models.DateTimeField(verbose_name='提交作业日期', auto_now_add=True)
def __str__(self):
return "{0}-{1}".format(self.class_study_record, self.student)
#数据库实例化,使用下面的命令
#python3 manage.py makemigrations
#python3 manage.py migrate
2、相关项目设置
settings.py文件设置内容:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
"stark.apps.StarkConfig"
]
STATIC_URL = '/static/'
STATICFILES_DIRS= [
os.path.join(BASE_DIR,"static")
]
注:
#static目录下面是Highcharts-6.1.0,是显示后面的柱形图所需的插件
#下载地址: https://www.hcharts.cn/download
3、逻辑部分及显示页面
urls.py文件:
from django.conf.urls import url
from django.contrib import admin
from stark.service.sites import site
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^stark/', site.urls),
]
stark组件service目录下sites.py文件:
from django.conf.urls import url
from django.shortcuts import HttpResponse, render, redirect
from django.utils.safestring import mark_safe
from django.urls import reverse
class Show_List(object):
def __init__(self, config, data_list):
self.config = config
self.data_list = data_list
def get_header(self):
# 处理表头
# header_list=["ID","名称","价格"]
header_list = []
for field in self.config.new_list_display():
if isinstance(field, str):
if field == "__str__":
val = self.config.model._meta.model_name.upper()
else:
field_obj = self.config.model._meta.get_field(field)
val = field_obj.verbose_name
else:
val = field(self.config, is_header=True)
header_list.append(val)
return header_list
def get_body(self):
# 处理表单数据
new_data_list = []
for obj in self.data_list:
temp = []
for field in self.config.new_list_display(): # ["nid","title","price","authors",edit] ['__str__'] ["title","price"]
if isinstance(field, str):
try:
from django.db.models.fields.related import ManyToManyField
field_obj = self.config.model._meta.get_field(field)
if isinstance(field_obj, ManyToManyField):
l = []
for i in getattr(obj, field).all():
l.append(str(i))
val = ",".join(l)
else:
val = getattr(obj, field)
print("val", val)
except Exception as e:
val = getattr(obj, field)
else:
val = field(self.config, obj)
temp.append(val)
new_data_list.append(temp)
return new_data_list
def get_new_actions(self):
action_list = []
for i in self.config.actions: # [patch_init,]
action_list.append({
"desc": i.desc,
"name": i.__name__,
})
return action_list
class ModelStark():
list_display = ["__str__", ]
search_fields = []
actions = []
def __init__(self, model, site):
self.model = model
self.site = site
def edit(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("编辑" % obj.pk)
def delete(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("删除" % obj.pk)
def checkbox(self, obj=None, is_header=False):
if is_header:
return "选择"
return mark_safe("" % obj.pk)
def get_list_url(self):
model_name = self.model._meta.model_name
app_label = self.model._meta.app_label
_url = reverse("%s_%s_list" % (app_label, model_name))
return _url
def new_list_display(self):
temp = []
temp.append(ModelStark.checkbox)
temp.extend(self.list_display)
temp.append(ModelStark.edit)
temp.append(ModelStark.delete)
return temp
def get_search_condition(self, request):
from django.db.models import Q
search_condition = Q()
val = request.GET.get("q")
if val:
search_condition.connector = "or"
for field in self.search_fields:
search_condition.children.append((field + "__contains", val))
return search_condition
def list_view(self, request):
if request.method == "POST":
action = request.POST.get("action")
selected_pk = request.POST.getlist("selected_pk")
action = getattr(self, action)
action(selected_pk)
# search
search_condition = self.get_search_condition(request)
# fliter
from django.db.models import Q
filter_condition = Q()
for key, value in request.GET.items():
filter_condition.children.append((key, value))
data_list = self.model.objects.all().filter(search_condition).filter(filter_condition)
print("list_display", self.list_display) # ["nid","title","price",edit]
sl = Show_List(self, data_list)
return render(request, "list_view.html", locals())
def get_mdoelForm(self):
from django.forms import ModelForm
class DemoModelForm(ModelForm):
class Meta:
model = self.model
fields = "__all__"
return DemoModelForm
def add(self, request):
if request.method == "POST":
form = self.get_mdoelForm()(request.POST)
if form.is_valid():
form.save()
return redirect(self.get_list_url())
else:
return render(request, "add.html", locals())
form = form = self.get_mdoelForm()()
return render(request, "add.html", locals())
def change(self, request, id):
obj = self.model.objects.filter(pk=id).first()
if request.method == "POST":
form = self.get_mdoelForm()(request.POST, instance=obj)
if form.is_valid():
form.save()
return redirect(self.get_list_url())
form = self.get_mdoelForm()(instance=obj)
return render(request, "change.html", locals())
def delete_view(self, request, id):
if request.method == "POST":
self.model.objects.get(pk=id).delete()
return redirect(self.get_list_url())
url = self.get_list_url()
return render(request, "delete.html", locals())
def extra_url(self):
return []
def get_urls2(self):
model_name = self.model._meta.model_name
app_label = self.model._meta.app_label
temp = [
url("^add/$", self.add, name="%s_%s_add" % (app_label, model_name)),
url("^$", self.list_view, name="%s_%s_list" % (app_label, model_name)),
url("^(\d+)/change/$", self.change, name="%s_%s_change" % (app_label, model_name)),
url("^(\d+)/delete/$", self.delete_view, name="%s_%s_delete" % (app_label, model_name)),
]
temp.extend(self.extra_url()) #额外添加一条url
return temp
@property
def urls2(self):
return self.get_urls2(), None, None
class StarkSite():
def __init__(self, ):
self._registry = {}
# 一级分发
def get_urls(self):
temp = []
for model, model_class_obj in self._registry.items(): # {Book:ModelAdmin(Book),Publish:ModelAdmn(Publish),....}
app_name = model._meta.app_label
model_name = model._meta.model_name
temp.append(url(r"%s/%s/" % (app_name, model_name), model_class_obj.urls2))
return temp
@property
def urls(self):
return self.get_urls(), None, None
def register(self, model, admin_class=None, **options):
if not admin_class:
admin_class = ModelStark
self._registry[model] = admin_class(model, self)
site = StarkSite()
app01应用下的stark.py文件:
from stark.service.sites import site,ModelStark
from django.utils.safestring import mark_safe
from app01 import models
from django.conf.urls import url
from django.shortcuts import HttpResponse,redirect,render
from django.http import JsonResponse
site.register(models.Department)
site.register(models.School)
site.register(models.UserInfo)
site.register(models.Course)
site.register(models.ClassList)
class Studentconfig(ModelStark): #学生表的配置类
def display_score(self,obj=None,is_header=False):
if is_header:
return "个人成绩"
return mark_safe("个人成绩"%obj.pk)
list_display = ["customer","class_list",display_score] #定义显示学生字段增加客户信息,已报班级,个人成绩三组字段
def score(self,request,sid):
if request.is_ajax():
cid=request.GET.get("cid")
sid=request.GET.get("sid")
ret=student_study_record_list=list(models.StudentStudyRecord.objects.filter(student_id=sid,class_study_record__class_obj_id=cid).values_list("class_study_record__day_num","score"))
print(ret)
ret=[["day"+str(i[0]),i[1]] for i in ret]
# print(ret)
return JsonResponse(ret,safe=False)
student=models.Student.objects.get(pk=sid)
class_list=student.class_list.all()
return render(request,"score.html",locals())
def extra_url(self):
temp=[]
temp.append(url("score/(\d+)",self.score))
return temp
site.register(models.Student,Studentconfig)
site.register(models.Customer)
site.register(models.ConsultRecord)
class ClassStudyRecordConfig(ModelStark):
def detail(self,obj=None,is_header=False):
if is_header:
return "详细信息"
return mark_safe("详细信息"%obj.pk) #url跳转到当前班级的详细信息
def record_score(self, obj=None, is_header=False):
if is_header:
return "录入成绩"
return mark_safe("录入成绩"%obj.pk) #url跳转到当前班级的录入成绩
list_display = ["class_obj","day_num",detail,record_score]
def patch_init(self,selected_pk): #定义批量初始化,对所有班级批量生成对应的学生记录对象
classstudyrecord_list=self.model.objects.filter(pk__in=selected_pk)
for classstudyrecord in classstudyrecord_list:
student_list=models.Student.objects.filter(class_list=classstudyrecord.class_obj)
for student in student_list:
models.StudentStudyRecord.objects.create(class_study_record=classstudyrecord,student=student)
patch_init.desc="批量初始化"
actions = [patch_init]
def record_score(self,request,id):
csr = models.ClassStudyRecord.objects.get(pk=id) #班级学习对象
student_study_record_list = models.StudentStudyRecord.objects.filter(class_study_record=csr)
score_choices = models.StudentStudyRecord.score_choices
update=False
if request.method=="POST": #提交数据
print("POST",request.POST)
for key,val in request.POST.items(): #页面取到的键值进行处理
if key=="csrfmiddlewaretoken":continue
field,pk=key.rsplit("_",1) #以下划线作为分割标准,取右边第一个
dic={field:val}
models.StudentStudyRecord.objects.filter(pk=pk).update(**dic) #更新的字典,使用**
update=True
return render(request,"record_score.html",locals())
def extra_url(self):
temp=[]
temp.append(url("record_score/(\d+)",self.record_score))
return temp
site.register(models.ClassStudyRecord,ClassStudyRecordConfig)
class StudentStudyRecordConfig(ModelStark):
def display_record(self,obj=None,is_header=False):
if is_header:
return "考勤"
return obj.get_record_display() #返回需要的考勤状态
def display_score(self,obj=None,is_header=False):
if is_header:
return "成绩"
return obj.get_score_display() #显示对应的后面的字母表示的成绩,而不是数字
list_display = ["student","class_study_record",display_record,display_score]
def patch_late(self,selected_pk): #批量修改考勤状态为迟到
self.model.objects.filter(pk__in=selected_pk).update(record="late")
patch_late.desc="迟到"
actions = [patch_late]
site.register(models.StudentStudyRecord,StudentStudyRecordConfig)
record_score.html录入成绩页面:
Title
录入成绩
score.html查看个人成绩页面:
Title
查看{{ student }}成绩
编号
班级
班主任
成绩柱状图
{% for cls in class_list %}
{{ forloop.counter }}
{{ cls.course }}({{ cls.semester }})
{{ cls.tutor }}
成绩柱状图
{% endfor %}
页面显示效果图如下: