1. 程式人生 > 其它 >實戰-DRF快速寫介面(認證許可權頻率)

實戰-DRF快速寫介面(認證許可權頻率)

實戰-DRF快速寫介面

開發環境

  • Python3.6
  • Pycharm專業版2021.2.3
  • Sqlite3
  • Django 2.2
  • djangorestframework3.13

測試工具

Postman

需求

  • 註冊介面,包含欄位使用者名稱,密碼,確認密碼,使用者型別
  • 登陸介面,校驗使用者名稱,密碼,生成隨機字串
  • 認證功能,除了註冊登陸介面外,所有介面都要登陸後訪問
  • 頻率限制功能,每分鐘訪問5次,book的所有介面,使用這個頻率類
  • 許可權限制功能,publish的所有操作需要超級使用者能訪問,其他的普通登陸使用者就可以操作

模型

from django.db import models

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=255)
    user_type = models.IntegerField(choices=((1, '超級管理員'), (2, '普通管理員'), (3, '普通使用者')))


class UserToken(models.Model):
    user = models.OneToOneField(to=User,on_delete=models.CASCADE)
    token = models.CharField(max_length=32)


class Book(models.Model):
    title = models.CharField(max_length=11)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    authors = models.ManyToManyField(to='Author')
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)

    @property
    def publish_detail(self):
        return {'name': self.publish.name,'Email':self.publish.email}

    @property
    def author_list(self):
        l = []
        print(self.authors.all())  # <QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]>
        for author in self.authors.all():
            print(author.authordetail)  # AuthorDetail object (1)
            l.append({'name': author.username, 'gender': author.gender,
                      'address': author.authordetail.address,'telephone':author.authordetail.telephone})
        return l




class Author(models.Model):
    username = models.CharField(max_length=11)
    gender = models.IntegerField(choices=((1, '男'), (2, '女'), (3, '未知')))
    authordetail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)

    @property
    def authordetail_info(self):
        return {'telephone': self.authordetail.telephone, 'address': self.authordetail.address}

class AuthorDetail(models.Model):
    telephone = models.BigIntegerField()
    address = models.CharField(max_length=32)


class Publish(models.Model):
    name = models.CharField(max_length=11)
    email = models.EmailField()


序列化器

from rest_framework import serializers

from .models import *

# 使用者序列化器
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'


# 書序列化器
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        # fields = '__all__'
        fields = ['id', 'title', 'price', 'publish', 'authors', 'publish_detail', 'author_list']

        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True},
        }



# 作者序列化器
class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        # 指定和哪個表有關係
        model = Author
        # fields = '__all__'
        fields = ['id', 'username', 'gender', 'telephone', 'address', 'authordetail_info']

    # 重寫欄位telephone和addr
    telephone = serializers.CharField(write_only=True)
    address = serializers.CharField(write_only=True, max_length=8, required=False)

    # 重寫create,操作兩個表
    def create(self, validated_data):
        # 先存作者詳情
        authordetail = AuthorDetail.objects.create(telephone=validated_data.get('telephone'),
                                                   address=validated_data.get('address'))
        # 存作者表
        author = Author.objects.create(author_detail=authordetail, gender=validated_data.get('gender'),
                                       username=validated_data.get('username'))
        # 這樣只返回author物件就行,直接存了兩個表,返回反序列化的物件
        return author


# 出版社序列化器
class PublishSerializer(serializers.ModelSerializer):
    class Meta():
        model = Publish
        fields = '__all__'

檢視

from django.contrib.auth.hashers import make_password, check_password
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from rest_framework.viewsets import ModelViewSet
from .auth import *
from .serializer import *
from .models import *


# 註冊檢視
class UserRegisterView(ViewSet):
    @action(methods=["POST"], detail=False)
    def register(self, request):
        usernmae = request.data.get('username')
        password = request.data.get('password')
        re_password = request.data.get('re_password')
        user_type = request.data.get('user_type')
        if User.objects.filter(username=usernmae):
            return Response({'msg': f'使用者{usernmae}已註冊!', 'code': 4000})
        else:
            if password == re_password:
                # make_password加密:make_password(password, salt=None, hasher='default')
                user_date = {'username': usernmae, 'password': make_password(password), 'user_type': user_type}
                user_serializer = UserSerializer(data=user_date)
                if user_serializer.is_valid():
                    user_serializer.save()
                    return Response({'code': 2001, 'msg': f'使用者{usernmae}註冊成功'})
                else:
                    return Response({'code': 4001, 'msg': '註冊失敗', 'errors': user_serializer.errors})
            else:
                return Response({'msg': '兩次密碼不一致', 'code': 4002})


# 登入檢視
class UserLoginView(ViewSet):

    @action(methods=["POST"], detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = User.objects.filter(username=username).first()
        # check_password(password, encoded, setter=None, preferred='default')
        if user and check_password(password, user.password):
            import uuid
            token = str(uuid.uuid4())
            UserToken.objects.update_or_create(user=user, defaults={'token': token})
            return Response({'code': 2000, 'msg': f'使用者{user.username}登入成功', 'token': token})
        return Response({'code': 4004, 'msg': '校驗失敗,使用者名稱或密碼錯誤'})


# 書接檢視
class BookView(ModelViewSet):
    authentication_classes = [LoginAuth,]
    throttle_classes = [IPThrottle,]
    queryset = Book.objects.all()
    serializer_class = BookSerializer

# 作者檢視
class AuthorView(ModelViewSet):
    authentication_classes = [LoginAuth,]
    queryset = Author.objects.all()
    serializer_class = AuthorSerializer


# 出版社檢視
class PublishView(ModelViewSet):
    authentication_classes = [LoginAuth, ]
    permission_classes = [UserPermission,]
    queryset = Publish.objects.all()
    serializer_class = PublishSerializer

認證許可權頻率

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.permissions import BasePermission
from rest_framework.throttling import SimpleRateThrottle

from app01 import models


# 認證類
class LoginAuth(BaseAuthentication):
    # 重寫authenticate方法
    def authenticate(self, request):
        # 獲取前端攜帶的token,token放在哪是自己規定的,比如從查詢引數中獲取
        token = request.query_params.get('token')
        # 比對隨機字串
        user_token = models.UserToken.objects.filter(token=token).first()
        if user_token:
            # 登入了,返回當前登入使用者和token
            return user_token.user, token
        else:
            # 沒有登入,拋異常
            raise AuthenticationFailed('您沒有登入,請登入')


# 許可權類
class UserPermission(BasePermission):
    def has_permission(self, request, view):
        # 沒有許可權的提示資訊
        self.message = '您是:%s,沒有許可權' % request.user.get_user_type_display()
        # 如果有許可權,返回True,沒有許可權返回False
        # 許可權類,在認證類之後,request.user有了當前登入使用者
        user_type = request.user.user_type
        print(user_type)
        if user_type < 3:  # 只要不是1,2,就沒有許可權
            return True
        else:
            return False


# 頻率類
class IPThrottle(SimpleRateThrottle):
    scope = 'ip'

    # get_cache_key返回什麼就以什麼方法做限制,限制條件必須唯一,比如使用者id
    def get_cache_key(self, request, view):
        # 限制ip地址,從request.META字典中獲取ip
        '''
        request.META:請求頭中的資料
        '''
        return request.META.get('REMOTE_ADDR')  # 客戶端ip

配置檔案

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'ip': '5/m'  # minute_3是scope的字串,一分鐘訪問5次
    }, }

路由

from django.contrib import admin
from django.urls import path,include
from app01 import views
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserLoginView,'user')
router.register('user',views.UserRegisterView,'user')


router.register('books',views.BookView,'books')
router.register('author',views.AuthorView,'author')
router.register('publish',views.PublishView,'publish')


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls)),
]

測試

下面是普通使用者,403了~