銷售員/學員/講師系統

来源:http://www.cnblogs.com/0zcl/archive/2017/04/06/6664810.html
-Advertisement-
Play Games

前言: 今晚寫一篇關於學員/講師/銷售員CRM系統。這個小項目是27號開始做的,大概搞了一星期不到。我把一些知識點總結下,還寫下當時剋服的BUG。 Django練習小項目:學員管理系統設計開發 帶著項目需求學習是最有趣和效率最高的,今天就來基於下麵的需求來繼續學習Django 項目需求: 拿到需求後 ...


前言: 今晚寫一篇關於學員/講師/銷售員CRM系統。這個小項目是27號開始做的,大概搞了一星期不到。我把一些知識點總結下,還寫下當時剋服的BUG。

 

Django練習小項目:學員管理系統設計開發

帶著項目需求學習是最有趣和效率最高的,今天就來基於下麵的需求來繼續學習Django 

項目需求:

  1. 分講師\學員\課程顧問角色
  2. 學員可以屬於多個班級,學員成績按課程分別統計
  3. 每個班級至少包含一個或多個講師
  4. 一個學員要有狀態轉化的過程 ,比如未報名前,報名後,畢業老學員
  5. 客戶要有咨詢紀錄, 後續的定期跟蹤紀錄也要保存
  6. 每個學員的所有上課出勤情況\學習成績都要保存
  7. 學校可以有分校區,預設每個校區的員工只能查看和管理自己校區的學員
  8. 客戶咨詢要區分來源

拿到需求後,先要分析,再設計表結構: 超級重要!!

  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 Code

4. 在對應的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">&laquo;</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">&raquo;</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
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • DevExpress 是一個比較有名的界面控制項套件,提供了一系列優秀的界面控制項。這篇文章將展示如何在擁有源代碼的情況下,對 DevExpress 的程式集進行重新編譯。 系統必備 Windows 7 SP1 以上操作系統 Visual Studio 2010 SP1 .Net Framework 4 ...
  • 平時我們開發中,經常使用Task,後續的.net版本種很多都和Task有關,比如asyn,await有了Task 我們很少就去關註Thread 了。Task 給我們帶來了很多的便利之處。是我們更少的去關註執行的歷程,更多的去關註邏輯。但是有些時候,有些應用。又不得不考慮task 的運行狀況,比如這個 ...
  • JMS(Java Message Service,Java消息服務)是一組Java應用程式介面(Java API),它提供創建、發送、接收、讀取消息的服務。它給消息中間件生產商提供了一個統一API的標準。 ...
  • 1 class egg 2 { 3 public static void main(String[]args) 4 { 5 for(int n=10;n<100000;n++) 6 { 7 if(n%2==1){ 8 if(n%3==0){ 9 ... ...
  • Problem B: 時間和日期類(III) Description 設計一個日期時間類,用於讀取輸入的數據,按格式輸出日期和時間。 設計日期時間類DateTime由2個成員組成,分別是一個Date類對象和一個Time類對象; 設計DateTime類需支持以下操作: DateTime::DateTi ...
  • 1 #include 2 #include 3 #include 4 using namespace std; 5 int f(int x,int n) 6 { 7 int now=1; 8 while(n) 9 { 10 if(n&1) 11 { 12 now=now*x; 13 ... ...
  • 題目背景 大家都知道,斐波那契數列是滿足如下性質的一個數列: • f(1) = 1 • f(2) = 1 • f(n) = f(n-1) + f(n-2) (n ≥ 2 且 n 為整數)。 題目描述 請你求出第n個斐波那契數列的數mod(或%)2^31之後的值。並把它分解質因數。 輸入輸出格式 輸入 ...
  • Laravel號稱巨匠級PHP框架,越來越多的PHPer選擇它作為開發框架,作為一個Laravel初學者相信很多人向我一樣被安裝擋在了門外。所以今天結合文檔和自己的學習經歷總結一下Laravel的安裝方法,希望大家在學習Laravel的時候少走些彎路。Laravel安裝方法大的來分可以分為利用Com ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...