0.准备环境
* 1. 新建项目 DRF_03
TEMPLATES = [
...
'DIRS': [BASE_DIR, 'templates']
INSTALLED_APPS = [
...
'rest_framework',
* 2. 创建模型表
from django.db import models
class Book(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=32, verbose_name='书名')
price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='价格')
author = models.CharField(max_length=32, verbose_name='作者')
# 创建表
生成操作记录 python manage.py makemigratons
生成数据表 python manage.py magrate
* 3. 定义序列化器
from rest_framework import serializers
from app01.models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
* 4. 定义路由
url(r'^api/books/v1/', views.BookAPIView1.as_view()),
url(r'^api/books/v1/(?P<pk>\d+)/', views.BookAPIView2.as_view())
* 5. 使用视图子类写接口
from django.shortcuts import render
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from app01.models import Book
from app01.serializer import BookSerializer
class BookAPIView1(ListCreateAPIView):
queryset = Book.objects
serializer_class = BookSerializer
class BookAPIView2(RetrieveUpdateDestroyAPIView):
queryset = Book.objects
serializer_class = BookSerializer
* 6. 第二组路由
url(r'^api/books/v2/', views.BookModelViewSet1.as_view(actions={'get': 'list', 'post': 'create'})),
url(r'^api/books/v2/(?P<pk>\d+)/',
views.BookModelViewSet1.as_view(actions={'get': 'retrieve', 'put': 'update', "delete": "destroy"}))
将第二个路由写进了第一个路由里面...
* 7. 使用视图集编写接口
from rest_framework.viewsets import ModelViewSet
class BookModelViewSet1(ModelViewSet):
queryset = Book.objects
serializer_class = BookSerializer
1. 路由
1.1 CBV传统路由
url(r'^api/books/v1/', views.BookAPIView1.as_view()),
请求来的时候, 先执行as_view方法, 返回一个view函数内存地址, view函数中通过dispacth中通过判定当前请求,
执行了对应的请求方法, 对应的请求方法再去调用视图子类的方法.
1.2 继承ViewSetMixin
eg:
get不带主键 --> get方法 --> list方法
get带主键 --> get方法 --> retrieve方法
继承了ViewSetMixin
get不带主键 --> get方法(list)
get带主键 --> get方法(retrieve方)
url(r'^api/books/v2/', views.BookModelViewSet1.as_view(actions={'get': 'list'}))
请求来的时候, 先执行as_view方法, 返回被重写的view函数内存地址, view函数中遍历actions字段,
为对象添绑定方法, 最后通过dispacth执行对应的请求方法时, 执行的是视图子类的方法.
1.3 自动生成路由
# 方式3 继承了ModelViewSet 使用 routers模块自动生成路由
from rest_framework import routers
routers模块有两个类
1. SimpleRouter 自动生成两条路由
2. DefaultRouter 自动生成六条路由
SimpleRouter 与 DefaultRouter 的使用方式是一模一样的
对象.register('前缀', '继承至ModelViewSet的视图类', 匿名函数等其他的参数...)
前缀会自动加上^/ , 视图类会自定加上.as_view()
from rest_framework import routers
router = routers.SimpleRouter()
router.register('api/books/v3', views.BookModelViewSet2)
print(router.urls)
"""
[<RegexURLPattern book-list ^api/books/v3/$>,
<RegexURLPattern book-detail ^api/books/v3/(?P<pk>[^/.]+)/$>]
"""
urlpatterns += router.urls
两个路由列表相加 = [列表]
class BookModelViewSet2(ModelViewSet):
queryset = Book.objects
serializer_class = BookSerializer
SimpleRouter
# 127.0.0.1:8000/xxx/
[<RegexURLPattern book-list ^api/books/v3/$>,
# 127.0.0.1:8000/xxx/num/
<RegexURLPattern book-detail ^api/books/v3/(?P<pk>[^/.]+)/$>]
DefaultRouter
# 127.0.0.1:8000/xxx/
[<RegexURLPattern book-list ^api/books/v3/$>,
# 127.0.0.1:8000/xxx.后缀/ 随意带后缀
<RegexURLPattern book-list ^api/books/v3\.(?P<format>[a-z0-9]+)/?$>,
# 127.0.0.1:8000/xxx/num/
<RegexURLPattern book-detail ^api/books/v3/(?P<pk>[^/.]+)/$>,
# 127.0.0.1:8000/xxx/num.后缀/ 随意带后缀
<RegexURLPattern book-detail ^api/books/v3/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>,
# 127.0.0.1:8000
<RegexURLPattern api-root ^$>,
# 127.0.0.1:8000/.后缀/ 随意带后缀
<RegexURLPattern api-root ^\.(?P<format>[a-z0-9]+)/?$>]
DefaultRouter 添加了一个主页路由, 然后在为每个路由设置一个可带.后缀的路由.
1.4 action装饰器
使用routers模块自动生成路由, 默认只能生成增, 删, 改, 查全部, 查一条的功能,
在视图类中定义其他的方法后, 自动生成改方法的路由则需要action装饰器.
# 导入action装饰器
from rest_framework.decorators import action
action装饰器有两个参数(必须填)
1. methods 为装饰的方法设置请求方式, 值为一个列表.
2. detail 生成的路由是否包含有名分组获取pk值,
@action(methods=['请求方法', ], detail=False)
def xxx(self, request)...
@action(methods=['请求方法', ], detail=True)
def xxx(self, request, pk)...
定义一个方法, 获取两条数据并返回.(提前创建几条数据)
self.get_queryset()[:2]
from rest_framework.decorators import action
from rest_framework.response import Response
class BookModelViewSet2(ModelViewSet):
queryset = Book.objects
serializer_class = BookSerializer
@action(methods=['get'])
def get_data(self, request):
book_obj = self.get_queryset()[:2]
book_dic = self.serializer_class(book_obj, many=True)
return Response(book_dic.data)
^api/books/v3/get_data/$ [name='book-get-data']
访问地址: 127.0.0.1:8000/api/books/v3/get_data/
@action(methods=['get'], detail=True)
def get_data(self, request, pk):
print(pk)
...
^api/books/v3/(?P<pk>[^/.]+)/get_data/$ [name='book-get-data']
请求地址: 127.0.0.1:8000/api/books/v3/主键/get_data/
2. 认证
认证的实现: 写一个类, 继承BseAuthentication,
在类中重写authenticate方法, 将该方法中写认证的逻辑.可以局部使用, 也可以全局使用.
2.1 认证源码解析
* 1. APIView类的dispatch方法中 self.initial(request, *args, **kwargs)
initial 有认证, 频率, 权限三大认证模块 , request是Request模块生成的request
* 2. 执行认证的方法将request对象传入
* 3. 方法中request.user, 去查找Request中查找对象的user静态方法
* 4. Request的user方法中, request._user中是否有值,
第一次知道值肯定不存在的, 执行._authentications方法
这里的self是 request, 这个方法是上面request.user调用的
* 5. 核心代码 self是Request的request对象
authenticators是一堆需要认证的类 产生的对象 组成的一个列表.
遍历从列表中拿出对象, 进行认证.
self是Request的request对象
authenticator.authenticate(self) 认证器(对象)调用认认证方法
authenticator对象是自己写的, authenticate方法也是需要自己写的
执行之后
认证成功了需要返回一个元组, (登入的用户, 认证的信息)
之后将这个数组解压赋值给self.user和self.auth, self是Request封装的request.
认证失败是需要抛异常, APIEcception
一般写的时候是, AuthenticatonFailed AuthenticatonFailed继承了APIEcception,
也符合APIEcception的异常捕获的条件. 以后写其他的认证, 抛出的异常也继承APIEcception, 在它的基础在细分.
authenticator.authenticate(self)
authenticator是对象, 对象点方法将自己最为第一个参数进行传递, 这里的self是request
authenticator.authenticate(authenticator, request) 这里传递了两个参数!
if user_auth_tuple is not None,
不为空, 认证类认证成功会返回的连个数据值分别被request的两个属性接受, 之后直接结束这个函数.
为空, 执行self.not_authenticated, 将request.user信息设置为游客
↓ authenticators的追溯
authenticators 要是Request实例化传入的值, 要么是()
这里的self是视图类的对象
执行get_authenticators得到的是一个列表生成器,
self.authentication_classes 需要自己在视图类中定义,
class xx(xxAPIView):
authentication_classes = ['类', ]
...
自己不定义就跑到配置文件去找.
遍历存放的类, 加括号调用, 得到存放对象的列表.
这里的self是视图类的对象
api.settings配置文件
2.2 视图类中做检验
在用户登入一次之后将token返回非客户端, 下次访问信息, 携带token进行校验, 校验通过能拿到用户的信息.
* 1. 准备两种表并写入数据
UserToken表一对一用户表, 通过token校验能马上拿到当前的用户.
class User(models.Model):
id = models.AutoField(primary_key=True, verbose_name='主键')
username = models.CharField(max_length=32, verbose_name='用户名称')
password = models.CharField(max_length=32, verbose_name='用户名密码')
grade = models.IntegerField(choices=((1, '黄金会员'), (2, '白银会员'), (3, '褐铜用户')), verbose_name='等级')
class UserToken(models.Model):
id = models.AutoField(primary_key=True, verbose_name='主键')
token = models.CharField(max_length=64, verbose_name='token')
user = models.OneToOneField(to=User, on_delete=models.CASCADE, verbose_name='外键字段,关联用户的id')
* 2. 生成表记录并迁移数据库
python manage.py makemigrations
python manage.py migrate
* 3. 为用户表添加上三条数据
* 4. 路由
url(r'^login/', views.LoginAPIView.as_view()),
* 5. 登入视图类, 每次重新登入成功都会触发uuid4(), 随之更新token的值
from rest_framework.views import APIView
from app01 import models
from uuid import uuid4
from rest_framework.response import Response
class LoginAPIView(APIView):
def post(self, request):
print(request.data)
username = request.data.get('username')
password = request.data.get('password')
user_obj = models.User.objects.filter(username=username, password=password).first()
if user_obj:
token = uuid4()
print(token)
models.UserToken.objects.update_or_create(defaults={'token': token}, user=user_obj)
return Response({'code': 200, 'msg': '登入成功!', 'token': token})
else:
return Response({'code': 100, 'msg': '用户名称或密码错误!'})
objects.update_or_create(self, defaults=None, **kwargs)
defaults为更新的值 **kwargs 为查询的依据
1. 使用依据匹配到数据, 将defaults的值更加到该数据库.
2. 使用依据查没有匹配到数据, 将defaults的值, **kwargs的值作为表的数据创建.
* 6. Postman中测试: 127.0.0.1:8000/login/
post请求携带json格式数据:
{
"username": "kid",
"password": "111"
}
2.3 自定义认证组件
如果有多个认证类, 前面都不需要返回用户对象和信息, 在最后才返回即可.
一旦在前面直接返回了, 后面的认证类就不会在走了.
* 1. 在app01下创建一个auth.py文件, 在文件中写校验类.
校验类需要继承 基本身份验证类,源码↓
父类中规范子类必须实现某个功能, 不写就抛出异常.
from rest_framework.authentication import BaseAut hentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models
class LoginCheck(BaseAuthentication):
def authenticate(self, request):
"""
self 是当前类的对象, request 是Request重新封装的request
写认证的逻辑,
认证成功返回一个数组,(登入的用户对象, token)
认证失败抛出 AuthenticationFailed异常
"""
print(request.data)
token = request.GET.get('token')
print(token)
if not token:
raise AuthenticationFailed('没有携带token值!')
token_obj = models.UserToken.objects.filter(token=token).first()
if not token_obj:
raise AuthenticationFailed('校验不合格!')
return (token_obj.user, token)
token_obj = models.UserToken.objects.filter(token=token).first()
通过token做校验, 检验成果之后, token_obj.user就直接拿到当前token对应的用户数据对象.
* 2. 局部使用认证
# 导入认证类
from app01.auth import LoginCheck
# 为需要校验的数图类设置authentication_classes属性
authentication_classes = [LoginCheck]
from rest_framework.decorators import action
from rest_framework.response import Response
from app01.auth import LoginCheck
class BookModelViewSet2(ModelViewSet):
authentication_classes = [LoginCheck]
queryset = Book.objects.all()
serializer_class = BookSerializer
@action(methods=['get'], detail=True)
def get_data(self, request, pk):
print(f'主键:{pk}')
print(f'用户的模型对象:{request.user}')
print(f'token值:{request.auth}')
book_obj = self.get_queryset()[:2]
book_dic = self.serializer_class(book_obj, many=True)
return Response(book_dic.data)
* 3. 校验测试
第一次请求不携带token
127.0.0.1:8000/api/books/v3/
第二次请求携带错误的token值
127.0.0.1:8000/api/books/v3/
第三次请求携带正确的token
127.0.0.1:8000/api/books/v3/
当检验成功之后, 返回两个值分别为request.user 和reqquest.auth设置了值.
在请求方法中可以使用这两个属性. 如果在请求方法中不需要使用则会两个属性, 那么定义认证中就不需要返回值了.
* APIView中访问request.user 是匿名用户.
* 认证类中没人返回参数 那么在请求中访问中request.user也是匿名用户.
127.0.0.1:8000/api/books/v3/1/get_data/?token=1e33392c-2f68-4c82-926a-dde4f5c5bac6
* 5. 全局配置 在settings.py 中
REST_FRAMEWORK ={
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.auth.LoginCheck',]
}
设置之后存在一个问题, 第一次登入也需要认证, 就陷入死局了.
为登入接口设置局部校验为空,
authentication_classes = []
查找自己的类属性优先级最高,
注销局部BookModelViewSet2的局部校验
为LoginAPIView设置局部校验为空.
class BookModelViewSet2(ModelViewSet):
...
class LoginAPIView(APIView):
authentication_classes = []
...
登入测试, 每一次重新登入, token的值都被重新修改了
127.0.0.1:8000/api/books/v3/1/get_data/?token=1e33392c-2f68-4c82-926a-dde4f5c5bac6
只有经过校验request才会有自己在校验类中返回的信息.
2.4 认证练习
1. 继承ModelViewSet, 实现所有接口, 单独写一个接口获取2条数据.
2. 路由自动生成, 获取两条数据的接口使用couter生成,
3. 认证组件全局使用, 登入禁用, token信息存放在请求头中.
1. 准备环境
* 1. 新建项, 不要模板层, templates信息删除.
* 2. 创建两张表
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
class Token(models.Model):
token_value = models.CharField(max_length=64)
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.CharField(max_length=32)
author = models.CharField(max_length=32)
publish = models.CharField(max_length=32)
python manage.py makemigrations
python manage.py migrate
用户表创建一个用户信息
为书籍表创建三条数据
* 3. 将rest_framework 添加到app应用列表中
INSTALLED_APPS = [
...
'rest_framework',
]
2. 登入功能
* 1. 登入路由
url(r'^login/', views.Login.as_view())
* 2. 登入视图类
from rest_framework.views import APIView
from books_msg import models
import uuid
from rest_framework.response import Response
from rest_framework import status
class Login(APIView):
def post(self, request):
print(request.data)
username = request.data.get('username')
password = request.data.get('password')
if not (username and password):
return Response({'code': 101, 'msg': '用户或密码没有输入!'}, status=status.HTTP_101_SWITCHING_PROTOCOLS)
user_obj = models.User.objects.filter(username=username, password=password).first()
print(user_obj)
if not user_obj:
return Response({'code': 101, 'msg': '用户或密码错误!'}, status=status.HTTP_101_SWITCHING_PROTOCOLS)
token = uuid.uuid4()
models.Token.objects.update_or_create(defaults={'token_value': token}, user=user_obj)
return Response({'code': 200, 'msg': '登入成功', 'token': token}, status=status.HTTP_200_OK)
* 3. Postman中登入测试
post请求 127.0.0.1:8000/login/
json数据:
{
"username": "kid",
"password": "123"
}
3. 编写接口
增删改查, 外加一个获取2条数据的请求.
* 1. 自动生成路由
from django.conf.urls import url
from django.contrib import admin
from books_msg import views
from rest_framework import routers
router = routers.SimpleRouter()
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.Login.as_view())
]
router.register('api/books/v1', views.BookModelSetView)
print(router.urls)
urlpatterns += router.urls
* 2. 书籍表模型序列化器
from rest_framework import serializers
from books_msg import models
class BookModelsSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = '__all__'
* 3. API视图类
继承了ModelSetView类必须设置queryset, serializer_class属性值
from rest_framework.viewsets import ModelViewSet
from books_msg import ModelSerializer
class BookModelSetView(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = ModelSerializer.BookModelsSerializer
* 3. Postman中测试, 查询所有的数据 127.0.0.1:8000/api/books/v1/
* 4. 获取两条数据的接口
class BookModelSetView(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = ModelSerializer.BookModelsSerializer
@action(methods=['get'], detail=False)
def get_two_data(self, request):
book_obj = self.queryset[:2]
book_dic = self.serializer_class(instance=book_obj, many=True)
return Response({'code': 200, 'msg': '获取成功', 'book_obj': book_dic.data}, status=status.HTTP_200_OK)
* 5. Postman中测试, 查询两条数据的接口 127.0.0.1:8000/api/books/v1/get_two_data/
4.校验类
* 1. 校验类, 继承BseAuthentication, 类中必须有authenticate方法, 在该方法中写检验的逻辑.
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from books_msg import models
class LoginAuthentication(BaseAuthentication):
def authenticate(self, request):
print(request.META)
token_value = request.META.get('HTTP_TOKEN')
if not token_value:
raise AuthenticationFailed('没有携带token值!')
token_obj = models.Token.objects.filter(token_value=token_value).first()
if not token_obj:
raise AuthenticationFailed('token值不正则')
return token_obj.user, token_value
请求头的携带自定义的数据, 键多了http_前缀, 还被转为大写.
放回 token_obj.user, token_value 分别被request.user 和 request.auth接收
在自定义获取两条数据的接口中可以查看request.user 和 request.auth的信息
def get_two_data(self, request):
print(request.user, request.user.username)
print(request.auth)
...
* 2. 全局配置登入校验, 登入不校验
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['books_msg.authenticates.LoginAuthentication', ]
}
class Login(APIView):
authentication_classes = []
...
* 3. Postman中测试,post请求: 127.0.0.1:8000/api/books/v1/get_two_data/
在请求头中添加token
终端展示request.user 和 request.auth的值
* 4. 重新登入, 刷新token值, 如果提示没有token, 就是局部禁用校验设置失败
|