Django-rest Framework
1. FBV CBV
1.1 开发模式
- 普通开发方式(前后端放在一起写)
- 前后端分离
1.2 后端开发
为前端提供URL(API/接口的开发)
注:永远返回HttpResponse
1.3 Django FBV、CBV
# FBV(function base view)def users(request):user_list = ['alex','oldboy']return HttpResponse(json.dumps((user_list)))
# CBV(class base view) # 路由:url(r'^students/', views.StudentsView.as_view()),# 视图:from django.views import Viewclass StudentsView(View):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')
1.3.1 CBV详解
CBV,基于反射实现根据请求方式不同,执行不同的方法。
-
原理:
url -> view方法 -> dispatch方法(反射执行其他:GET/POST/DELETE/PUT)
-
流程:
class StudentsView(View):def dispatch(self, request, *args, **kwargs):print('before')ret = super(StudentsView,self).dispatch(request, *args, **kwargs)print('after')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')
【扩展】:
-
super()方法,
super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。总之前人留下的经验就是:保持一致性。要不全部用类名调用父类,要不就全部用 super,不要一半一半
1.3.1 回顾Django中间件
中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出
- 中间件流程图
# 重要的四个方法process_request(self, request)process_view(self, request, view, *args, **kwargs)process_exception(self, request, exception)process_response(self, request, response)
# 模板处理的方法process_template_response
- 使用中间件做过什么?
- 权限
- 做IP访问频率限制
- 某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过20次。
- 用户登录认证
- django的csrf是如何实现?
process_view方法
- 检查视图是否被 @csrf_exempt (免除csrf_token认证)
- 去请求体或cookie中获取token
FBV,csrf_token认证情况
情况一:所有函数都要认证,某一个不需要认证 (@csrf_exempt)
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_exempt # 该函数无需认证
def users(request):user_list = ['tom','jeck']return HttpResponse(json.dumps((user_list)))
情况二:所有都不认证,某个函数需要认证(@csrf_protect)
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 # 该函数需认证
def users(request):user_list = ['tom','jeck']return HttpResponse(json.dumps((user_list)))
CBV,csrf_token认证时需要使用
- @method_decorator(csrf_exempt)
- 在dispatch方法中(单独方法无效)
# 方式一:
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decoratorclass 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):print('get方法')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.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator@method_decorator(csrf_exempt,name='dispatch')
class StudentsView(View):def get(self,request,*args,**kwargs):print('get方法')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')
2. restful设计规范
什么是RESTful
- REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
- REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
- REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
- 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
- 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
RESTful主要是面向资源(视网络上任何东西都是资源)设计它的接口的, 它有四个规范:- 每个资源都存在唯一的标识URI- 每个资源都具有四个动作谓词, 分别是GET/POST/PUT/DELETE- 每次的动作都是无状态的, 即是HTTP的短连接(Connection: close|keep-alive)- 交互的资源数据类型一般是json或xml.
3. DRF 框架
安装
pip3 install djangorestframework
3.1 基本流程
url.py
from django.urls import path, include
from web.views.api import TestViewurlpatterns = [path('test/', TestView.as_view),
]
views.py
from rest_framework.views import APIView
from rest_framework.response import Responseclass TestView(APIView):def dispatch(self, request, *args, **kwargs):"""请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法注意:APIView中的dispatch方法有好多好多的功能"""return super().dispatch(request, *args, **kwargs)def get(self, request, *args, **kwargs):return Response('GET请求,响应内容')def post(self, request, *args, **kwargs):return Response('POST请求,响应内容')def put(self, request, *args, **kwargs):return Response('PUT请求,响应内容')
3.2 认证/权限
局部&全局设置
3.2.1 源码
...def dispatch(self, request, *args, **kwargs):"""`.dispatch()` is pretty much the same as Django's regular dispatch,but with extra hooks for startup, finalize, and exception handling."""self.args = argsself.kwargs = kwargs# 对原生request进行加工# Request(# request,# parsers=self.get_parsers(),# authenticators=self.get_authenticators(),# negotiator=self.get_content_negotiator(),# parser_context=parser_context )# request(原生的request, [BasicAuthentication对象,]) \# 获取原生request, 用request._request# 获取认证类的对象, request.authentications# 1.封装requestrequest = self.initialize_request(request, *args, **kwargs)self.request = requestself.headers = self.default_response_headers # deprecate?try:# 2.认证self.initial(request, *args, **kwargs)# Get the appropriate handler methodif request.method.lower() in self.http_method_names:handler = getattr(self, request.method.lower(),self.http_method_not_allowed)else:handler = self.http_method_not_allowedresponse = handler(request, *args, **kwargs)except Exception as exc:response = self.handle_exception(exc)self.response = self.finalize_response(request, response, *args, **kwargs)return self.response
def initial(self, request, *args, **kwargs):"""Runs anything that needs to occur prior to calling the method handler."""self.format_kwarg = self.get_format_suffix(**kwargs)# Perform content negotiation and store the accepted info on the requestneg = self.perform_content_negotiation(request)request.accepted_renderer, request.accepted_media_type = neg# Determine the API version, if versioning is in use.version, scheme = self.determine_version(request, *args, **kwargs)request.version, request.versioning_scheme = version, scheme# Ensure that the incoming request is permitted# 3.实现认证self.perform_authentication(request)# 4.权限判断self.check_permissions(request)# 5.访问频率self.check_throttles(request)
3.2.1.1 实现认证:
def perform_authentication(self, request):"""Perform authentication on the incoming request.Note that if you override this and simply 'pass', then authenticationwill instead be performed lazily, the first time either`request.user` or `request.auth` is accessed."""request.user
@propertydef user(self):"""Returns the user associated with the current request, as authenticatedby the authentication classes provided to the request."""if not hasattr(self, '_user'):with wrap_attributeerrors():# 获取认证对象,进行一步步认证self._authenticate()return self._user
def _authenticate(self):"""Attempt to authenticate the request using each authentication instancein turn."""# 循环认证对象[BasicAuthentication对象,]for authenticator in self.authenticators:try:# 执行认证类的authenticate方法# ① 如果authenticate方法抛出异常,self._not_authenticated()执行# ② 有返回值,必须是元组(self.user, self.auth)# ③ 返回None,我不管,下一个认证user_auth_tuple = authenticator.authenticate(self)except exceptions.APIException:self._not_authenticated()raise # 没通过认证触发异常if user_auth_tuple is not None:self._authenticator = authenticatorself.user, self.auth = user_auth_tuplereturnself._not_authenticated()
def _not_authenticated(self):"""Set authenticator, user & authtoken representing an unauthenticated request.Defaults are None, AnonymousUser & None."""self._authenticator = Noneif api_settings.UNAUTHENTICATED_USER:self.user = api_settings.UNAUTHENTICATED_USER() # AnonymousUser 设置默认值表示匿名用户else:self.user = Noneif api_settings.UNAUTHENTICATED_TOKEN:self.auth = api_settings.UNAUTHENTICATED_TOKEN() # Noneelse:self.auth = None
3.2.1.2 权限判断
def check_permissions(self, request):"""Check if the request should be permitted.Raises an appropriate exception if the request is not permitted."""# 遍历[权限类的对象,权限类的对象,]for permission in self.get_permissions():if not permission.has_permission(request, self):self.permission_denied(request,message=getattr(permission, 'message', None),code=getattr(permission, 'code', None))
权限类对象:
def get_permissions(self):"""Instantiates and returns the list of permissions that this view requires."""return [permission() for permission in self.permission_classes] # 列表生成式
如果上面permission.has_permission(request, self):
为False
def permission_denied(self, request, message=None, code=None):"""If request is not permitted, determine what kind of exception to raise."""if request.authenticators and not request.successful_authenticator:raise exceptions.NotAuthenticated()raise exceptions.PermissionDenied(detail=message, code=code) # 抛出异常 表示权限验证失败
3.2.1.3 访问频率
def check_throttles(self, request):"""Check if request should be throttled.Raises an appropriate exception if the request is throttled."""throttle_durations = []for throttle in self.get_throttles():if not throttle.allow_request(request, self):throttle_durations.append(throttle.wait())if throttle_durations:# Filter out `None` values which may happen in case of config / rate# changes, see #1438durations = [duration for duration in throttle_durationsif duration is not None]duration = max(durations, default=None)self.throttled(request, duration)
def get_throttles(self):"""Instantiates and returns the list of throttles that this view uses."""return [throttle() for throttle in self.throttle_classes]
3.2.2 认证使用
from django.views import View
from rest_framework.views import APIView
from rest_framework.authentication import BasicAuthentication
from rest_framework import exceptions
from rest_framework.request import Requestclass MyAuthentication(object):def authenticate(self,request):token = request._request.GET.get('token')# 获取用户名和密码,去数据校验if not token:raise exceptions.AuthenticationFailed('用户认证失败')return ("zhb",None)def authenticate_header(self,val):passclass DogView(APIView):authentication_classes = [MyAuthentication,]def get(self,request,*args,**kwargs):print(request)print(request.user) # 拿到上面authenticate方法返回值元组里的第一个元素"zhb"# self.dispatch # 源码流程入口ret = {'code':1000,'msg':'xxx'}return HttpResponse(json.dumps(ret),status=201)def post(self,request,*args,**kwargs):return HttpResponse('创建Dog')def put(self,request,*args,**kwargs):return HttpResponse('更新Dog')def delete(self,request,*args,**kwargs):return HttpResponse('删除Dog')
3.2.3 全局配置(示例代码)
urls.py文件:
from django.conf.urls import url
from django.contrib import admin
from api import viewsurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^api/v1/auth/$', views.AuthView.as_view()),url(r'^api/v1/order/$', views.OrderView.as_view()),url(r'^api/v1/info/$', views.UserInfoView.as_view()),
]
model.py文件 (创建两张表)
from django.db import modelsclass UserInfo(models.Model):user_type_choices = ((1,'普通用户'),(2,'VIP'),(3,'SVIP'),)user_type = models.IntegerField(choices=user_type_choices)username = models.CharField(max_length=32,unique=True)password = models.CharField(max_length=64)class UserToken(models.Model):user = models.OneToOneField(to='UserInfo')token = models.CharField(max_length=64)
settings.py 文件里面全局设置认证,视图里面则不需要设置
REST_FRAMEWORK = {# 全局使用的认证类"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication', ],# "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.FirstAuthtication', ],# "UNAUTHENTICATED_USER":lambda :"匿名用户""UNAUTHENTICATED_USER":None, # 匿名,request.user = None"UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.SVIPPermission'], # 权限判断全局设置"DEFAULT_THROTTLE_CLASSES":["api.utils.throttle.UserThrottle"],"DEFAULT_THROTTLE_RATES":{"Luffy":'3/m', # 匿名用户每分钟3次"LuffyUser":'10/m', # 登录用户没分中10次}
}
api–>views.py
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BasicAuthentication
from api.utils.permission import SVIPPermission
from api.utils.permission import MyPermission1
from api.utils.throttle import VisitThrottle
from api import modelsORDER_DICT = {1:{'name': "媳妇",'age':18,'gender':'男','content':'...'},2:{'name': "老狗",'age':19,'gender':'男','content':'...。。'},
}def md5(user):import hashlibimport timectime = str(time.time())m = hashlib.md5(bytes(user,encoding='utf-8'))m.update(bytes(ctime,encoding='utf-8'))return m.hexdigest()class AuthView(APIView):"""用于用户登录认证"""authentication_classes = [] # 全局设置了,这里设置空列表,表示此处不需要认证permission_classes = []throttle_classes = [VisitThrottle,]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'] = 1001ret['msg'] = "用户名或密码错误"# 为登录用户创建tokentoken = md5(user)# 存在就更新,不存在就创建models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})ret['token'] = tokenexcept Exception as e:ret['code'] = 1002ret['msg'] = '请求异常'return JsonResponse(ret)class OrderView(APIView):"""订单相关业务(只有SVIP用户有权限)"""def get(self,request,*args,**kwargs):# request.user# request.authself.dispatchret = {'code':1000,'msg':None,'data':None}try:ret['data'] = ORDER_DICTexcept Exception as e:passreturn JsonResponse(ret)class UserInfoView(APIView):"""订单相关业务(普通用户、VIP)"""permission_classes = [MyPermission1, ]def get(self,request,*args,**kwargs):return HttpResponse('用户信息')
3.2.3.1 auth 认证
api --> utils --> auth.py 将认证类抽取出来
from rest_framework import exceptions
from api import models
from rest_framework.authentication import BaseAuthenticationclass FirstAuthtication(BaseAuthentication):def authenticate(self,request):passdef authenticate_header(self, request):passclass Authtication(BaseAuthentication):def authenticate(self,request):token = request._request.GET.get('token')token_obj = models.UserToken.objects.filter(token=token).first()if not token_obj:raise exceptions.AuthenticationFailed('用户认证失败')# 在rest framework内部会将整个两个字段赋值给request,以供后续操作使用return (token_obj.user, token_obj)def authenticate_header(self, request):return 'Basic realm="api"'
3.2.3.2 认证小结
a. 问题:有些API需要用户登录成功之后,才能访问;有些无需登录就能访问。
b. 基本使用认证组件解决:1. 创建两张表2. 用户登录(返回token并保存到数据库)
c. 认证流程原理- 上面源码d. 再看一遍源码1. 局部视图使用&全局使用2. 匿名是request.user = Nonee. 内置认证类1. 认证类,必须继承:from rest_framework.authentication import BaseAuthentication2. 其他认证类:BasicAuthentication
梳理:
1. 使用 - 创建类:继承BaseAuthentication; 实现:authenticate方法- 返回值:- None,我不管了,下一认证来执行。- raise exceptions.AuthenticationFailed('用户认证失败') # from rest_framework import exceptions- (元素1,元素2) # 元素1赋值给request.user; 元素2赋值给request.auth - 局部使用from rest_framework.authentication import BaseAuthentication,BasicAuthenticationclass UserInfoView(APIView):"""订单相关业务"""authentication_classes = [BasicAuthentication,]def get(self,request,*args,**kwargs):print(request.user)return HttpResponse('用户信息')- 全局使用:REST_FRAMEWORK = {# 全局使用的认证类"DEFAULT_AUTHENTICATION_CLASSES:['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication', ],# "UNAUTHENTICATED_USER":lambda :"匿名用户""UNAUTHENTICATED_USER":None, # 匿名,request.user = None"UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None}
2. 源码流程- dispatch- 封装request- 获取定义的认证类(全局/局部),通过列表生成时创建对象。- initial- perform_authenticationrequest.user(内部循环....)
3.2.4 权限
api --> utils --> permission.py 将权限判断类抽取出来
from rest_framework.permissions import BasePermissionclass SVIPPermission(BasePermission): # 继承BasePermission(内置类方法)message = "必须是SVIP才能访问"def has_permission(self,request,view):if request.user.user_type != 3:return Falsereturn Trueclass MyPermission1(BasePermission):def has_permission(self,request,view):if request.user.user_type == 3:return Falsereturn True
3.2.5 权限小结
1. 使用- 类,必须继承:BasePermission,必须实现:has_permission方法from rest_framework.permissions import BasePermissionclass SVIPPermission(BasePermission):message = "必须是SVIP才能访问"def has_permission(self,request,view):if request.user.user_type != 3:return Falsereturn True- 返回值: - True, 有权访问- False,无权访问- 局部 class UserInfoView(APIView):"""订单相关业务(普通用户、VIP)"""permission_classes = [MyPermission1, ]def get(self,request,*args,**kwargs):return HttpResponse('用户信息')- 全局 REST_FRAMEWORK = {"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.SVIPPermission']}2. 源码流程 ...
3.2.6 访问频率(节流)
api --> utils --> throttle.py 将访问频率类抽取出来
自己实现:
import time
VISIT_RECORD = {}
class VisitThrottle(BaseThrottle):"""60S内只能访问3次"""def __init__(self):self.history = Nonedef allow_request(self,request,view):# 1. 获取用户IPremote_addr = self.get_ident(request)# 获取当前时间ctime = time.time()if remote_addr not in VISIT_RECORD:VISIT_RECORD[remote_addr] = [ctime,]return Truehistory = VISIT_RECORD.get(remote_addr)self.history = history# 当前IP对应的列表中的最后一个时间记录<当前时间减60秒,即60S前的时间删除while history and history[-1] < ctime - 60:history.pop()# 判断当前IP对应的列表中个数<3个,则把当前时间插入列表if len(history) < 3:history.insert(0,ctime)return True# return True # 表示可以继续访问# return False # 表示访问频率太高,被限制def wait(self):# 还需要等多少秒才能访问ctime = time.time()return 60 - (ctime - self.history[-1])
使用Django内置类
from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):scope = "Luffy"def get_cache_key(self, request, view):return self.get_ident(request)class UserThrottle(SimpleRateThrottle):scope = "LuffyUser"def get_cache_key(self, request, view):return request.user.username
SimpleRateThrottle 类里面实现的方法(源码):
主要是allow_request()
wait()
方法
class SimpleRateThrottle(BaseThrottle):"""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 Viewclass. 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."""cache = default_cachetimer = time.timecache_format = 'throttle_%(scope)s_%(ident)s'scope = NoneTHROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATESdef __init__(self):if not getattr(self, 'rate', None):self.rate = self.get_rate()self.num_requests, self.duration = self.parse_rate(self.rate)def get_cache_key(self, request, view):"""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."""raise NotImplementedError('.get_cache_key() must be overridden')def get_rate(self):"""Determine the string representation of the allowed request rate."""if not getattr(self, 'scope', None):msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %self.__class__.__name__)raise ImproperlyConfigured(msg)try:return self.THROTTLE_RATES[self.scope]except KeyError:msg = "No default throttle rate set for '%s' scope" % self.scoperaise ImproperlyConfigured(msg)def parse_rate(self, rate):"""Given the request rate string, return a two tuple of:<allowed number of requests>, <period of time in seconds>"""if rate is None:return (None, None)num, period = rate.split('/')num_requests = int(num)duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]return (num_requests, duration)def allow_request(self, request, view):"""Implement the check to see if the request should be throttled.On success calls `throttle_success`.On failure calls `throttle_failure`."""if self.rate is None:return Trueself.key = self.get_cache_key(request, view)if self.key is None:return Trueself.history = self.cache.get(self.key, [])self.now = self.timer()# Drop any requests from the history which have now passed the# throttle durationwhile self.history and self.history[-1] <= self.now - self.duration:self.history.pop()if len(self.history) >= self.num_requests:return self.throttle_failure()return self.throttle_success()def throttle_success(self):"""Inserts the current request's timestamp along with the keyinto the cache."""self.history.insert(0, self.now)self.cache.set(self.key, self.history, self.duration)return Truedef throttle_failure(self):"""Called when a request to the API has failed due to throttling."""return Falsedef wait(self):"""Returns the recommended next request time in seconds."""if self.history:remaining_duration = self.duration - (self.now - self.history[-1])else:remaining_duration = self.durationavailable_requests = self.num_requests - len(self.history) + 1if available_requests <= 0:return Nonereturn remaining_duration / float(available_requests)
DRF (django rest framework) 补充
DRF,是django第三方组件,能帮助我们快速实现遵循rest ful 规范的接口。
本质是一个Django的组件(app);
帮助我们快速实现遵循rest ful 规范的接口;- 帮助我们做了csrf的豁免- 页面渲染(将json放到页面了)- 序列化(直接对QuerySet对象序列化)- request.data (拿到用户数据并反序列化)
# 安装
pip install djangorestframework
使用drf开始restful API :
1. 在app中注册drf组件
在settings的app中注册:
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','rest_framework'
]
2. 序列化
直接对QuerySet对象序列化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7cLfX6qA-1610934599975)(C:\Users\zhanghaibo04\AppData\Roaming\Typora\typora-user-images\image-20210113154607228.png)]
【扩展】:视图继承的类:
APIView: 没有提供增删改查功能,全部需要自己写。
ModelViewSet: 内部实现了增删改查,无需自己写。
url.py
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eA50QCb6-1610934599977)(C:\Users\zhanghaibo04\AppData\Roaming\Typora\typora-user-images\image-20210113162942561.png)]
view.py 视图函数实现(也需要 定义上面的实例化类)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9xjfQK2j-1610934599978)(C:\Users\zhanghaibo04\AppData\Roaming\Typora\typora-user-images\image-20210113162825955.png)]
3. drf 版本控制
3.1 局部控制
局部控制,只能某个接口能获取到值
路由:
urlpatterns = [url(r'^admin/', admin.site.urls),url(r'^api/(?P<version>\w+)/', include('api.urls')), # api/ 后设置版本
]
urlpatterns = [url(r'^order/$', views.OrderView.as_view()),
]
导入模块及视图函数:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning # 导入版本模块class OrderView(APIView):# 原本 versioning_class=None,此处重写versioning_classversioning_class = URLPathVersioning def get(self,request,*args,**kwargs):print(request.version) # request.version获取版本return Response('...')
3.2 全局控制
全局控制,多有接口都能能获取到值
在settings.py中配置如下REST_FRAMEWORK
代码:
REST_FRAMEWORK = {'DEFAULT_VERSIONING_CLASS': "rest_framework.versioning.URLPathVersioning"
}
urlpatterns = [url(r'^admin/', admin.site.urls),url(r'^api/(?P<version>\w+)/', include('api.urls')),
]
urlpatterns = [url(r'^order/$', views.OrderView.as_view()),
]
from rest_framework.views import APIView
from rest_framework.response import Response
class OrderView(APIView):def get(self,request,*args,**kwargs):print(request.version)return Response('...')
4. 视图
视图继承类的三种情况:
-
直接继承APIView,适用于非ORM简单操作(即不操作数据库),需要自定义功能时,使用。
from rest_framework.views import APIViewclass TestView(APIView):def get(self,request,*args,**kwargs):pass
-
继承ListAPIView,DestroyAPIView,UpdateAPIView,RetrieveAPIView,CreateAPIView,项目中只要实现某几个接口时,而不是增删改查。
from api import models from rest_framework import serializers from rest_framework.generics import ListAPIView,DestroyAPIView,UpdateAPIView,RetrieveAPIView,CreateAPIView class CourseSerializers(serializers.ModelSerializer):class Meta:model = models.Coursefields = "__all__"class CourseView(ListAPIView,CreateAPIView): # 只实现增加功能queryset = models.Course.objects.all()serializer_class = CourseSerializers
-
继承ModelViewSet,功能中需要实现对表的增删改查时。
from rest_framework import serializers from rest_framework.viewsets import ModelViewSetclass CourseSerializers(serializers.ModelSerializer):class Meta:model = models.Coursefields = "__all__"class CourseView(ModelViewSet):queryset = models.Course.objects.all()serializer_class = CourseSerializers
面试题:GenericAPIView的作用?
指定了接口执行的流程。如果继承了GenericAPIView的类,他们的在内部取数据时,调用 self.get_queryset()它定义在GenericAPIView,它内部返回self.queryset
5. 案例
5.1 zhb项目
创建zhb项目,5 张表
from django.db import modelsclass Tag(models.Model):"""标签表"""caption = models.CharField(verbose_name='标签名称',max_length=32)class Course(models.Model):"""课程表,例如:Linux、Python、测试课程"""title = models.CharField(verbose_name='课程名称',max_length=32)class Module(models.Model):"""模块表"""name = models.CharField(verbose_name='模块名',max_length=32)course = models.ForeignKey(verbose_name='课程', to='Course', on_delete=models.CASCADE)class Video(models.Model):"""视频表"""title = models.CharField(verbose_name='视频名称', max_length=32)vid = models.CharField(verbose_name='xxx视频ID', max_length=64)tag = models.ManyToManyField(verbose_name='标签',to='Tag',)
5.2 第一个接口
实现接口返回一个字符串(含有版本)
# settings 中注册
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','api.apps.ApiConfig','rest_framework',
]# settings中配置版本
REST_FRAMEWORK = {'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning','ALLOWED_VERSIONS': ['v1', 'v2'] # 允许版本
}
urlpatterns = [path('admin/', admin.site.urls),re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),
]
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioningclass TestView(APIView):def get(self, request, *args, **kwargs):print(request.version)return Response('成功')
5.3 第二个接口
访问接口时,返回一个XML文件
<cross-domain-policy>
<allow-access-from domain="*.polyv.net"/>
</cross-domain-policy>
urlpatterns = [path('admin/', admin.site.urls),re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),re_path('api/(?P<version>\w+)/crossdomain/', views.CrossView.as_view()),
]
class CrossView(APIView):def get(self, request, *args, **kwargs):with open('crossdomain.xml') as f:data = f.read()return Response(data)
5.4 基于APIView
实现对课程表的:
-
获取所有的数据
-
增加数据
urlpatterns = [path('admin/', admin.site.urls),re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),re_path('api/(?P<version>\w+)/cross/', views.CrossView.as_view()),re_path('api/(?P<version>\w+)/course/', views.CourseView.as_view()), ]
# 新建ser.py 用作新建序列化类 from api import models from rest_framework import serializersclass CourseSerializer(serializers.ModelSerializer):class Meta:model = models.Coursefields = "__all__"
# view函数 from api import models from api.ser import CourseSerializer from rest_framework.views import APIView from rest_framework.response import Responseclass CourseView(APIView):def get(self, request, *args, **kwargs):result = {'status':1000, 'data':None, 'error':None}queryset = models.Course.objects.all()ser = CourseSerializer(instance=queryset, many=True)result['data'] = ser.datareturn Response(result)def post(self, request, *args, **kwargs):# 1.获取用户提交的数据 request.data# 2.校验数据的合法性 序列化# 3.校验通过save# 4.不通过报错result = {'status':1000, 'data':None, 'error':None}ser = CourseSerializer(data=request.data)if ser.is_valid():ser.save()return Response(result)result['error'] = ser.errorsresult['status'] = 2000return Response(result)
5.5 基于ListAPIView,CreateAPIView
实现5.4功能
urlpatterns = [path('admin/', admin.site.urls),re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),re_path('api/(?P<version>\w+)/cross/', views.CrossView.as_view()),re_path('api/(?P<version>\w+)/course/$', views.CourseView.as_view()),re_path('api/(?P<version>\w+)/course/new/$', views.CourseNewView.as_view()),
]
from api import models
from api.ser import CourseSerializer
from rest_framework.response import Response
from rest_framework.generics import ListAPIView, CreateAPIViewclass CourseNewView(ListAPIView,CreateAPIView):queryset = models.Course.objects.all()serializer_class = CourseSerializer
5.6 对Course表实现增删改查
urlpatterns = [path('admin/', admin.site.urls),re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),re_path('api/(?P<version>\w+)/cross/', views.CrossView.as_view()),re_path('api/(?P<version>\w+)/course/$', views.CourseView.as_view()),re_path('api/(?P<version>\w+)/course/new/$', views.CourseNewView.as_view()),re_path('api/(?P<version>\w+)/course/curd/$', views.CourseCurdView.as_view({'get': 'list', 'post': 'create'})),re_path('api/(?P<version>\w+)/course/curd/(?P<pk>\d+)/$', views.CourseCurdView.as_view({'get':'retrieve','put':'update','delete':'destroy','patch':'partial_update'})),
]
from api import models
from api.ser import CourseSerializer
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSetclass CourseCurdView(ModelViewSet):queryset = models.Course.objects.all()serializer_class = CourseSerializer
5.7 对5.6的功能改变
对Course表实现增删改查,对列表页面的功能不要再去数据库获取,而去文件中获取即可
urlpatterns = [path('admin/', admin.site.urls),re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),re_path('api/(?P<version>\w+)/cross/', views.CrossView.as_view()),re_path('api/(?P<version>\w+)/course/$', views.CourseView.as_view()),re_path('api/(?P<version>\w+)/course/new/$', views.CourseNewView.as_view()),re_path('api/(?P<version>\w+)/course/curd/$', views.CourseCurdView.as_view({'get': 'list', 'post': 'create'})),re_path('api/(?P<version>\w+)/course/curd/(?P<pk>\d+)/$', views.CourseCurdView.as_view({'get':'retrieve','put':'update','delete':'destroy','patch':'partial_update'})),re_path('api/(?P<version>\w+)/course/file/$', views.CourseFileView.as_view({'get': 'list', 'post': 'create'})),re_path('api/(?P<version>\w+)/course/file/(?P<pk>\d+)/$', views.CourseFileView.as_view({'get':'retrieve','put':'update','delete':'destroy','patch':'partial_update'})),
class CourseFileView(ModelViewSet):queryset = models.Course.objects.all()serializer_class = CourseSerializer# 重写list方法,改变原有的功能def list(self, request, *args, **kwargs):with open('crossdomain.xml') as f:data = f.read()return Response(data)
5.8 获取列表数据
基于APIView + serializer实现对模块表 实现获取数据 【ForeignKey】
-
显示所有数据
class ModuleSerializer(serializers.ModelSerializer):class Meta:model = models.Modulefields = "__all__"
class ModuleView(APIView):def get(self, request, *args, **kwargs):queryset = models.Module.objects.all()ser = ModuleSerializer(instance=queryset, many=True)return Response(ser.data)
-
只显示id和name
只修改自定义序列化类ModuleSerializer即可如下:
class ModuleSerializer(serializers.ModelSerializer):class Meta:model = models.Module# fields = "__all__"# fields = ['id', 'name'] # 显示想要显示的字段exclude = ['course'] # 排除掉course
-
显示id、name和课程名称
class ModuleSerializer(serializers.ModelSerializer):cname = serializers.CharField(source='course.title') # 定义连表需要显示的字段class Meta:model = models.Module# fields = "__all__"fields = ['id', 'name', 'cname'] # 显示想要显示的字段
-
显示id、name、课程名称和级别
# 修改表结构,模块表添加级别字段class Module(models.Model):"""模块表"""level_choise = {'1': '初级','2': '中级','3': '高级',}level = models.ImageField(verbose_name='级别',choices=level_choise,default=1)name = models.CharField(verbose_name='模块名',max_length=32)course = models.ForeignKey(verbose_name='课程', to='Course', on_delete=models.CASCADE)
class ModuleSerializer(serializers.ModelSerializer):cname = serializers.CharField(source='course.title') # 定义连表需要显示的字段level_test = serializers.CharField(source='get_level_display') # 定义本表需要显示的字段(以中文显示)class Meta:model = models.Modulefields = ['id', 'name', 'level_test', 'cname'] # 显示想要显示的字段
# 显示结果:[{"id": 1,"name": "数据结构","level_test": "初级","cname": "Python"},{"id": 2,"name": "正则表达式","level_test": "初级","cname": "Python"} ]
5.9 单条数据展示、增加
基于CreateAPIView,RetrieveAPIView等系列,实现对module表:单条数据展示、增加
urlpatterns = [re_path('api/(?P<version>\w+)/module/new/$', views.ModuleNewView.as_view()), # 只对应POST方法re_path('api/(?P<version>\w+)/module/new/(?P<pk>\d+)/$', views.ModuleNewView.as_view()), # 只对应GET方法
]
class ModuleNewSerializer(serializers.ModelSerializer):class Meta:model = models.Modulefields = "__all__"
from rest_framework.generics import ListAPIView, CreateAPIView,RetrieveAPIViewclass ModuleNewView(CreateAPIView,RetrieveAPIView,):queryset = models.Module.objects.all()serializer_class = ModuleNewSerializer
5.10 对module表实现增删改查
基于ModelViewSet实现对module表实现增删改查:
urlpatterns = [re_path('api/(?P<version>\w+)/module/set/$', views.ModuleSetView.as_view({'get':'list','post':'create'})),re_path('api/(?P<version>\w+)/module/set/(?P<pk>\d+)/$', views.ModuleSetView.as_view({'get':'retrieve','delete':'destroy','patch':'partial_update','put':'update'})),
]
class ModuleSetSerializer(serializers.ModelSerializer):class Meta:model = models.Modulefields = "__all__"
from rest_framework.viewsets import ModelViewSetclass ModuleSetView(ModelViewSet):queryset = models.Module.objects.all()serializer_class = ModuleSetSerializer# 重写partial_update方法实现部分更新,也可以不重写,因为源码里也是如此实现的def partial_update(self, request, *args, **kwargs):pk = kwargs.get('pk')module_object = models.Module.objects.filter(id=pk).first()ser = ModuleSetSerializer(instance=module_object,data=request.data,many=False,partial=True)if ser.is_valid():ser.save()return Response('成功')return Response(ser.errors)
5.11 多对多表操作
-
对Video表做接口,获取视频列表、单条视频信息
urlpatterns = [re_path('api/(?P<version>\w+)/video/$', views.VideoView.as_view()),re_path('api/(?P<version>\w+)/video/(?P<pk>\d+)/$', views.VideoView.as_view()), ]
class VideoSerializer(serializers.ModelSerializer):class Meta:model = models.Videofields = "__all__"
class VideoView(ListAPIView, CreateAPIView):queryset = models.Video.objects.all()serializer_class = VideoSerializer
-
对Video表做增删改查
- 多对多定制显示操作
# 这样定义可以省略{'get':'list','post':'create'...}from django.urls import path, re_path, include from rest_framework import routers from api import viewsroute = routers.DefaultRouter() route.register(r'video_set', views.VideoSetView)urlpatterns = [re_path('api/(?P<version>\w+)/',include(route.urls)) ]
# 打印出多对多关联表的内容class VideoSetSerializer(serializers.ModelSerializer):tag_test = serializers.SerializerMethodField() # 多对多定制显示操作class Meta:model = models.Videofields = ['id', 'title', 'vid', 'tag', 'tag_test']# 钩子函数(get_+自定义字段),每次展示钩子函数都会执行一遍,# obj表示每次展示每条数据对象def get_tag_test(self, obj):# 与之关联的tag表中的所有数据tag_list = obj.tag.all()return [{'id': row.id, 'caption': row.caption} for row in tag_list]
class VideoSetView(ModelViewSet):queryset = models.Video.objects.all()serializer_class = ser.VideoSetSerializer
6. drf提供了哪些功能
1、免除csrftoken的认证
2、提供了一些视图类:APIView、ListAPIView、ModelViewSet,他的内部帮助我们写了get/post/delete...方法,帮助我们可以快速实现增删改查。
3、渲染器,页面的渲染。
4、序列化(表单验证+序列化)
5、解析器,解析request.body中的数据格式,并将其赋值到request.data中。
6、版本控制
7. drf总结
1、HTTP请求方法有哪些?方法是干什么的
get/post/put/patch/delete
2、用drf做接口开发:数据显示
- 单表:最简单,定义files字段即可
- FK:基于source参数连表操作
- M2M:基于SerializerMethodField + 自定义一个钩子方法
- 注意:choise显示中文可以使用source字段
"get_字段名_display"
3、有三种写接口的方法
- APIView
- ListAPIView
- ModelViewSet
4、局部更新
局部更新应用用patch请求