1、路由Routers
对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息
REST framework提供了两个router
- SimpleRouter
- DefaultRouter
1.1、使用方法
1.1.1、创建router对象,并注册视图集
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'router_book', BookModelViewSet, base_name='book')
register(prefix, viewset, base_name)
- prefix 该视图集的路由前缀
- viewset 视图集
- base_name 路由别名的前缀
如上述代码会形成的路由如下
^router_book/$ [name='book-list']
^router_book/(?P<pk>[^/.]+)/$ [name='book-detail']
1.1.2、添加路由数据
1.1.2.1、方式一
urlpatterns = [
...
]
urlpatterns += router.urls
1.1.2.2、方式二
urlpatterns = [
...
url(r'^', include(router.urls))
]
1.2、示例
使用路由类给视图集生成了路由地址
# 必须是继承ModelViewSet的视图类才能自动生成路由
from rest_framework.viewsets import ModelViewSet,ReadOnlyModelViewSet
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# 这种方法不会自动生成,需要用action配置
def login(self,request):
"""学生登录功能"""
print(self.action)
return Response({"message":"登录成功"})
路由配置
from django.urls import path, re_path
from . import views
urlpatterns = [
...
]
"""使用drf提供路由类router给视图集生成路由列表"""
# 实例化路由类
# drf提供一共提供了两个路由类给我们使用,他们用法一致,功能几乎一样
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
# 注册视图集
# router.register("路由前缀",视图集类)
router.register("router_stu",views.StudentModelViewSet)
# 把生成的路由列表追加到urlpatterns
print( router.urls )
urlpatterns += router.urls
上面的代码就成功生成了路由地址[增/删/改/查一条/查多条的功能],但是不会自动我们在视图集自定义方法的路由
所以我们如果也要给自定义方法生成路由,则需要进行action动作的声明
1.3、视图集中附加action的声明
在视图集中,如果想要让Router自动帮助我们为自定义的动作生成路由信息,需要使用rest_framework.decorators.action装饰器。
以action装饰器装饰的方法名会作为action动作名,与list、retrieve等同
action装饰器可以接收两个参数:
- methods::声明该action对应的请求方式,列表传递
- detail:声明该action的路径是否与单一资源对应
xxx/<pk>/action方法名/
True 表示路径格式是xxx/<pk>/action方法名/
False 表示路径格式是xxx/action方法名/
示例
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# methods 设置当前方法允许哪些http请求访问当前视图方法
# detail 设置当前视图方法是否是操作一个数据
# detail为True,表示路径名格式应该为 router_stu/{pk}/login/
@action(methods=['get'], detail=False)
def login(self, request):
return Response({'msg': '登陆成功'})
@action(methods=['put'], detail=True)
def get_new_5(self, request,pk):
return Response({'msg': '获取5条数据成功'})
由路由器自动为此视图集自定义action方法形成的路由会是如下内容:
^router_stu/get_new_5/$ name: router_stu-get_new_5
^router_stu/{pk}/login/$ name: router_stu-login
1.4、路由router形成URL的方式
1.4.1、SimpleRouter
1.4.2、DefaultRouter
DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据
2、认证Authentication
2.1、自定义认证方案
2.1.1、models
class User(models.Model):
username=models.CharField(max_length=32)
password=models.CharField(max_length=32)
user_type=models.IntegerField(choices=((1,'超级用户'),(2,'普通用户'),(3,'初级用户')))
class UserToken(models.Model):
user=models.OneToOneField(to='User')
token=models.CharField(max_length=64)
2.1.2、新建认证类
2.1.2.1、方式一
from rest_framework.authentication import BaseAuthentication
class TokenAuth():
def authenticate(self, request):
token = request.GET.get('token')
token_obj = models.UserToken.objects.filter(token=token).first()
if token_obj:
return
else:
raise AuthenticationFailed('认证失败')
def authenticate_header(self,request):
pass
2.1.2.2、方式二
from rest_framework.authentication import BaseAuthentication
class TokenAuth(BaseAuthentication):
def authenticate(self, request):
token = request.GET.get('token')
token_obj = models.UserToken.objects.filter(token=token).first()
if token_obj:
return
else:
raise AuthenticationFailed('认证失败')
2.1.3、视图
def get_random(name):
import hashlib
import time
md=hashlib.md5()
md.update(bytes(str(time.time()),encoding='utf-8'))
md.update(bytes(name,encoding='utf-8'))
return md.hexdigest()
class Login(APIView):
def post(self,reuquest):
back_msg={'status':1001,'msg':None}
try:
name=reuquest.data.get('name')
pwd=reuquest.data.get('pwd')
user=models.User.objects.filter(username=name,password=pwd).first()
if user:
token=get_random(name)
models.UserToken.objects.update_or_create(user=user,defaults={'token':token})
back_msg['status']='1000'
back_msg['msg']='登录成功'
back_msg['token']=token
else:
back_msg['msg'] = '用户名或密码错误'
except Exception as e:
back_msg['msg']=str(e)
return Response(back_msg)
class Course(APIView):
authentication_classes = [TokenAuth, ]
def get(self, request):
return HttpResponse('get')
def post(self, request):
return HttpResponse('post')
2.1.4、全局使用
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["TokenAuth的名称空间",]
}
2.1.5、局部使用
# 局部使用,只需要在视图类里加入:
authentication_classes = [TokenAuth, ] # TokenAuth需要导入
2.2、内置认证方案(需要配合权限使用)
可以在配置文件中配置全局默认的认证方案
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]
...
认证失败会有两种可能的返回值:
- 401 Unauthorized 未认证
- 403 Permission Denied 权限被禁止
2.3、认证功能源码分析
APIView —> dispatch —> self.initial(request, *args, **kwargs) —> self.perform_authentication(request) —> Request.user —> self._authenticate(self):Request类的方法 —> self.authenticators:Request类的属性 —> 在Request对象实例化的时候传入的 —> Request的实例化是在dispatch的时候 —> APIView:self.get_authenticators() —> return [auth() for auth in self.authentication_classes] —> 如果在自己定义的视图类中写了authentication_classes=[类1,类2] —> Request的self.authenticators就变成了我们配置的一个个类的对象
3、权限Permissions
权限控制可以限制用户对于视图的访问和对于具体数据对象的访问
- 在执行视图的dispatch()方法前,会先进行视图访问权限的判断
- 在通过get_object()获取具体对象时,会进行模型对象访问权限的判断
3.1、自定义权限
3.1.1、编写权限类
# 限制只有超级用户能访问
from rest_framework.permissions import BasePermission
class UserPermission(BasePermission):
message = '不是超级用户,查看不了'
def has_permission(self, request, view):
# user_type = request.user.get_user_type_display()
# if user_type == '超级用户':
# 权限在认证之后,所以能取到user
user_type = request.user.user_type
print(user_type)
if user_type == 1:
return True
else:
return False
3.1.2、全局使用
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["TokenAuth的名称空间",]
"DEFAULT_PERMISSION_CLASSES":["UserPermission的名称空间",]
}
3.1.3、局部使用
# 局部使用只需要在视图类里加入:
permission_classes = [UserPermission,]
3.1.4、说明
如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部
has_permission(self, request, view): 是否可以访问视图, view表示当前视图对象
has_object_permission(self, request, view, obj): 是否可以访问数据对象, view表示当前视图, obj为数据对象
3.2、内置权限
3.2.1、内置权限类
from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly
AllowAny 允许所有用户
IsAuthenticated 仅通过认证的用户
IsAdminUser 仅管理员用户
IsAuthenticatedOrReadOnly 已经登陆认证的用户可以对数据进行增删改操作,没有登陆认证的只能查看数据。
3.2.2、全局使用
可以在配置文件中全局设置默认的权限管理类
REST_FRAMEWORK = {
....
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
如果未指明,则采用如下默认配置
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
)
3.2.3、局部使用
也可以在具体的视图中通过permission_classes属性来设置
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = (IsAuthenticated,)
...
3.2.4、实际操作
# 创建超级用户,登陆到admin,创建普通用户(注意设置职员状态,也就是能登陆)
# 全局配置IsAuthenticated
# setting.py
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
# urls.py
path('test/', views.TestView.as_view()),
# views.py
class TestView(APIView):
def get(self,request):
return Response({'msg':'个人中心'})
# 登陆到admin后台后,直接访问可以,如果没登陆,不能访问
##注意:如果全局配置了
rest_framework.permissions.IsAdminUser
# 就只有管理员能访问,普通用户访问不了
3.3、权限源码分析
APIView的dispatch ---> APIView的initial ---> APIView的check_permissions(request)
for permission in self.get_permissions(): # 权限类对象放到列表中
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
通过阅读源码, 我们知道错误信息的中文显示
在权限类中加一个 message=字符串