DRF 路由和认证

215 阅读4分钟

这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战

路由

# 继承了ViewSetMixin和GenericAPIView和类可以使用drf提供了路由系统,自动生成url,
# 也可以说继承了视图集GenericViewSet的子类都可以使用路由系统
# 需要注意,此时默认通过actions分配的请求方法是五个视图扩展类的方法
# 所以简便使用路由系统时最好搭配五个视图扩展类,也可以自己实现,如list方法

使用方法:

# views.py中的视图类满足继承要求,视图集合类
# urls.py
1:导入routers模块
	from rest_framework import routers
2:通过两个类中的一个实例化对象
	 routers.DefaultRouter 生成的路由个数更多[华而不实]
     routers.SimpleRouter	个人推荐
     router = routers.SimpleRouter()
3:注册
	# router.register('前缀','继承自ModelViewSet视图类','别名')
     router.register('books', views.BookViewSet) # 不要加斜杠了
4:增加到urlpattern中
	# router.urls # 自动生成的路由,两条
	urlpatterns += router.urls

代码实现

# views.py
class BookModelViewSet(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = ser.BookModelSerializer

    def list(self, request, *args, **kwargs):
        book_list = self.get_queryset()[:3]
        ser_obj = self.get_serializer(book_list, many=True)
        return Response(ser_obj.data)

    
# urls.py
# 第一步
from rest_framework import routers
from app01 import views
# 第二步
router = routers.SimpleRouter()
# 第三步
router.register(prefix='books', viewset=views.BookModelViewSet)

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]
# 第四步
urlpatterns += router.urls


# 新增的两条url
print(router.urls)
[
    <RegexURLPattern book-list ^books/$>, 
    <RegexURLPattern book-detail ^books/(?P<pk>[^/.]+)/$>
]

action装饰器

# 用途
	为了给继承自ModelViewSet的视图类中定义的函数也添加路由
# 使用
	from rest_framework.decorators import action
    class BookModelViewSet(ModelViewSet):

        queryset = models.Book.objects.all()
        serializer_class = ser.BookModelSerializer

        @action(methods=['get'], detail=False)
        def get_top3(self, request):
            print(request.user.username)
            book_list = self.get_queryset()[:3]
            ser_obj = self.get_serializer(book_list, many=True)
            return Response(ser_obj.data)
# 参数说明:
	methods参数赋值一个列表,放可以访问该方法的请求方式
    detail参数是布尔值:
    	detail=False表示方法是不使用pk;^books/get_top3/$
        反之使用pk参数;^books/(?P<pk>[^/.]+)/get_top3/$
        
# 总结:action装饰器,放在被装饰的函数上方,method:请求方式,detail:是否带pk

认证的使用

# 使用流程
1 写一个类,继承BaseAuthentication,重写authenticate,写认证的逻辑;
2 认证通过,返回两个值,一个值最终给了Requet对象的user;
3 认证失败,抛异常:APIException或者AuthenticationFailed

# 使用范围
	全局使用,项目的配置文件
	局部使用,写在视图类中

认证的配置

# 全局配置,项目的settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.app_auth.LoginAuthentication", ]
}

# 局部配置,当前视图类使用
authentication_classes = [LoginAuthentication, ]

# 局部禁用
authentication_classes = []

# 可以有多个认证,按照列表中的顺序以此执行

认证源码分析

# APIView的dispatch方法,替换了原生django的当次请求对象
# 替换的过程是实例化一个drf的Request对象,并给该request对象传入一个存放认证器类对象的列表
# dispatch方法内调用initial方法,在initial方法内调用登陆认证方法perform_authentication()

# 在该登陆认证方法内,调用request.user,[user是request对象的方法伪装成了数据属性]
# request的user内调用reuest的_authenticate方法是登陆认证的核心所在

# 在reuest的_authenticate方法内,循环request对象保存的认证器对象列表,以此校验
# 循环过程中,调用认证器对象的authenticate方法,并把request传进去。
# 这是就开始执行我们自己写的认证类对象的authenticate方法,
# 在authenticate方法内,认证成功,返回两个值;认证失败,抛出AuthenticationFailed异常

# 认证成功,authenticate方法需要返回两个数据分别赋值给request.user和request.auth

# 因此,认证通过进入视图后,可以在request中取出当前登陆用户对象request.user

核心源码

# request对象的_authenticate方法内
def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        依次调用每一个认证类对象的authenticate方法,并传入request对象
    	self.user, self.auth = user_auth_tuple 这是为啥认证成功要返回两个值的原因
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

代码案例

  • models.py
from django.db import models

# Create your models here.
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8,decimal_places=2)
    publish = models.CharField(max_length=32)

    def __str__(self):
        return self.name


class Username(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    user_type = models.IntegerField(choices=((1,'普通用户'),(2,'超级用户')))


class UserToken(models.Model):
    user = models.OneToOneField(to='Username',on_delete=models.DO_NOTHING)
    token = models.CharField(max_length=128)
  • permission.py(自定义认证类)‘
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
# 这里也可以写APIException, AuthenticationFailed继承了APIException
from app01.models import UserToken

class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 认证逻辑,如果认证通过返回true,失败返回false
        token = request.META.get('HTTP_TOKEN')
        if token:
            user_token_obj = UserToken.objects.filter(token=token).first()
            if user_token_obj:
                return user_token_obj.user,token
            
            raise AuthenticationFailed('认证失败')
        raise AuthenticationFailed('请求地址中需要携带token')
  • settings.py全局配置
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.permission.MyAuthentication",]
}

  • views.py
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
# Create your views here.
from app01 import models
from app01.ser import BookModelSerializer
class BookModelView(ModelViewSet):

    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer

from rest_framework.views import APIView
from rest_framework.response import Response
import uuid

class Login(APIView):
    authentication_classes = []
    def post(self,request):
        name = request.data.get('name')
        age = request.data.get('age')
        user_obj = models.Username.objects.filter(name=name,age=age).first()
        if user_obj:
            token = uuid.uuid4()
            models.UserToken.objects.update_or_create(defaults={'token':token},user=user_obj)
            return Response(data={'status':100,'msg':'登陆成功','token':token})
        return Response({'status':100,'msg':'用户名或者年龄错误',})

  • urls.py
from django.urls import path
from app01 import views

from rest_framework import routers
router = routers.SimpleRouter()
router.register('books',viewset=views.BookModelView)


urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/',views.Login.as_view()),
]
urlpatterns += router.urls

内置认证方案(需要配合权限使用)

可以在配置文件中配置全局默认的认证方案

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',  # session认证
        'rest_framework.authentication.BasicAuthentication',   # 基本认证
    )
}

也可以在每个视图中通过设置authentication_classess属性来设置

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView

class ExampleView(APIView):
    # 类属性
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    ...

结语

文章首发于微信公众号程序媛小庄,同步于掘金

码字不易,转载请说明出处,走过路过的小伙伴们伸出可爱的小指头点个赞再走吧(╹▽╹)