day90

1 基于jwt的多方式登陆
2 自定义user表,签发token,认证类
3 book,publish,author表关系及抽象表建立
4 book表对单及对群增删改查

## 1 基于jwt的多方式登陆

```python
1 手机号+密码   用户名+密码  邮箱+密码
2 流程分析(post请求):
    -路由:自动生成
    -视图类:ViewSet(ViewSetMixin, views.APIView)
    -序列化类:重写validate方法,在这里面对用户名和密码进行校验
    
3 代码实现
```

### 路由

```python
path('login/', views.LoginViewSet.as_view({'post':'create'})),
```



### 视图

```python
class LoginViewSet(ViewSet):
    def create(self, request, *args, **kwargs):
        # 实例化得到一个序列化类的对象
        # ser=LoginSerializer(data=request.data,context={'request':request})
        ser = LoginSerializer(data=request.data)
        # 序列化类的对象的校验方法
        ser.is_valid(raise_exception=True)  # 字段自己的校验,局部钩子校验,全局钩子校验
        # 如果通过,表示登录成功,返回手动签发的token
        token = ser.context.get('token')
        username = ser.context.get('username')
        return APIResponse(token=token, username=username)
        # 如果失败,不用管了
```



### 序列化类

```python
from rest_framework import serializers
from app01.models import UserInfo
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from rest_framework_jwt.views import obtain_jwt_token

class LoginSerializer(serializers.ModelSerializer):
    username = serializers.CharField()

    class Meta:
        model = UserInfo
        fields = ['username', 'password']

    def validate(self, attrs):
        # username可能是邮箱,手机号,用户名
        username = attrs.get('username')
        password = attrs.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = UserInfo.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = UserInfo.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = UserInfo.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.check_password(password):
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            # token是要在视图类中使用,现在我们在序列化类中
            # self.context.get('request')
            # 视图类和序列化类之间通过context这个字典来传递数据
            self.context['token'] = token
            self.context['username'] = user.username
            return attrs

        else:
            raise ValidationError('用户名或密码错误')
```





## 2 自定义user表,签发token,认证类

### 表模型

```python
class MyUser(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    phone = models.CharField(max_length=32)
    email = models.EmailField()
```

### 路由

```
path('login2/', views.MyLoginView.as_view()),
```

### 视图

```python
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from rest_framework_jwt.views import obtain_jwt_token


class MyLoginView(APIView):
    def post(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = MyUser.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = MyUser.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = MyUser.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.password == password:
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, username=user.username)
        else:
            return APIResponse(code=101, msg='用户名或密码错误')
```



## 3 book,publish,author表关系及抽象表建立

```python
# 注意:以后所有的数据删除,尽量用软删除,使用一个字段标志是否删除,而不是真正的从数据库中删除
    -好处:1 这样删除数据不会影响索引,不会导致索引失效
          2 之前存的用户数据还在,以备以后使用
# 表模型如下
# 抽象出一个基表(不在数据库生成,abstract=True),只用来继承

class BaseModel(models.Model):
    is_delete = models.BooleanField(default=False)
    create_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        # 基表必须设置abstract,基表就是给普通Model类继承使用的,设置了abstract就不会完成数据库迁移完成建表
        abstract = True


class Book(BaseModel):
    name = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to='Publish', db_constraint=False, on_delete=models.DO_NOTHING)
    # 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联
    # ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表
    authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)

    @property
    def publish_name(self):
        return self.publish.name
    @property
    def author_list(self):
        # ll=[]
        # for author in self.authors.all():
        #     ll.append({'name':author.name,'sex':author.get_sex_display()})
        return [{'name': author.name, 'sex': author.get_sex_display()} for author in self.authors.all()]


class Publish(BaseModel):
    name = models.CharField(max_length=16)
    address = models.CharField(max_length=64)


class Author(BaseModel):
    name = models.CharField(max_length=16)
    sex = models.IntegerField(choices=[(0, ''), (1, '')], default=0)


class AuthorDetail(BaseModel):
    mobile = models.CharField(max_length=11)
    # 有作者可以没有详情,删除作者,详情一定会被级联删除
    # 外键字段为正向查询字段,related_name是反向查询字段
    author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)

```





## 4 book表单增群增

```python
class BookView(APIView):
    def post(self, request, *args, **kwargs):
        if isinstance(request.data, dict):
            # 增一条
            ser = serializer.BookSerializer(data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(data=ser.data)
        elif isinstance(request.data, list):
            # 增多条
            ser = serializer.BookSerializer(data=request.data, many=True)
            # 内部如何实现的?
            # many=True,ser不是BookSerializer对象,而是ListSerializer对象,套了一个个的BookSerializer
            print(type(ser))
            ser.is_valid(raise_exception=True)
            #
            from rest_framework.serializers import ListSerializer
            ser.save()  # ListSerializer的save
            return APIResponse(msg='增加%s条成功' % len(request.data))
```



## 5 book表单查群查

```python
class BookView(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        if pk:
            # 单查
            # 方式一
            # book=models.Book.objects.filter(id=pk).filter(is_delete=False).first()
            # if not book:
            #     raise Exception('要查询的不存在')

            # 方式二
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book)

        else:
            # 查所有
            book_list = models.Book.objects.all().filter(is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, many=True)
        return APIResponse(data=ser.data)
```

## 6 book表单改群改

```python
class BookView(APIView):
    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        if pk:
            # 单条修改
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book, data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改成功')
        else:
            # 分析:ListSerializer的update方法没有写,需要我们自己写
            from rest_framework.serializers import ListSerializer
            # pks=[item['id'] for item in request.data]

            # 如果不重写ListSerializer的update方法,这是存不进去的
            pks = []
            for item in request.data:
                pks.append(item['id'])
                item.pop('id')

            print(request.data)
            book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, data=request.data, many=True)
            print(type(ser))
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改%s条成功')

            # 你们能想到的方法
            # pks = []
            # for item in request.data:
            #     pks.append(item['id'])
            #     item.pop('id')
            # book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
            #
            # for i,book in enumerate(book_list):
            #     ser = serializer.BookSerializer(instance=book, data=request.data[i])
            #     ser.is_valid(raise_exception=True)
            #     ser.save()
            # return APIResponse(msg='修改%s条成功'%len(book_list))
```

## 7 book表的单删群删

```python
class BookView(APIView):

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        pks = []
        if pk:
            # 单条删除
            # res=models.Book.objects.filter(id=pk).update(is_delete=True)
            # print(res)
            # return APIResponse(msg='删除成功')
            pks.append(pk)
        else:
            pks = request.data

        res = models.Book.objects.filter(id__in=pks).update(is_delete=True)
        if res >= 1:
            return APIResponse(msg='删除%s条成功' % res)
        else:
            # raise Exception('没有要删除的数据')
            return APIResponse(code=999, msg='没有要删除的数据')
```



## 8 序列化类

```python
from app01 import models


class ListBookSerializer(serializers.ListSerializer):
    # def create(self, validated_data):
    #     print('=======',validated_data)
    #     return '1'
    def update(self, instance, validated_data):
        print(instance) # book_list:是一堆图书对象
        print(validated_data) # 列表套字典,是要修改的数据

        return [self.child.update(book, validated_data[i]) for i, book in enumerate(instance)]


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        list_serializer_class=ListBookSerializer # 指定many=True的时候,生成的ListBookSerializer的对象了
        fields = ['name', 'price', 'publish', 'authors', 'publish_name', 'author_list']
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True},
            'publish_name': {'read_only': True},
            'author_list': {'read_only': True},

        }

    # def create(self, validated_data):
    #     print(validated_data)
```

### 9 路由

```python
path('books/', views.BookView.as_view()),
re_path('books/(?P<pk>\d+)', views.BookView.as_view()),
```
# settings.py
AUTH_USER_MODEL = 'app01.UserInfo'

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.utils.common_exception_handler',
}
# urls.py
from django.contrib import admin
from django.urls import path, re_path
from rest_framework.routers import SimpleRouter
from app01 import views
router = SimpleRouter()
router.register('login', views.LoginView, basename='login')
urlpatterns = [
    path('admin/', admin.site.urls),
    path('login1/', views.MyLoginView.as_view()),
    path('order/', views.OrderView.as_view()),
    path('book/', views.BookView.as_view()),
    re_path('book/(?P<pk>\d+)', views.BookView.as_view()),
]
urlpatterns += router.urls
# models.py
from django.db import models

# Create your models here.
from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    phone = models.CharField(max_length=32, unique=True)


# 自定义user表
class MyUser(models.Model):
    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=32)
    phone = models.CharField(max_length=32)
    email = models.EmailField()


# 抽象出一个基表,仅用来继承,不在数据库中生成表(abstract = True)
class BaseModel(models.Model):
    is_delete = models.BooleanField(default=False)
    create_time = models.DateField(auto_now_add=True)

    class Meta:
        abstract = True  # 基表必须设置abstract,基表就是给普通Model类继承使用的,设置了abstract就不会完成数据库迁移完成建表


class Book(BaseModel):
    name = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to='Publish', db_constraint=False, on_delete=models.DO_NOTHING)  # db_constraint:是否建立外键
    # 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联
    # ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表
    authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)    # related_name:反向查询时的表名

    @property
    def publish_name(self):
        return self.publish.name

    @property
    def author_list(self):
        # ll=[]
        # for author in self.authors.all():
        #     ll.append({'name':author.name,'sex':author.get_sex_display()})
        return [{'name': author.name, 'sex': author.get_sex_display()} for author in self.authors.all()]


class Publish(BaseModel):
    name = models.CharField(max_length=16)
    address = models.CharField(max_length=64)


class Author(BaseModel):
    name = models.CharField(max_length=16)
    sex = models.IntegerField(choices=[(0, ''), (1, '')], default=0)


class AuthorDetail(BaseModel):
    mobile = models.CharField(max_length=11)
    # 有作者可以没有详情,删除作者,详情一定会被级联删除
    # 外键字段为正向查询字段,related_name是反向查询字段
    author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)
# auth.py
import jwt
from rest_framework_jwt.utils import jwt_decode_handler
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework import exceptions


# 自定义认证类
class JwtAuthentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_AUTHORIZATION')
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            msg = 'Signature has expired.'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = 'Error decoding signature.'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed('未知错误')
        user = payload
        return (user, token)
# utils.py
from rest_framework.response import Response
from rest_framework.views import exception_handler


class APIResponse(Response):
    def __init__(self, code=100, msg='成功', data=None, status=None, headers=None, content_type=None, **kwargs):
        dic = {'code': code, 'msg': msg}
        if data:
            dic['data'] = data
        dic.update(kwargs)
        super().__init__(data=dic, status=status, headers=headers, content_type=content_type)


def common_exception_handler(exc, context):
    response = exception_handler(exc, context)
    if response is None:
        response = Response({'code': 999, 'msg': str(exc)})
    return response
# serializer.py
from rest_framework import serializers
from app01 import models
import re
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
from rest_framework.exceptions import ValidationError


class LoginSerializer(serializers.ModelSerializer):
    username = serializers.CharField()  # 需要重写username字段,否则会当作新增请求

    class Meta:
        model = models.UserInfo
        fields = ['username', 'password']

    def validate(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        if re.match('1[3-9]\d{9}', username):  # 以手机号登录
            user = models.UserInfo.objects.filter(phone=username).first()
        elif re.match('.+@.+', username):  # 以邮箱登录
            user = models.UserInfo.objects.filter(email=username).first()
        else:  # 以用户名登录
            user = models.UserInfo.objects.filter(username=username).first()
        if user and user.check_password(password):
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            self.context['token'] = token  # ser.context为views与serializer进行数据通信的字典
            self.context['username'] = username
            return attrs
        else:
            raise ValidationError('用户名或密码错误')


class MyListSerializer(serializers.ListSerializer):
    # many=True时,生成的是ListSerializer对象,其中update方法需要重写,否则数据无法存入
    def update(self, instance, validated_data):
        return [self.child.update(book, validated_data[i]) for i, book in enumerate(instance)]


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = ['name', 'price', 'publish', 'authors', 'publish_name', 'author_list']
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True},
            'publish_name': {'read_only': True},
            'author_list': {'read_only': True}
        }
        list_serializer_class = MyListSerializer  # 指定当many=True时,生成MyListSerializer的对象(默认为ListSerializer)
# views.py
from django.shortcuts import render

# Create your views here.
from rest_framework.viewsets import ViewSet
from rest_framework.views import APIView
from app01.utils import APIResponse
from app01 import serializer
import re
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
from app01 import models
from app01 import auth


# 基于jwt的多方式登录
class LoginView(ViewSet):
    def create(self, request, *args, **kwargs):  # 自动生成路由会将post请求对应为create方法,所以这里要写create
        ser = serializer.LoginSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        token = ser.context['token']  # ser.context为views与serializer进行数据通信的字典
        username = ser.context['username']
        return APIResponse(token=token, username=username)


class MyLoginView(APIView):
    def post(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        if re.match('1[3-9]\d{9}', username):
            user = models.MyUser.objects.filter(phone=username).first()
        elif re.match('.+@.+', username):
            user = models.MyUser.objects.filter(email=username).first()
        else:
            user = models.MyUser.objects.filter(username=username).first()
        # if user and user.check_password(password):         # check_password()为内置auth中的方法,这里自己写的不能用check_password
        if user and user.password == password:
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, username=user.username)
        else:
            return APIResponse(code=101, msg='用户名或密码错误1')


class OrderView(APIView):
    authentication_classes = [auth.JwtAuthentication, ]

    def get(self, request, *args, **kwargs):
        return APIResponse(msg='查询订单')


class BookView(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if pk:
            # 如果能通过pk取到值,代表访问路径中传入了pk,则代表单查
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book)
        else:
            # 群查
            book_list = models.Book.objects.all().filter(is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, many=True)
        return APIResponse(data=ser.data)

    def post(self, request, *args, **kwargs):
        if isinstance(request.data, dict):
            # 如果request.data是字典对象,那么就是单增
            ser = serializer.BookSerializer(data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(data=ser.data)
        elif isinstance(request.data, list):
            # 如果是列表对象,则是多条新增
            ser = serializer.BookSerializer(data=request.data, many=True)   # many=True即可实现群增
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='新增%s条数据成功' % len(request.data))

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        pks = []
        if pk:
            pks.append(pk)
        else:
            pks = request.data
        res = models.Book.objects.filter(id__in=pks).update(is_delete=True)     # 软删除
        if res >= 1:    # res为影响的条数
            return APIResponse(msg='删除%s条成功' % res)
        else:
            raise Exception('没有要删除的数据')

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        pks = []
        if pk:
            book = models.Book.objects.filter(id=pk).first()
            ser = serializer.BookSerializer(instance=book, data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改成功')
        else:
            for item in request.data:
                pks.append(item['id'])
                item.pop('id')
            book_list = models.Book.objects.filter(id__in=pks, is_delete=False).all()
            ser = serializer.BookSerializer(instance=book_list, data=request.data, many=True)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改%s条数据成功' % len(book_list))

 

posted @ 2020-11-17 19:39  板鸭没有腿  阅读(129)  评论(0)    收藏  举报