云片網發送驗證碼
apps/utils/yunpian.py
# -*- coding:utf-8 -*-
__author__ = 'cao.yh'
__date__ = '2018/4/13 下午2:17'
import requests
import json
class YunPian(object):
def __init__(self, api_key):
self.api_key = api_key
self.single_send_url = 'https://sms.yunpian.com/v2/sms/single_send.json'
def send_sms(self, code, mobile):
params = {
'apikey': self.api_key,
'mobile': mobile,
'text': '【慕學生鮮】您的驗證碼是{code}。如非本人操作,請忽略本短信'.format(code=code),
}
response = requests.post(self.single_send_url, data=params)
re_dict = json.loads(response.text)
return re_dict
注意text內容必須要與后臺已申請過簽名并審核通過的模板保持一致
編寫發送短信驗證碼接口
用戶傳過來的手機號碼需要兩次驗證:
- 手機號是否合法
- 手機號是否已經被注冊
users/serializers.py
class SmsSerializer(serializers.Serializer):
mobile = serializers.CharField(required=True, max_length=11)
def validate_mobile(self, mobile):
"""
驗證手機號碼
:param mobile:
:return:
"""
# 手機是否注冊
if VerifyCode.objects.filter(mobile=mobile).count():
raise serializers.ValidationError('手機號碼已經注冊')
# 驗證手機號碼合法
if not re.match(REGEX_MOBILE, mobile):
raise serializers.ValidationError('手機號碼格式錯誤')
# 驗證碼發送頻率
one_minute_age = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
if VerifyCode.objects.filter(add_time__gt=one_minute_age, mobile=mobile).count():
raise serializers.ValidationError('請一分鐘后再次發送')
return mobile
繼承serializers.Serializer的原因:VerifyCode Model中,code中是必填項,但獲取驗證碼的時候前端只會傳一個mobile字段,所以會導致驗證失敗。
users/views.py(POST------>需要重寫create方法):
class SmsCodeViewSet(viewsets.GenericViewSet, mixins.CreateModelMixin):
"""
發送短信驗證碼
"""
serializer_class = SmsSerializer
def generate_code(self):
"""
生成四位數驗證碼
:return:
"""
seeds = '1234567890'
random_str = []
for i in range(4):
random_str.append(choice(seeds))
return "".join(random_str)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
# 自定義的create()的內容
# 從validated_data中獲取mobile
mobile = serializer.validated_data['mobile']
# 隨機生成code
code = self.generate_code()
# 發送驗證碼短信
yun_pian = YunPian(APIKEY)
sms_status = yun_pian.send_sms(code=code, mobile=mobile)
if sms_status['code']!= 0:
return Response({
'mobile': sms_status['code']
}, status=status.HTTP_400_BAD_REQUEST)
else:
code_record = VerifyCode(code=code, mobile=mobile)
# 保存驗證碼
code_record.save()
return Response({
'mobile': mobile
}, status=status.HTTP_201_CREATED)
將返回的json在yunpian中loads成dict
然后取出dict中的code和msg進行判斷與返回。我們不需要向前端返回status。而是遵循restful api的規范。http狀態碼即可區分成功或失敗。消息并不代表。
發送成功之后再保存驗證碼
編寫用戶注冊接口
注冊頁面需要我們輸入手機號碼 驗證碼 和密碼。
users/serializers.py(繼承modelSerializer,因為字段都是必填項,都有的,雖然相比較用戶model多了一個code字段)
class UserRegisterSerializer(serializers.ModelSerializer):
# error_message:自定義錯誤消息提示的格式
code = serializers.CharField(required=True, allow_blank=False, min_length=4, max_length=4, help_text='驗證碼',
error_messages={
'blank': '請輸入驗證碼',
'required': '請輸入驗證碼',
'min_length': '驗證碼格式錯誤',
'max_length': '驗證碼格式錯誤',
})
# 利用drf中的validators驗證username是否唯一
username = serializers.CharField(required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message='用戶已經存在')])
# 對code字段單獨驗證(validate_+字段名)
def validate_code(self, code):
verify_records = VerifyCode.objects.filter(mobile=self.initial_data['username']).order_by('-add_time')
if verify_records:
last_record = verify_records[0]
# 判斷驗證碼是否過期
five_minutes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) # 獲取5分鐘之前的時間
if last_record.add_time > five_minutes_ago:
raise serializers.ValidationError('驗證碼過期')
# 判斷驗證碼是否正確
if last_record.code != code:
raise serializers.ValidationError('驗證碼錯誤')
# 不用將code返回到數據庫中,只是做驗證
# return code
else:
raise serializers.ValidationError('驗證碼錯誤')
# attrs:每個字段validate之后總的dict
def validate(self, attrs):
attrs['mobile'] = attrs['username']
# 從attrs中刪除code字段
del attrs['code']
return attrs
class Meta:
model = User
fields = ('username', 'code', 'mobile')
編寫視圖函數:users/views.py
class UserViewset(CreateModelMixin, viewsets.GenericViewSet):
"""
用戶
"""
serializer_class = UserRegisterSerializer
配置路由:
# 配置users的url
router.register(r'users', UserViewset, base_name="users")
drf驗證默認的返回格式:
HTTP 400 Bad Request
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept
{
"username": [
"用戶已經存在"
],
"code": [
"驗證碼錯誤"
]
}
- 單個字段出錯: 字段 + 數組
- 聯合字段出錯: non_fields_error
django信號量實現密碼密文存儲
題外話:重載Serializer的create方法可以實現相同的功能:
def create(self, validated_data):
user = super(UserRegSerializer, self).create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
return user
新建文件users/signals.py:
# encoding: utf-8
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model
User = get_user_model()
# 參數一接收哪種信號,參數二是接收哪個model的信號
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
# 是否新建,因為update的時候也會進行post_save
if created:
password = instance.password
instance.set_password(password)
instance.save()
做完剛才這些操作,還要重載一個配置 users/app.py:
這是AppConfig中我們可以在子類中自定義的函數,它將會在django啟動時被運行。
def ready(self):
import users.signals