【认证】
models.py
1 from django.db import models 2 3 4 # Create your models here. 5 class User(models.Model): 6 username = models.CharField(max_length=50) 7 password = models.CharField(max_length=50) 8 user_type = models.IntegerField(choices=((1, '普通用户'), (2, '普通管理员'), (3, '超级管理员')), default=1) 9 # 认证登录的用户是否是user里的用户 10 @property 11 def is_authenticated(self): 12 return True 13 14 15 class UserToken(models.Model): 16 user = models.OneToOneField(User, on_delete=models.CASCADE) 17 token = models.CharField(max_length=500) 18 19 20 class Book(models.Model): 21 name = models.CharField(max_length=50) 22 publish = models.CharField(max_length=50) 23 price = models.IntegerField() 24 25 def __str__(self): 26 return self.name
VIEWS.PY
# ============================================认证类 # 登录接口 class UserView(ViewSet): authentication_classes = [] # 登录接口局部禁用 permission_classes = [] @action(methods=['POST'], detail=False) def login(self, request): # 用户名,密码在request.data中 username = request.data.get('username') password = request.data.get('password') # 验证用户名和密码 user = User.objects.filter(username=username, password=password).first() if user: # 验证通过,生成随机字符串,存到usertoken中 # 之前存在就修改,没有就新增 token = str(uuid.uuid4()) UserToken.objects.update_or_create(user=user, defaults={'token': token}) return Response({'code': 200, 'msg': '登录成功', 'token': token}) else: return Response({'code': 400, 'msg': '用户名或密码错误'})
=======================================================
authenticate.py 里面代码
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app05.models import UserToken
class LoginAuth(BaseAuthentication):
重写父类里面的authenticate方法
def authenticate(self, request):
# request是新的,从request中取出用户携带的token
# 放在请求头中
token = request.META.get('HTTP_TOKEN')
user_token = UserToken.objects.filter(token=token).first()
if user_token:
user = user_token.user
return user, token
else:
raise AuthenticationFailed('认证失败')
-----------------------------------
-----------------------------------
总结:
# 1 写一个认证类,继承BaseAuthentication
# 2 重写authenticate方法,在该方法在中实现登录认证:token在哪带的?
# 3 如果认证成功,返回两个值【返回None或两个值】
# 4 认证不通过,抛异常AuthenticationFailed
# 5 局部使用和全局使用
-局部:只在某个视图类中使用( 当前视图类管理的所有接口都会在运行前进行认证的校验 )
class BookDetailView(ViewSetMixin, RetrieveAPIView):
authentication_classes = [LoginAuth]
-----------------------------------
-全局:全局所有接口都生效(但是登录接口不能要,其他接口需要登录后才能访问,登录接口不可能还需要登录后才能访问,那就没法登录了!!!)
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES':['app05.authenticate.LoginAuth']}
-----------------------------------
-所以要局部禁用 把authentication_classes设为空列表 取消掉全局的登录认证:
class UserView(ViewSet):
authentication_classes = []
@action(methods=['POST'], detail=False)
def login(self, request):
pass
.。
。
。
。
。
(权限)
views.py
# 权限,需要普通用户以上才能访问,但是必须要认证通过
class BooksView(GenericViewSet, ListModelMixin, DestroyModelMixin, CreateModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer # authentication_classes = [LoginAuth]
# 局部限制
permission_classes = []
===========================================
permissions.py文件
from rest_framework.permissions import BasePermission
# 权限,只有超级用户才能访问,其他用户没有权限
class UserPermission(BasePermission):
def has_permission(self, request, view):
# 判断权限,如果有权限,返回True,否则返回False
# 认证通过,拿到当前登录用户,request.user中拿到
# is_authenticated在用户表中没有,需要去重新定义
if request.user.is_authenticated:
if request.user.user_type == 3:
return True
else:
# 定制提示信息,get_user_type_display拿到数字对应的字符串
user_type = request.user.get_user_type_display()
self.message = f'你是{user_type}没有权限访问'
return False
else:
self.message = '请先登录'
return False
----------------------------------
总结
# 1 写一个权限类,继承BasePermission
# 2 重写has_permission方法,在该方法在中实现权限认证,在这方法中,request.user就是当前登录用户
# 3 如果有权限,返回True
# 4 没有权限,返回False,定制返回的中文: self.message='中文'
# 5 局部使用和全局使用
-局部:只在某个视图类中使用【当前视图类管理的所有接口】
class BookDetailView(ViewSetMixin, RetrieveAPIView):
permission_classes = [CommonPermission]
-------------------------------------
-全局:全局所有接口都生效
REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': [
'app01.permissions.CommonPermission'],
}
-如果要全局生效,必须配合局部禁用,在不需要权限认证的视图类里面,把permission_classes设为空列表
。
。
。
。
(频率限制)
views.py
# 需求:查询所有接口,同一个ip一分钟只能访问3次
# 频率类,不继承BaseThrottle,继承SimpleRateThrottle,目的少写代码
# 重写SimpleRateThrottle里面的 get_cache_key 方法
class PublishView(GenericViewSet): # 方式一:做限制,局部限制使用 # permission_classes = [UserPermission] # 频率限制使用 throttle_classes = [CustomThrottle] def list(self, request): return Response('所有数据')
================================
throttling.py文件
# 频率得使用
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
class CustomThrottle(SimpleRateThrottle):
# 1分钟3次
rate = '3/m'
def get_cache_key(self, request, view):
# 返回什么,就会以什么做限制---ip地址限制:用户id
return request.META.get('REMOTE_ADDR')
--------------------------------------
总结
# 1 写一个频率类,继承SimpleRateThrottle
# 2 重写get_cache_key方法,返回什么,就以什么做限制----》ip,用户id做限制
# 3 配置一个类属性:scope = 'book_5_m'
# 4 在配置文件中配置
'DEFAULT_THROTTLE_RATES': {
'book_5_m': '5/m',
},
# 5 局部使用和全局使用
-局部:只在某个视图类中使用【当前视图类管理的所有接口】
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
throttle_classes = [CommonThrottle] # 局部配置
-全局:全局所有接口都生效
REST_FRAMEWORK = {'DEFAULT_THROTTLE_RATES': {'rate888': '5/m',},
'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.CommonThrottle'],
}
-要配合局部禁用,将不需要频率限制的接口的视图类里面,把throttle_classes设为空列表 :
。
。
。
(分页。排序。过滤)
1 # 查询所有图书接口 2 class BookListView(GenericViewSet, ListModelMixin): 3 queryset = Book.objects.all() 4 serializer_class = BookSerializer 5 permission_classes = [] 6 throttle_classes = [] 7 # 分页 8 # pagination_class = CommonPageNumberPagination 9 # pagination_class = CommonLimitOffsetPagination 10 pagination_class = CommonCursorPagination 11 12 # 排序,必须继承GenericAPIView,在视图类中配置 13 # filter_backends = [OrderingFilter] 14 # 按照哪个字段排序,默认按照id排序,按照字段排序以后是按照价格升序,如果想降序:ordering=-price 15 # ordering_fields = ['price'] 16 17 # 过滤,可以和排序一起用 18 # filter_backends = [OrderingFilter, SearchFilter] 19 # ordering_fields = ['price', 'name'] 20 # # 也可以多个字段模糊匹配 21 # search_fields = ['name', 'publish'] 22 23 # 第三方过滤==============按照名字精准匹配 pip3 install django-filter 24 # filter_backends = [OrderingFilter, DjangoFilterBackend] 25 # filterset_fields = ['name', 'price'] 26 27 # 自定义过滤 28 filter_backends = [CommonFilter] 29 30 # 排序之定制返回格式 31 # def list(self, request, *args, **kwargs): 32 # qs = self.get_queryset() 33 # ser = BookSerializer(instance=qs, many=True) 34 # return Response({'code': 200, 'msg': '请求成功', 'results': ser.data})
================================================================================================分页
page.py
from rest_framework.pagination import LimitOffsetPagination, PageNumberPagination, CursorPagination
# 基本分页
class CommonPageNumberPagination(PageNumberPagination):
page_size = 2 # 每页显示2条
page_query_param = 'page' # http://127.0.0.1:8008/app01/api/v1/books1/?page=2
page_size_query_param = 'size' # http://127.0.0.1:8008/app01/api/v1/books1/?page=2&size=3 查询第二页,每页显示3条
max_page_size = 10 # 每页最多显示10条
# 偏移分页
class CommonLimitOffsetPagination(LimitOffsetPagination):
default_limit = 2 # 每页显示2条
limit_query_param = 'limit' # 控制每页显示多少条
# http://127.0.0.1:8013/app05/api/v1/books1/?offset=3 从第3条开始,取两条
# http://127.0.0.1:8013/app05/api/v1/books1/?offset=3&limit=1 从第3条开始,取1条
offset_query_param = 'offset' # 偏移量
max_limit = 10
# 游标分页
class CommonCursorPagination(CursorPagination):
cursor_query_param = 'cursor' # 查询条件 http://127.0.0.1:8008/app01/api/v1/books1/?cursor=asfasf
page_size = 2
ordering = 'id' # 排序字段,只能上一页,下一页这样选择,聊率高,字段必须是模型表中有的
==========================================================================================过滤
# 自定义过滤类
from django.db.models import Q
from rest_framework.filters import BaseFilterBackend
class CommonFilter(BaseFilterBackend):
# 必须重写这个方法
def filter_queryset(self, request, queryset, view):
# 完成过滤,返回 qs对象
# 查询价格为66 或者 名字中包含海的数据
# http://127.0.0.1:8008/app01/api/v1/books1/?price=42&name=红
price = request.query_params.get('price', None)
name = request.query_params.get('name', None)
if price and name:
queryset = queryset.filter(Q(price=price) | Q(name__contains=name))
if price:
# queryset=queryset.filter(Q(price=price) | Q(name__contains=name))
queryset = queryset.filter(price=price)
if name:
queryset = queryset.filter(name__contains=name)
return queryset
PS:补充
# 安装:django-filter
pip3.8 install django-filter==22.1 -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com/simple/
腾讯源太垃圾了,用豆瓣
路由
全局生效的settings里面的配置