美多商場 - 使用者部分 - 4 使用者中心個人資訊
1 使用者個人中心說明與郵箱啟用欄位新增
1.1 個人中心介紹
前端訪問個人資訊頁面時,需要向後端請求個人資訊。
在本頁面中要顯示使用者的Email郵箱資訊,而對於郵箱資訊我們要實現對於郵箱的驗證功能,並在本頁面中顯示郵箱是否已驗證,如下所示,
這裡有一個郵箱,而郵箱在註冊的時候,並沒有讓使用者輸入,所以會在這裡留一個輸入的入口,所以第一次訪問基本資訊,是這樣:
使用者儲存之後,還要對郵箱進行校驗:
1.2 增加啟用欄位
這裡需要修改User模型類,增加郵箱是否驗證的欄位。
class User(AbstractUser): """ 使用者資訊 """ mobile = models.CharField(max_length=11, unique=True, verbose_name="手機號") email_active = models.BooleanField(default=False, verbose_name='郵箱驗證狀態')
進行資料庫遷移
python manage.py makemigrations
python manage.py migrate
2 返回使用者跟人資訊資料後端介面
2.1 後端邏輯
2.1.1 後端介面設計:
請求方式: GET /user/
請求引數: 無
返回資料: JSON
返回值 | 型別 | 是否必須 | 說明 |
---|---|---|---|
id | int | 是 | 使用者id |
username | str | 是 | 使用者名稱 |
mobile | str | 是 | 手機號 |
str | 是 | email郵箱 | |
email_active | bool | 是 | 郵箱是否通過驗證 |
根據介面增加檢視邏輯:
而get中的邏輯,其實就是獲取詳情的邏輯,所以我們可以繼承RetrieveModelMixin
還可以直接繼承RetrieveAPIView:
繼承如下:
序列化器如下:
序列化器程式碼如下:
class UserDetailSerializer(serializers.ModelSerializer): """ 使用者詳細資訊序列化器 """ class Meta: model = User fields = ('id', 'username', 'mobile', 'email', 'email_active')
下來指定查詢集:
但是RetrieveAPI中獲取詳情資料的url是/users/<pk>/而我們設計的介面是/user/。
那隻能重寫get_object了:
這裡是如何獲取的user物件呢?
2.1.2 如何獲取user
而這裡我們壓根不用查詢資料庫,因為我們直接返回登入成功的使用者資訊即可,關鍵就是如何獲取登入成功的使用者。
如何獲取使用者呢?之前在講django的時候,說過HttpRequest物件中就有這個已經登入的使用者物件user:
但是現在是在類檢視的函式中,如何拿到request物件呢?
所以程式碼如下:
from rest_framework.permissions import IsAuthenticated
class UserDetailView(RetrieveAPIView):
"""
使用者詳情
"""
serializer_class = serializers.UserDetailSerializer
permission_classes = [IsAuthenticated]
def get_object(self):
return self.request.user
注意:訪問檢視必須要求使用者已通過認證(即登入之後)
2.1.3 認證授權
這個檢視我們應該是要求使用者必須登入之後,才能訪問,那怎麼辦?
來用DRF提供的認證授權機制:
APIView支援的屬性如下:
這裡就有一個permission_classes,可以知道檢測型別。
執行的許可權檢測型別為:
我們這裡選擇僅通過認證的使用者(也就是僅登入的使用者):
許可權使用方式如下:
我們選擇第二種即可,所以程式碼如下
2.2 前端請求獲取使用者個人資訊
修改user_center_info.html,增加Vue的變數
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>美多商城-使用者中心</title>
<link rel="stylesheet" type="text/css" href="css/reset.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
<script type="text/javascript" src="js/host.js"></script>
<script type="text/javascript" src="js/vue-2.5.16.js"></script>
<script type="text/javascript" src="js/axios-0.18.0.min.js"></script>
<script>
var user_id = sessionStorage.user_id || localStorage.user_id;
var token = sessionStorage.token || localStorage.token;
if (!(user_id && token)) {
location.href = '/login.html?next=/user_center_info.html';
}
</script>
</head>
<body>
<div id="app" v-cloak>
<div class="header_con">
<div class="header">
<div class="welcome fl">歡迎來到美多商城!</div>
<div class="fr">
<div class="login_btn fl">
歡迎您:<em>{{ username }}</em>
<span>|</span>
<a @click="logout">退出</a>
</div>
<div class="user_link fl">
<span>|</span>
<a href="user_center_info.html">使用者中心</a>
<span>|</span>
<a href="cart.html">我的購物車</a>
<span>|</span>
<a href="user_center_order.html">我的訂單</a>
</div>
</div>
</div>
</div>
<div class="search_bar clearfix">
<a href="index.html" class="logo fl"><img src="images/logo.png"></a>
<div class="sub_page_name fl">| 使用者中心</div>
<form method="get" action="/search.html" class="search_con fr mt40">
<input type="text" class="input_text fl" name="q" placeholder="搜尋商品">
<input type="submit" class="input_btn fr" name="" value="搜尋">
</form>
</div>
<div class="main_con clearfix">
<div class="left_menu_con clearfix">
<h3>使用者中心</h3>
<ul>
<li><a href="user_center_info.html" class="active">· 個人資訊</a></li>
<li><a href="user_center_order.html">· 全部訂單</a></li>
<li><a href="user_center_site.html">· 收貨地址</a></li>
<li><a href="user_center_pass.html">· 修改密碼</a></li>
</ul>
</div>
<div class="right_content clearfix">
<div class="info_con clearfix">
<h3 class="common_title2">基本資訊</h3>
<ul class="user_info_list">
<li><span>使用者名稱:</span>{{ username }}</li>
<li><span>手機號:</span>{{ mobile }}</li>
<li>
<span>Email:</span>
<div v-if="set_email">
<input v-model="email" type="email" name="email">
<input @click="save_email" type="button" name="" value="保 存">
<input @click="set_email=false" type="reset" name="" value="取 消">
<div v-if="email_error">郵箱格式錯誤</div>
</div>
<div v-else-if="email">
{{ email }}
<div v-if="email_active">已驗證</div>
<div v-else>
待驗證<input @click="save_email" :disabled="send_email_btn_disabled" type="button" :value="send_email_tip">
</div>
</div>
<div v-else>
<input @click="set_email=true" type="button" name="" value="設 置">
</div>
</li>
</ul>
</div>
<h3 class="common_title2">最近瀏覽</h3>
<div class="has_view_list">
<ul class="goods_type_list clearfix">
<li>
<a href="detail.html"><img src="images/goods/goods003.jpg"></a>
<h4><a href="detail.html">360手機 N6 Pro 全網通</a></h4>
<div class="operate">
<span class="prize">¥2699.00</span>
<span class="unit">臺</span>
<a href="#" class="add_goods" title="加入購物車"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods004.jpg"></a>
<h4><a href="#">360手機 N6 Pro 全網通</a></h4>
<div class="operate">
<span class="prize">¥2699.00</span>
<span class="unit">臺</span>
<a href="#" class="add_goods" title="加入購物車"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods005.jpg"></a>
<h4><a href="#">360手機 N6 Pro 全網通</a></h4>
<div class="operate">
<span class="prize">¥2699.00</span>
<span class="unit">臺</span>
<a href="#" class="add_goods" title="加入購物車"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods006.jpg"></a>
<h4><a href="#">360手機 N6 Pro 全網通</a></h4>
<div class="operate">
<span class="prize">¥2699.00</span>
<span class="unit">臺</span>
<a href="#" class="add_goods" title="加入購物車"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods007.jpg"></a>
<h4><a href="#">急速路由</a></h4>
<div class="operate">
<span class="prize">¥64.5</span>
<span class="unit">6.45/500g</span>
<a href="#" class="add_goods" title="加入購物車"></a>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="footer">
<div class="foot_link">
<a href="#">關於我們</a>
<span>|</span>
<a href="#">聯絡我們</a>
<span>|</span>
<a href="#">招聘人才</a>
<span>|</span>
<a href="#">友情連結</a>
</div>
<p>CopyRight © 2016 北京美多商業股份有限公司 All Rights Reserved</p>
<p>電話:010-****888 京ICP備*******8號</p>
</div>
</div>
<script type="text/javascript" src="js/user_center_info.js"></script>
</body>
</html>
在js目錄中新建user_center_info.js
var vm = new Vue({
el: '#app',
data: {
host,
user_id: sessionStorage.user_id || localStorage.user_id,
token: sessionStorage.token || localStorage.token,
username: '',
mobile: '',
email: '',
email_active: false,
set_email: false,
send_email_btn_disabled: false,
send_email_tip: '重新發送驗證郵件',
email_error: false,
histories: []
},
mounted: function(){
// 判斷使用者的登入狀態
if (this.user_id && this.token) {
axios.get(this.host + '/user/', {
// 向後端傳遞JWT token的方法
headers: {
'Authorization': 'JWT ' + this.token
},
responseType: 'json',
})
.then(response => {
// 載入使用者資料
this.user_id = response.data.id;
this.username = response.data.username;
this.mobile = response.data.mobile;
this.email = response.data.email;
this.email_active = response.data.email_active;
})
.catch(error => {
if (error.response.status==401 || error.response.status==403) {
location.href = '/login.html?next=/user_center_info.html';
}
});
} else {
location.href = '/login.html?next=/user_center_info.html';
}
},
methods: {
// 退出
logout: function(){
sessionStorage.clear();
localStorage.clear();
location.href = '/login.html';
},
// 儲存email
save_email: function(){
}
}
});
3 郵件與驗證
業務說明:
在使用者中心頁面中,我們允許使用者設定郵箱
當用戶點選儲存後,我們會向用戶傳送郵件以驗證郵箱的有效性。
為了避免使用者未收到驗證郵箱,我們提供“重新發送驗證郵件”按鈕允許使用者重新發送郵件。
郵箱驗證成功,顯示已驗證。
技術說明:
在郵件中提供的啟用連結地址,為了能區分是哪個使用者在進行郵箱驗證,需要在連結中包含使用者和郵箱的識別資訊,如user_id和email資料,但是基於安全性的考慮,不能將這兩個資料直接暴露在郵件連結中,而是需要進行隱藏和簽名處理(能夠檢測出是否修改過連結資料)。可以使用前面學過的itsdangerous對user_id和email資料進行處理,生成token作為連結的引數。
4 使用Django傳送郵件
我們需要在儲存郵箱的同時,傳送一封郵件,那我們需要先分析一下如何傳送郵件。
那我們如何給傳送郵件伺服器傳送一個smtp協議的請求,去傳送郵件呢?
我們需要找一個傳送郵件的伺服器,我們以163為例。
Django中內建了郵件傳送功能,被定義在django.core.mail模組中。傳送郵件需要使用SMTP伺服器,常用的免費伺服器有:163、126、QQ,下面以163郵件為例。
1)註冊163郵箱itcast88,登入後設置。
2)在新頁面中點選“客戶端授權密碼”,勾選“開啟”,彈出新視窗填寫手機驗證碼。
3)填寫授權碼。
4)提示開啟成功。
5) 在Django配置檔案中,設定郵箱的配置資訊
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
#傳送郵件的郵箱
EMAIL_HOST_USER = '[email protected]'
#在郵箱中設定的客戶端授權密碼
EMAIL_HOST_PASSWORD = 'python808'
#收件人看到的發件人
EMAIL_FROM = 'python<[email protected]>'
6) 使用Django提供的模組傳送郵件
在django.core.mail
模組提供了send_mail
來發送郵件。
send_mail
(subject, message, from_email, recipient_list,html_message=None)
- subject 郵件標題
- message 普通郵件正文, 普通字串
- from_email 發件人
- recipient_list 收件人列表
- html_message 多媒體郵件正文,可以是html字串
例如:
msg='<a href="http://www.itcast.cn/subject/pythonzly/index.shtml" target="_blank">點選啟用</a>'
send_mail('註冊啟用','',settings.EMAIL_FROM, ['[email protected]'], html_message=msg)
5 儲存郵箱併發送驗證郵件
5.1 儲存郵箱後端介面實現
接下來處理儲存郵箱,介面如下:
後端介面設計:
請求方式:PUT /email/
請求引數: JSON 或 表單
引數 | 型別 | 是否必須 | 說明 |
---|---|---|---|
str | 是 | Email郵箱 |
返回資料: JSON
返回值 | 型別 | 是否必須 | 說明 |
---|---|---|---|
id | int | 是 | 使用者id |
str | 是 | Email郵箱 |
具體邏輯分析如下:
上述的邏輯其實就是更新的一個邏輯,所以我們可以繼承UpdateModelMixin:
原始碼如下,大家可以看下update方法,做的事情與我們分析的是一致的:
當然我們會直接繼承UpdateApiView:
繼承之後程式碼如下:
注意:這裡為啥要重寫get_object,因為我們的url是不接受pk引數的,所以UpdateApiView無法確定我們要更新哪個模型類,所以我們要重寫get_object,告訴他更新哪個模型類。我們這裡更新的是user模型類。
序列化器如下:
序列化器中就兩個欄位id和email,關於這倆欄位的方向問題,都不用處理。
id,預設只能做序列化操作。
email,序列化器和反序列化都要做,也是預設。
注意:這裡咱們還沒有處理到更新郵箱的邏輯,我們在下邊傳送郵件的時候,處理。
5.2 定義傳送郵件的任務
配置Email:
celery_tasks新建任務email:
配置任務: