一、认证组件
简介:
-
登录认证的限制
-
认证组件是drf框架给我们提供的认证接口,它能够在请求进入视图函数/类前进验证(例如:认证用户是否登录),对不符合认证的请求进行拦截并返回校验失败的信息
(1)、登录接口
|
# 认证是基于登录的接口上面操作的 所以前戏编写一个简单的登录接口 |
|
|
|
models.py |
|
class User(models.Model): # 简易的用户信息账号密码 |
|
username = models.CharField(max_length=32) |
|
password = models.CharField(max_length=32) |
|
|
|
def __str__(self): |
|
return self.username |
|
|
|
'跟User表是一对一外键关联,存储用户登录状态用的 [这个表可以没有,如果没有,把字段直接写在User表上也可以]' |
|
class UserToken(models.Model): # 用户信息登录记录表 |
|
user = models.OneToOneField(to='User', on_delete=models.CASCADE) # 一对一关联 |
|
token = models.CharField(max_length=32, null=True) # 如果用户没有登录则没有值 如果登录则有值 |
|
|
|
views.py |
|
'登录接口功能:自动生成路由+登录功能,不用序列化,因此继承ViewSet即可' |
|
class UserView(ViewSet): |
|
|
|
def login(self, request): |
|
username = request.data.get('username') # 获取用户名与密码 |
|
password = request.data.get('password') |
|
user = User.objects.filter(username=username, password=password).first() # 比对用户名与密码 |
|
if user: |
|
token = str(uuid.uuid4()) |
|
# uuid4 随机获得永不重复的字符串 机制跟Cookie中的验证码一样 |
|
# 在userToken表中存储一下:1 从来没有登录过,插入一条, 2 登录过,修改记录 |
|
|
|
|
|
UserToken.objects.update_or_create(defaults={'token': token}, user=user) |
|
# 通过user去UserToken表中查数据,如果能查到,使用defaults的数据更新,如果查不到,直接通过user和defaults的数据新增 |
|
# kwargs 传入的东西查找,能找到,使用defaults的更新,否则新增一条 |
|
return Response({'code': 100, 'msg': '登录成功', 'token': token}) |
|
else: |
|
return Response({'code': 101, 'msg': '用户名或密码错误'}) |
|
|
|
urls.py |
|
from rest_framework.routers import SimpleRouter, DefaultRouter |
|
router = SimpleRouter() |
|
router.register('users', views.UserView, 'users') |
|
urlpatterns += router.urls |
|
|
|
'''这个时候一个简单的登录接口就写好了 每次登录都会更新Token 相当于登录了之前的设备就无效了 ''' |
update_or_create
源码如下:
|
def update_or_create(self, defaults=None, **kwargs): |
|
defaults = defaults or {} |
|
self._for_write = True |
|
with transaction.atomic(using=self.db): |
|
try: |
|
obj = self.select_for_update().get(**kwargs) |
|
except self.model.DoesNotExist: |
|
params = self._extract_model_params(defaults, **kwargs) |
|
obj, created = self._create_object_from_params(kwargs, params, lock=True) |
|
if created: |
|
return obj, created |
|
for k, v in defaults.items(): |
|
setattr(obj, k, v() if callable(v) else v) |
|
obj.save(using=self.db) |
|
return obj, False |
(2)、认证组件使用步骤
1.需要写一个认证类,因此我们需要在应用中另外创建一个py文件编写认证类,需要继承
BaseAuthentication
这个类
-
通过查看源码我们可以发现有个
authenticate
方法需要我们重写,否则就会报错,这就是我们需要编写认证功能的类
|
class BaseAuthentication: |
|
def authenticate(self, request): |
|
raise NotImplementedError(".authenticate() must be overridden.") |
|
|
|
def authenticate_header(self, request): |
|
pass |
2.重写
authenticate
方法,在该方法在中实现登录认证
-
token
在哪带的?如何认证它是登录了的? -
用
token
来判断是否登陆,登陆了在访问的时候带上token
,目前阶段我们直接在地址栏中携带token
的数据,后面可以在请求头中添加token
的数据
3、如果认证成功,返回两个值【返回
None
或两个值(固定的:当前登录用户,token)】
4、认证不通过,用
AuthenticationFailed
类抛异常
代码如下: authenticate.py(认证类)
|
# 自己写的认证类,继承某个类 |
|
|
|
from rest_framework.authentication import BaseAuthentication |
|
from rest_framework.exceptions import AuthenticationFailed |
|
from .models import UserToken |
|
|
|
|
|
class LoginAuth(BaseAuthentication): |
|
def authenticate(self, request): |
|
# 在这里实现认证,如果是登录的,继续往后走返回两个值,如果不是抛异常 |
|
# 请求中是否携带token,判断是否登录,放在地址栏中 |
|
token = request.query_params.get('token', None) # 查找是否有token这个变量名的值,如果没有就返回None,默认好像返回的是数字 |
|
if token: # 前端传入token了,去表中查,如果能查到,登录了,返回两个值[固定的:当前登录用户,token] |
|
user_token = UserToken.objects.filter(token=token).first() |
|
if user_token: |
|
return user_token.user, token |
|
else: |
|
# 没有登录抛异常 |
|
raise AuthenticationFailed('token认证失败') |
|
else: |
|
raise AuthenticationFailed('token没传') |
|
|
|
# 前端传入的请求头中的数据从哪取? GET,body,POST,data |
5、认证类的使用
- 当我们编写好了认证类中的认证代码,接着就需要导入到视图层然后使用他
|
from rest_framework.generics import ListAPIView, RetrieveAPIView |
|
from rest_framework.viewsets import ViewSetMixin |
|
from .authenticate import LoginAuth |
|
|
|
# 查询所有 |
|
class BookView(ViewSetMixin, ListAPIView): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
|
|
|
|
|
|
|
|
class BookDetailView(ViewSetMixin, RetrieveAPIView): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
authentication_classes = [LoginAuth] # 需要写一个认证类,需要咱们自行编写 |
6、局部使用和全局使用
- 局部使用:只在某个视图类中使用【当前视图类管理的所有接口】
|
class BookDetailView(ViewSetMixin, RetrieveAPIView): |
|
authentication_classes = [LoginAuth] |
-
全局使用:在配置文件
settings.py
中编写,全局所有接口都生效
|
REST_FRAMEWORK = { |
|
'DEFAULT_AUTHENTICATION_CLASSES':['app01.authenticate.LoginAuth'] |
|
} |
注意事项:不要在配置文件中乱导入不使用的东西,否则会报错,但是在导入类似认证类这样的文件时,可以写上导入的代码然后再修改,最后写进配置中,这样可以减少错误
-
局部禁用:(登陆接口很明显是不需要校验是否登陆的,因此有了这个局部禁用的需求,我们把他的
authentication_classes
配置成空就是局部禁用)
|
class BookDetailView(ViewSetMixin, RetrieveAPIView): |
|
authentication_classes = [] |
7、测试路由参考
(3)、整体代码
views.py
|
# 查询所有 |
|
class BookView(ViewSetMixin, ListAPIView): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
|
|
|
|
# 查询单个 |
|
class BookDetailView(ViewSetMixin, RetrieveAPIView): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
authentication_classes = [LoginAuth] # 需要写一个认证类,需要咱们自行编写 |
authenticate.py(认证类)
|
# 自己写的认证类,继承某个类 |
|
|
|
from rest_framework.authentication import BaseAuthentication |
|
from rest_framework.exceptions import AuthenticationFailed |
|
from .models import UserToken |
|
|
|
|
|
class LoginAuth(BaseAuthentication): |
|
def authenticate(self, request): |
|
# 在这里实现认证,如果是登录的,继续往后走返回两个值,如果不是抛异常 |
|
# 请求中是否携带token,判断是否登录,放在地址栏中 |
|
token = request.query_params.get('token', None) # 查找是否有token这个变量名的值,如果没有就返回None,默认好像返回的是数字 |
|
if token: # 前端传入token了,去表中查,如果能查到,登录了,返回两个值[固定的:当前登录用户,token] |
|
user_token = UserToken.objects.filter(token=token).first() |
|
if user_token: |
|
return user_token.user, token |
|
else: |
|
# 没有登录抛异常 |
|
raise AuthenticationFailed('token认证失败') |
|
else: |
|
raise AuthenticationFailed('token没传') |
|
|
|
# 前端传入的请求头中的数据从哪取? GET,body,POST,data |
urls.py
|
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.UserView, 'user') |
|
router.register('books', views.BookView, 'books') |
|
router.register('books', views.BookDetailView, 'books') |
|
|
|
urlpatterns = [ |
|
path('admin/', admin.site.urls), |
|
path('api/v1/', include(router.urls)), |
|
|
|
] |
权限组件
简介:
-
在我们使用的一些
app
或者网页中(爱奇艺,腾讯视频),都会有一些会员接口(需要购买会员才能够使用或者观看),权限组件就是对用户的这一权限进行验证,在请求进入视图类/函数代码前进行校验,校验失败后直接将请求拦截,并返回校验失败的信息
(1)、权限组件的使用步骤
模块地址:
|
from rest_framework.permissions import BasePermission |
用法简介:
|
# 1、创建一个专门用于编写权限组件的py文件,写一个权限类,继承BasePermission |
|
# 2、重写has_permission方法(在该方法在中实现权限认证,在这方法中,request.user就是当前登录用户) |
|
# 3、如果有权限,返回True |
|
# 4、没有权限,返回False(定制返回的中文: self.message='中文') |
|
# 5、局部使用和全局使用 |
|
-局部使用: # 在某个视图类中设置接口(不会影响别的视图类) |
|
class BookDetailView(ViewSetMixin, RetrieveAPIView): |
|
permission_classes = [CommonPermission] |
|
|
|
-全局使用: # django的settings.py中配置,影响全局 |
|
REST_FRAMEWORK = { |
|
'DEFAULT_PERMISSION_CLASSES': [ |
|
'app01.permissions.CommonPermission', |
|
], |
|
} |
|
|
|
-局部禁用:# 全局配置局部禁用 |
|
class BookDetailView(ViewSetMixin, RetrieveAPIView): |
|
permission_classes = [] |
(2)、代码用法
models.py(修改User表的配置后需要重新进行数据库迁移)
|
class User(models.Model): |
|
username = models.CharField(max_length=32) |
|
password = models.CharField(max_length=32) |
|
user_type = models.IntegerField(choices=((1, '超级管理员'), (2, '普通用户'), (3, '2B用户')), default=2) |
perssion.py(权限类代码)
|
# 写权限类,写一个类,继承基类BasePermission,重写has_permission方法,在方法中实现权限认证,如果有权限return True ,如果没有权限,返回False |
|
from rest_framework.permissions import BasePermission |
|
|
|
|
|
class CommonPermission(BasePermission): |
|
def has_permission(self, request, view): |
|
# 实现权限的控制 ---》知道当前登录用户是谁?当前登录用户是 request.user |
|
if request.user.user_type == 1: |
|
return True |
|
else: |
|
# 没有权限,向对象中放一个属性 message |
|
# 如果表模型中,使用了choice,就可以通过 get_字段名_display() 拿到choice对应的中文 |
|
self.message = '您是【%s】,您没有权限' % request.user.get_user_type_display() |
|
return False |
views.py(视图类代码)
|
class BookView(ModelViewSet): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
# 局部认证 |
|
authentication_classes = [LoginAuth] |
|
# 权限认证(将编写的频率类导入过来) |
|
permission_classes = [CommentPermission] |
三、频率组件
简介:
- 频率是指,控制某个接口访问频率(次数)
(1)、频率组件的使用步骤
模块地址:
|
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle |
|
# BaseThrottle:需要手动编写的代码较多 |
|
# SimpleRateThrottle: 需要手动编写的代码较少(用这个) |
用法简介
|
# 1、创建一个专门用来编写频率组件的py文件,写一个频率类,继承SimpleRateThrottle |
|
# 2、重写get_cache_key方法,返回什么,就以什么做限制----》ip(用户id做限制) |
|
# 3、配置一个类属性scope = 'book_5_m' |
|
# 4、在django的配置文件中编写频率次数 |
|
REST_FRAMEWORK = { |
|
'DEFAULT_THROTTLE_RATES': { |
|
'book_5_m': '5/m', # 一分钟五次 |
|
}, |
|
} |
|
# 5、局部使用和全局使用 |
|
-局部使用: # 只影响当前的视图类 |
|
class BookView(ModelViewSet): |
|
throttle_classes = [CommentThrottle] |
|
|
|
-全局配置:影响全局 |
|
REST_FRAMEWORK = { |
|
'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.CommonThrottle'], |
|
|
|
} |
|
|
|
-局部禁用: |
|
class BookView(ModelViewSet): |
|
throttle_classes = [CommentThrottle] |
(2)、代码用法
throttle.py(频率类代码)
|
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle |
|
|
|
|
|
class CommentThrottle(SimpleRateThrottle): |
|
# 创建一个用于控制频率的变量名(需要传入配置文件) |
|
scope = 'book_5_m' |
|
|
|
def get_cache_key(self, request, view): |
|
# 返回什么就以什么做限制(request.META.get('REMOTE_ADDR')以IP做限制) |
|
return request.META.get('REMOTE_ADDR') |
视图类代码
|
class BookView(ModelViewSet): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
# 频率组件 |
|
throttle_classes = [CommentThrottle] |
django配置文件
|
REST_FRAMEWORK = { |
|
# 控制访问频率 |
|
'DEFAULT_THROTTLE_RATES': { |
|
'book_5_m': '5/m', # 一分钟五次 |
|
}, |
|
} |
四、过滤的多种用法
简介:
- 过滤是指在使用查询的时候,我们可以通过条件来过滤掉不需要的内容
|
# restful规范中,要求了,请求地址中带过滤条件 |
|
-5个接口中,只有一个接口需要有过滤和排序,查询所有接口 |
(1)、继承APIView自己写
|
class BookView(APIView): |
|
def get(self,request): |
|
# 获取get请求携带的参数 |
|
name=request.query_params.get('name') |
|
# 通过filter进行过滤 |
|
books = Book.objects.filter(name=name) |
(2)、使用drf的内置过滤(继承GenericAPIview)
模块地址: 该方法为模糊查询
|
from rest_framework.filters import SearchFilter |
代码用法:
|
class BookView(ModelViewSet): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
# 实例化过滤对象 |
|
filter_backends = [SearchFilter] |
|
# 指定过滤的字段(模糊查询) |
|
search_fields = ['name', 'price'] |
搜索方式:
|
# name或price中只要有关键字就会搜出来 (只能用search=xxx的方式) |
|
http://127.0.0.1:8000/api/v1/books/?search=西游记 |
(3)、使用第三方插件过滤(精准过滤)
第三方插件:
|
# 插件名称: |
|
django-filter |
|
|
|
# 安装插件: |
|
pip3.8 install django-filter |
模块地址:
|
from django_filters.rest_framework import DjangoFilterBackend |
代码用法:
|
from rest_framework.viewsets import ModelViewSet |
|
from .models import Book |
|
from .serializer import BookSerializer |
|
from django_filters.rest_framework import DjangoFilterBackend |
|
|
|
|
|
class BookView(ModelViewSet): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
|
|
# 第三方过滤插件 |
|
filter_backends = [DjangoFilterBackend] |
|
# 查询的字段 |
|
filterset_fields = ['pk','name', 'price'] |
搜索方式:
|
http://127.0.0.1:8000/api/books/?price=99 |
|
http://127.0.0.1:8000/api/books/?price=99&name=呐喊 |
4、使用过滤组件
1.定制过滤组件的使用方式与步骤
模块地址:
|
from rest_framework.filters import BaseFilterBackend |
用法简介:
|
# 1、创建一个专门用于过滤的py文件,写一个类继承BaseFilterBackend |
|
# 2、重写filter_queryset方法,在方法内部进行过滤 |
|
# 3、直接返回过滤后的对象 |
|
# 4、如果没有过滤直接返回所有数据 |
|
# 5、局部使用 |
|
-局部使用: |
|
class BookView(ViewSetMixin, ListAPIView): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
# 在类表内填写过滤的类 |
|
filter_backends = [CommonFilter] # 可以定制多个,从左往右,依次执行 |
2.代码用法
过滤类代码filters.py
|
from rest_framework.filters import BaseFilterBackend |
|
|
|
|
|
class CommonFilter(BaseFilterBackend): |
|
# 重写的类,编写过滤吧的内容 |
|
def filter_queryset(self, request, queryset, view): |
|
# 获取过滤的条件 |
|
filter_comment = request.query_params.get('price_gt', None) |
|
# 判断前端是否传入过滤条件 |
|
if filter_comment: |
|
# 根据条件进行赛选内容 |
|
books_queryset_filter = queryset.filter(price__gt=100) |
|
# 返回过滤后的数据 |
|
return books_queryset_filter |
|
return queryset |
视图类代码
|
class BookView(ModelViewSet): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
|
|
# 第三方过滤插件 |
|
filter_backends = [CommonFilter] |
五、排序的使用
用法简介
- 排序需要和自定义过滤继承同一个父类,需要将排序的对象填入在过滤的列表内,并且放在其他参数的前方 模块地址:
|
from rest_framework.filters import OrderingFilter |
(2)、代码用法
视图类代码
|
from rest_framework.viewsets import ModelViewSet |
|
from .models import Book |
|
from .serializer import BookSerializer |
|
from django_filters.rest_framework import DjangoFilterBackend |
|
from .filters import CommonFilter |
|
from rest_framework.filters import OrderingFilter |
|
|
|
|
|
class BookView(ModelViewSet): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
|
|
# 第三方过滤插件(OrderingFilter放在其他过滤参数前) |
|
filter_backends = [OrderingFilter, DjangoFilterBackend, CommonFilter] |
|
# 查询的字段 |
|
filterset_fields = ['id', 'name', 'price'] |
|
# 指定排序的字段(-是降序,默认升序) |
|
ordering_fields = ['price'] |
搜索用法
|
# 默认升序 |
|
http://127.0.0.1:8000/api/books/?price_gt=60&ordering=price |
|
|
|
# 降序 |
|
http://127.0.0.1:8000/api/books/?price_gt=60&ordering=-price |
六、分页
- 分页功能,只有查询所有接口,才有分页
- drf内置了三个分页器,对应三种分页方式
- 内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
使用步骤
- 步骤一:创建一个py文件编写分页用到的自定义类,分页的三个类并不能直接使用,需要我们进行配置
- 步骤二:编写这个自定义类
- 步骤三:导入视图类中,并添加配置
代码 page.py(自定义的分页类)
|
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination |
|
|
|
|
|
# 网页用它 |
|
class CommonPageNumberPagination(PageNumberPagination): |
|
page_size = 2 # 每页显示2条 |
|
page_query_param = 'page' # page=10 查询第10页的数据,每页显示2条 |
|
page_size_query_param = 'size' # page=10&size=5 查询第10页,每页显示5条 |
|
max_page_size = 5 # 每页最大显示10条 |
|
|
|
''' |
|
page_size 每页数目 |
|
page_query_param 前端发送的页数关键字名,默认为”page” |
|
page_size_query_param 前端发送的每页数目关键字名,默认为None |
|
max_page_size 前端最多能设置的每页数量 |
|
''' |
|
|
|
|
|
# LimitOffset |
|
class CommonLimitOffsetPagination(LimitOffsetPagination): |
|
default_limit = 3 # 每页显示2条 |
|
limit_query_param = 'limit' # limit=3 取3条 |
|
offset_query_param = 'offset' # offset=1 从第一个位置开始,取limit条 |
|
max_limit = 5 |
|
# offset=3&limit=2 0 1 2 3 4 5 |
|
''' |
|
default_limit 默认限制,默认值与PAGE_SIZE设置一直 |
|
limit_query_param limit参数名,默认’limit’ |
|
offset_query_param offset参数名,默认’offset’ |
|
max_limit 最大limit限制,默认None |
|
''' |
|
|
|
# app 用下面 |
|
|
|
class CommonCursorPagination(CursorPagination): |
|
cursor_query_param = 'cursor' # 查询参数 |
|
page_size = 2 # 每页多少条 |
|
ordering = 'id' # 排序字段 |
|
|
|
''' |
|
cursor_query_param:默认查询字段,不需要修改 |
|
page_size:每页数目 |
|
ordering:按什么排序,需要指定 |
|
''' |
views.py
|
# 分页功能 必须是继承GenericAPIView ,如果是继承APIView,要自己写(你写) |
|
from .page import CommonPageNumberPagination as PageNumberPagination |
|
from .page import CommonLimitOffsetPagination as LimitOffsetPagination |
|
from .page import CommonCursorPagination as CommonCursorPagination |
|
|
|
|
|
|
|
class BookView(ViewSetMixin, ListAPIView): |
|
queryset = Book.objects.all() |
|
serializer_class = BookSerializer |
|
permission_classes = [] |
|
authentication_classes = [] |
|
throttle_classes = [] |
|
# 之前的东西一样用 ,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用 |
|
# pagination_class = PageNumberPagination |
|
#基本分页方式(基本是这种,网页端):http://127.0.0.1:8000/api/v1/books/?page=2&size=3 |
|
|
|
# pagination_class = LimitOffsetPagination |
|
# 偏移分页 http://127.0.0.1:8000/api/v1/books/?limit=4&offset=1 |
|
# 从第一条开始,取4条 |
|
|
|
pagination_class = CommonCursorPagination |
|
# 游标分页,只能下一页,上一页,不能跳到中间,但它的效率最高,大数据量分页,使用这种较好 |