python2.0_day18_Django自帶的用戶認證模塊的使用


用戶驗證
我們基於一個項目來學習利用Django框架中的user表實現用戶認證
Django練習小項目:學員管理系統設計開發
項目需求:
1.分講師\學員\課程顧問角色,
2.學員可以屬於多個班級,學員成績按課程分別統計
3.每個班級至少包含一個或多個講師
4.一個學員要有狀態轉化的過程 ,比如未報名前,報名后,畢業老學員
5.客戶要有咨詢紀錄, 后續的定期跟蹤紀錄也要保存
6.每個學員的所有上課出勤情況\學習成績都要保存
7.學校可以有分校區,默認每個校區的員工只能查看和管理自己校區的學員
8.客戶咨詢要區分來源


學員管理系統表結構
  1 #_*_coding:utf-8_*_
  2 from django.db import models
  3 
  4 # Create your models here.
  5 from django.core.exceptions import ValidationError
  6 
  7 from django.db import models
  8 from django.contrib.auth.models import User
  9 
 10 class_type_choices= (('online',u'網絡班'),
 11                      ('offline_weekend',u'面授班(周末)',),
 12                      ('offline_fulltime',u'面授班(脫產)',),
 13                      )
 14 class UserProfile(models.Model):
 15     user = models.OneToOneField(User)
 16     name = models.CharField(u"姓名",max_length=32)
 17     def __unicode__(self):
 18         return self.name
 19 
 20 
 21 class School(models.Model):
 22     name = models.CharField(u"校區名稱",max_length=64,unique=True)
 23     addr = models.CharField(u"地址",max_length=128)
 24     staffs = models.ManyToManyField('UserProfile',blank=True)
 25     def __unicode__(self):
 26         return self.name
 27 
 28 
 29 class Course(models.Model):
 30     name = models.CharField(u"課程名稱",max_length=128,unique=True)
 31     price = models.IntegerField(u"面授價格")
 32     online_price = models.IntegerField(u"網絡班價格")
 33     brief = models.TextField(u"課程簡介")
 34     def __unicode__(self):
 35         return self.name
 36 
 37 
 38 class ClassList(models.Model):
 39     course = models.ForeignKey('Course')
 40     course_type = models.CharField(u"課程類型",choices=class_type_choices,max_length=32)
 41     semester = models.IntegerField(u"學期")
 42     start_date = models.DateField(u"開班日期")
 43     graduate_date = models.DateField(u"結業日期",blank=True,null=True)
 44     teachers = models.ManyToManyField(UserProfile,verbose_name=u"講師")
 45 
 46 
 47     #def __unicode__(self):
 48     #    return "%s(%s)" %(self.course.name,self.course_type)
 49 
 50     class Meta:
 51         verbose_name = u'班級列表'
 52         verbose_name_plural = u"班級列表"
 53         unique_together = ("course","course_type","semester")
 54 
 55 
 56 class Customer(models.Model):
 57     qq = models.CharField(u"QQ號",max_length=64,unique=True)
 58     name = models.CharField(u"姓名",max_length=32,blank=True,null=True)
 59     phone = models.BigIntegerField(u'手機號',blank=True,null=True)
 60     stu_id = models.CharField(u"學號",blank=True,null=True,max_length=64)
 61     #id = models.CharField(u"身份證號",blank=True,null=True,max_length=128)
 62     source_type = (('qq',u"qq群"),
 63                    ('referral',u"內部轉介紹"),
 64                    ('51cto',u"51cto"),
 65                    ('agent',u"招生代理"),
 66                    ('others',u"其它"),
 67                    )
 68     source = models.CharField(u'客戶來源',max_length=64, choices=source_type,default='qq')
 69     referral_from = models.ForeignKey('self',verbose_name=u"轉介紹自學員",help_text=u"若此客戶是轉介紹自內部學員,請在此處選擇內部學員姓名",blank=True,null=True,related_name="internal_referral")
 70     # 我們知道一般在表中創建外鍵關聯字段時,ForeignKey()外鍵關聯的都是其他表,不是本表.
 71     # 本項目中我們存在一個業務是需要關聯本表的需求: 學員可以轉介紹學員.
 72     # 那么如何實現呢,就是在 ForeignKey('self'),verbose_name是在admin后台顯示的字段名稱.help_text就是提示信息.
 73     # related_name這個注意了:只要是關聯自己就必須有這個屬性設置(因為當用forekey()關聯其他表時,反向查詢常規方法,而這種關聯本表的foreignKey()反向查詢就用related_name定義的值,這里就是internal_referral,用的時候作為字段使用)
 74     # 比如,我要查張三介紹了的幾個學員 '張三'.internal_referral
 75 
 76     course = models.ForeignKey(Course,verbose_name=u"咨詢課程")
 77     class_type = models.CharField(u"班級類型",max_length=64,choices=class_type_choices)
 78     customer_note = models.TextField(u"客戶咨詢內容詳情",help_text=u"客戶咨詢的大概情況,客戶個人信息備注等...")
 79     status_choices = (('signed',u"已報名"),
 80                       ('unregistered',u"未報名"),
 81                       ('graduated',u"已畢業"),
 82                       )
 83 
 84     status = models.CharField(u"狀態",choices=status_choices,max_length=64,default=u"unregistered",help_text=u"選擇客戶此時的狀態")
 85     consultant = models.ForeignKey(UserProfile,verbose_name=u"課程顧問")
 86     date = models.DateField(u"咨詢日期",auto_now_add=True)
 87 
 88     class_list = models.ManyToManyField('ClassList',verbose_name=u"已報班級",blank=True)
 89 
 90     def __unicode__(self):
 91         return "%s,%s" %(self.qq,self.name )
 92 
 93 
 94 
 95 class ConsultRecord(models.Model):
 96     customer = models.ForeignKey(Customer,verbose_name=u"所咨詢客戶")
 97     note = models.TextField(u"跟進內容...")
 98     status_choices = ((1,u"近期無報名計划"),
 99                       (2,u"2個月內報名"),
100                       (3,u"1個月內報名"),
101                       (4,u"2周內報名"),
102                       (5,u"1周內報名"),
103                       (6,u"2天內報名"),
104                       (7,u"已報名"),
105                       )
106     status = models.IntegerField(u"狀態",choices=status_choices,help_text=u"選擇客戶此時的狀態")
107 
108     consultant = models.ForeignKey(UserProfile,verbose_name=u"跟蹤人")
109     date = models.DateField(u"跟進日期",auto_now_add=True)
110 
111     def __unicode__(self):
112         return u"%s, %s" %(self.customer,self.status)
113 
114     class Meta:
115         verbose_name = u'客戶咨詢跟進記錄'
116         verbose_name_plural = u"客戶咨詢跟進記錄"
117 
118 
119 
120 class CourseRecord(models.Model):
121     course = models.ForeignKey(ClassList,verbose_name=u"班級(課程)")
122     day_num = models.IntegerField(u"節次",help_text=u"此處填寫第幾節課或第幾天課程...,必須為數字")
123     date = models.DateField(auto_now_add=True,verbose_name=u"上課日期")
124     teacher = models.ForeignKey(UserProfile,verbose_name=u"講師")
125     def __unicode__(self):
126         return u"%s 第%s天" %(self.course,self.day_num)
127     class Meta:
128         verbose_name = u'上課紀錄'
129         verbose_name_plural = u"上課紀錄"
130         unique_together = ('course','day_num')
131 
132 
133 class StudyRecord(models.Model):
134     course_record = models.ForeignKey(CourseRecord, verbose_name=u"第幾天課程")
135     student = models.ForeignKey(Customer,verbose_name=u"學員")
136     record_choices = (('checked', u"已簽到"),
137                       ('late',u"遲到"),
138                       ('noshow',u"缺勤"),
139                       ('leave_early',u"早退"),
140                       )
141     record = models.CharField(u"上課紀錄",choices=record_choices,default="checked",max_length=64)
142     score_choices = ((100, 'A+'),
143                      (90,'A'),
144                      (85,'B+'),
145                      (80,'B'),
146                      (70,'B-'),
147                      (60,'C+'),
148                      (50,'C'),
149                      (40,'C-'),
150                      (0,'D'),
151                      (-1,'N/A'),
152                      (-100,'COPY'),
153                      (-1000,'FAIL'),
154                      )
155     score = models.IntegerField(u"本節成績",choices=score_choices,default=-1)
156     date = models.DateTimeField(auto_now_add=True)
157     note = models.CharField(u"備注",max_length=255,blank=True,null=True)
158 
159     def __unicode__(self):
160         return u"%s,學員:%s,紀錄:%s, 成績:%s" %(self.course_record,self.student.name,self.record,self.get_score_display())
161 
162     class Meta:
163         verbose_name = u'學員學習紀錄'
164         verbose_name_plural = u"學員學習紀錄"
165         unique_together = ('course_record','student')
學員公里系統表結構

我們首先創建一個新的Django project項目
1  django-admin startproject  s12day18stu_crm
我們知道使用命令創建project時,默認是不會創建templates目錄的,所以這里我們使用命令創建這個目錄用於存放html模版文件
創建一個app,用來開發學員管理系統項目
1  python3.5 manage.py startapp app01

 

先把表結構拷貝進來.
首先我們看
1 from django.contrib.auth.models import User
2 class UserProfile(models.Model):
3     user = models.OneToOneField(User)
4     name = models.CharField(u"姓名",max_length=32)
5     def __unicode__(self):
6         return self.name
user字段用的是models.OneToOneField(User),這里的User是Django中用戶驗證模塊
OneToOneField
這里user = models.OneToOneField(User),相當於做了一個外鍵,只是一般外鍵是1對多的,但是這里是1對1的,為什么1對1呢?
你想如果是普通的外鍵,假如有兩個賬戶Alex1和Alex2 都外連同一個外鍵,那么問當我們用django中的這個賬戶登錄時,到底登錄到哪個賬戶呢?所以這里必須1對1,
mysql中是不能實現限制1對1的功能.所以這里OneToOneField()其實是在Django中實現的限制的.
那么我們為什么創建一個userprofile()還要用Django中的User表做外鍵呢?直接定義用戶登錄名和密碼不就行了嗎?問的好!
session的簡單需求介紹
1.首先瀏覽器訪問網頁的連接屬於短鏈接(非點擊一次鏈接請求,服務器返回請求內容,然后就斷開了),在點一次就屬於心的請求.
2.我們知道當你訪問一個網站,比如京東,加入你在登錄界面輸入用戶名和密碼后,成功登錄到系統.
2. 當你在購買物品后,點擊支付,這時候把請求發給服務端,服務端這時候接收到請求后,對於服務端只是一個請求,支付的前提是你已經是登錄后的賬戶,但是服務器怎么知道?
基本上所有網站都是通過session來實現身份標記的.當你登錄到網站后,服務端就會生成一個session(一個字符串),服務端在返回請求時把session返回給請求端.
那么這樣一個生成session的過程,是需要我們進行代碼實現的.所以我們使用Django中user模塊,在驗證的時候就實現了session的生成,
並且user模塊不僅實現了生成session,同時還實現了密碼的加鹽加密.(加鹽加密就是在md5加密的基礎上在加一些我們自己的特定字符串.)

上面OneToOneField 和 session的知識點內容介紹完后,我們就可以創建這個app01這個應用的數據庫了.

1. 首先創建一個s12day18stu_crm庫(因為我們創建Django Project的時候的名字是s12day18stu_crm),其中要指定數據庫的字符編碼
create database s12day18stu_crm default CHARSET utf8;
2. 然周執行python3.5 manage.py migrate,第一次執行時不需要生成配置文件,因為默認是添加Django里的admin和auth表,差不多有9張表把.
3. 把app01 添加到settings.py文件里
4. 生成新的配置文件python3.5 manage.py makemigrations
5. 創建app01程序數據庫

這些步驟都完成后,我們可以在數據庫中查看到app01_開頭的表,和Django后台系統使用的auth_ 開頭的以及Django_開頭的表
6. 開啟Django服務
python3.5 manage.py runserver 127.0.0.1:8000
7. http://127.0.0.1:8000/admin訪問后台,提示輸入用戶名和密碼
8. 創建用戶名和密碼python3.5 manage.py createsuperuser
9. 把app01程序下的數據庫表加到admin后台管理
 1 from django.contrib import admin
 2     # Register your models here.
 3     from app01 import models
 4     admin.site.register(models.Customer)        # 客戶表(學員表)
 5     admin.site.register(models.ConsultRecord)   #客戶咨詢跟進記錄表
 6     admin.site.register(models.ClassList)   #班級表
 7     admin.site.register(models.CourseRecord) # 班級上課記錄表
 8     admin.site.register(models.StudyRecord)  # 學員學習記錄表
 9     admin.site.register(models.UserProfile)  #用戶表
10     admin.site.register(models.Course)   #課程表

通過后台管理數據庫
這里有兩個點需要測試:
1. 客戶表創建客戶時,學員轉介紹 選擇本表里的其他學員
2. 班級上課記錄表是班級與第幾節課以及類型(網絡\面授)聯合唯一.然后學員學習在關聯班級上課記錄表,如果想知道某一條班級上課記錄表有多少學生上課怎么辦?
解決思路是,首先從班級上課記錄表查 學員學習記錄的條目是反向關聯.然后我們就應該我們應該查詢應該是學員學習記錄表,且篩選條件為班級上課記錄,這里應該定義一個Django admin后台的action動作 初始化 該班級下的所有學員創建一條新課程的學員學習記錄.
然后在查看學員學習記錄時應該通過帥選filter選擇某一條班級課程記錄.

學習視頻中,老師實現了,后台管理界面中首先按照學校分類,點進去后才是 客戶表\客戶咨詢跟進記錄表\班級表....課程表.
但是視頻中如何實現未看到,需要找高人指點.

緊接着學習的知識點:用戶認證的實現
我們先給app01寫一個首頁:
1.在urls.py寫一個URL
1         from django.conf.urls import url
2         from django.contrib import admin
3         from app01 import views
4 
5         urlpatterns = [
6             url(r'^admin/', admin.site.urls),
7             url(r'^$', views.index),
8         ]
    2.在views里定義index
1         def index(request):
2             return render(request,'index.html')
    3.在templates目錄下創建index.html
1         <body>
2             Welcome To Oldboy CRM
3         </body>
    4. 訪問http://127.0.0.1:8000查看訪問結果
OK的
那么怎么實現只有登錄后才能看到這個頁面?
如果有N多個頁面都需是需要登錄后才能訪問的,我們應該在每一個視圖里都加一個if else判斷,判斷的內容是request里的session內容是不是和服務器一直(當然前提還得寫一個登錄后生成session的函數).
簡單點的方法是就寫一個驗證函數,然后在視圖里,每一個函數前面加一個裝飾器.
實現思路就是這樣,而在於Django中,這個裝飾器Django已經寫好了.我們直接調用就可以了

5.更改views.py文件,加入驗證的裝飾器
1         from django.shortcuts import render
2         from django.contrib.auth.decorators import login_required  #引入登錄驗證模塊
3 
4         # Create your views here.
5         @login_required
6         def index(request):
7             return render(request,'index.html')
然后登錄測試:

         

    結果:我們訪問的是http://127.0.0.1:8000卻被轉到:http://127.0.0.1:8000/accounts/login/?next=/
這是因為我們沒登錄,Django默認把URL轉到這個URL,所以我們應該在這個URL里寫一個登錄頁面
6.寫一個登錄頁面,URL為:http://127.0.0.1:8000/accounts/login/
添加urls
1         urlpatterns = [
2             url(r'^admin/', admin.site.urls),
3             url(r'^$', views.index),
4             url(r'^accounts/login/$', views.acc_login),
5         ]
        編輯app01/views.py
1         def acc_login(request):
2             return render(request,'login.html')
        創建templates/login.html
我們先看index.html
1             <body>
2             <h1>oldboy CRM</h1>
3             {% block page-container%}
4                 Welcome To Oldboy CRM
5             {% endblock %}
6             </body>
        我們創建login.html直接讓它即成index.html,然后在重新block里的內容
查看login.html內容如下:
1             {% extends 'index.html' %}
這時候我們訪問網址:http://127.0.0.1:8000/accounts/login/?next=/ 結果如下:

      

確認無誤時,我們開始重寫block,如下:
 1             {% extends 'index.html' %}
 2 
 3             {% block page-container %}
 4             <form action="" method="post"> {% csrf_token %}
 5                 Username:<input type="text" name="username">
 6                 Password:<input type="password" name="password">
 7                 <input type="submit" value="Log me in">
 8             </form>
 9 
10             {% endblock %}
訪問測試如下:

這時候我們隨便輸入用戶名和密碼就可以提交后台了.
接下來我們就需要在views里對login.html中post的提交內容進行處理了
1 def acc_login(request):
2     if request.method == 'POST':
3         print(request.POST)
4     return render(request,'login.html')
這時候我們在瀏覽器輸入用戶名和密碼,在Django的命令行窗口輸出如下內容:
<QueryDict: {'username': ['admin'], 'password': ['admin123'], 'csrfmiddlewaretoken': ['p0P8hrImljJ7HVJYIYLVgpGniT70MicK']}>
我們看到用戶名和密碼都被打印出來了.前面我們說過一般網站驗證都不會名文驗證,都是加鹽加密.也就是收到用戶輸入的內容后,傳到后台,后台在進行加鹽加密封裝后與數據庫里的記錄做對比.
前面說過我們此節用戶驗證的部分要使用Django自帶的而不是自己寫.所以我們想把用戶輸入進來的賬戶進行驗證,就要用到Django自帶的驗證方法..
於是代碼如下
 1 from django.shortcuts import render,redirect
 2 from django.contrib.auth.decorators import login_required
 3 from django.contrib.auth import authenticate
 4 def acc_login(request):
 5     if request.method == 'POST':
 6         print(request.POST)
 7         # 取出用戶名和密碼,實例化Django自己的用戶驗證實例
 8         user = authenticate(username = request.POST.get('username'),
 9                             password = request.POST.get('password')
10                             )
11         if user is not None:  # 如果用戶不為空,則認證成功
12             # return render()  # 當登錄成功后,就不不是render一個頁面了,應該跳轉到首頁了.
13             return redirect('/') # 驗證成功后,跳轉到首頁url
14         else:
15             # 如果登錄失敗,返回報錯信息
16             login_err = 'Wrong username or password'
17             return render(request,'login.html',{'login_err':login_err})
18     return render(request,'login.html')
定義login.html
 1 {% extends 'index.html' %}
 2 
 3 {% block page-container %}
 4 <form action="" method="post">{% csrf_token %}
 5     Username:<input type="text" name="username">
 6     Password:<input type="password" name="password">
 7     <input type="submit" value="Log me in">
 8 </form>
 9 <div>
10     {% if login_err %}
11         <p style="color: red"> {{login_err}}</p>
12     {% endif %}
13 </div>
14 {% endblock %}
這時候我們訪問首頁http://127.0.0.1:8000/,首先會跳轉到登錄頁面http://127.0.0.1:8000/accounts/login/?next=/
輸入錯誤的密碼,提示錯誤,如下:

當我們輸入正確的用戶名和密碼,結果沒有跳轉(不是沒跳轉,只是又再次跳轉到驗證頁面).為什么?
因為我們只是在login中驗證了賬戶的用戶名和密碼.而沒有把session存下來.所以我們應該把session存下來
 1 from django.shortcuts import render,redirect,HttpResponseRedirect
 2 from django.contrib.auth.decorators import login_required
 3 from django.contrib.auth import authenticate,login #新導入login
 4 # 引入Django自帶的用戶驗證模塊
 5 # Create your views here.
 6 @login_required
 7 def index(request):
 8     return render(request,'index.html')
 9 
10 def acc_login(request):
11     if request.method == 'POST':
12         print(request.POST)
13         # 取出用戶名和密碼,實例化Django自己的用戶驗證實例
14         user = authenticate(username = request.POST.get('username'),
15                             password = request.POST.get('password')
16                             )
17         if user is not None:
18             login(request,user)  # 實例化login,此時就會生成session,並保存到request
19             return redirect('/') #
20             # return HttpResponseRedirect('/') #也可以用這個跳轉
21         else:
22             # 如果登錄失敗,返回報錯信息
23             login_err = 'Wrong username or password'
24             return render(request,'login.html',{'login_err':login_err})
25     return render(request,'login.html')
我們再次訪問測試:

  -》  


跳轉成功了,問題又來了,我如果想在首頁中顯示我的用戶名,就像京東,右上角那樣,未登錄顯示,"登錄",登錄后顯示"用戶名"
我們首先想到的就是在模版html判斷是不是登錄,代碼如下:
 1 <h1>oldboy CRM</h1>
 2 {% block page-container%}
 3     Welcome To Oldboy CRM
 4     <div>
 5         {% if request.user.is_authenticated %}
 6         <!--這里要注意,request在views中會自動返回,無論你在views視圖里return時有沒有返回-->
 7         <!--<span>{{ request.user }}</span>-->
 8         <!--這里顯示的是用戶名,想顯示全名如下-->
 9         <span>{{ request.user.userprofile.name }}</span>
10         {% else %}
11         <span>登錄/注冊</span>
12         {% endif %}
13     </div>
14 {% endblock %}
15 </body>
這時候我們就可以在頁面看到用戶全名了.
接下來我們來看如何退出
現在index.html寫一個退出的按鈕
 1 <body>
 2 <h1>oldboy CRM</h1>
 3 {% block page-container%}
 4     Welcome To Oldboy CRM
 5     <div>
 6         {% if request.user.is_authenticated %}
 7         <!--這里要注意,request在views中會自動返回,無論你在views視圖里return時有沒有返回-->
 8         <!--<span>{{ request.user }}</span>-->
 9         <!--這里顯示的是用戶名,想顯示全名如下-->
10         <span>{{ request.user.userprofile.name }}</span>
11         {% else %}
12         <span>登錄/注冊</span>
13         {% endif %}
14     </div>
15 <a href="/accounts/logout/">退出系統</a>
16 {% endblock %}
17 </body>
添加urls
1 urlpatterns = [
2     url(r'^admin/', admin.site.urls),
3     url(r'^$', views.index),
4     url(r'^accounts/login/$', views.acc_login),
5     url(r'^accounts/logout/$', views.acc_logout),
6 ]
在app01/views.py添加退出視圖
1 from django.contrib.auth import authenticate,login,logout # 導入logout
2 def acc_logout(request):
3     logout(request)
4     return HttpResponseRedirect('/')
頁面測試結果:

-》




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM