销售员/学员/讲师系统
前言: 今晚寫一篇關于學員/講師/銷售員CRM系統。這個小項目是27號開始做的,大概搞了一星期不到。我把一些知識點總結下,還寫下當時克服的BUG。
?
Django練習小項目:學員管理系統設計開發
帶著項目需求學習是最有趣和效率最高的,今天就來基于下面的需求來繼續學習Django?
項目需求:
拿到需求后,先要分析,再設計表結構: 超級重要!!
1 from django.db import models 2 3 from django.contrib.auth.models import User #django自帶的用戶認證表 4 # Create your models here. 5 course_type_choice = (("online", u"網絡班"), 6 ("offline_weekend", u"面授班(周末)"), 7 ("offline_fulltime", u"面授班(脫產)"), 8 ) # 課程類型 9 10 class School(models.Model): #學校表 11 name = models.CharField(max_length=128, unique=True) 12 city = models.CharField(max_length=64) 13 addr = models.CharField(max_length=128) 14 15 def __str__(self): #給前端界面顯示學校名 16 return self.name 17 18 19 class UserProfile(models.Model): #內部員工表 20 # User是一張表,在UserProfile關聯User表,類似繼承User表,也可以拓展別的字段 21 # 這里不能用ForeignKey(一對多),比如User表里有一個zcl, 22 # 用FK,則可以在UserProfile創建多個zcl用戶,實際上UserProfile應當只有一個用戶 23 # 用OneToOne關聯,只能有一個UserProfile用戶與User關聯,其它用戶不能關聯, 24 # 在數據庫層面OneToOne與ForeignKey實現是相同的,都是用FK, OneToOne是django admin層面做限制的 25 user = models.OneToOneField(User,verbose_name=u"登陸用戶名") 26 name = models.CharField(max_length=64, verbose_name=u"全名") 27 school = models.ForeignKey("School") #比如領導可以管理多個學校,但有些老師就只能對應一個學校 28 user_type_choice = (("salespeople", u"銷售員"), 29 ("teachers", u"講師"), 30 ("others", u"其它"), 31 ) 32 user_type = models.CharField(verbose_name=u"用戶類型",max_length=64, choices=user_type_choice, default="others") 33 34 def __str__(self): 35 return self.name 36 37 class Meta: 38 # 加上權限。can_del_customer是存在數據庫中的,"可以刪除用戶"是顯示在界面的 39 # permissions = (("can_del_customer",u"可以刪除用戶"),) 40 # 加入三條權限 41 permissions = (("view_customer_list",u"可以查看客戶列表"), # 對銷售員的權限 42 ("view_customer_info", u"可以查看客戶詳情"), 43 ("edit_own_customer_info", u"可以修改自己的客戶信息"), 44 45 ("view_class_list", u"可以查看班級列表"), # 對講師的權限 46 ("view_class_info", u"可以查看班級詳情"), 47 ("edit_own_class_info", u"可以修改自己的班級信息"), 48 49 ) 50 51 52 class CustomerTrackRecord(models.Model): #客戶跟蹤記錄表 53 customer = models.ForeignKey("Customer") #一個客戶可有多個跟蹤記錄 54 track_record = models.TextField(u"跟蹤記錄") 55 track_date = models.DateField(auto_now_add=True) #跟蹤日期 56 tracker = models.ForeignKey(UserProfile) #一條跟蹤記錄只能有一個追蹤人 57 status_choices = ((1, u"近期無報名計劃"), 58 (2, u"2個月內報名"), 59 (3, u"1個月內報名"), 60 (4, u"2周內報名"), 61 (5, u"1周內報名"), 62 (6, u"2天內報名"), 63 (7, u"已報名"), 64 ) 65 status = models.IntegerField(u"狀態",choices=status_choices,help_text=u"選擇客戶此時的狀態") 66 67 def __str__(self): 68 return self.customer.qq 69 70 71 class Course(models.Model): #課程表 72 name = models.CharField(max_length=64, unique=True) #課程名 73 online_price = models.IntegerField() #網絡班課程價格 74 offline_price = models.IntegerField() #面授班課程價格 75 introduction = models.TextField() #課程介紹 76 77 def __str__(self): 78 return self.name 79 80 81 class ClassList(models.Model): # 班級表 82 course = models.ForeignKey(Course, verbose_name=u"課程") # 關聯課程表 83 semester = models.IntegerField(verbose_name=u"學期") 84 teachers = models.ManyToManyField(UserProfile, verbose_name=u"講師") # 多對多關聯 85 start_date = models.DateField(verbose_name=u"開班日期") # 開班日期 86 graduate_date = models.DateField(blank=True,null=True) # 結業日期 87 # 課程類型 88 course_type = models.CharField(max_length=64, choices=course_type_choice,default="offline_weekend") 89 90 def __str__(self): 91 return "%s[%s期][%s]" % (self.course, self.semester, self.get_course_type_display()) 92 93 class Meta: 94 # 聯合唯一,python網絡班15期只能有一個 95 unique_together = ("course", "semester", "course_type") 96 97 98 class Customer(models.Model): # 學員表 99 qq = models.CharField(max_length=64, unique=True) 100 # 名字可為空,剛來咨詢時不會告訴name 101 name = models.CharField(max_length=64, blank=True, null=True) 102 phone = models.BigIntegerField(blank=True, null=True) # 不用IntegerField,不夠長 103 course = models.ForeignKey("Course") # 學員咨詢的課程,只記錄咨詢的一個課程,若有多個可備注說明 104 course_type = models.CharField(verbose_name=u"課程類型", max_length=64, choices=course_type_choice, default="offline_weekend") 105 consult_memo = models.TextField(verbose_name=u"咨詢備注") # 咨詢內容 106 source_type_choice = (("qq", u"qq群"), 107 ("referral", u"內部轉介紹"), 108 ("51CTO", u"51CTO"), 109 ("agent", u"招生代理"), 110 ("others", u"其它"), 111 ) #客戶來源 112 source_type = models.CharField(max_length=64, choices=source_type_choice, default="others") 113 # 表示自關聯(Customer表關聯Customer表),也可用referral_from = models.ForeignKey("Customer") 114 # 1.加上self 2.自關聯要加上related_name,通過internal_referral反查數據 115 # 反向關聯得加上related_name: eg:A介紹B來上課,對A通過referral_from可找到B;反之需通過referral 116 # 該字段表示該學生被誰介紹來上課的 117 referral_from = models.ForeignKey("self", blank=True, null=True, related_name="referral") 118 119 status_choices = (("singed", u"已報名"), 120 ("unregistered", u"未報名"), 121 ("graduated", u"已畢業"), 122 ("drop_off", u"退學"), 123 ) # 客戶來源 124 status = models.CharField(max_length=64, choices=status_choices, default="unregistered") 125 consultant = models.ForeignKey("UserProfile", verbose_name="課程顧問") 126 date = models.DateField(u"咨詢日期", auto_now_add=True) # auto_now_add創建時自動添加當前日期 127 class_list = models.ManyToManyField("ClassList", blank=True) # 對于多對多字段,不需要null=true 128 129 def __str__(self): 130 return "%s[%s]" % (self.qq, self.name) 131 132 133 class CourseRecord(models.Model): # 上課記錄表 134 class_obj = models.ForeignKey(ClassList) # 關聯班級 135 day_num = models.IntegerField(u"第幾節課") 136 course_date = models.DateField(auto_now_add=True, verbose_name=u"上課時間") 137 teacher = models.ForeignKey(UserProfile) # 講師 138 139 # students = models.ManyToManyField(Customer) 不能在這里多對多,if do this,can't 查看出勤情況 140 def __str__(self): 141 return "%s[day%s]" % (self.class_obj, self.day_num) 142 143 class Meta: # 聯合唯一 python自動化12期網絡班 12;只能有一個12天 144 unique_together = ("class_obj", "day_num") 145 146 147 class StudyRecord(models.Model): 148 # 關聯上課記錄表,上課記錄表有第幾節課字段,同時也與ClassList關聯,可知道是哪個班第幾期 149 course_record = models.ForeignKey(CourseRecord) 150 student = models.ForeignKey(Customer) # 關聯學員表 151 record_choices = (('checked', u"已簽到"), 152 ('late',u"遲到"), 153 ('no_show',u"缺勤"), 154 ('leave_early',u"早退"), 155 ) 156 record = models.CharField(u"狀態", choices=record_choices,default="no_show",max_length=64) 157 score_choices = ((100, 'A+'), 158 (90,'A'), 159 (85,'B+'), 160 (80,'B'), 161 (70,'B-'), 162 (60,'C+'), 163 (50,'C'), 164 (40,'C-'), 165 (0,'D'), 166 (-1,'N/A'), # 暫無成績 167 (-100,'COPY'), 168 (-1000,'FAIL'), 169 ) 170 score = models.IntegerField(u"本節成績",choices=score_choices,default=-1) 171 date = models.DateTimeField(auto_now_add=True) 172 note = models.CharField(u"備注",max_length=255,blank=True,null=True) 173 174 def __str__(self): 175 return "%s,%s,%s" % (self.course_record,self.student,self.get_record_display()) View Code?
先來張圖看看效果: 下圖是銷售員Alex登陸后看到的界面
點擊右上方Alex已招學員,出現下圖界面:
?
一、前端界面實現
界面看著我感覺是蠻漂亮的,登陸界面和信息界面都是搞bootstrap模版的。只要將bootstrap模版修改下,就變成所需要的界面啦。不會修改的可以看看如何使用bootstrap。
?
二、字數顯示限制
如果備注過多,會使界面不好看,要想使備注只顯示一定的字數,可用下列方法:?只顯示13個字節
<td>{{ customer.consult_memo|truncatechars:13}}</td>?
三、報名狀態加色
第一種方法,比較麻煩,有興趣可看django進階-modelform&admin action
第二種方法更簡單
1. 在bootstrap添加自定義的css樣式文件,custom.css
2. 在基礎模版(我定義的是base.html,其它html模塊是繼承它的)導入custom.css文件:
<link href="/static/bootstrap-3.3.7-dist/css/custom.css" rel="stylesheet">3. 你隨意在custom.css定義樣式
.singed{background-color:yellow; }.unregistered{background-color:#ff6664; }.graduated{background-color:#32ff0a; }.drop_off{background-color:bisque; } View Code4. 在對應的customer.html的標簽加入樣式; customer.status是后臺傳給前端的,是學生的報名狀態。
<td class="{{ customer.status }}">{{ customer.get_status_display }}</td>
四、分頁功能
其實Alex銷售員登陸后看到的界面只有兩條客戶的信息,這是我在后臺寫的。注意看左下角有個分頁,類似與百度搜索的分頁。其實分頁實現起來還是有點難度的。
先看django官方文檔。官方文檔寫得很詳細!!
>>> from django.core.paginator import Paginator >>> objects = ['john', 'paul', 'george', 'ringo'] >>> p = Paginator(objects, 2) >>> p.count 4 >>> p.num_pages 2 >>> type(p.page_range) # `<type 'rangeiterator'>` in Python 2. <class 'range_iterator'> >>> p.page_range range(1, 3) >>> page1 = p.page(1) >>> page1 <Page 1 of 2> >>> page1.object_list ['john', 'paul'] >>> page2 = p.page(2) >>> page2.object_list ['george', 'ringo'] >>> page2.has_next() False >>> page2.has_previous() True >>> page2.has_other_pages() True >>> page2.next_page_number() Traceback (most recent call last): ... EmptyPage: That page contains no results >>> page2.previous_page_number() 1 >>> page2.start_index() # The 1-based index of the first item on this page 3 >>> page2.end_index() # The 1-based index of the last item on this page 4 >>> p.page(0) Traceback (most recent call last): ... EmptyPage: That page number is less than 1 >>> p.page(3) Traceback (most recent call last): ... EmptyPage: That page contains no results View Code后臺實現:
1 def customers(request): 2 print(">>>>request:",request) 3 # 查找所有客戶,獲取所有信息的結果集,但并不是所有信息都已經取出來了(如果有上萬條數據,不能一次性取出來,先取一部分), 4 customer_list = models.Customer.objects.all() 5 print(">>>>customers:", customer_list) 6 paginator = Paginator(customer_list, 2) # 生成分頁實例: 每一頁有兩條數據 7 page = request.GET.get("page") # 獲取前端點擊的頁數,參數page可自定義 8 try: 9 customer_objs = paginator.page(page) # 生成第page頁的對象 10 except PageNotAnInteger: 11 # If page is not an integer, deliver first page 12 # 如果輸入的頁碼不是下標,則返回第一頁 13 customer_objs = paginator.page(1) 14 except EmptyPage: 15 # If page is out of range (e.g. 9999), deliver last page of results. 16 # 如果輸入的頁碼超出,則跳轉到最后的頁碼 17 customer_objs = paginator.page(paginator.num_pages) 18 19 return render(request, "crm/customer.html", {"customer_list": customer_objs})前端實現:
1 <div class="pagination"> 2 3 <nav> 4 <ul class="pagination"> 5 {% if customer_list.has_previous %} 6 <li class=""><a href="?page={{ customer_list.previous_page_number }}" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 7 {% endif %} 8 9 {% for page_num in customer_list.paginator.page_range %} 10 <!-- abs_page為函數名,后兩個為參數 --> 11 {% abs_page customer_list.number page_num %} 12 13 {% endfor %} 14 15 {% if customer_list.has_next %} 16 <li class=""><a href="?page={{ customer_list.next_page_number }}" aria-label="Next"><span aria-hidden="true">»</span></a></li> 17 {% endif %} 18 </ul> 19 </nav> 20 21 </div>第一次進入http://127.0.0.1:8000/crm/customer/頁面時,請求為get方式,后臺接收到的page參數為空,故會出PagenotAnInterger異常,故會返回到第一頁!!
注意前端的第九行代碼:?customer_list.paginator.page_range是頁數的范圍。customer_list只是一個第幾頁的實例而已,是無法獲取到頁數的范圍的。
但是問題來了,如果,你有100條數據,每頁只放兩條數據,意味著界面得有50個button,基本上頁面是放不下的。
如果頁面過多,看下百度怎么處理:
可用abs絕對值,若當前頁面為第6頁,想讓3、4、5和7、8、9也顯示出來,可在循環判斷頁面時,利用abs, 當|循環的頁面值-當前的頁面值|<=3 ,則顯示。
但問題又來了,前端的templates可沒有abs取絕對值這種后臺才有的方法,怎么辦??
?
自定義template tags
https://docs.djangoproject.com/es/1.9/howto/custom-template-tags/?
效果圖:
??
后臺是如何自定義模版??
首先自定義templates模版,我隨便建了個文件custom_tags.py,必須放在新建包templatetags下:
custom_tags.py: 當頁碼絕對值之差小于3時,則返回頁碼按鈕的html給前端,反之不返回。
1 from?django?import?template 2 from?django.utils.html?import?format_html 3 ? 4 register?=?template.Library()??#django的語法庫 5 6 ? 7 @register.simple_tag 8 def?abs_page(current_page, loop_page): 9 ????offset?=?abs(current_page?-?loop_page) 10 ????if?offset <?3: 11 ????????if?current_page?==?loop_page: 12 ????????????page_ele?=?"<li class='active'><a href='?page=%s'>%s</a></li>"?%?(current_page, current_page) 13 ????????else: 14 ????????????page_ele?=?"<li class=''><a href='?page=%s'>%s</a></li>"?%?(loop_page, loop_page) 15 ????????return?format_html(page_ele)??#將字符串轉化為html,返回給前端 16 ????else: 17 ????????return?""?
五、modelform進階
modelform之前有寫過,django進階-modelform&admin action, 但主要是寫django自帶的admin。
現在我有個需求,銷售員Alex想查看客戶的詳細信息。只需只擊客戶的ID號,便可查看,當然也可以修改。
前端:
<td><a href="/crm/customers/{{customer.id}}/">{{customer.id}}</a></td>urls:
# 當學員id當作參數,傳給customer_detail方法 url(r'^customers/(\d+)/$', views.customer_detail),后臺:
def customer_detail(request,customer_id):customer_obj = models.Customer.objects.get(id=customer_id)form = forms.CustomerModelForm()return render(request,"crm/customer_detail.html",{"customer_form":form})看前端界面顯示:?雖然能顯示出表單,但無法顯示出學員的信息,而且太丑了!!
如何顯示出學員的信息:
customer_obj = models.Customer.objects.get(id=customer_id)form = forms.CustomerModelForm(instance=customer_obj) # 將數據對象當作參數傳入如何使前端界面更漂亮:
forms.py表單文件:
1 from django.forms import Form,ModelForm 2 from CRM import models 3 4 5 # 客戶的form表單,可用于修改客戶的信息,增加客戶的前端界面 6 class CustomerModelForm(ModelForm): 7 8 class Meta: 9 model = models.Customer # 綁定Customer表 10 exclude = () 11 12 # 重構modelform的初始化類的方式;前面已經繼承modelform,下面進行重構 13 def __init__(self, *args, **kwargs): 14 super(CustomerModelForm, self).__init__(*args, **kwargs) 15 16 for field_name in self.base_fields: 17 field = self.base_fields[field_name] # 循環取出所有字段 18 field.widget.attrs.update({"class": "form-control"}) # 給字段加上樣式 View Code前端:?樣式是從bootstrap參考來的
1 {% block page_content %} 2 <!-- action為空表示數據提交到當前url --> 3 <form class="form-horizontal" method="post" action="">{% csrf_token %} 4 {% for field in customer_form %} 5 <div class="form-group"> 6 {% if field.field.required %} <!--若是必填字段 --> 7 <label class="col-sm-2 control-label"> 8 <span style="color: red;font-size: larger">*</span>{{ field.label }} 9 </label> 10 {% else %} <!-- label在django默認為加粗 --> 11 <label style="font-weight: normal" class="col-sm-2 control-label">{{ field.label }}</label> 12 {% endif %} 13 <div class="col-sm-8"> 14 {{ field }} 15 {% if field.errors %} <!--錯誤提示modelform已經幫我們封裝好了--> 16 <ul> 17 {% for error in field.errors %} 18 <li style="color: red">{{ error }}</li> 19 {% endfor %} 20 </ul> 21 {% endif %} 22 </div> 23 </div> 24 {% endfor %} 25 <div class="col-md-10"> 26 <button type="submit" class="btn btn-success pull-right">Save</button> 27 </div> 28 </form> 29 30 {% endblock %} View Code效果圖:
修改后保存信息
1 def customer_detail(request,customer_id): 2 #通過modelform顯示某用戶的詳細信息,修改后可保存 3 customer_obj=models.Customer.objects.get(id=customer_id) 4 if request.method=="POST": 5 #必須加instance=customer_obj告訴修改哪條數據,否則就是創建數據了 6 form=forms.CustomerModelForm(request.POST,instance=customer_obj) 7 if form.is_valid(): 8 form.save()#修改后保存 9 else: 10 form=forms.CustomerModelForm(instance=customer_obj) 11 return render(request,"crm/customer_detail.html",{"customer_form":form}) View Code?
六、必填與非必填字段
效果圖:?必填字段有加粗,且左上角有紅色*號
只需修改下前端代碼即可:
1 {% if field.field.required %} <!--若是必填字段 --> 2 <label class="col-sm-2 control-label"> 3 <span style="color: red;font-size: larger">*</span>{{ field.label }} 4 </label> 5 {% else %} <!-- label在django默認為加粗 --> 6 <label style="font-weight: normal" class="col-sm-2 control-label">{{ field.label }}</label> 7 {% endif %}?
權限分配, 這個改天再寫博客整理下:?三個角色的權限是不同的。對銷售員來講,無法修改非本人招收客戶的信息。
?
七、url別名
啥是url別名??
1 #當學員id當作參數,傳給customer_detail方法, 2 #給該url起別名,一調用別名customer_detail,就關聯上url 3 url(r'^customers/(\d+)/$',views.customer_detail,name="customer_detail"),現在銷售員想查看客戶的詳細信息,只需一點擊客戶的ID號便可查看。so, ID號必須是個a標簽,下面來看看前端實現:
1 <!-- 這里查看學員的詳細信息不應該寫列,否則當url一改變,得來這里改代碼 --> 2 <!-- <td><a href = " /crm/customers/ {{customer.id}} / "> {{ customer.id }} </a></td> --> 3 4 <td><a href = "{% url 'customer_detail' customer.id %}"> {{ customer.id }} </a></td>注意了,如果不用url別名的話,就用第2行代碼。但是,這樣項目的可維護性大大降低了。當你需改動url時,必須到前端修改對應的a標簽。如果用了url別名,就不用再來前端修改了。
看到沒,我用瀏覽器審查元素,瀏覽器已經自動將ID號的a標簽,轉化為一條url. 神奇!!
?
轉載于:https://www.cnblogs.com/0zcl/p/6664810.html
總結
以上是生活随笔為你收集整理的销售员/学员/讲师系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【bzoj1263】[SCOI2006]
- 下一篇: 使用showMessageDialog显