因为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("<a href='%s/change'>编辑</a>" % obj.pk) def delete(self, obj=None, is_header=False): if is_header: return "操作" return mark_safe("<a href='%s/delete'>删除</a>" % obj.pk) def checkbox(self, obj=None, is_header=False): if is_header: return "选择" return mark_safe("<input type='checkbox' name='selected_pk' value=%s>" % 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("<a href='score/%s'>个人成绩</a>"%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("<a href='/stark/app01/studentstudyrecord/?class_study_record=%s'>详细信息</a>"%obj.pk) #url跳转到当前班级的详细信息 def record_score(self, obj=None, is_header=False): if is_header: return "录入成绩" return mark_safe("<a href='record_score/%s'>录入成绩</a>"%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录入成绩页面:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.1.1/css/bootstrap.css"> </head> <body> <h4>录入成绩</h4> <div> <div> <div> <form action="" method="post"> {% csrf_token %} <table class="table table-bordered table-striped table-hover"> <thead> <tr> <th>编号</th> <th>姓名</th> <th>考勤</th> <th>成绩</th> <th>批语</th> </tr> </thead> <tbody> {% for student_study_record in student_study_record_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ student_study_record.student }}</td> <td> {{ student_study_record.get_record_display }} </td> <td> <select name="score_{{ student_study_record.pk }}" id=""> {#下拉框显示当前学生成绩#} {% for foo in score_choices %} {% if student_study_record.score == foo.0 %} <option selected value="{{ foo.0 }}">{{ foo.1 }}</option> {#已经有成绩的显示器成绩,没有的显示默认#} {% else %} <option value="{{ foo.0 }}">{{ foo.1 }}</option> {% endif %} {% endfor %} </select> </td> <td><textarea name="homework_note_{{ student_study_record.pk }}" id="" cols="40" rows="5">{{ student_study_record.homework_note|default_if_none:"" }}</textarea> </td> </tr> {% endfor %} </tbody> </table> <input type="submit" class="btn btn-warning pull-right" value="保存"> {#保存按钮#} {% if update %} <span style="color:green">更新成功</span> {% endif %} </form> </div> </div> </div> </body> <script> setTimeout(function(){document.getElementById("show_text").style.display="none";},2000); {# 更新成功显示2秒后消失 #} </script> </html>
score.html查看个人成绩页面:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.1.1/css/bootstrap.css"> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script src="/static/Highcharts-6.1.0/code/highcharts.js"></script> </head> <body> <h3>查看{{ student }}成绩</h3> <div> <div> <div class="col-md-8 col-md-offset-1"> <table class="table table-bordered table-striped table-hover"> <thead> <tr> <th>编号</th> <th>班级</th> <th>班主任</th> <th>成绩柱状图</th> </tr> </thead> <tbody> {% for cls in class_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ cls.course }}({{ cls.semester }})</td> <td>{{ cls.tutor }}</td> <td><a cid="{{ cls.pk }}">成绩柱状图</a></td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> <div id="container" style="width: 600px"></div> <script> $(".chart_score").click(function () { var cid=$(this).attr("cid"); $.ajax({ url:"", type:"get", data:{ sid:"{{ student.pk }}", cid:cid }, success:function (data) { var chart = Highcharts.chart('container', { chart: { type: 'column' }, title: { text: '个人成绩分布图' }, subtitle: { text: '数据截止 2017-03,来源: <a href="https://en.wikipedia.org/wiki/List_of_cities_proper_by_population">Wikipedia</a>' }, xAxis: { type: 'category', labels: { rotation: -45 // 设置轴标签旋转角度 } }, yAxis: { min: 0, title: { text: '分数' } }, legend: { enabled: false }, tooltip: { pointFormat: '分数: <b>{point.y:.2f} 百万</b>' }, series: [{ name: '总人口', data: data, dataLabels: { enabled: true, rotation: -90, color: 'red', align: 'right', format: '{point.y:.1f}', // :.1f 为保留 1 位小数 y: 10 } }] }); } }) }) </script> </body> </html>
页面显示效果图如下:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。