django-rest-framework(实战篇)——用户手机
2018-04-13 本文已影响54人
Ccccolin_aha
云片网发送验证码
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