drf简介
Django REST Framework是一个建立在Django基础之上的Web应用开发框架
可以快速的开发REST API接口应用 在DjangoRestFramework中提供了序列化器Serialzier的定义 可以帮助我们简化序列化与反序列化的过程
还提供丰富的类视图、扩展类、视图集
来简化视图的编写工作DjangoRestFramework还提供了认证、权限、限流、过滤、分页、接口文档等
功能的支持 DjangoRestFramework提供了一个API 的Web可视化界面来方便查看测试接口
下载
pip install djangorestframework
使用
需要在
settings.py
中的INSTALLED_APPS
添加rest_framework应用
INSTALLED_APPS = [
'rest_framework',
]
drf框架体验
创建模型
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
迁移
# 切换到项目根目录下,执行迁移命令
python manage.py makemigrations
python manage.py migrate
视图类ApiView
APIView
是基于Django
原生的View
编写接口的,是drf
提供的一个类(APIView)
,使用drf编写视图类,都是继承这个类及子类,APIView
本身就是继承了Django
原生的View
1.视图Views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from app01 import models
class BookView(APIView):
def get(self, request):
book_list = models.Book.objects.all()
l = []
for book in book_list:
l.append({'name': book.name, 'price': book.price})
return Response({'code': 100, 'msg': '查询成功', 'results': l})
def post(self,request):
data = json.loads(request.body)
models.Book.objects.create(name=request.data.get('name'), price=request.data.get('price'))
# Response 响应的具体数据内容会被转换(render渲染)成符合前端需求的类型
return Response({'code': 100, 'msg': '添加成功','results': request.data})
class BookDetailView(APIView):
def get(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
l = []
l.append({'name': book_obj.name, 'price': book_obj.price})
return Response({'code': 100, 'msg': '查询一条成功', 'results': l})
def put(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
if book_obj:
book_obj.name = request.data.get('name')
book_obj.price = request.data.get('price')
book_obj.save()
return Response({'code': 100, 'msg': '修改一条成功','results':request.data})
else:
return Response({'code': 101, 'msg': '当前不存在此条数据'})
def delete(self,request,pk):
book_obj = models.Book.objects.filter(pk=pk).exists()
if book_obj:
models.Book.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '删除一条成功'})
else:
return Response({'code': 101, 'msg': '当前不存在此条数据'})
2.路由urls.py
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views.BookView.as_view()),
path('books/<int:pk>', views.BookDetailView.as_view()),
]
drf框架的APIView类继承自django的View类,并对as_view()、dispatch()方法进行了重写,在dispatch调用initialize_request,进行认证认证操作,对原先的self.request进行了一层的包装
首先在路由中:path('books/', views.BookView.as_view()),当请求来了
然后会执行as_view(),因为是继承着APIView所以会进入到APIView中执行它的as_view,
而APIView的as_view其实执行结果和Django原生View的as_view是一样的,不过去除了csrf认证
class APIView(View):
#默认去setting的,可以自己设置值,则会覆盖。
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES #认证
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES #限流
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES # 权限
#。。。。其他
@classmethod
def as_view(cls, **initkwargs):
'''
如果视图类有queryset属性,且其类型为models.query.QuerySet,则设置一个函数force_evaluation
这个函数用于防止直接评估queryset,因为结果会被缓存并在多个请求之间重复使用
'''
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
def force_evaluation():
raise RuntimeError(
'Do not evaluate the `.queryset` attribute directly, '
'as the result will be cached and reused between requests. '
'Use `.all()` or call `.get_queryset()` instead.'
)
cls.queryset._fetch_all = force_evaluation
'''
这里调用了父类的as_view方法,父类是Django原生的View
然后把自己的参数传递了进去,并返回被csrf_exempt装饰的视图,这个csrf会把所有的请求csrf检验认证去掉
'''
view = super().as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
'''
csrf_exempt是一个装饰器,用于豁免csrf验证
相当于在所有的方法上加上了这个装饰器
'''
return csrf_exempt(view)
'''
总结:当路由匹配成功后,会执行APIView父类的as_view,并且会先执行csrf_exempt(View)(request)
然后按照正常执行Django原生的as_view方法,然后会执行它的view(request),最终会返回一个
return self.dispatch(request, *args, **kwargs),它会从视图类中寻找,发现没有,
因为视图类继承的是APIView,所以会优先从APIview中寻找是否有dispatch这个方法,结果找到了
'''
'dispatch方法用于处理传入的请求'
def dispatch(self, request, *args, **kwargs):
'保存参数'
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
'''
这里把原来的request传入到self.initialize_request对象中
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(), # 这里已经认证了
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
然后会执行def initialize_request方法,最后它返回了一个Request(reqeust)
它又把这个原来的request传入进去了,然后实例化对象,调用Request的初始化方法
下面就是Request的__init__方法,我们也对其进行解读
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
这里它把传入过来的原request放到这个self._request中,这里的self已经不是视图类了,
因为这个Request类没有继承任何一个类,它就是它自己,所以这个self是Request
self._request = request
# 指定解析器、身份验证器、内容协商器的参数,默认为 None 或空元组
self.parsers = parsers or ()
self.authenticators = authenticators or ()
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
# 用于保存请求数据的属性,初始值为 Empty(可能是一个自定义的占位符类)
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
'''
'''
从上面一路追源码可以看到,此时这里的self.request已经不是最开始的request了,变成了DRF提供的Request类的对象了
而是initialize_request经过一系列操作得到的新的request了,然后此处的self则还是视图类,
所以后续视图类的方法中,可以直接使用self.request取出它,视图类的request=新的request了
'''
self.request = request
try:
'执行initial方法,这里执行了三大认证,认证/频率/权限'
self.initial(request, *args, **kwargs)
'通过反射方法,去视图类中执行跟请求方式同名的方法'
# 这个就跟原来的那个View类的dispatch 判断当前是否拥有八大方法如果有则执行
if 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_allowed
'因为这里得到同名的方法后传入的也是新的Request类对象了,那么视图类方法的传入的request也是新的request'
response = 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
drf 框架的Request类对data进行了处理,所以使用drf框架视图类,可以直接通过request.data访问到数据,而不像django需要根据情况访问request.body,POST,FILES
class Empty:
"""
Placeholder for unset attributes.
Cannot use `None`, as that may be a valid value.
"""
pass
class Request:
"""
允许增强标准 'HttpRequest' 实例的包装器。
args:
- request(HttpRequest) 的 API 请求。原始请求实例。
- 解析器(列表/元组)。用于解析请求内容。
- 身份验证器(列表/元组)。用于尝试的验证者对请求的用户进行身份验证。
"""
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
self._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
# 没有设置data.setter,所以Request.data是一个只读属性
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data
def _load_data_and_files(self):
"""
将请求内容解析为 self.data。
"""
if not _hasattr(self, '_data'):
self._data, self._files = self._parse()
if self._files:
self._full_data = self._data.copy()
self._full_data.update(self._files)
else:
self._full_data = self._data
# 如果是表单媒体类型,请将数据和文件复制到底层的HTTP请求中,以便适当地处理可关闭的对象。
if is_form_media_type(self.content_type):
self._request._post = self.POST
self._request._files = self.FILES
@property
def stream(self):
"""
Returns an object that may be used to stream the request content.
"""
if not _hasattr(self, '_stream'):
self._load_stream()
return self._stream
def _load_stream(self):
"""
Return the content body of the request, as a stream.
"""
meta = self._request.META
try:
content_length = int(
meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0))
)
except (ValueError, TypeError):
content_length = 0
if content_length == 0:
self._stream = None
elif not self._request._read_started:
self._stream = self._request # 这里的request是django原先的request
else:
self._stream = io.BytesIO(self.body) # 这里的body没能找到定义,后面再看看
# 解析的过程主要是下面这个方法
def _parse(self):
"""
Parse the request content, returning a two-tuple of (data, files)
May raise an `UnsupportedMediaType`, or `ParseError` exception.
"""
media_type = self.content_type
try:
stream = self.stream
except RawPostDataException:
if not hasattr(self._request, '_post'):
raise
# If request.POST has been accessed in middleware, and a method='POST'
# request was made with 'multipart/form-data', then the request stream
# will already have been exhausted.
if self._supports_form_parsing():
return (self._request.POST, self._request.FILES)
stream = None
if stream is None or media_type is None:
if media_type and is_form_media_type(media_type):
empty_data = QueryDict('', encoding=self._request._encoding)
else:
empty_data = {}
empty_files = MultiValueDict()
return (empty_data, empty_files)
parser = self.negotiator.select_parser(self, self.parsers)
if not parser:
raise exceptions.UnsupportedMediaType(media_type)
try:
parsed = parser.parse(stream, media_type, self.parser_context)
except Exception:
# If we get an exception during parsing, fill in empty data and
# re-raise. Ensures we don't simply repeat the error when
# attempting to render the browsable renderer response, or when
# logging the request or similar.
self._data = QueryDict('', encoding=self._request._encoding)
self._files = MultiValueDict()
self._full_data = self._data
raise
# Parser classes may return the raw data, or a
# DataAndFiles object. Unpack the result as required.
try:
return (parsed.data, parsed.files)
except AttributeError:
empty_files = MultiValueDict()
return (parsed, empty_files)
# 另外Request中还可以访问到认证后的ueser,如果有的话
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
#设置self.user, self.auth,设置self.user实际上调用@user.setter修饰的方法,最后还是设置的self._user
self._authenticate()
return self._user
@user.setter
def user(self, value):
"""
Sets the user on the current request. This is necessary to maintain
compatibility with django.contrib.auth where the user property is
set in the login and logout functions.
Note that we also set the user on Django's underlying `HttpRequest`
instance, ensuring that it is available to any middleware in the stack.
"""
self._user = value
self._request.user = value
@property
def auth(self):
"""
Returns any non-user authentication information associated with the
request, such as an authentication token.
"""
if not hasattr(self, '_auth'):
with wrap_attributeerrors():
self._authenticate()
return self._auth
继承APIView+Request和Django原生View区别
从上述代码中可以看到继承APIView+Response和Django原生View区别到底在哪?
1.首先记住APIView类是drf提供的一个类,而drf是一个app,在django中三方模块都是一个个应用,
它注册app,rest_framework,就可以在浏览器中直接访问,drf自带的页面查看数据等。
而原生则无法查看,会直接报错,但是可以通过接口测试工具访问,但是仅只有纯json数据
2.在进行post请求的时候,原生View需要注释掉csrf中间件,而APIView无需注释
3.原生View传递数据时,如果是中文,需要声明ensure_ascii为False,而APIView无需
4.原生view使用form-data/urlencoded用获取json格式的不一样,需要从request.body中按照所需切割,比较麻烦,
而APIView内部对数据进行了封装,直接使用request.data可自行判断是否是那种编码格式然后进行自行转换好,无需我们
进行操作
Response
当一个请求完成后,我们一般都会返回drf
中的Rsponse
对象,那么该对象其中最重要的作用就是序列化。
将需要返回给页面的数据进行JSON
处理,除此之外还会对返回的页面等进行包装
下面是Rsponse
的初始化函数:
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
参数详解:
参数 | 描述 |
---|---|
data | 返回的数据,内部会进行序列化,需传入一个字典 |
status | 返回的http状态码,默认是200 |
template_name | 渲染并返回的模板 |
headers | 返回的响应头,可以组织一个字典往响应头中放入token信息等 |
content_type | 响应的编码格式,入application/json以及text/html等 |
有了这些参数,我们可以这样做:
from rest_framework.response import Response
from rest_framework.views import APIView
class Test(APIView):
def get(self,request):
ret_Msg = {"status":"100","message":""} # 返回的信息
return Response(
data=ret_Msg,
status=200, # 代表成功
headers={"token":"xxxx"},
)
这样的话在请求头中就能拿到返回的token
:
返回状态码
drf
中内置了很多状态码的常量,我们在返回状态码时可以使用它们。
# from rest_framework import status
HTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101
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
HTTP_208_ALREADY_REPORTED = 208
HTTP_226_IM_USED = 226
HTTP_300_MULTIPLE_CHOICES = 300
HTTP_301_MOVED_PERMANENTLY = 301
HTTP_302_FOUND = 302
HTTP_303_SEE_OTHER = 303
HTTP_304_NOT_MODIFIED = 304
HTTP_305_USE_PROXY = 305
HTTP_306_RESERVED = 306
HTTP_307_TEMPORARY_REDIRECT = 307
HTTP_308_PERMANENT_REDIRECT = 308
HTTP_400_BAD_REQUEST = 400
HTTP_401_UNAUTHORIZED = 401
HTTP_402_PAYMENT_REQUIRED = 402
HTTP_403_FORBIDDEN = 403
HTTP_404_NOT_FOUND = 404
HTTP_405_METHOD_NOT_ALLOWED = 405
HTTP_406_NOT_ACCEPTABLE = 406
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
HTTP_408_REQUEST_TIMEOUT = 408
HTTP_409_CONFLICT = 409
HTTP_410_GONE = 410
HTTP_411_LENGTH_REQUIRED = 411
HTTP_412_PRECONDITION_FAILED = 412
HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
HTTP_414_REQUEST_URI_TOO_LONG = 414
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
HTTP_417_EXPECTATION_FAILED = 417
HTTP_418_IM_A_TEAPOT = 418
HTTP_422_UNPROCESSABLE_ENTITY = 422
HTTP_423_LOCKED = 423
HTTP_424_FAILED_DEPENDENCY = 424
HTTP_426_UPGRADE_REQUIRED = 426
HTTP_428_PRECONDITION_REQUIRED = 428
HTTP_429_TOO_MANY_REQUESTS = 429
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451
HTTP_500_INTERNAL_SERVER_ERROR = 500
HTTP_501_NOT_IMPLEMENTED = 501
HTTP_502_BAD_GATEWAY = 502
HTTP_503_SERVICE_UNAVAILABLE = 503
HTTP_504_GATEWAY_TIMEOUT = 504
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
HTTP_506_VARIANT_ALSO_NEGOTIATES = 506
HTTP_507_INSUFFICIENT_STORAGE = 507
HTTP_508_LOOP_DETECTED = 508
HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509
HTTP_510_NOT_EXTENDED = 510
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
配置Rsponse
如果你在浏览器中访问该页面,可以发现它返回的其实是一个页面:
但是在
postman
中,返回的则是json
格式的字符串。
{
"status": "100",
"message": ""
}
原因是因为它会根据request
对象中的请求头accept
来自动转换对应的数据格式,如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。
可以在rest_framework.settings查找所有的drf
默认配置项
DEFAULTS = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer', # 返回json
'rest_framework.renderers.BrowsableAPIRenderer', # 返回页面
],
}
如果我们想让浏览器访问页面时也返回JSON
格式,则可以进行下面两种配置方法。
局部配置
只指定某一个视图返回规定的数据格式:
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import settings
from rest_framework.renderers import JSONRenderer
class Test(APIView):
renderer_classes=[JSONRenderer,] # 该视图只返回JSON数据
def get(self,request):
ret_Msg = {"status":"100","message":""} # 返回的信息
return Response(
data=ret_Msg,
status=200, # 代表成功
headers={"token":"xxxx"},
)
全局配置
由于它返回时查找顺序是先找局部,再找Django.settings.py
,最后再找rest_framework.settings.py
,所以我们可以再Django.settings.py
下进行覆写:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer', # 所有视图均返回JSON格式数据
],
}
参考文章 blog.csdn.net/achen_m/art… www.cnblogs.com/Yunya-Cnblo…