rest_framework开发API
一. 什么是RESTful
-
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
-
REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
-
所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
-
对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
二. RESTful API设计的十条规范
1.API与用户的通信协议,总是使用HTTPs协议
2.域名
https://api.example.com 尽量将API部署在专用域名(会存在跨域问题) https://example.org/api/ API很简单(不存在跨域问题)
3.版本
URL,如:https://api.example.com/v1/ 请求头 跨域时,引发发送多次请求
4.路径
视网络上任何东西都是资源,均使用名词表示(可复数)
https://api.example.com/v1/zoos https://api.example.com/v1/animals https://api.example.com/v1/employees
5.method
GET :从服务器取出资源(一项或多项) POST :在服务器新建一个资源 PUT :在服务器更新资源(客户端提供改变后的完整资源)全部更新 PATCH :在服务器更新资源(客户端提供改变的属性)局部更新 DELETE :从服务器删除资源
6.过滤
通过在url上传参的形式传递搜索条件状态码
https://api.example.com/v1/zoos?limit=10:指定返回记录的数量 https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置 https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数 https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序 https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件
7.状态码
/* 1** 信息,服务器收到请求,需要请求者继续执行操作 2** 成功,操作被成功接收并处理 3** 重定向,需要进一步的操作以完成请求 4** 客户端错误,请求包含语法错误或无法完成请求 5** 服务器错误,服务器在处理请求的过程中发生了错误 */ 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务) 204 NO CONTENT - [DELETE]:用户删除数据成功。 301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI 304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。 客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 400 Bad Request 客户端请求的语法错误,服务器无法理解 403 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求 404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 405 Method Not Allowed 客户端请求中的方法被禁止 406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求 410 Gone -[GET]:客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 500 INTERNAL SERVER ERROR - [*]:服务器内部发生错误,用户将无法判断发出的请求是否成功。
8.错误处理
状态码是4xx时,应返回错误信息,error当做key。
{ error: "Invalid API key" }
9.返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档
10.Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
{ "count": 7, "next": "http://127.0.0.1:8000/api/v1/viewSets4/?page=3", "previous": "http://127.0.0.1:8000/api/v1/viewSets4/", "results": [ { "id": 4, "user_type": 2, "username": "zhao", "password": "123", "group": { "id": 2, "title": "普通组" }, "roles": [ { "id": 5, "title": "护士" } ] },
三. 基于Django实现FBV CBV
FBV(Function Base View)
基本使用:
urls.py FBV的路由
re_path(r'^users/', views.users),
views.py 视图函数
from django.views.decorators.csrf import csrf_exempt,csrf_protect #通过装饰符 解决CSRF验证问题可以发post请求 @csrf_exempt def users(request): print(request.method) dataList = ['oldwang','xiaozhang','laosong'] #通过json.dumps 对数据进行序列化 return HttpResponse(json.dumps(dataList))
CBV(Class Base View)
urls.py
re_path(r'^students/', views.StudentsView.as_view()), re_path(r'^teachers/', views.TeachersView.as_view()),
views.py
#CBV from django.utils.decorators import method_decorator # @method_decorator(csrf_exempt,name='dispatch') class StudentsView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(StudentsView,self).dispatch(request, *args, **kwargs) def get(self,request,*args,**kwargs): return HttpResponse("get") def post(self,request,*args,**kwargs): return HttpResponse("post") def put(self,request,*args,**kwargs): return HttpResponse("put") def delete(self,request,*args,**kwargs): return HttpResponse("delete") class TeachersView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): #通过反射 执行类方法 func = getattr(self,request.method.lower()) # print("------>",func) #<bound method TeachersView.post of <lesson1.views.TeachersView object at 0x000002251089CDA0>> ret = func(self, request, *args, **kwargs) #ret 是类TeachersView 拥有的get/post/put/delete方法的返回值 return ret def get(self,request,*args,**kwargs): return HttpResponse("get") def post(self,request,*args,**kwargs): return HttpResponse("post") def put(self,request,*args,**kwargs): return HttpResponse("put") def delete(self,request,*args,**kwargs): return HttpResponse("delete")
CBV源码分析
FBV中给视图函数添加/免除 csrf认证的方法
3. FBV中给视图函数添加/免除 csrf认证的方法 情况一: MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', #全站使用csrf认证 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] from django.views.decorators.csrf import csrf_exempt,csrf_protect @csrf_exempt def users(request): #该函数无需认证 print(request.method) dataList = ['oldwang','xiaozhang','laosong'] return HttpResponse(json.dumps(dataList)) 情况二: MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', #'django.middleware.csrf.CsrfViewMiddleware', #全站都不使用csrf认证 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] from django.views.decorators.csrf import csrf_exempt,csrf_protect @csrf_protect def users(request): #该函数需要认证 print(request.method) dataList = ['oldwang','xiaozhang','laosong'] return HttpResponse(json.dumps(dataList))
在CBV中类方法添加/免除 csrf认证的方法
注意:@method_decorator(csrf_exempt) 必须要加在自定义的dispatch方法上,加在类的函数头上无效
4.在CBV中类方法添加/免除 csrf认证的方法 注意:@method_decorator(csrf_exempt) 必须要加在自定义的dispatch方法上 加在类的函数头上无效 方式一: from django.utils.decorators import method_decorator class StudentsView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(StudentsView,self).dispatch(request, *args, **kwargs) def get(self,request,*args,**kwargs): return HttpResponse("get") def post(self,request,*args,**kwargs): return HttpResponse("post") def put(self,request,*args,**kwargs): return HttpResponse("put") def delete(self,request,*args,**kwargs): return HttpResponse("delete") 方式二: from django.utils.decorators import method_decorator @method_decorator(csrf_exempt,name='dispatch') class StudentsView(View): # @method_decorator(csrf_exempt) # def dispatch(self, request, *args, **kwargs): # return super(StudentsView,self).dispatch(request, *args, **kwargs) def get(self,request,*args,**kwargs): return HttpResponse("get") def post(self,request,*args,**kwargs): return HttpResponse("post") def put(self,request,*args,**kwargs): return HttpResponse("put") def delete(self,request,*args,**kwargs): return HttpResponse("delete")
四.基于Django rest_framework实现API开发
认证 (* * * * *)
a.问题1:有些API需要用户登录之后 才能访问 有些无需登录也可以访问
b.认证组件的基本使用
1.首先创建三张表(userInfo userToken 一对一关系)
2.用户登录(返回token并保持到数据库userToken表中)
3.查看登录用户订单信息(http://127.0.0.1:8000/api/v1/order/?token=8b6d1cd084661ec62767298788504928) //携带token的是登录用户
app01/urls.py
from django.db import models class UserInfo(models.Model): user_type_tuple = ( (1,'普通用户'), (2,'VIP'), (3,'SVIP'), ) user_type = models.IntegerField(choices=user_type_tuple) username = models.CharField(max_length=64,unique=True) password = models.CharField(max_length=128) class UserToken(models.Model): user = models.OneToOneField(to=UserInfo,on_delete=models.CASCADE) token = models.CharField(max_length=128)
models.py
from django.db import models class UserInfo(models.Model): user_type_tuple = ( (1,'普通用户'), (2,'VIP'), (3,'SVIP'), ) user_type = models.IntegerField(choices=user_type_tuple) username = models.CharField(max_length=64,unique=True) password = models.CharField(max_length=128) class UserToken(models.Model): user = models.OneToOneField(to=UserInfo,on_delete=models.CASCADE) token = models.CharField(max_length=128) class Orders(models.Model): shopper = models.ForeignKey(to=UserInfo,on_delete=models.CASCADE) goods = models.CharField(max_length=255) price = models.FloatField() numbers = models.IntegerField()
全局authentication 认证配置settings.py
REST_FRAMEWORK = { #############authentication 认证################### "DEFAULT_AUTHENTICATION_CLASSES":['app01.utils.auth.Authentication1','app01.utils.auth.MyAuthentication',], # "DEFAULT_AUTHENTICATION_CLASSES":['app01.utils.auth.Authentication1',], #认证不通过 "UNAUTHENTICATED_USER":lambda :"匿名用户", "UNAUTHENTICATED_TOKEN":None, }
全局认证类 app01/utils/auth.py
from app01 import models from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication class Authentication1(BaseAuthentication): """ 该认证什么都没做 直接跳过 """ def authenticate(self, request): pass def authenticate_header(self, request): pass class MyAuthentication(BaseAuthentication): def authenticate(self, request, *args, **kwargs): token = request._request.GET.get('token') token_obj = models.UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed('用户认证失败') return (token_obj.user, token_obj.token) # 该方法如果不需要重写功能,可以继承父类的该方法 不用写了 def authenticate_header(self, request): pass
视图 app01/views.py
from django.http import JsonResponse from app01 import models from rest_framework.views import APIView,Request import hashlib,time from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_protect,csrf_exempt def md5(user): #时间撮+用户名构成 t = str(user)+str(time.time()) return hashlib.md5(t.encode('utf8')).hexdigest() @method_decorator(csrf_exempt,name='dispatch') class AuthenticView(APIView): """ # 为已注册用户(用户名&密码保存在userinfo中)添加token, 下次请求携带token即可识别出是已登录用户 """ #http://127.0.0.1:8000/auth/ 这个类 不需要认证 authentication_classes = [] def post(self,request,*args,**kwargs): ret = {'code':1000,'msg':None,'token':None} try: user = request._request.POST.get("username") pwd = request._request.POST.get("password") obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '用户名或密码不正确' token = md5(user) #在UserToken表中创建或修改用户的 token models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '请求异常' return JsonResponse(ret) class OrderView(APIView): """ 订单相关的业务 """ #认证类对象列表配置在全局 # authentication_classes = [MyAuthentication,] def get(self,request,*args,**kwargs): ret = {'code': 1000, 'msg': None,'data':[],"userinfo":None} print("=========>",request.user,request.auth) try: orders_data = models.Orders.objects.filter(shopper_id=request.user.id) for item in orders_data: ret['data'].append({ "goods":item.goods, "prices":item.price, "numbers":item.numbers, }) ret['userinfo'] = { "username":request.user.username, "user_type":request.user.user_type, } except Exception as e: ret['msg'] = "获取订单信息失败" return JsonResponse(ret)
c.认证源码流程分析
权限 (* * * * *)
需求:
user_type 用户类型中有 普通用户/VIP/SVIP,用户订单详情页只允许SVIP访问
app01/utils/myPermission.py
from rest_framework.permissions import BasePermission class SvipPermission(BasePermission): message = "必须是SVIP才允许访问" def has_permission(self, request, view): #只允许SVIP用户可以访问 if(request.user.user_type !=3): return False return True #继承的BasePermission中有该方法 由于内容不需要扩充,因此不用重写 # def has_object_permission(self, request, view, obj): # return True class MyPermission(BasePermission): #当没有权限访问时的提示信息 message="只允许普通用户和VIP用户可以访问" def has_permission(self, request, view): if(request.user.user_type !=3): #普通用户 和VIP用户可以访问 return True return False
urls.py 和 settings.py 的配置
##urls.py re_path(r'^setToken/',views.SetTokenView.as_view()), re_path(r'^api/v1/order/',views.OrderView.as_view()), re_path(r'^api/v1/userinfo/',views.UserInfoView.as_view()), ##settings.py REST_FRAMEWORK = { ###################认证 authentication######################## "DEFAULT_AUTHENTICATION_CLASSES":['app01.utils.auth.MyAuthentication',], "UNAUTHENTICATED_USER":lambda :"匿名用户", "UNAUTHENTICATED_TOKEN":None, ####################权限 permission 全局配置################### "DEFAULT_PERMISSION_CLASSES":['app01.utils.myPermission.SvipPermission'], }
app01/views.py
from django.shortcuts import render,HttpResponse from django.http import JsonResponse from app01 import models from rest_framework.views import APIView import hashlib,time from app01.utils.myPermission import MyPermission,SvipPermission def md5(user): t = str(user)+str(time.time()) return hashlib.md5(t.encode('utf8')).hexdigest() from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt,csrf_protect @method_decorator(csrf_exempt,name='dispatch') class SetTokenView(APIView): authentication_classes = [] permission_classes = [] def post(self,request,*args,**kwargs): ret = {'code':1000,'msg':None} try: user = request._request.POST.get("username") pwd = request._request.POST.get("password") obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '用户名或密码不正确' # 为已存在的用户添加token,下次请求携带token即可识别出是登录用户 token = md5(user) models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '请求异常' return JsonResponse(ret) class OrderView(APIView): # self.dispatch #这里使用全局定义的权限,authentication_classes 和permission_classes不用写 # permission_classes = [SvipPermission,] def get(self,request,*args,**kwargs): ret = {'code': 1000, 'msg': None,'data':[]} try: orders_data = models.Orders.objects.filter(shopper_id=request.user.id) for item in orders_data: ret['data'].append({ "goods":item.goods, "prices":item.price, "numbers":item.numbers, }) except Exception as e: pass return JsonResponse(ret) class UserInfoView(APIView): #这里不使用全局权限 permission_classes = [MyPermission,] def get(self,request,*args,**kwargs): return HttpResponse("普通用户、VIP有权限访问")
节流 (* * * * *)
节流顾名思义就是控制用户对网站的访问频率,例如匿名用户允许访问3次/分钟,登录用户允许访问10次/分钟
节流的原理:
urls.py
urlpatterns = [ re_path(r'^admin/', admin.site.urls), re_path(r'^auth/',views.AuthView.as_view()), re_path(r'^api/v1/order/',views.OrderView.as_view()), re_path(r'^api/v1/userinfo/',views.UserInfoView.as_view()), ]
settings.py
REST_FRAMEWORK = { #认证配置 "DEFAULT_AUTHENTICATION_CLASSES":['app01.utils.auth.Authentication1','app01.utils.auth.MyAuthentication'], # "DEFAULT_AUTHENTICATION_CLASSES":['app01.utils.auth.Authentication1',], #认证不通过 "UNAUTHENTICATED_USER":lambda :"匿名用户", "UNAUTHENTICATED_TOKEN":None, #权限配置 "DEFAULT_PERMISSION_CLASSES":['app01.utils.myPermission.SvipPermission'], #节流配置 "DEFAULT_THROTTLE_CLASSES":['app01.utils.myThrottle.anonymousThrottle',], #全局都配置匿名用户节流 "DEFAULT_THROTTLE_RATES":{ "ipAddrThrottle":"3/m", #匿名用户一分钟可以访问3次 "usernameThrottle":"10/m" #登录用户一分钟可以访问10次 } }
app01/utils/myThrottle
from rest_framework.throttling import BaseThrottle,SimpleRateThrottle import time VISIT_RECODE = {} class VisitThrottle(BaseThrottle): def __init__(self): self.history = None def allow_request(self, request, view): # remote_addr = request.META.get('REMOTE_ADDR') #remote_addr也可以调用父类的 remote_addr = self.get_ident(request) ctime = time.time() if remote_addr not in VISIT_RECODE: VISIT_RECODE[remote_addr] = [ctime] return True history = VISIT_RECODE.get(remote_addr) self.history = history while history and ctime-history[-1]>60: history.pop() if len(history)<6: history.insert(0,ctime) return True def wait(self): """ Optionally, return a recommended number of seconds to wait before the next request. :return: 可选,返回提示数字 距离下一次可访问还有多长时间 """ ctime = time.time() return 60-(ctime-self.history[-1]) class anonymousThrottle(SimpleRateThrottle): """ A simple cache implementation, that only requires `.get_cache_key()`to be overridden. The rate (requests / seconds) is set by a `rate` attribute on the View class. The attribute is a string of the form 'number_of_requests/period'. Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') Previous request information used for throttling is stored in the cache. """ #rest_framework框架为我们提供了一般频率节流类 # 因此我们只需要重写.get_cache_key() 和设置rate即可 #scope 是范围,规定节流频率 3/m 每分钟3次,在全局中配置 scope = "ipAddrThrottle" def get_cache_key(self, request, view): """ #根据什么节流?一般有IP和用户名 Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ print("self.get_ident(request)--",self.get_ident(request)) return self.get_ident(request) # 根据IP地址节流 class userThrottle(SimpleRateThrottle): scope = "usernameThrottle" # def get_cache_key(self, request, view): return request.user.username # 根据登录用户的用户名节流
views.py
class OrderView(APIView): # authentication_classes = [] # permission_classes = [] #使用全局配置的节流 def get(self,request,*args,**kwargs): ret = {'code': 1000, 'msg': None,'data':[]} print("=========>",request.user,request.auth) # print(request.auth) try: orders_data = models.Orders.objects.filter(shopper_id=request.user.id) for item in orders_data: ret['data'].append({ "goods":item.goods, "prices":item.price, "numbers":item.numbers, }) ret["userinfo"] = { "username":request.user.username, "user_type":request.user.user_type, } except Exception as e: pass return JsonResponse(ret) from app01.utils.myThrottle import userThrottle class UserInfoView(APIView): #这里不使用全局权限 # permission_classes = [MyPermission,] #不使用全局节流 throttle_classes = [userThrottle,] def get(self,request,*args,**kwargs): return HttpResponse("svip有权限访问,10/m")
postman测试效果:
版本 (* * )
versioning.py 中的类
BaseVersioning HostNameVersioning NamespaceVersioning QueryParameterVersioning(BaseVersioning) 推荐**** GET /something/?version=0.1 HTTP/1.1 Host: example.com Accept: application/json URLPathVersioning(BaseVersioning) 推荐***** urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'), url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail') ] GET /1.0/something/ HTTP/1.1 Host: example.com Accept: application/json
views.py
因此自己写的MyVersion 也要继承BaseVersioning
class MyVersion(BaseVersioning): """ 继承BaseVersioning,自己写的version版本 == QueryParameterVersioning 127.0.0.1:8000/api/users/?version=v1 """ def determine_version(self, request, *args, **kwargs): version = request.query_params.get('version') return version class VersionView(APIView): #局部设置 # versioning_class = MyVersion # versioning_class = QueryParameterVersioning # versioning_class = URLPathVersioning (推荐***) #可以在settings中全局配置 def get(self,request,*args,**kwargs): # print('===>',request.query_params.get('version')) # print('===>',request._request.GET.get('version')) #获取版本 print(request.version) #v1 # 获取处理版本的类对象(如QueryParameterVersioning,URLPathVersioning) print(request.versioning_scheme) # <rest_framework.versioning.URLPathVersioning object at 0x00000299BDF4A2E8> #反向生成URL(基于restframework,不需要指定version 默认将request.version传入) u1 = request.versioning_scheme.reverse(viewname="uuu",request=request) print(u1) #http://127.0.0.1:8000/api/v1/version/ # 反向生成URL(基于django) u2 = reverse(viewname="uuu",kwargs={"version":"v1"}) print(u2) #/api/v1/version/ return HttpResponse('用户查看版本')
urls.py和settings.py的配置
urls.py urlpatterns = [ re_path(r'^(?P<version>[v1|v2]+)/version/$',views.VersionView.as_view(),name="uuu"), ] settings.py REST_FRAMEWORK = { #version配置 "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", "DEFAULT_VERSION":"v1", "ALLOWED_VERSIONS":['v1','v2'], "VERSION_PARAM":"version", }
源码流程分析:
1.re_path(r'^(?P<version>[v1|v2]+)/version/$',views.VersionView.as_view(),name="uuu"), 路由进来首先执行APIView.as_view() 2.as_view方法里面 view = super().as_view(**initkwargs) 执行父类View.as_view方法,父类as_view 里面 return self.dispatch(request, *args, **kwargs) 首先在自身的VersionView中找没有,再在APIView中找 有self.dispatch方法 3.self.initial(request, *args, **kwargs) version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme #可以通过request.version 取得版本 scheme = self.versioning_class() --->versioning_class = api_settings.DEFAULT_VERSIONING_CLASS 默认的api_settings中有 BaseVersioning HostNameVersioning NamespaceVersioning QueryParameterVersioning URLPathVersioning print(request.versioning_scheme) # <rest_framework.versioning.URLPathVersioning object at 0x00000299BDF4A2E8>
解析器 (* *)
views.py
from rest_framework.parsers import JSONParser,FormParser class ParseView(APIView): #局部配置 parser_classes = [JSONParser,FormParser] def post(self,request,*args,**kwargs): #获取请求体数据 print(request._request) print(request.data) return HttpResponse("ParseView"
settings.py 中全局配置
REST_FRAMEWORK = { "DEFAULT_PARSER_CLASSES":["rest_framework.parsers.JSONParser","rest_framework.parsers.FormParser"], }
postman测试效果:
序列化 (* * * * *)
序列化用于对用户请求数据进行验证和数据进行序列化。
参考:https://www.cnblogs.com/xvchengqi/p/10103468.html
https://www.cnblogs.com/wupeiqi/articles/7805382.html
准备工作
数据库准备 models.py
from django.db import models class UserGroup(models.Model): title = models.CharField(max_length=32) class UserInfo(models.Model): user_type_tuple = ( (1,'普通用户'), (2,'VIP'), (3,'SVIP'), ) user_type = models.IntegerField(choices=user_type_tuple) username = models.CharField(max_length=64,unique=True) password = models.CharField(max_length=128) #一个组有多个成员 group = models.ForeignKey(to=UserGroup,on_delete=models.CASCADE) #给外键 起个名字 #一个人喜欢多种角色 一个角色也可以被多个人喜欢 roles = models.ManyToManyField("Role") class UserToken(models.Model): user = models.OneToOneField(to=UserInfo,on_delete=models.CASCADE) token = models.CharField(max_length=128) class Role(models.Model): title = models.CharField(max_length=32)
路由系统 urls.py
from django.urls import re_path from app01 import views urlpatterns = [ re_path(r'^(?P<version>[v1|v2]+)/userinfo/$',views.UserInfoView.as_view()), # re_path(r'^users/$',views.VersionView.as_view()), re_path(r'^(?P<version>[v1|v2]+)/users/$',views.VersionView.as_view(),name="uuu"), re_path(r'^(?P<version>[v1|v2]+)/parse/$',views.ParseView.as_view(),name="ppp"), re_path(r'^(?P<version>[v1|v2]+)/roles/$',views.RolesView.as_view(),name="roro"), re_path(r'^(?P<version>[v1|v2]+)/group/(?P<pk>\d+)$',views.GroupView.as_view(),name="gp"), re_path(r'^(?P<version>[v1|v2]+)/userinfovalid/$',views.UserInfoValidView.as_view(),name="gvv"), ]
视图的实现 views.py
1 from rest_framework import serializers 2 class Roleserializer(serializers.Serializer): 3 id = serializers.IntegerField() #id名称必须要于数据库的名称一致 4 title = serializers.CharField() 5 6 class RolesView(APIView): 7 def get(self,request,*args,**kwargs): 8 #方式一:通过json.dumps 序列化(注意不能直接序列化Django的Queryset对象) 9 # roles = models.Role.objects.all().values("id","title") 10 # roles = list(roles) 11 # ret = json.dumps(roles,ensure_ascii=False) 12 13 # 方式二:自定义serializer类 14 roles = models.Role.objects.all() 15 ser = Roleserializer(instance=roles,many=True) 16 # ser.data 是QuerySet对象转化的restframework数据类型,可以直接使用json.dumps 转化为字符串 17 # [OrderedDict([('id', 1), ('title', '护士')]), OrderedDict([('id', 2), ('title', '空姐')])] 18 19 print(ser.data) 20 ret = json.dumps(ser.data,ensure_ascii=False) 21 22 # role = models.Role.objects.all().first() 23 # ser = Roleserializer(instance=role, many=False) # 注意如果取出的单条QuerySet记录 many=False 24 # ret = json.dumps(ser.data, ensure_ascii=False) 25 26 return HttpResponse(ret)
自定义序列化字段
class UserInfoSerializer(serializers.Serializer): #source 对应的是数据库字段,如果不设置source时 xxxx 必须是与数据库的字段名一致 xxxx = serializers.CharField(source="user_type") #要想显示IntegerField中choices字典 键对应内容 使用 obj.get_字段名称_display oooo= serializers.CharField(source="get_user_type_display") username = serializers.CharField() password = serializers.CharField() #取ForeignKey UserInfo类中的字段group是外键 gp = serializers.CharField(source="group.title") #取ManyToManyField rls = serializers.SerializerMethodField(source="roles",method_name="get_rls") #函数名称必须是 get_xxx def get_rls(self,row): print(row) #UserInfo object (6) role_obj_list = row.roles.all() print(role_obj_list) #<QuerySet [<Role: Role object (4)>, <Role: Role object (5)>]> ret = [] for item in role_obj_list: ret.append({"id":item.id,"title":item.title}) return ret #另一种序列化类,继承了Serializer,自动生成所有字段 class UserInfoSerializer2(serializers.ModelSerializer): #自定义展示的字段内容 oooo = serializers.CharField(source="get_user_type_display") rls = serializers.SerializerMethodField(source="roles", method_name="get_rls") class Meta: model = models.UserInfo # fields = "__all__" #自动生成所有字段 #自定义显示字段 fields = ["id","username","password","oooo","roles","group","rls"] #深度,往所有字段里面再找一层(适用于ForeignKey ManyToMany 字段链接到另一张表) #官方推荐 depth<10 depth = 1 def get_rls(self,row): role_obj_list = row.roles.all() ret = [] for item in role_obj_list: ret.append({"id":item.id,"title":item.title}) return ret class UserInfoSerializer3(serializers.ModelSerializer): oooo = serializers.CharField(source="get_user_type_display") #反向生成url(超链接身份字段) "group": "http://127.0.0.1:8000/api/v1/group/1", group = serializers.HyperlinkedIdentityField(view_name="gp",lookup_field="group_id",lookup_url_kwarg="pk") class Meta: model = models.UserInfo # fields = "__all__" #自动生成所有字段 #自定义显示字段 fields = ["id","username","password","oooo","group","roles"] depth = 1 class UserInfoView(APIView): def get(self,request,*args,**kwargs): userinfo = models.UserInfo.objects.all() ser = UserInfoSerializer3(instance=userinfo, many=True,context={'request':request}) ret = json.dumps(ser.data, ensure_ascii=True) return HttpResponse(ret) class GroupSerializer(serializers.ModelSerializer): class Meta: model = models.UserGroup fields = "__all__" class GroupView(APIView): def get(self,request,*args,**kwargs): pk = kwargs.get("pk") obj = models.UserGroup.objects.filter(id=pk).first() print(pk,obj) #3 UserGroup object (3) ser = GroupSerializer(instance=obj, many=False) ret = json.dumps(ser.data, ensure_ascii=True) return HttpResponse(ret)
对请求数据进行验证(钩子函数-全局/局部)
############序列化-对数据进行验证################# #全局钩子有效性验证 import re class usernameValid(object): def __init__(self,base): self.base = base def __call__(self, value,*args, **kwargs): if not re.match("^[a-zA-Z_]",value): message = "用户名必须是大小写字母或下划线开头" raise serializers.ValidationError(message) if value.startswith(self.base.lower()): message = "用户名不能以sb/SB开头" raise serializers.ValidationError(message) def set_context(self,serializer_field): pass class UserInfoValid(serializers.Serializer): #可以自定义错误提示信息 # username = serializers.CharField(error_messages={'required':'用户名不能为空'}) #自定义验证规则:要求用户名必须以字母 下划线开头,长度大于3小于16,且不能以sb(SB)开头 username = serializers.CharField(min_length=3,max_length=16, error_messages={'required':'用户名不满足格式要求'}, validators=[usernameValid("sb"),]) password = serializers.CharField() def validate_password(self, value): if not re.match('[0-9a-zA-Z]',value): raise serializers.ValidationError('密码必须是数字/字母组合') else: return value class UserInfoValidView(APIView): def post(self,request,*args,**kwargs): ret = {"code":1000,"msg":None,"data":None} #request.data 是经过解析器解析到的请求数据 ser = UserInfoValid(data=request.data) #is_valid提交的字段通过验证 if ser.is_valid(): # print(ser.validated_data) #OrderedDict([('username', 'xiong')]) print(ser.validated_data["username"]) #xiong ret["data"] = ser.validated_data else: print(ser.errors) #{'title': [ErrorDetail(string='组名称必须以 会所 开头', code='invalid')]} ret["code"] = 1001 ret["msg"] = ser.errors return JsonResponse(ret)
postman测试效果:
分页(* * *)
1.第一种分页:继承PageNumberPagination的基本用法
MyPageNumberPagination
#rest_framework.response.Response有两种功能 # 1.json序列化数据 # 2.让页面渲染的更好看些 from rest_framework.response import Response from rest_framework.pagination import PageNumberPagination #第一种分页 自定义一个PageNumberPagination类,扩展一些方法 class MyPageNumberPagination(PageNumberPagination): #一页显示数据2条数据 page_size = 2 # 通过 127.0.0.1:8000/api/v1/pager/?page=2 换页 page_query_param = 'page' # ?page=2&size=4 每页显示4条数据 page_size_query_param = 'size' #每页最大显示5条数据 max_page_size = 5
基本用法:
#第一种分页 基本用法 class PagerView1(APIView): def get(self,request,*args,**kwargs): #创建分页对象 # pg = PageNumberPagination() pg = MyPageNumberPagination() #从数据库中获取分页对象 userList = models.UserInfo.objects.all() pager_list = pg.paginate_queryset(queryset=userList,request=request,view=self) #对序列化分页之后的数据进行序列化 ser = PagerSerializer(instance=pager_list, many=True) # return Response(ser.data) return pg.get_paginated_response(ser.data) # pg.get_paginated_response()封装一个Response对象,多了3条数据 # return Response(OrderedDict([ # ('count', self.page.paginator.count), # ('next', self.get_next_link()), # ('previous', self.get_previous_link()), # ('results', data) # ]))
2.第二种分页:继承LimitOffsetPagination的基本用法
MyLimitOffsetPagination
from rest_framework.pagination import LimitOffsetPagination #第二种分页 自定义一个LimitOffsetPagination类,扩展一些方法 class MyLimitOffsetPagination(LimitOffsetPagination): #设置默认一页显示2条数据 default_limit = 2 #改变一页显示的数据条数 limit_query_param = 'limit' #换页 offset_query_param = 'offset' #一页最多显示5条数据 max_limit = 5 #http://127.0.0.1:8000/api/v1/pager2/?limit=3&offset=2 第二页显示3条数据
基本用法:
#第二种分页 基本用法 class PagerView2(APIView): def get(self,request,*args,**kwargs): #获取数据表中所有数据 userList = models.UserInfo.objects.all() #创建分页对象 # pg = LimitOffsetPagination() pg = MyLimitOffsetPagination() #从数据库中获取分页对象 pager_list = pg.paginate_queryset(queryset=userList,request=request,view=self) #对序列化分页之后的数据进行序列化 ser = PagerSerializer(instance=pager_list, many=True) # return Response(ser.data) return pg.get_paginated_response(ser.data) # pg.get_paginated_response()封装一个Response对象,多了3条数据 # return Response(OrderedDict([ # ('count', self.page.paginator.count), # ('next', self.get_next_link()), # ('previous', self.get_previous_link()), # ('results', data) # ]))
3.第三种分页:继承CursorPagination的基本用法
MyCursorPagination
from rest_framework.pagination import CursorPagination #第三种分页 自定义一个CursorPagination类,扩展一些方法 class MyCursorPagination(CursorPagination): # ?cursor=xxoo 调转到相应页面(注意xxoo是经过加密的) cursor_query_param = 'cursor' # 设置默认一页显示2条数据 page_size = 2 # 设置排序规则 按username 升序 ordering = 'username'
基本用法:
# 第三种分页 基本用法 class PagerView3(APIView): def get(self, request, *args, **kwargs): # 获取数据表中所有数据 userList = models.UserInfo.objects.all() # 创建分页对象 # pg = CursorPagination() pg = MyCursorPagination() # 从数据库中获取分页对象 pager_list = pg.paginate_queryset(queryset=userList, request=request, view=self) # 对序列化分页之后的数据进行序列化 ser = PagerSerializer(instance=pager_list, many=True) # return Response(ser.data) return pg.get_paginated_response(ser.data) # pg.get_paginated_response()封装一个Response对象,多了3条数据 # return Response(OrderedDict([ # ('next', self.get_next_link()), # ('previous', self.get_previous_link()), # ('results', data) #data --->是一个数据列表 # ]))
urls.py
#分页的3中方式 re_path(r'^(?P<version>[v1|v2]+)/pager1/$',views.PagerView1.as_view()), re_path(r'^(?P<version>[v1|v2]+)/pager2/$',views.PagerView2.as_view()), re_path(r'^(?P<version>[v1|v2]+)/pager3/$',views.PagerView3.as_view()),
settijngs.py
RETS_FRAMEWORK={ #分页 "PAGE_SIZE":2, }
视图(* * *)
类的父子继承关系:从父到子
django的 View ->APIView ->GenericAPIView ->GenericViewSet ->GenericViewSet ->ModelViewSet
urls.py
#视图 re_path(r'^(?P<version>[v1|v2]+)/viewSets1/$', views.viewSetsView1.as_view()), re_path(r'^(?P<version>[v1|v2]+)/viewSets2/$', views.viewSetsView2.as_view({"get":"list"})), re_path(r'^(?P<version>[v1|v2]+)/viewSets3/$', views.viewSetsView3.as_view({"get":"list"})), # 使用ModelViewSet 一个路由要写4条url #http://127.0.0.1:8000/api/v1/viewSets4/ re_path(r'^(?P<version>[v1|v2]+)/viewSets4/$',views.viewSetsView4.as_view({"get": "list", "post": "create"})), # http://127.0.0.1:8000/api/v1/viewSets4/2.json 在浏览器中以json数据返回 不是以Django REST framework的样式方法 re_path(r'^(?P<version>[v1|v2]+)/viewSets4\.(?P<format>\w+)/$', views.viewSetsView4.as_view({"get": "list", "post": "create"})), re_path(r'^(?P<version>[v1|v2]+)/viewSets4/(?P<pk>\d+)/$', views.viewSetsView4.as_view({"get":"retrieve","delete":"destroy","put":"update","patch":"partial_update"})), re_path(r'^(?P<version>[v1|v2]+)/viewSets4/(?P<pk>\d+)\.(?P<format>\w+)/$',views.viewSetsView4.as_view({"get": "list", "post": "create"})),
#版本1.GenericAPIView继承自views.APIView 是没有多大用的
//re_path(r'^(?P<version>[v1|v2]+)/viewSets1/$', views.viewSetsView1.as_view()), class viewSetsView1(generics.GenericAPIView): # 从数据库中查询 queryset = models.UserInfo.objects.all() # 序列化 serializer_class = MySerializer # 分页 pagination_class = MyPageNumberPagination def get(self,request,*args,**kwargs): userList = self.get_queryset() users_obj = self.paginate_queryset(queryset=userList) # 对分页后的数据序列化 ser = self.get_serializer(instance=users_obj,many=True) #返回响应结果,ser.data 是序列化后的数据 return Response(ser.data)
版本2.GenericViewSet继承自ViewSetMixin 和 generics.GenericAPIView
ViewSetMixin重写了as_view 方法,可以将as_view({"get":"list"}) list方法匹配到get方法
//re_path(r'^(?P<version>[v1|v2]+)/viewSets2/$', views.viewSetsView2.as_view({"get":"list"})), class viewSetsView2(viewsets.GenericViewSet): # 从数据库中查询 queryset = models.UserInfo.objects.all() # 序列化 serializer_class = MySerializer # 分页 pagination_class = MyPageNumberPagination def list(self,request,*args,**kwargs): userList = self.get_queryset() users_obj = self.paginate_queryset(queryset=userList) # 对分页后的数据序列化 ser = self.get_serializer(instance=users_obj,many=True) #返回响应结果,ser.data 是序列化后的数据 return Response(ser.data)
# 版本3.ListModelMixin 里面重写了list方法,我们继承之后 就不用写该方法了
//re_path(r'^(?P<version>[v1|v2]+)/viewSets3/$', views.viewSetsView3.as_view({"get":"list"})), from rest_framework import mixins class viewSetsView3(mixins.ListModelMixin,viewsets.GenericViewSet): #GenericAPIView类实现如下字段 #从数据库中查询 queryset = models.UserInfo.objects.all() #序列化 serializer_class = MySerializer #分页 pagination_class = MyPageNumberPagination #ListModelMixin 里面重写的list方法: # def list(self, request, *args, **kwargs): # queryset = self.filter_queryset(self.get_queryset()) # # page = self.paginate_queryset(queryset) # if page is not None: # serializer = self.get_serializer(page, many=True) # return self.get_paginated_response(serializer.data) # # serializer = self.get_serializer(queryset, many=True) # return Response(serializer.data)
版本4(终极版本) ModelViewSet(实现 增删改查)
class ModelViewSet(mixins.CreateModelMixin, #增加数据 mixins.RetrieveModelMixin, #获取一条数据(需要id) mixins.UpdateModelMixin, #更新一条数据(需要id) mixins.DestroyModelMixin, #删除一条数据(需要id) mixins.ListModelMixin, #获取所有数据 GenericViewSet): """ A viewset that provides default `create() ==post `, `retrieve()==get(1)`, `update()==put`, `partial_update()==patch`, `destroy()==delete` and `list()==get` actions. """ pass //# 使用ModelViewSet 一个url要写两条路由: //re_path(r'^(?P<version>[v1|v2]+)/viewSets4/$',views.viewSetsView4.as_view({"get": "list", "post": "create"})), //re_path(r'^(?P<version>[v1|v2]+)/viewSets4/(?P<pk>\d+)/$',views.viewSetsView4.as_view({"get":"retrieve","delete":"destroy","put":"update","patch":"partial_update"})),
views.py
from rest_framework.viewsets import ModelViewSet class viewSetsView4(ModelViewSet): #GenericAPIView类实现如下字段 #从数据库中查询 queryset = models.UserInfo.objects.all() #序列化 serializer_class = MySerializer #分页 pagination_class = MyPageNumberPagination
总结:这么多视图类 使用时要选择哪个继承呢??
1.要实现增删改查 ModelViewSet
2.增删 CreateModelMixin DestroyModelMixin
3.复杂的逻辑 GenericViewSet 或者 APIView
路由(* *)
上面视图中一个url要写4种路由匹配方式,十分麻烦,rest_framework帮我们写好了routers
urls.py
from rest_framework import routers router = routers.DefaultRouter() router.register(r'vs4',views.viewSetsView4) urlpatterns = [ #路由 re_path(r'^(?P<version>[v1|v2]+)/',include(router.urls)), #自动生成下面4个url # ^ api / ^ (?P < version >[v1 | v2]+) / ^ vs4 /$ [name = 'userinfo-list'] # ^ api / ^ (?P < version >[v1 | v2]+) / ^ vs4\.(?P < format >[a-z0-9]+) /?$ [name = 'userinfo-list'] # ^ api / ^ (?P < version >[v1 | v2]+) / ^ vs4 / (?P < pk >[^ /.]+) /$ [name = 'userinfo-detail'] # ^ api / ^ (?P < version >[v1 | v2]+) / ^ vs4 / (?P < pk >[^ /.]+)\.(?P < format >[a-z0-9]+) /?$ [ name = 'userinfo-detail'] ]
渲染器(* *)
#渲染器 re_path(r'^(?P<version>[v1|v2]+)/renderer/$', views.RendererView.as_view({"get": "list", "post": "create"})), ###########################渲染器########################### from rest_framework.renderers import JSONRenderer,AdminRenderer,BrowsableAPIRenderer class RendererView(ModelViewSet): # renderer_classes = [JSONRenderer,] #JSONRenderer 在浏览器中以json数据展示 # renderer_classes = [JSONRenderer,AdminRenderer] #AdminRenderer 表格形式展示 renderer_classes = [JSONRenderer,BrowsableAPIRenderer] #默认的(不需要配置) json数据以一种好看的格式在浏览器渲染的界面显示(推荐不要修改啦) #从数据库中查询 queryset = models.UserInfo.objects.all() #序列化 serializer_class = MySerializer #分页 pagination_class = MyPageNumberPagination
参考:https://www.cnblogs.com/wupeiqi/articles/7805382.html
https://www.cnblogs.com/zhangliang91/p/10925706.html