django-rest-framework开发

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": [
        "验证码错误"
    ]
}

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
上一篇下一篇

猜你喜欢

热点阅读