1. 从request先说起
在Django原生的request里,请求的数据可以从request.GET或者request.POST里面取到。
需要注意的是,如果是POST请求,request.POST里面只能取到Content-Type=application/x-www-form-urlencoded 的数据,我们常用的json数据就只能从报文request.body里取。 b'{"a": 1,"b": "2"}'
So。。。
RestFramework的APIView 封装的新request就解决了这个问题,post请求的任何数据都可以从request.data里面拿到 ,而get请求需要在request.GET里面取。
并且它还保留了原生request的接口:request._request
2. 序列化
简单使用
开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json
之类的表示形式的方式。我们可以通过声明与Django forms非常相似的序列化器(serializers)来实现。
准备models部分:
class Book(models.Model): title=models.CharField(max_length=32) price=models.IntegerField() pub_date=models.DateField() CHOICES = ((1, "Python"), (2, "Go"), (3, "Linux")) category = models.IntegerField(choices=CHOICES, verbose_name="图书的类别") publish=models.ForeignKey("Publish") authors=models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name=models.CharField(max_length=32) email=models.EmailField() def __str__(self): return self.name class Author(models.Model): name=models.CharField(max_length=32) age=models.IntegerField() def __str__(self): return self.name
序列化类:
from rest_framework import serializers from .models import Book class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) title = serializers.CharField(max_length=32, validators=[my_validate]) # 自定义验证方法,校验优先级高于下面的validate_title方法 CHOICES = ((1, "Python"), (2, "Go"), (3, "Linux")) category = serializers.ChoiceField(choices=CHOICES, source="get_category_display", read_only=True) # read_only表示get方法拿到的值 w_category = serializers.ChoiceField(choices=CHOICES, write_only=True) # write_only 表示只进行操作不进行展示 pub_time = serializers.DateField() publisher = PublisherSerializer( read_only=True) # 如若只想展示关键信息不想全部展示,只需要指定 publish = serializers.CharField(source="publish.name") publisher_id = serializers.IntegerField(write_only=True) # authors = serializers.SerializerMethodField() # 所以处理多对多关系使用SerializerMethodField 方法 # def get_authors(self, obj): # get_字段 可以返回任何类型你想返回的值 # temp = [] # for author in obj.authors.all(): # temp.append(author.name) # return temp # 处理多对多的get请求时有两种方法:1、如上,想展示什么字段都可以。2、如下,嵌套定义的序列化类,这里想展示的数量不能自己定制 author = AuthorSerializer(many=True, read_only=True) # many=True 表示有多个值 author_list = serializers.ListField(write_only=True) def create(self, validated_data): # post请求重新create方法 book = Book.objects.create(title=validated_data["title"], category=validated_data["w_category"], pub_time=validated_data["pub_time"], publisher_id=validated_data["publisher_id"]) book.author.add(*validated_data["author_list"]) return book def update(self, instance, validated_data): # 更新重写update instance.title = validated_data.get("title", instance.title) instance.category = validated_data.get("category", instance.category) instance.pub_time = validated_data.get("pub_time", instance.pub_time) instance.publisher_id = validated_data.get("publisher_id", instance.publisher_id) if validated_data.get("author_list"): instance.author.set(validated_data["author_list"]) instance.save() return instance def validate_title(self, value): # 指定某个字段的验证 if "python" not in value.lower(): raise serializers.ValidationError("标题必须含有python") return value def validate(self, attrs): # 联合验证 if attrs["w_category"] == 1 and attrs["publisher_id"] == 1: return attrs else: raise serializers.ValidationError("分类以及标题不符合要求") def my_validate(value): # 额外为字段定制的校验 if "敏感信息" in value.lower(): raise serializers.ValidationError("不能含有敏感信息") else: return value
views部分:
from rest_framework.views import APIView from rest_framework.response import Response # 可以用Django原生的Response,但是rest_framework的response做了一些封装。可以格式化你的json数据,并且浏览器访问会返回一个api页面 from .models import * from django.shortcuts import HttpResponse # from django.core import serializers class BookViewSet(APIView): def get(self, request, *args, **kwargs): book_list = Book.objects.all() # 序列化方式1: # from django.forms.models import model_to_dict # import json # data=[] # for obj in book_list: # data.append(model_to_dict(obj)) # print(data) # return HttpResponse("ok") # 序列化方式2: # data=serializers.serialize("json",book_list) # return HttpResponse(data) # 序列化方式3: bs = BookSerializers(book_list, many=True) # many=true 代表了返回的是一个queryset列表,返回一个对象就不用加 return Response(bs.data)
ModelSerializer
class BookSerializer(serializers.ModelSerializer): category_display = serializers.SerializerMethodField(read_only=True) # 自定义要获取的字段内容,在下面用get_<field-name>钩子来自定义内容 publisher_info = serializers.SerializerMethodField(read_only=True) # 此方法 authors = serializers.SerializerMethodField(read_only=True) def get_category_display(self, obj): return obj.get_category_display() # orm中Choice_field显示中文的方式 def get_authors(self, obj): # get_字段:可以返回你想要返回的值 authors_query_set = obj.author.all() return [{"id": author_obj.id, "name": author_obj.name} for author_obj in authors_query_set] def get_publisher_info(self, obj): # obj 是我们序列化的每个Book对象 publisher_obj = obj.publisher return {"id": publisher_obj.id, "title": publisher_obj.title} class Meta: model = Book # fields = ["id", "title", "pub_time"] fields = "__all__" # 遇到一对多或多对多默认取主键,所以需要自己定义取得数据 # depth = 1 #外键嵌套的层级数量 extra_kwargs = {"category": {"write_only": True}, "publisher": {"write_only": True}, "author": {"write_only": True}} # 使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数
提交post请求
def post(self,request,*args,**kwargs): bs=BookSerializers(data=request.data,many=False) if bs.is_valid(): # print(bs.validated_data) bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
重写save中的create方法
post请求需要重写create()方法,put请求需要重写update()方法
class BookSerializers(serializers.ModelSerializer): class Meta: model=Book fields="__all__" # exclude = ['authors',] # depth=1 def create(self, validated_data): authors = validated_data.pop('authors') obj = Book.objects.create(**validated_data) obj.authors.add(*authors) return obj
单条数据的get和put请求
class BookDetailViewSet(APIView): def get(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj) return Response(bs.data) def put(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj,data=request.data) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
超链接API:Hyperlinked
class BookSerializers(serializers.ModelSerializer): publish= serializers.HyperlinkedIdentityField(
view_name='publish_detail',
lookup_field="publish_id",
lookup_url_kwarg="pk") class Meta: model=Book fields="__all__" #depth=1
urls部分:
urlpatterns = [ url(r'^books/$', views.BookViewSet.as_view(),name="book_list"), url(r'^books/(?P<pk>d+)$', views.BookDetailViewSet.as_view(),name="book_detail"), url(r'^publishers/$', views.PublishViewSet.as_view(),name="publish_list"), url(r'^publishers/(?P<pk>d+)$', views.PublishDetailViewSet.as_view(),name="publish_detail"), ]
3. 视图优化
上一节的视图部分:
from rest_framework.views import APIView from rest_framework.response import Response from .models import * from django.shortcuts import HttpResponse from django.core import serializers from rest_framework import serializers class BookSerializers(serializers.ModelSerializer): class Meta: model=Book fields="__all__" #depth=1 class PublshSerializers(serializers.ModelSerializer): class Meta: model=Publish fields="__all__" depth=1 class BookViewSet(APIView): def get(self,request,*args,**kwargs): book_list=Book.objects.all() bs=BookSerializers(book_list,many=True,context={'request': request}) return Response(bs.data) def post(self,request,*args,**kwargs): print(request.data) bs=BookSerializers(data=request.data,many=False) if bs.is_valid(): print(bs.validated_data) bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class BookDetailViewSet(APIView): def get(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj,context={'request': request}) return Response(bs.data) def put(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj,data=request.data,context={'request': request}) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class PublishViewSet(APIView): def get(self,request,*args,**kwargs): publish_list=Publish.objects.all() bs=PublshSerializers(publish_list,many=True,context={'request': request}) return Response(bs.data) def post(self,request,*args,**kwargs): bs=PublshSerializers(data=request.data,many=False) if bs.is_valid(): # print(bs.validated_data) bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class PublishDetailViewSet(APIView): def get(self,request,pk): publish_obj=Publish.objects.filter(pk=pk).first() bs=PublshSerializers(publish_obj,context={'request': request}) return Response(bs.data) def put(self,request,pk): publish_obj=Publish.objects.filter(pk=pk).first() bs=PublshSerializers(publish_obj,data=request.data,context={'request': request}) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
3.1 使用混合(mixins)
from web.models import * from web.serializer import BookSerializer from rest_framework.mixins import (ListModelMixin, CreateModelMixin, DestroyModelMixin, UpdateModelMixin, RetrieveModelMixin) from rest_framework.generics import GenericAPIView class BookViewSet(ListModelMixin, CreateModelMixin, GenericAPIView): queryset = Book.objects.all() # 必须要指定一个quetyset serializer_class = BookSerializer # 必须要指定一个序列化类 def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class BookDetailViewSet(RetrieveModelMixin, # 单条数据操作视图 UpdateModelMixin, DestroyModelMixin, GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializer def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)
3.2 使用通用的基于类的视图
通过使用mixin类,我们使用更少的代码重写了这些视图,但我们还可以再进一步。REST框架提供了一组已经混合好(mixed-in)的通用视图,我们可以使用它来简化我们的views.py
模块。
from rest_framework import mixins from rest_framework import generics class BookViewSet(generics.ListCreateAPIView): queryset = Book.objects.all() serializer_class = BookSerializers class BookDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Book.objects.all() serializer_class = BookSerializers
3.3 viewsets.ModelViewSet
urls.py:
urlpatterns = [ re_path(r'^books/$', views.BookViewSet.as_view({"get": "list", "post": "create"}), name="book_list"), re_path(r'^books/(?P<pk>d+)$', views.BookViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }), name="book_detail"), ]
views.py:
from rest_framework.viewsets import ModelViewSet class BookViewSet(ModelViewSet): queryset = Book.objects.all() # 指定一个RetrieveModelMixin serializer_class = BookSerializer # 指定一个序列化类
4. 认证与权限组件
4.1 认证组件
局部视图认证
先定义一个认证类:
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed
class Authentication(BaseAuthentication): def authenticate(self,request): token=request._request.GET.get("token") token_obj=UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed("验证失败!") return (token_obj.user,token_obj)
在views.py:
def get_random_str(user): import hashlib,time ctime=str(time.time()) md5=hashlib.md5(bytes(user,encoding="utf8")) md5.update(bytes(ctime,encoding="utf8")) return md5.hexdigest() from app01.service.auth import * from django.http import JsonResponse class LoginViewSet(APIView): authentication_classes = [Authentication,] def post(self,request,*args,**kwargs): res={"code":1000,"msg":None} try: user=request._request.POST.get("user") pwd=request._request.POST.get("pwd") user_obj=UserInfo.objects.filter(user=user,pwd=pwd).first() print(user,pwd,user_obj) if not user_obj: res["code"]=1001 res["msg"]="用户名或者密码错误" else: token=get_random_str(user) UserToken.objects.update_or_create(user=user_obj,defaults={"token":token}) res["token"]=token except Exception as e: res["code"]=1002 res["msg"]=e return JsonResponse(res,json_dumps_params={"ensure_ascii":False})
全局视图认证组件
settings.py配置如下:
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",] }
4.2 权限组件
局部视图权限
在app01.service.permissions.py中:
from rest_framework.permissions import BasePermission class SVIPPermission(BasePermission): message="SVIP才能访问!" def has_permission(self, request, view): if request.user.user_type==3: return True return False
在views.py:
from app01.service.permissions import * class BookViewSet(generics.ListCreateAPIView): permission_classes = [SVIPPermission,] queryset = Book.objects.all() serializer_class = BookSerializers
全局视图权限
settings.py配置如下:
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",] }
5. throttle(访问频率)组件
局部视图throttle
在app01.service.throttles.py中:
from rest_framework.throttling import BaseThrottle VISIT_RECORD={} class VisitThrottle(BaseThrottle): def __init__(self): self.history=None def allow_request(self,request,view): remote_addr = request.META.get('REMOTE_ADDR') print(remote_addr) import time ctime=time.time() if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr]=[ctime,] return True history=VISIT_RECORD.get(remote_addr) self.history=history while history and history[-1]<ctime-60: history.pop() if len(history)<3: history.insert(0,ctime) return True else: return False def wait(self): import time ctime=time.time() return 60-(ctime-self.history[-1])
在views.py中:
from app01.service.throttles import * class BookViewSet(generics.ListCreateAPIView): throttle_classes = [VisitThrottle,] queryset = Book.objects.all() serializer_class = BookSerializers
全局视图throttle
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",] }
内置throttle类
在app01.service.throttles.py修改为:
class VisitThrottle(SimpleRateThrottle): scope="visit_rate" def get_cache_key(self, request, view): return self.get_ident(request)
settings.py设置:
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",], "DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", # 5:5次 m:分 } }
6. 解析器
request类
django的request类和rest-framework的request类的源码解析
局部视图
from rest_framework.parsers import JSONParser,FormParser class PublishViewSet(generics.ListCreateAPIView): parser_classes = [FormParser,JSONParser] queryset = Publish.objects.all() serializer_class = PublshSerializers def post(self, request, *args, **kwargs): print("request.data",request.data) return self.create(request, *args, **kwargs)
全局视图
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",], "DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", }, "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.FormParser',] }
分页
简单分页
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination class PNPagination(PageNumberPagination): page_size = 1 page_query_param = 'page' page_size_query_param = "size" max_page_size = 5 class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializers def list(self,request,*args,**kwargs): book_list=Book.objects.all() pp=LimitOffsetPagination() pager_books=pp.paginate_queryset(queryset=book_list,request=request,view=self) print(pager_books) bs=BookSerializers(pager_books,many=True) #return Response(bs.data) return pp.get_paginated_response(bs.data)
偏移分页
from rest_framework.pagination import LimitOffsetPagination