因为stark用到了templates里面的html页面文件,所以要整合在一个app里,在stark里面创建名字为templates的Python Package,将之前的html页面拷贝在stark组件里面的templates里面,然后拷贝stark组件

使用自己的stark组件实现crm系统_第1张图片


二、实现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
    


录入成绩

    
        
                             {% csrf_token %}                                                                                    编号                         姓名                         考勤                         成绩                         批语                                                                                    {% for student_study_record in  student_study_record_list %}                                                      {{ forloop.counter }}                             {{ student_study_record.student }}                                                              {{ student_study_record.get_record_display }}                                                                                                {#下拉框显示当前学生成绩#}                                     {% for foo in score_choices %}                                         {% if student_study_record.score == foo.0 %}                                          {{ foo.1 }}          {#已经有成绩的显示器成绩,没有的显示默认#}                                         {% else %}                                          {{ foo.1 }}                                         {% endif %}                                     {% endfor %}                                                                                           {{ student_study_record.homework_note|default_if_none:"" }}                                                                           {% endfor %}                                                                                  {#保存按钮#}                 {% if update %}                 更新成功                 {% endif %}                      
    


score.html查看个人成绩页面:




    
    Title
    
    
    


查看{{ student }}成绩

    
                                                                            编号                     班级                     班主任                     成绩柱状图                                                                    {% for cls in  class_list %}                                              {{ forloop.counter }}                         {{ cls.course }}({{ cls.semester }})                         {{ cls.tutor }}                         成绩柱状图                                      {% endfor %}