? ? ? ?
? ? CRM項目總結
?
?
???? 一:開發背景
?
在公司日益擴大的過程中,不可避免的會伴隨著更多問題出現。
對外 : 如何更好的管理客戶與公司的關系?如何更及時的了解客戶日益發展的需求變化?公司的產品是否真的符合客戶需求?以及公司新產品信息是否更有針對性的及時推送給客戶?
對內 : 公司發展壯大,部門越來越多,如何明確每個部門的權限?如何合理的調配公司人員?如何合理的分配客戶資源?如何精確的處理績效考核?以及更重要的在銷售管理中,如何更及時的了解是什么阻礙了公司的發展?是什么影響了銷售的業績?銷售的服務是否讓客戶滿意?
面對這么多的問題,這個時候來一套完整的CRM就顯得很有必要了。
?
二:開發周期
?
- 計劃在兩個月內完成項目的初步設計以及功能的初步實現。
- 基礎功能完成后,會預留兩個月時間來完成bug修復以及微調根據公司業務發展的業務需求。
- 項目上線后,開發人員持續跟進項目。根據公司業務發展和管理體系實時調整。
?
三:功能
?


1 from django.db import models 2 from rbac import models as rbac_model 3 # Create your models here. 4 5 6 class Department(models.Model): 7 """ 8 部門表 9 市場部 1000 10 銷售 1001 11 """ 12 title = models.CharField(verbose_name='部門名稱', max_length=16) 13 code = models.IntegerField(verbose_name='部門編號',unique=True,null=False) 14 15 def __str__(self): 16 return self.title 17 18 19 class UserInfo(models.Model): 20 """ 21 員工表 22 """ 23 auth = models.OneToOneField(verbose_name='用戶權限', to=rbac_model.User,null=True,blank=True) 24 name = models.CharField(verbose_name='員工姓名', max_length=16) 25 username = models.CharField(verbose_name='用戶名', max_length=32) 26 password = models.CharField(verbose_name='密碼', max_length=64) 27 email = models.EmailField(verbose_name='郵箱', max_length=64) 28 openid = models.CharField(verbose_name='微信唯一ID', max_length=64, null=True, blank=True) 29 depart = models.ForeignKey(verbose_name='部門', to="Department",to_field="code") 30 31 def __str__(self): 32 return self.name 33 34 35 class Course(models.Model): 36 """ 37 課程表 38 """ 39 name = models.CharField(verbose_name='課程名稱', max_length=32) 40 41 def __str__(self): 42 return self.name 43 44 45 class School(models.Model): 46 """ 47 校區表 48 49 """ 50 title = models.CharField(verbose_name='校區名稱', max_length=32) 51 52 def __str__(self): 53 return self.title 54 55 56 class ClassList(models.Model): 57 """ 58 班級表 59 如: 60 燒餅 打餅班 1期 10000 2017-11-11 2018-5-11 61 """ 62 school = models.ForeignKey(verbose_name='校區', to='School') 63 course = models.ForeignKey(verbose_name='課程名稱', to='Course') 64 65 semester = models.IntegerField(verbose_name="班級(期)") 66 price = models.IntegerField(verbose_name="學費") 67 start_date = models.DateField(verbose_name="開班日期") 68 graduate_date = models.DateField(verbose_name="結業日期", null=True, blank=True) 69 memo = models.CharField(verbose_name='說明', max_length=256, blank=True, null=True, ) 70 teachers = models.ManyToManyField(verbose_name='任課老師', to='UserInfo', related_name='teach_classes',limit_choices_to={'depart_id__in':[1003,1004]}) 71 tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo', related_name='classes',limit_choices_to={'depart_id':1002}) 72 73 def __str__(self): 74 return "{0}({1}期)".format(self.course.name, self.semester) 75 76 77 class Customer(models.Model): 78 """ 79 客戶表 80 """ 81 qq = models.CharField(verbose_name='qq', max_length=64, unique=True, help_text='QQ號必須唯一') 82 83 name = models.CharField(verbose_name='學生姓名', max_length=16) 84 gender_choices = ((1, '男'), (2, '女')) 85 gender = models.SmallIntegerField(verbose_name='性別', choices=gender_choices) 86 87 education_choices = ( 88 (1, '重點大學'), 89 (2, '普通本科'), 90 (3, '獨立院校'), 91 (4, '民辦本科'), 92 (5, '大專'), 93 (6, '民辦專科'), 94 (7, '高中'), 95 (8, '其他') 96 ) 97 education = models.IntegerField(verbose_name='學歷', choices=education_choices, blank=True, null=True, ) 98 graduation_school = models.CharField(verbose_name='畢業學校', max_length=64, blank=True, null=True) 99 major = models.CharField(verbose_name='所學專業', max_length=64, blank=True, null=True) 100 101 experience_choices = [ 102 (1, '在校生'), 103 (2, '應屆畢業'), 104 (3, '半年以內'), 105 (4, '半年至一年'), 106 (5, '一年至三年'), 107 (6, '三年至五年'), 108 (7, '五年以上'), 109 ] 110 experience = models.IntegerField(verbose_name='工作經驗', blank=True, null=True, choices=experience_choices) 111 work_status_choices = [ 112 (1, '在職'), 113 (2, '無業') 114 ] 115 work_status = models.IntegerField(verbose_name="職業狀態", choices=work_status_choices, default=1, blank=True, 116 null=True) 117 company = models.CharField(verbose_name="目前就職公司", max_length=64, blank=True, null=True) 118 salary = models.CharField(verbose_name="當前薪資", max_length=64, blank=True, null=True) 119 120 source_choices = [ 121 (1, "qq群"), 122 (2, "內部轉介紹"), 123 (3, "官方網站"), 124 (4, "百度推廣"), 125 (5, "360推廣"), 126 (6, "搜狗推廣"), 127 (7, "騰訊課堂"), 128 (8, "廣點通"), 129 (9, "高校宣講"), 130 (10, "渠道代理"), 131 (11, "51cto"), 132 (12, "智匯推"), 133 (13, "網盟"), 134 (14, "DSP"), 135 (15, "SEO"), 136 (16, "其它"), 137 ] 138 source = models.SmallIntegerField('客戶來源', choices=source_choices, default=1) 139 referral_from = models.ForeignKey( 140 'self', 141 blank=True, 142 null=True, 143 verbose_name="轉介紹自學員", 144 help_text="若此客戶是轉介紹自內部學員,請在此處選擇內部學員姓名", 145 related_name="internal_referral" 146 ) 147 course = models.ManyToManyField(verbose_name="咨詢課程", to="Course") 148 149 status_choices = [ 150 (1, "已報名"), 151 (2, "未報名") 152 ] 153 status = models.IntegerField( 154 verbose_name="狀態", 155 choices=status_choices, 156 default=2, 157 help_text=u"選擇客戶此時的狀態" 158 ) 159 consultant = models.ForeignKey(verbose_name="課程顧問", to='UserInfo', related_name='consultant',limit_choices_to={'depart_id':1005}) 160 date = models.DateField(verbose_name="咨詢日期", auto_now_add=True) 161 162 recv_date = models.DateField(verbose_name='接單時間',null=True,blank=True) 163 164 last_consult_date = models.DateField(verbose_name="最后跟進日期", auto_now_add=True) 165 166 def __str__(self): 167 return "姓名:{0},QQ:{1}".format(self.name, self.qq, ) 168 169 170 class CustomerDistribution(models.Model): 171 """客戶分配表""" 172 user = models.ForeignKey(verbose_name="當前客戶顧問",to='UserInfo',limit_choices_to={'depart_id':1005},related_name="cds",null=True,blank=True) 173 customer = models.ForeignKey(verbose_name="客戶",to="Customer",related_name="dealers",null=True,blank=True) 174 ctime = models.DateField(auto_now_add=True,null=True,blank=True) 175 status_choices = ( 176 (1,'正在跟進'), 177 (2,'已成單'), 178 (3,'三天未跟進'), 179 (4,'十五天未成單'), 180 ) 181 status = models.IntegerField(verbose_name="狀態",choices=status_choices,default=1) 182 memo = models.CharField(verbose_name="更多信息",max_length=255,null=True,blank=True) 183 184 185 class SaleRank(models.Model): 186 """銷售權重與分配""" 187 user = models.ForeignKey(to="UserInfo",verbose_name='課程顧問',limit_choices_to={'depart':1005}) 188 num = models.IntegerField(verbose_name='數量') 189 weight = models.IntegerField(verbose_name='權重') 190 def __str__(self): 191 return '權重:{0}?;num:{1}'.format(self.weight,self.num) 192 193 194 class ConsultRecord(models.Model): 195 """ 196 客戶跟進記錄 197 """ 198 customer = models.ForeignKey(verbose_name="所咨詢客戶", to='Customer') 199 consultant = models.ForeignKey(verbose_name="跟蹤人", to='UserInfo',limit_choices_to={'depart_id':1005}) 200 date = models.DateField(verbose_name="跟進日期", auto_now_add=True) 201 note = models.TextField(verbose_name="跟進內容...") 202 203 204 class PaymentRecord(models.Model): 205 """ 206 繳費記錄 207 """ 208 customer = models.ForeignKey(Customer, verbose_name="客戶") 209 210 class_list = models.ForeignKey(verbose_name="班級", to="ClassList", blank=True, null=True) 211 212 pay_type_choices = [ 213 (1, "訂金/報名費"), 214 (2, "學費"), 215 (3, "轉班"), 216 (4, "退學"), 217 (5, "退款"), 218 ] 219 pay_type = models.IntegerField(verbose_name="費用類型", choices=pay_type_choices, default=1) 220 paid_fee = models.IntegerField(verbose_name="費用數額", default=0) 221 turnover = models.IntegerField(verbose_name="成交金額", blank=True, null=True) 222 quote = models.IntegerField(verbose_name="報價金額", blank=True, null=True) 223 note = models.TextField(verbose_name="備注", blank=True, null=True) 224 date = models.DateTimeField(verbose_name="交款日期", auto_now_add=True) 225 consultant = models.ForeignKey(verbose_name="負責老師", to='UserInfo', help_text="誰簽的單就選誰") 226 227 228 class Student(models.Model): 229 """ 230 學生表(已報名) 231 """ 232 customer = models.OneToOneField(verbose_name='客戶信息', to='Customer') 233 234 username = models.CharField(verbose_name='用戶名', max_length=32) 235 password = models.CharField(verbose_name='密碼', max_length=64) 236 emergency_contract = models.CharField(max_length=32, blank=True, null=True, verbose_name='緊急聯系人') 237 class_list = models.ManyToManyField(verbose_name="已報班級", to='ClassList', blank=True) 238 239 company = models.CharField(verbose_name='公司', max_length=128, blank=True, null=True) 240 location = models.CharField(max_length=64, verbose_name='所在區域', blank=True, null=True) 241 position = models.CharField(verbose_name='崗位', max_length=64, blank=True, null=True) 242 salary = models.IntegerField(verbose_name='薪資', blank=True, null=True) 243 welfare = models.CharField(verbose_name='福利', max_length=256, blank=True, null=True) 244 date = models.DateField(verbose_name='入職時間', help_text='格式yyyy-mm-dd', blank=True, null=True) 245 memo = models.CharField(verbose_name='備注', max_length=256, blank=True, null=True) 246 247 def __str__(self): 248 return self.username 249 250 251 class CourseRecord(models.Model): 252 """ 253 上課記錄表 254 """ 255 class_obj = models.ForeignKey(verbose_name="班級", to="ClassList") 256 day_num = models.IntegerField(verbose_name="節次", help_text=u"此處填寫第幾節課或第幾天課程...,必須為數字") 257 teacher = models.ForeignKey(verbose_name="講師", to='UserInfo',limit_choices_to={'depart_id__in':[1003,1004]}) 258 date = models.DateField(verbose_name="上課日期", auto_now_add=True) 259 260 course_title = models.CharField(verbose_name='本節課程標題', max_length=64, blank=True, null=True) 261 course_memo = models.TextField(verbose_name='本節課程內容概要', blank=True, null=True) 262 has_homework = models.BooleanField(default=True, verbose_name="本節有作業") 263 homework_title = models.CharField(verbose_name='本節作業標題', max_length=64, blank=True, null=True) 264 homework_memo = models.TextField(verbose_name='作業描述', max_length=500, blank=True, null=True) 265 exam = models.TextField(verbose_name='踩分點', max_length=300, blank=True, null=True) 266 267 def __str__(self): 268 return "{0} day{1}".format(self.class_obj, self.day_num) 269 270 271 class StudyRecord(models.Model): 272 course_record = models.ForeignKey(verbose_name="第幾天課程", to="CourseRecord") 273 student = models.ForeignKey(verbose_name="學員", to='Student') 274 record_choices = (('checked', "已簽到"), 275 ('vacate', "請假"), 276 ('late', "遲到"), 277 ('noshow', "缺勤"), 278 ('leave_early', "早退"), 279 ) 280 record = models.CharField("上課紀錄", choices=record_choices, default="checked", max_length=64) 281 score_choices = ((100, 'A+'), 282 (90, 'A'), 283 (85, 'B+'), 284 (80, 'B'), 285 (70, 'B-'), 286 (60, 'C+'), 287 (50, 'C'), 288 (40, 'C-'), 289 (0, ' D'), 290 (-1, 'N/A'), 291 (-100, 'COPY'), 292 (-1000, 'FAIL'), 293 ) 294 score = models.IntegerField("本節成績", choices=score_choices, default=-1) 295 homework_note = models.CharField(verbose_name='作業評語', max_length=255, blank=True, null=True) 296 note = models.CharField(verbose_name="備注", max_length=255, blank=True, null=True) 297 298 homework = models.FileField(verbose_name='作業文件', blank=True, null=True, default=None) 299 stu_memo = models.TextField(verbose_name='學員備注', blank=True, null=True) 300 date = models.DateTimeField(verbose_name='提交日期', auto_now_add=True) 301 302 def __str__(self): 303 return "{0}-{1}".format(self.course_record, self.student)
?
?
- 銷售
a) ?綜合一段時間的銷售業績。根據每個銷售的權重比,自動分配客戶資源,最大化客戶轉化率。
? ? ? ? ? ? ?i. ?實現機制:
根據權重表中數據合理分配客戶資源,配合客戶表和客戶分配表。
分配時,按權重降序排序。按照分配人員數量,生成單次循環列表。每次分配列表結束,重新獲取分配列表。
在客戶資源分配時,使用到redis來配合完成。好處是:減少數據庫查詢消耗和I/O開銷,提高數據處理速度。并且使用事務綁定,建立回滾機制,確保合理分配。


1 import redis 2 from app01 import models 3 from oomph_2.settings import SALE_ID_LIST, SALE_ID_LIST_ORIGIN, SALE_ID_RESET 4 5 POOL = redis.ConnectionPool(host='192.168.20.150',port=6379) 6 # host是服務端的,這個port是redis固定的,只要是連接redis的這個port就固定是6379,密碼是服務端設置的。 7 CONN = redis.Redis(connection_pool=POOL) 8 9 10 class AutoSale(object): 11 12 @classmethod 13 def fetch_users(cls): 14 # [obj(銷售顧問id,num),obj(銷售顧問id,num),obj(銷售顧問id,num),obj(銷售顧問id,num),] 15 sales = models.SaleRank.objects.all().order_by('-weight') 16 print(sales) 17 sale_id_list = [] 18 count = 0 19 while True: 20 flag = False 21 for row in sales: 22 if count < row.num: 23 sale_id_list.append(row.user_id) 24 flag = True 25 count += 1 26 if not flag: 27 break 28 print('SALE_ID_LIST===',sale_id_list) 29 if sale_id_list: 30 CONN.rpush(SALE_ID_LIST,*sale_id_list) # 用來操作的數據 31 CONN.rpush(SALE_ID_LIST_ORIGIN,*sale_id_list) # 保留一份源數據 32 return True 33 return False 34 35 36 @classmethod 37 def get_sale_id(cls): 38 # 查看原來數據是否存在 39 40 sale_id_origin_count = CONN.llen(SALE_ID_LIST_ORIGIN) 41 if not sale_id_origin_count: 42 # 取數據庫中獲取數據,并且賦值list_origin,pop數據 43 status = cls.fetch_users() 44 if not status: 45 return None 46 user_id = CONN.lpop(SALE_ID_LIST) 47 if user_id: 48 return user_id 49 50 reset = CONN.get(SALE_ID_RESET) 51 if reset: 52 CONN.delete(SALE_ID_LIST_ORIGIN) 53 status = cls.fetch_users() 54 if not status: 55 return None 56 CONN.delete(SALE_ID_RESET) 57 return CONN.lpop(SALE_ID_LIST) 58 else: 59 ct = CONN.llen(SALE_ID_LIST_ORIGIN) 60 for i in range(ct): 61 v = CONN.lindex(SALE_ID_LIST_ORIGIN,i) 62 CONN.rpush(SALE_ID_LIST,v) 63 return CONN.lpop('sale_list_id') 64 # if cls.rollback_list: 65 # """ 66 # 可能存在已經取到了銷售,也分配了,可是在寫進數據庫的過程中出現了問題, 67 # 在customer.py里面,我們使用了事務,雖然事務回滾了,可是因為我們使用的是生成器,所以自己寫了rollback方法 68 # """ 69 # return cls.rollback_list.pop() 70 # 71 # if not cls.users: 72 # cls.fetch_users() 73 # # 如果沒有課程顧問,就返回None 74 # if not cls.users: 75 # return None 76 # 77 # if not cls.iter_users: 78 # cls.iter_users = iter(cls.users) 79 # try: 80 # user_id = next(cls.iter_users) 81 # except StopIteration as e: 82 # if cls.reset_status: 83 # cls.fetch_users() 84 # cls.reset_status = False 85 # cls.iter_users = iter(cls.users) 86 # user_id = cls.get_sale_id() 87 # return user_id 88 @classmethod 89 def reset(cls): 90 CONN.set(SALE_ID_RESET,1) 91 92 @classmethod 93 def rollback(cls,nid): 94 # cls.rollback_list.insert(0,nid) 95 CONN.lpush(SALE_ID_LIST,nid) # callback的id,往前面放
b)?????? 銷售與客戶關系處理。
該功能下,包括銷售人員得到任務后對應的客戶狀態改為開始接洽,記錄起始時間,訂單狀態從公司資源更改為銷售人員的個人資源,其他人在訂單轉移前不可接觸訂單信息。
銷售人員在跟進訂單時,每一次與客戶接洽都會在數據庫中生成一條記錄。
若訂單在十五日內被銷售人員轉化成功,則將該客戶的狀態由待轉化變為轉化成功,并在正式客戶表中生成該客戶的記錄。在銷售人員的訂單記錄中將這筆訂單的狀態改為轉化成功。
若當前與客戶接洽的銷售人員三天未跟進訂單或是在十五天內未促成交易。則相關訂單信息會被移動到公司公共資源中,并且原先跟進訂單的銷售人員不可以選擇繼續跟進(直至該訂單再次被移入公司公共資源)。原銷售人員的訂單跟進記錄中會顯示有一單未能轉化,并顯示原因(重新接手該訂單后即使轉化成功,本條記錄不會被覆蓋)。
在我的客戶的視圖中,是可以查看當前銷售所有的銷售記錄(已成單,未成單,以及未成單的訂單流失原因)。
實現機制:
包含公共資源,搶單功能,當前銷售個人客戶界面,單條和批量導入客戶信息。


import json,datetime from django.conf.urls import url from django.db.models import Q from django.http import StreamingHttpResponse from django.urls import reverse from django.utils.safestring import mark_safe from django.shortcuts import render,HttpResponse,redirect from django.forms import ModelForm from app01 import models from cccccccccccccc import AutoSale from stark.service import v1 from utils import message from django.db import transaction from app01.stark import BasePermissionclass SingleModelForm(ModelForm):class Meta:model = models.Customerexclude = ['consultant','status','recv_date','last_consult_date']class CustomerConfig(BasePermission,v1.StarkConfig):order_by = ['-status']def display_gender(self,obj=None,is_header=None):if is_header:return '性別'return obj.get_gender_display()def display_education(self,obj=None,is_header=None):if is_header:return '學歷'return obj.get_education_display()def display_source(self,obj=None,is_header=None):if is_header:return '客戶來源'return obj.get_source_display()def display_course(self,obj=None,is_header=None):if is_header:return '咨詢課程'html = []obj_list = obj.course.all()for obj1 in obj_list:temp = "<a style='display:inline-block;padding:3px 5px;border:2px solid red;margin:2px;' href='/stark/app01/customer/%s/%s/dc/'>%s <span class='glyphicon glyphicon-trash'></span></a>" %(obj.pk,obj1.pk,obj1.name)html.append(temp)return mark_safe(''.join(html))def display_status(self,obj=None,is_header=None):"""客戶狀態是可以點擊修改的:param obj::param is_header::return:"""if is_header:return '客戶狀態'# print('obj......',obj) 姓名:yy,QQ:123456return obj.get_status_display()def record(self,obj=None,is_header=None):"""客戶跟進記錄,http://127.0.0.1:8000/stark/app01/consultrecord/?customer=1"""if is_header:return '客戶跟進記錄'return mark_safe("<a href='/stark/app01/consultrecord/?customer=%s'>查看跟進記錄</a>"%(obj.pk))list_display = ['name','referral_from',display_gender,display_education,display_source,display_course,display_status,record]edit_link = ['name']def delete_course(self,request,customer_id,course_id):"""刪除當前用戶感興趣的課程:param request::param customer_id::param course_id::return:"""# print('self.model_class=,=',self.model_class.objects.all())# < QuerySet[ < Customer: 姓名:騷偉, QQ: 123456 >] >customer_obj = self.model_class.objects.filter(pk=customer_id).first()# 在多對多字段中可以remove, customer_obj.course.remove(course_id)# ####################作業: 刪除完成跳轉回來的時候,帶著走的時候的url# self.request.GET# self._query_param_key# 構造QueryDict# urlencode()return redirect(self.get_list_url())def extra_url(self):app_model_name = (self.model_class._meta.app_label,self.model_class._meta.model_name,)patterns = [url(r'^(\d+)/(\d+)/dc/$', self.wrap(self.delete_course), name="%s_%s_dc" %app_model_name),url(r'^public/$', self.wrap(self.public_view), name="%s_%s_public" %app_model_name),url(r'^user/$', self.wrap(self.user_view), name="%s_%s_user" %app_model_name),url(r'^(\d+)/competition/$', self.wrap(self.competition_view),name = "%s_%s_competition" % app_model_name),url(r'^single/$', self.wrap(self.single_view), name="%s_%s_single" % app_model_name),url(r'^multi/$', self.wrap(self.multi_view), name="%s_%s_multi" % app_model_name),# url(r'^loadfiles/$', self.wrap(self.get_loadfiles_view), name="%s_%s_loadfiles" % app_model_name), ]return patternsdef public_view(self,request):"""公共客戶資源, 未報名 & (15天未接單 or 三天未跟進)"""current_user_id = 9# 當前日期current_date = datetime.datetime.now().date()# 最后接單時間no_deal = current_date - datetime.timedelta(days=15)# 最后跟進日期no_follow_date = current_date - datetime.timedelta(days=3)"""公共客戶"""# 方法一:# customer_list = models.Customer.objects.filter(Q(recv_date__lt=no_deal)|Q(last_consult_date__lt=no_follow),status=2)# customer_list = models.Customer.objects.filter(Q(recv_date__lt=no_deal)|Q(last_consult_date__lt=no_follow_date),status=2)# print('9999999999',customer_list)# 方法二:con = Q()q1 = Q(status=2)q2 = Q()q2.connector = 'OR'q2.children.append(('recv_date__lt', no_deal))q2.children.append(('last_consult_date__lt', no_follow_date))con.add(q1,'AND')con.add(q2,'AND')customer_list = models.Customer.objects.filter(con)return render(request, 'public_view.html', {'customer_list':customer_list, "current_user_id":current_user_id})def competition_view(self,request,cid):"""搶單表:param request::param cid: customer_id 來自于公共資源里面的搶單選項,public_view:return:"""current_user_id = 9 # 這個是從session中拿的"""搶單之后,它會修改客戶表里面的: recv_date & last_consult_date & 課程顧問可以搶單的前提條件是: 之前的顧問不是自己,狀態必須是未報名,并且滿足3/15的要求"""current_date = datetime.datetime.now().date()no_deal = current_date - datetime.timedelta(days=15) # 最后接單日期no_follow_date = current_date - datetime.timedelta(days=3) # 最后跟進日期# 更新數據row_count = models.Customer.objects.filter(Q(recv_date__lt=no_deal) | Q(last_consult_date__lt=no_follow_date),status=2,id=cid).exclude(consultant_id=current_user_id).update(recv_date=current_date,last_consult_date=current_date,consultant_id=current_user_id)if not row_count:return HttpResponse('配嘛...')# 如果存在的話,我們就把它添加到客戶分配表CustomerDistribution里面models.CustomerDistribution.objects.create(ctime=current_date,customer_id=cid,user_id=current_user_id)return HttpResponse('嗯嗯,歸你歸你')def user_view(self,request):"""當前登錄用戶的所有的客戶(在我這成單的,以及我正在跟進的)"""current_user_id = 9customer_list = models.CustomerDistribution.objects.filter(user_id=current_user_id).order_by('status')return render(request, 'user_view.html', {'customer_list':customer_list})def single_view(self,request):"""單條錄入客戶信息"""if request.method == 'GET':form = SingleModelForm()return render(request, 'single_view.html', {'form':form})else:current_date = datetime.datetime.now().date()form = SingleModelForm(request.POST)if form.is_valid():print(form.cleaned_data)sale_id = AutoSale.get_sale_id()if not sale_id:return HttpResponse('暫無課程顧問,請添加后再分配')try:with transaction.atomic():# 客戶表保存form.instance.consultant_id = sale_idform.instance.recv_date = current_dateform.instance.last_consult_date = current_datenew_customer = form.save() # 將數據添加到客戶表。這就算創建完成了# 將關系添加到客戶分配表models.CustomerDistribution.objects.create(customer=new_customer,ctime=current_date,user_id=sale_id)# 發送郵件信息# message.send_message('17701335022@163.com','saofei','fk','fk you')except Exception as e:# 創建客戶和分配銷售異常 AutoSale.rollback(sale_id)return HttpResponse('錄入異常')return HttpResponse('錄入成功')else:return render(request, 'single_view.html', {'form':form})def multi_view(self,request):"""批量導入"""if request.method == 'GET':return render(request, 'multi_view.html')else:from django.core.files.uploadedfile import InMemoryUploadedFilefile_obj = request.FILES.get('exfile')# 老方法: 對應218# print(file_obj,type(file_obj))# with open('submit.xlsx','wb') as f :# for chunk in file_obj:# f.write(chunk)import xlrd# 不再創建xlsx文件workbook = xlrd.open_workbook(file_contents=file_obj.read())# 老方法: 先寫再讀# workbook = xlrd.open_workbook('submit.xlsx')sheet = workbook.sheet_by_index(0) # 這個當前sheet索引為0的 表單頁# print(sheet.nrows) # 當前共有多少行maps = {0 : 'name',1 : 'qq',}row_dict = {}for index in range(1,sheet.nrows): # 第一行是標題,從索引為1的開始拿數據row = sheet.row(index)# [text: 'zz', number: 1123.0]# [text: 'aa', number: 23.0]# 拿到的是列表,可是我們要轉化成字典# {text: 'zz', number: 1123.0}for i in range(len(maps)):key = maps[i]cell = row[i]row_dict[key] = cell.valueprint(row_dict)# 獲取客戶id,錄入客戶表,錄入分配表current_date = datetime.datetime.now().date()sale_id = AutoSale.get_sale_id()print('236',sale_id)if not sale_id:return HttpResponse('暫無課程顧問,請添加后再執行操作!')try:with transaction.atomic():# 客戶表保存new_customer = models.Customer.objects.create(**row_dict,consultant_id=sale_id,recv_date=current_date,last_consult_date=current_date)print(new_customer)# 將關系添加到客戶分配表models.CustomerDistribution.objects.create(customer=new_customer,ctime=current_date,user_id=sale_id)# 發送郵件信息# message.send_message('17701335022@163.com','saofei','fk','fk you')print(new_customer)except Exception as e:# 創建客戶和分配銷售異常 AutoSale.rollback(sale_id)return HttpResponse('錄入異常')return HttpResponse('錄入成功')else:return render(request, 'multi_view.html')# # 為用戶提供模版# def download_file(request):# # do something# # the_file_name='11.png' #顯示在彈出對話框中的默認的下載文件名# filename='media/uploads/11.png' #要下載的文件路徑# response=StreamingHttpResponse(readFile(filename))# response['Content-Type']='application/octet-stream'# response['Content-Disposition']='attachment;filename="{0}"'.format(the_file_name)# return response# def readFile(filename,chunk_size=512):# with open(filename,'rb') as f:# while True:# c=f.read(chunk_size)# if c:# yield c# else:# break v1.site.register(models.Customer,CustomerConfig)
?
2. 管理
涉及到兩張表,StudyRecord & CourseRecord。參照表結構,查看相關字段。
a) ?在成功的將客戶轉化為學員后,根據其所填選的具體信息如:校區,課程,以及初填信息。并且初始化學員賬號信息,方便學員可以登錄該系統查看自己的學分以及考勤記錄(此處用到了Highcharts,任課老師也可查看學生相關信息)。


1 {% load staticfiles %} 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>Title</title> 7 </head> 8 <body> 9 10 <h2>所在班級</h2> 11 <ul class="fa-list-ul" id="classList" sid="{{ obj.sid }}"> 12 {% for obj in class_list %} 13 <li cid="{{ obj.id }}">{{ obj.course.name }}({{ obj.semester }}期)</li> 14 {% endfor %} 15 </ul> 16 <h2>成績圖</h2> 17 <div id="container" style="width:600px;height:400px"></div> 18 19 <script src="{% static '/stark/bootstrap/js/jquery-3.1.1.js' %}"></script> 20 <script src="{% static 'highcharts.js' %}"></script> 21 <script> 22 $(function () { 23 $('#classList li').click(function () { 24 var cid = $(this).attr('cid'); 25 var sid = $('#classList').attr('sid'); 26 $.ajax({ 27 url :' /stark/app01/student/chart', 28 type : 'get', 29 data : {'cid':cid,'sid':sid}, 30 dataType : 'JSON', 31 success:function (arg) { 32 var config = { 33 chart: { 34 type: 'column' 35 }, 36 title: { 37 text: '學生成績' 38 }, 39 subtitle: { 40 text: '數據截止 2017-03,來源: <a href="https://en.wikipedia.org/wiki/List_of_cities_proper_by_population">Wikipedia</a>' 41 }, 42 xAxis: { 43 type: 'category', 44 labels: { 45 rotation: -45, 46 style: { 47 fontSize: '13px', 48 fontFamily: 'Verdana, sans-serif' 49 } 50 } 51 }, 52 yAxis: { 53 min: 0, 54 title: { 55 text: '分數' 56 } 57 }, 58 legend: { 59 enabled: false 60 }, 61 tooltip: { 62 pointFormat: '成績: <b>{point.y} </b>' 63 }, 64 series: [{ 65 name: '成績', 66 data: [], 67 dataLabels: { 68 enabled: true, 69 rotation: -90, 70 color: '#FFFFFF', 71 align: 'right', 72 format: '{point.y}', // one decimal 73 y: 10, // 10 pixels down from the top 74 style: { 75 fontSize: '13px', 76 fontFamily: 'Verdana, sans-serif' 77 } 78 } 79 }] 80 }; 81 82 config.series[0].data =arg.data; 83 84 //$('#container').highcharts(config); 85 Highcharts.chart('container',config); 86 87 } 88 }) 89 }) 90 }) 91 92 93 </script> 94 </body> 95 </html>
?
b) 出勤記錄:任課教師可初始化學員出勤信息,并支持批量修改。
學員哪天進入班級,就從哪天開始生成記錄。考勤與成績掛鉤。
實現機制:


1 def mutil_init(self,request): 2 """自定義批量初始化方法""" 3 # 上課記錄id列表 4 pk_list = request.POST.getlist('pk') 5 # 上課記錄對象列表 6 record_list = models.CourseRecord.objects.filter(id__in=pk_list) 7 # print(record_list) 8 # # 這種是,遍歷每一個學生,查看是否存在記錄。 9 # for record in record_list: 10 # student_list = models.Student.objects.filter(class_list=record.class_obj) 11 # bulk_list = [] 12 # for student in student_list: 13 # exists = models.StudyRecord.objects.filter(student=student,course_record=record).exists() 14 # if exists: 15 # continue 16 # bulk_list.append(models.StudyRecord(student=student,course_record=record)) 17 # models.StudyRecord.objects.bulk_create(bulk_list) 18 19 # 下面這種是,只要有當天的學習記錄,后面不管還有沒有學生來,都不能添加 20 for record in record_list: 21 if models.StudyRecord.objects.filter(course_record=record).exists(): 22 continue 23 student_list = models.Student.objects.filter(class_list=record.class_obj) 24 # 為每一個學生創建dayn的學習記錄 25 bulk_list = [] 26 for student in student_list: 27 bulk_list.append(models.StudyRecord(student=student,course_record=record)) 28 models.StudyRecord.objects.bulk_create(bulk_list) 29 # return redirect('/stark/app01/courserecord/') 30 return HttpResponse('初始化成功!') 31 32 show_actions = True 33 mutil_init.short_desc = "學生初始化" 34 actions = [mutil_init,] # 因為這個是批量操作,咱們需要寫點方法,里面是我們要實現的東西,所以函數
c) 錄入成績:
這里是通過點擊在課程記錄頁面,我們手動生成的查看成績的a標簽,進入到成績錄入界面的。通過當前點擊的課程記錄的id,拿到學習記錄對象,再進行編輯。
并且,因為要使前端拿到每個學習記錄id,我們就需要想辦法將值傳過去。
在這使用了type來動態創建類。
fields字段,寫了一個score_學習記錄id,還有一個home_note_學習記錄id。該方法下,其實是由默認值的,so 在傳值的時候回有initial。并且post請求時,是用的update


1 def score_list(self,request,record_id): 2 """ 3 錄入成績頁面 4 :param request: 5 :param record_id: 老師上課記錄ID 6 :return: 7 """ 8 if request.method == 'GET': 9 from django.forms import Form 10 from django.forms import fields 11 from django.forms import widgets 12 ##方式一 13 # study_record_list = models.StudyRecord.objects.filter(course_record_id=record_id) #這一天上課的所有的學生的學習記錄 14 # score_choices = models.StudyRecord.score_choices 15 # return render(request,"score_list.html",{"study_record_list":study_record_list,"score_choices":score_choices}) 16 17 # 改款 18 # class TestForm(Form): 19 # score = fields.ChoiceField(choices=models.StudyRecord.record_choices) 20 # homeword_note = fields.CharField(widget=widgets.Textarea()) 21 22 # 因為前端要拿到id和對象,所以使用type來創建,來自定義字段 23 study_record_list = models.StudyRecord.objects.filter(course_record_id=record_id) 24 data = [] 25 for obj in study_record_list: 26 TestForm = type('TempForm',(Form,),{ 27 'score_%s'%obj.pk : fields.ChoiceField(choices=models.StudyRecord.score_choices), 28 'homework_note_%s'%obj.pk : fields.CharField(widget=widgets.Textarea()) 29 }) 30 data.append({'obj':obj,'form':TestForm(initial={'score_%s' %obj.pk:obj.score,'homework_note_%s' %obj.pk:obj.homework_note})}) 31 return render(request,'score_list.html',{'data':data}) 32 else: 33 data_dict = {} 34 """ 35 構造這樣的字典,目的是保存更新數據庫里面的數據,字典的結構的 36 { 37 3:{"score":2,"homework_note":2} 38 4:{"score":4,"homework_note":4} 39 } 40 """ 41 for key, value in request.POST.items(): 42 if key == "csrfmiddlewaretoken": 43 continue 44 name, nid = key.rsplit('_', 1) 45 if nid in data_dict: 46 data_dict[nid][name] = value 47 else: 48 data_dict[nid] = {name: value} 49 50 for nid, update_dict in data_dict.items(): 51 print(data_dict.items()) 52 models.StudyRecord.objects.filter(id=nid).update(**update_dict) 53 54 return redirect(request.path_info)


1 {% load staticfiles %} 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>成績錄入</title> 7 <link rel="stylesheet" href="{% static 'stark/bootstrap/css/bootstrap.css' %}"> 8 </head> 9 <body> 10 <h2>自定義成績錄入</h2> 11 <form method="post"> 12 {% csrf_token %} 13 <table class="table"> 14 {% for row in data %} 15 <tr> 16 <td>{{ row.obj.course_record }}</td> 17 <td>{{ row.obj.student }}</td> 18 <td>{{ row.obj.get_record_display }}</td> 19 {% for field in row.form %} 20 <td>{{ field }}</td> 21 {% endfor %} 22 </tr> 23 {% endfor %} 24 25 </table> 26 <input type="submit" value="保存"> 27 </form> 28 29 </body> 30 </html>
?
3. 會議室預定
開發背景:公司在壯大,各部門大小事也變得更多。你們部門要不要開個會互相解決一群大齡男青年的問題嘛。要的話,你要不要有次序的來用會議室嘛?要的話,你是不是要預定一下嘛?要的話,會議室預定要不要做一個嘛。。。來,交給程序來完成。

具體實現: ? ?記得要綁定事務!!!
第一步: 獲取當天的預定信息。
第二步: 生成會議室信息。 在生成會議室信息里面,需要區分當前預定是否是自己預定的。自己訂的可以取消,但是不能取消非自己預定的會議室信息。
第三步提交: 點擊提交后,首先要判斷日期是否正確,只能預定大于當前時間之后的會議室信息。并且,要區分一下是新增還是修改。
第四部刪除: 這里需要用到Q查詢,組合多個條件來實現。
1 # 刪除會議室預定信息 2 from django.db.models import Q 3 remove_booking = Q() 4 for room_id, time_id_list in post_data['DEL'].items(): 5 for time_id in time_id_list: 6 temp = Q() 7 temp.connector = 'AND' 8 temp.children.append(('user_id',2,)) 9 temp.children.append(('date', choice_date)) 10 temp.children.append(('room_id', room_id,)) 11 temp.children.append(('time_id', time_id,)) 12 13 remove_booking.add(temp, 'OR') 14 if remove_booking: 15 models.Book.objects.filter(remove_booking).delete()
?
4: 調查問卷
參照:Django_調查問卷
?
5:最騷的東西:RBAC ? 會單獨總結。
已經在寫了。
鏈接:RBAC?? 編寫流程在ReadMe里面。
?