DRF学习
DRF
安装
- pip install djangorestframework
注册
-
在settings.py中进行注册
INSTALLED_APPS = [ ... 'rest_framework', ]
DRF简单使用
models模型数据
from django.db import models
from django.contrib.auth.models import User
class Merchant(models.Model):
"""
商家
"""
name = models.CharField(max_length=200, verbose_name='商家名称', null=False)
address = models.CharField(max_length=200, verbose_name='商家', null=False)
logo = models.CharField(max_length=200, verbose_name='商家logo', null=False)
notice = models.CharField(max_length=200, verbose_name='商家的公告', null=True, blank=True)
up_send = models.DecimalField(verbose_name='起送价', default=0, max_digits=6, decimal_places=2)
lon = models.FloatField(verbose_name='经度')
lat = models.FloatField(verbose_name='纬度')
creater = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
class GoodsCategory(models.Model):
"""
商家商品分类
"""
name = models.CharField(max_length=20, verbose_name='分类名称')
merchant = models.ForeignKey(Merchant, on_delete=models.CASCADE, verbose_name='所属商家', related_name='categories')
class Goods(models.Model):
"""
商品
"""
name = models.CharField(max_length=200, verbose_name='商品名称')
picture = models.CharField(max_length=200, verbose_name='商品图片')
intro = models.CharField(max_length=200)
price = models.DecimalField(verbose_name='商品价格', max_digits=6, decimal_places=2) # 最多6位数,2位小数。9999.99
category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, related_name='goods_list')
- 模型和下面的文件并不在一个目录下
- 下面的可以手动创建一个目录,添加python文件直接写代码即可
序列化字段 serializers.py
from rest_framework import serializers
from meituan.models import Merchant
class MerchantSerializers(serializers.ModelSerializer):
class Meta:
model = Merchant
# 需要序列化的字段
fields = "__all__"
# exclude = ['name']
编写视图函数 views.py
from rest_framework import viewsets
from meituan.models import Merchant
from .serializers import MerchantSerializers
# 这个视图已经包含了CRUD
class MerchantViewset(viewsets.ModelViewSet):
queryset = Merchant.objects.all()
serializer_class = MerchantSerializers
映射路径 urls.py
from rest_framework.routers import DefaultRouter
from .views import MerchantViewset
router = DefaultRouter()
router.register('merchant', MerchantViewset, basename='merchant')
app_name = 'quickstart'
urlpatterns = [] + router.urls
序列化
- drf中的序列化主要是用来将模型序列化成JSON格式的对象。但是除了序列化,他还具有表单验证功能,数据存储和更新功能
非模板序列化
-
继承与 serializers.Serializer
-
自定义需要序列化的字段,可以重写更新,添加数据的方法
# 序列化字段(非模板序列化) class MerchantSerializer(serializers.Serializer): id = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=200, required=True, error_messages={"required": "name字段是必填的!"}) address = serializers.CharField(max_length=200, required=True) notice = serializers.CharField(max_length=200, required=False) logo = serializers.CharField(max_length=200) up_send = serializers.DecimalField(required=False, max_digits=6, decimal_places=2) lon = serializers.FloatField(required=True) lat = serializers.FloatField(required=True) def create(self, validated_data): # 创建数据 # validated_data是一个字段,比如{"name": "sixu"} # 但是create需要传入的是关键字参数,加上两个星号就可以将name转为关键字参数 # eg: {"name": "sixu"} ===> name="sixu" return Merchant.objects.create(**validated_data) def update(self, instance, validated_data): # 修改数据之前我要拿到数据,如果没有数据就用之前的数据 instance.name = validated_data.get("name", instance.name) instance.address = validated_data.get("address", instance.address) instance.notice = validated_data.get("notice", instance.notice) instance.logo = validated_data.get("logo", instance.logo) instance.up_send = validated_data.get("up_send", instance.up_send) instance.lon = validated_data.get("lon", instance.lon) instance.lat = validated_data.get("lat", instance.lat) # 保存修改 instance.save() # 返回数据 return instance
模板序列化
-
也可以自定义需要序列化的字段,但是不用这个麻烦
-
继承与 serializers.ModelSerializer
- fields 指定序列化的字段
- exclude 指定不参与序列化的字段
# 使用模板序列化 class MerchantSerializer(serializers.ModelSerializer): class Meta: # 导入models模型中的类 model = Merchants # 序列化所有字段,也可以序列化指定字段(属性) 比如: fields = ['name','xxx'] fields = "__all__" # 可以指定那些字段(属性)不进行序列化 # exclude = ['name','age']
对个别字段进行验证
-
validated_data:可对想要进行验证的属性进行验证
-
validated:可以对全部的属性进行验证
class GoodsCategorySerializer(serializers.ModelSerializer): merchant = MerchantSerializer(read_only=True) goods_list = GoodSerializer(read_only=True, many=True) merchant_id = serializers.IntegerField(write_only=True) class Meta: model = GoodsCategory fields = "__all__" # 验证merchant_id是否存在 def validated_merchant_id(self, value): if not Merchant.objects.get(pk=value).exists(): raise serializers.ValidationError("商家不存在!") return value def create(self, validated_data): merchant_id = validated_data.get("merchant_id") merchant = Merchant.objects.get(pk=merchant_id) return GoodsCategory.objects.create(**validated_data, merchant=merchant)
序列化的嵌套
-
有时候在一个序列化中,我们可能需要其他模型的序列化。这时候就可以使用到序列化的嵌套
-
比如我们在GoodsCategory中想要获取Merchant以及这个分类下的商品Goods
class GoodsCategorySerializer(serializers.ModelSerializer): # 序列化的嵌套获取商家的详细信息 merchant = MerchantSerializer(read_only=True) # 获取商品分类的详细信息, 必须是 xxxx_list goods_list = GoodSerializer(read_only=True, many=True) # 发送请求时传入的商家id merchant_id = serializers.IntegerField(write_only=True) class Meta: model = GoodsCategory fields = "__all__" # 验证merchant_id是否存在 def validated_merchant_id(self, value): if not Merchant.objects.get(pk=value).exists(): raise serializers.ValidationError("商家不存在!") return value def create(self, validated_data): merchant_id = validated_data.get("merchant_id") merchant = Merchant.objects.get(pk=merchant_id) return GoodsCategory.objects.create(**validated_data, merchant=merchant)
视图函数的使用
-
可以在视图函数中拿到由序列化验证之后的数据
# 使用Response这函数要引入 from rest_framework.response import Response from rest_framework.decorators import api_view from .serializers import MerchantSerializer, GoodsCategorySerializer from django.http import JsonResponse from meituan.models import Merchant, GoodsCategory, Goods @api_view(['GET', 'POST']) def merchant(request): if request.method == "GET": # 获取数据 queryset = Merchant.objects.all() # 序列化数据 # instance需要传递一个ORM对象或者是queryset对象,将orm模型序列化为JSON # many: 拿到的是多个而不是一个 serializer = MerchantSerializer(instance=queryset, many=True) # serializer 可以理解为是一个列表,里面存放的是一个个序列化对象, 类型是:rest_framework.serializers.ListSerializer print(serializer, type(serializer)) # serializer.data返回的数据格式是:[{},{}...] 类型是:rest_framework.utils.serializer_helpers.ReturnList print(serializer.data, type(serializer.data)) # 返回数据使用JsonResponse # return JsonResponse(serializer.data, status=200, safe=False) return Response(serializer.data, status=200) else: # 获取数据 serializer = MerchantSerializer(data=request.POST) # 验证数据 if serializer.is_valid(): # 保存数据 serializer.save() # 返回成功信息 return JsonResponse(serializer.data, status=200) else: # 返回失败信息 return JsonResponse(serializer.errors, status=400)
read_only 和 write_only
- read_only=True:这个字段只只能读,只有在返回数据的时候会使用
- write_only=True:这个字段只能被写,只有在新增数据或者更新数据的时候会用到
验证
验证用户上传上来的字段是否满足要求。可以通过以下三种方式来实现。
- 验证在Field中通过参数的形式进行指定。比如required等。
- 通过重写validate(self,attrs)方法进行验证。attrs中包含了所有字段。如果验证不通过,那么调用raise serializer.ValidationError('error')即可。
- 重写validate_字段名(self,value)方法进行验证。这个是针对某个字段进行验证的。如果验证不通过,也可以抛出异常。
请求和响应
Request和Response对象
Requeset
- request.POST:只能处理表单数据,获取通过POST方式上传上来的数据
- request.data:以处理任意的数据。可以获取通过
POST、PUT、PATCH等方式上传上来的数据 - request.query_params:查询参数
Response
- Response 可以自动的根据返回的数据类型来决定返回什么样的格式。并且会自动的监听如果是浏览器访问,那么会返回这个路由的信息
状态码
from rest_framework import status
# 这个类中有已经写好的状态码可供使用
比如:
HTTP_200_OK = 200
HTTP_201_CREATED = 201
HTTP_202_ACCEPTED = 202
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
HTTP_204_NO_CONTENT = 204
HTTP_205_RESET_CONTENT = 205
HTTP_206_PARTIAL_CONTENT = 206
HTTP_207_MULTI_STATUS = 207
使用Request和Response
-
Request和Response 都只能在 DRF 的 APIView 中才能使用
-
引入 rest_framework.decorators.api_view 装饰
from rest_framework.decorators import api_view@api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = SnippetSerializer(snippet) return Response(serializer.data) elif request.method == 'PUT': serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) -
如果是类视图,那么可以让你的类继承自 rest_framework.views.APIView
from rest_framework.views import APIView class MerchantView(APIView): def get(self,request): return Response("你好")
类视图
APIView
- APIView是DRF中类视图最基本的父类。基本用法跟Django中自带的View类是一样的。也是自己分别实现get、post等方法
- APIView中还继承了一些常用的属性,比如authentication_classes、permission_classes、throttle_classes
from rest_framework.views import APIView
from meituan.models import Merchant
from django.http import Http404
from rest_framework import status
from .serializers import MerchantSerializer
from rest_framework.response import Response
# 使用APIView的写法
class MerchantView(APIView):
"""
检索, 更新和删除一个merchant实例对象.
"""
def get_object(self, pk):
try:
return Merchant.objects.get(pk=pk)
except Merchant.DoesNotExist:
raise Http404
def get(self, request, pk=None):
if pk:
merchant = self.get_object(pk)
serializer = MerchantSerializer(merchant)
return Response(serializer.data)
else:
queryset = Merchant.objects.all()
serializer = MerchantSerializer(instance=queryset, many=True)
return Response(serializer.data)
def put(self, request, pk):
merchant = self.get_object(pk)
serializer = MerchantSerializer(merchant, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
merchant = self.get_object(pk)
merchant.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
minix
- mixins翻译成中文是混入,组件的意思。在DRF中,针对获取列表,检索,创建等操作,都有相应的mixin
from meituan.models import Merchant
from .serializers import MerchantSerializer
from rest_framework import generics
from rest_framework import mixins
class MerchantView(
generics.GenericAPIView,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin
):
queryset = Merchant.objects.all()
serializer_class = MerchantSerializer
def get(self, request, pk=None):
if pk:
# 如果有pk参数就查一条
return self.retrieve(request)
else:
# 否则查所有
return self.list(request)
# 重写perform_create这个方法可以写自己的保存逻辑
# def perform_create(self, serializer):
# # 比如说将当前用户添加到数据中
# serializer.save(created=self.request.user)
# 添加数据
def post(self, request):
return self.create(request)
# 修改数据
def put(self, request, pk):
return self.update(request, pk)
# 删除数据
def delete(self, request, pk):
return self.destroy(request, pk)
Generic写法
- 针对这些mixin,DRF还进一步的进行了封装,放到generics下
- generics.ListAPIView:实现获取列表的。实现get方法。
- generics.CreateAPIView:实现创建数据的。实现post方法。
- generics.UpdateAPIView:实现更新数据的。实现put方法。
- generics.DestroyAPIView:实现删除数据的。实现delete方法。
- generics.RetrieveAPIView:实现检索数据的。
- generics.ListCreateAPIView:实现列表和创建数据的。
- generics.RetrieveUpdateAPIView:实现检索和更新数据的。
- generics.RetrieveDestroyAPIView:实现检索和删除数据的。
- generics.RetrieveUpdateDestroyAPIView:实现检索和更新和删除数据的。
from meituan.models import Merchant
from .serializers import MerchantSerializer
from rest_framework import generics
# 使用Generic的写法(通用视图)
class MerchantView(
generics.ListAPIView,
generics.CreateAPIView,
generics.UpdateAPIView,
generics.DestroyAPIView
# RetrieveAPIView 和 ListAPIView 不能共用
# generics.RetrieveAPIView
):
queryset = Merchant.objects.all()
serializer_class = MerchantSerializer
# 如果想在通用视图下共用 RetrieveAPIView 和 ListAPIView
# 则需要重新写要要给类视图于另一个做区分
# 需要在映射路径中重新定义一个路径
class MerchantOneView(generics.RetrieveAPIView):
queryset = Merchant.objects.all()
serializer_class = MerchantSerializer
# 设置检索的字段
# lookup_field = 'name'
# 修改这个字段在url中显示的key,不修改的话默认是 lookup_field 的值
# 如果有多个的话,好像会报错
# lookup_url_kwarg = "pk"
-
视图的写法
urlpatterns = [ path('merchant/',views.MerchantView.as_view()), path('merchant/<int:pk>/',views.MerchantView.as_view()) ] -
url和method产生的结果
method url 结果 get /merchant/31/ 获取id=31的merchant数据 post /merchant/ 添加新的merchant数据 put /merchant/31/ 修改id=31的merchant数据 delete /merchant/31 删除id=31的merchant数据 -
因为这里retrieve占用了get方法,所以如果想要实现获取列表的功能,那么需要再重新定义一个url和视图
-
这也是为什么List和Retrieve不能同时存在一个视图中的原因
# views.py class MerchantListView(generics.ListAPIView,): serializer_class = MerchantSerializer queryset = Merchant.objects.all() # urls.py urlpatterns = [ path('merchant/',views.MerchantView.as_view()), path('merchant/<int:pk>/',views.MerchantView.as_view()), path('merchants/',views.MerchantListView.as_view()) ... ]
视图集
-
在视图函数中继承 viewsets.ModelViewSet
from rest_framework import viewsets # 使用视图集的方式 class MerchantViewSet(viewsets.ModelViewSet): queryset = Merchant.objects.all() serializer_class = MerchantSerializer -
在urls中注册路由
from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register("merchant",MerchantViewSet,basename="merchant") urlpatterns = []+router.urls -
在视图集中添加 url
from rest_framework.decorators import action class MerchantViewSet(viewsets.ModelViewSet): queryset = Merchant.objects.all() serializer_class = MerchantSerializer # url_path可以设置这个路径的url,默认的url路径是方法名 @action(['GET'],detail=False, url_path='cs') def changsha(self,request,*args,**kwargs): queryset = self.get_queryset() queryset = queryset.filter(name__contains="长沙") serializer = MerchantSerializer(queryset,many=True) return Response(serializer.data)
权限验证
BasicAuthentication
JWT
- 安装pyjwt
- pip install pyjwt
获取 jwt_token 和 解析 jwt_token
import jwt
import time
def get_jwt_token(userid):
# 设置过期时间
timestamp = int(time.time() * 60*60)
# 设置加密密钥
key = 'sixu'
# algorithm是加密方式,默认是HS256
token = jwt.encode({"userid": userid, "exp": timestamp}, key=key, algorithm='HS256')
return token
# 解析jwt_token
def get_jwt_payload():
# 获取token
token = get_jwt_token(1)
# 解析token
jwt_payload = jwt.decode(token, key='sixu', algorithms=['HS256'])
return jwt_payload
# 验证获取token
token = get_jwt_token(1)
print("token:", token)
# 验证解析token
ss = get_jwt_payload()
print(ss.get("userid"))
在django里使用 jwt_token
-
写一个类进行获取和解析
import jwt import time from django.conf import settings from rest_framework.authentication import BaseAuthentication, get_authorization_header from rest_framework import exceptions from django.contrib.auth import get_user_model User = get_user_model() # 生成jwt的token函数 def generate_jwt(user): # 设置过期时间为7天后 timestamp = int(time.time() + 60*60*24*7) # 测试token过期 # timestamp = int(time.time() + 2) # 获取token key是加密的密钥 # exp是过期时间,必须是这个名字 token = jwt.encode({"userId": user.id, "exp": timestamp}, key=settings.SECRET_KEY, algorithm='HS256') return token class JWTAuthentication(BaseAuthentication): """ Authorization: JWT 401f7ac837da42b97f613d789819ff93537bee6a """ keyword = 'JWT' model = None def authenticate(self, request): # 获取请求头中的数据,获取的是编码后的数据 auth = get_authorization_header(request).split() # 分割后auth应该是这个样子的 # auth ===> ['JWT','xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'] # 先与 keyword 进行对比,如果不想等直接返回None(验证失败) if not auth or auth[0].lower() != self.keyword.lower().encode(): return None if len(auth) == 1: msg = '没有JWT的值' raise exceptions.AuthenticationFailed(msg) elif len(auth) > 2: msg = '无效的token' raise exceptions.AuthenticationFailed(msg) try: # 对token进行解码 auth分割后下标为1的元素是token jwt_token = auth[1] payload = jwt.decode(jwt_token, key=settings.SECRET_KEY, algorithms='HS256') userId = payload.get("userId") try: user = User.objects.get(id=userId) return user, jwt_token except Exception: msg = '无效的token' raise exceptions.AuthenticationFailed(msg) except UnicodeError: msg = '编码存在问题' raise exceptions.AuthenticationFailed(msg) except jwt.ExpiredSignatureError: msg = 'token已过期' raise exceptions.AuthenticationFailed(msg) -
在视图函数中调用这个工具类生成token
from meituan.models import Merchant from rest_framework.decorators import api_view from rest_framework.response import Response from .serializers import MerchantSerializer from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated from .authentications import generate_jwt, JWTAuthentication # 获取django自带的的user对象 from django.contrib.auth import get_user_model User = get_user_model() # 使用视图集的方式 class MerchantViewSet(viewsets.ModelViewSet): queryset = Merchant.objects.all() serializer_class = MerchantSerializer # authentication_classes: 判断用户是否已经登录 authentication_classes = [JWTAuthentication] # permission_classes: 根据用户的权限进行限制访问 permission_classes = [IsAuthenticated] # 配置上面的两行代码之后 # 在进行请求的时候就需要进行验证 # 在请求的请求头中添加以下信息 # AUTHORIZATION basic c2l4dToxMjM0NTY= // 这种格式就是验证的格式 AUTHORIZATION 是请求头中的key. basic c2l4dToxMjM0NTY= 是key的value,注意用空格隔开 # c2l4dToxMjM0NTY= 是怎么来的呢 # 1. 导入base64的包 # 2. base64.b64encode(b'sixu:123456') 进行编码 sixu:123456,是管理员的账号和密码用冒号分开 # 调用这个视图函数就会返回一个jwt_token @api_view(['GET']) def token_view(request): token = generate_jwt(User.objects.get(id__contains=2)) return Response({'token': token}) -
在urls.py文件中进行映射
from django.urls import path from .views import MerchantViewSet, token_view from rest_framework.routers import DefaultRouter app_name = "authpermission" router = DefaultRouter() router.register('merchant', MerchantViewSet, basename='merchant') urlpatterns = [ path('token/', token_view, name='token') ] + router.urls
自定义权限
-
写一个自定义权限类
-
authentications.py
# 自定义权限 from rest_framework import permissions class PermissionsDemo(permissions.BasePermission): # 第一次限制(小区门卫) def has_permission(self, request, view): # 发送请求的时候在请求头中设置 referer, 值任意输 用来测试 if request.META.get('HTTP_REFERER'): return True return False # 第二次限制(单元楼) def has_object_permission(self, request, view, obj): if '长沙' in obj.name: return True return False
-
-
在视图函数中添加 permission_classes 用来启用自定义权限
from meituan.models import Merchant from rest_framework.decorators import api_view from rest_framework.response import Response from .serializers import MerchantSerializer from rest_framework import viewsets from rest_framework.authentication import BasicAuthentication from rest_framework.permissions import IsAuthenticated, IsAdminUser from .authentications import generate_jwt, JWTAuthentication # 导入自定义权限包 from .pemissions import PermissionsDemo # 获取django自带的的user对象 from django.contrib.auth import get_user_model class MerchantViewSet(viewsets.ModelViewSet): queryset = Merchant.objects.all() serializer_class = MerchantSerializer # authentication_classes: 判断用户是否已经登录, 根据那种方式进行判断 authentication_classes = [JWTAuthentication, BasicAuthentication] # permission_classes: 根据用户的权限进行限制访问, # IsAuthenticated: 用户登陆的才可以访问 # IsAdminUser: 管理员才可以访问 # permission_classes = [IsAuthenticated, IsAdminUser] # 自定义权限的使用,别忘导包,这个是在这个视图进行限制,可以在settings.py中对所有视图进行限制 permission_classes = [PermissionsDemo]
限速节流
- 通过限制访问的次数和频率来限制行为
- 限速节流的配置分为两种,第一种是在settings.REST_FRAMEWORK中进行设置,第二种是针对每个视图函数进行配置
- 限度的单位有 second/minute/hour/day
全局配置
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
# 对匿名用户
'rest_framework.throttling.AnonRateThrottle',
# 对登陆用户
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
# 一天访问2次
'anon': '2/day',
# 一天访问3次
'user': '3/day'
}
}
视图配置
-
可以只针对某个视图进行限速节流
-
views.py
class MerchantViewSet(viewsets.ModelViewSet): queryset = Merchant.objects.all() serializer_class = MerchantSerializer # 自定义限速节流 throttle_scope = 'merchant' -
在settings.py文件中进行其中
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ # 自定义限速节流 'rest_framework.throttling.ScopedRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { # 自定义次数 'merchant': '2/day' } }

浙公网安备 33010602011771号