drf学习
# drf 学习
# 快速上手
安装
pip install djangorestframework=3.12.4
1版本要求: djangorestframework=3.12.4 Python (3.5,3.6,3.7,3.8,3.9) Django(2.2, 3.0, 3.1) djangorestframework=3.11.2 Python (3.5,3.6,3.7,3.8) Django(1.11, 2.0, 2.1, 2.2, 3.0)
1
2
3
4
5
6
7配置,在
settings.py
中添加配置INSTALLED_APPS = [ ... # 注册 rest_framework 'rest_framework' ] # drf相关配置写在这里 REST_FRAMEWORK = { }
1
2
3
4
5
6
7
8
9
10URL和视图
# urls.py urlpatterns = [ # path('admin/', admin.site.urls), # path("api/<str:version>/users", views.users), # path("api/<str:version>/users/<int:pk>", views.users), path('users/', views.UserView.as_view()), ]
1
2
3
4
5
6
7
8
9
10# views.py from rest_framework.views import APIView from rest_framework.response import Response class UserView(APIView): def get(self, request, *args, **kwargs): return Response({"code": 1000, "data": "xxx"}) def post(self, request, *args, **kwargs): return Response({"code": 1000, "data": "xxx"})
1
2
3
4
5
6
7
8
9
10
11
12
提示
其实drf框架是django基础进行的扩展,所以上述执行的底层实现流程还是和django的CBV类似。
drf中重写了as_view
和dispatch
方法,就是在原来的django的功能的基础上加了一些功能
as_view
,免除了csrf
的验证,前后端分离的项目一般不会使用csrf_token
认证,会使用jwt
认证dispatch
内部添加了版本处理、认证、权限、访问频率限制等诸多功能。
现在运行的一个页面
# 请求数据的封装
以前Django开发,视图中的request是django.core.handlers.wsgi.WSGIRequest
类的对象,包含了请求相关的所有数据。
而使用drf框架时,视图的request是rest_framework.request.Request
类的对象,其实又是对django的request进行了一次封装,包含了除django原request对象以外,还包含其他后期会使用的其他对象。
内部包含了django的request、认证、解析器等。
新的request对象 = (django的request, 其他数据)
源码位置
rest_framework.requset.Request
类
点击查看Request源码
class Request:
"""
Wrapper allowing to enhance a standard `HttpRequest` instance.
Kwargs:
- request(HttpRequest). The original request instance.
- parsers(list/tuple). The parsers to use for parsing the
request content.
- authenticators(list/tuple). The authenticators used to try
authenticating the request's user.
"""
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
'The `request` argument must be an instance of '
'`django.http.HttpRequest`, not `{}.{}`.'
.format(request.__class__.__module__, request.__class__.__name__)
)
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
if self.parser_context is None:
self.parser_context = {}
self.parser_context['request'] = self
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
force_user = getattr(request, '_force_auth_user', None)
force_token = getattr(request, '_force_auth_token', None)
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
def __repr__(self):
return '<%s.%s: %s %r>' % (
self.__class__.__module__,
self.__class__.__name__,
self.method,
self.get_full_path())
def _default_negotiator(self):
return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
@property
def content_type(self):
meta = self._request.META
return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
@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
@property
def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data
@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._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
@auth.setter
def auth(self, value):
"""
Sets any non-user authentication information associated with the
request, such as an authentication token.
"""
self._auth = value
self._request.auth = value
@property
def successful_authenticator(self):
"""
Return the instance of the authentication instance class that was used
to authenticate the request, or `None`.
"""
if not hasattr(self, '_authenticator'):
with wrap_attributeerrors():
self._authenticate()
return self._authenticator
def _load_data_and_files(self):
"""
Parses the request content into `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
# if a form media type, copy data & files refs to the underlying
# http request so that closable objects are handled appropriately.
if is_form_media_type(self.content_type):
self._request._post = self.POST
self._request._files = self.FILES
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
else:
self._stream = io.BytesIO(self.body)
def _supports_form_parsing(self):
"""
Return True if this requests supports parsing form data.
"""
form_media = (
'application/x-www-form-urlencoded',
'multipart/form-data'
)
return any([parser.media_type in form_media for parser in self.parsers])
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)
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
def _not_authenticated(self):
"""
Set authenticator, user & authtoken representing an unauthenticated request.
Defaults are None, AnonymousUser & None.
"""
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
def __getattr__(self, attr):
"""
If an attribute does not exist on this instance, then we also attempt
to proxy it to the underlying HttpRequest object.
"""
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
@property
def DATA(self):
raise NotImplementedError(
'`request.DATA` has been deprecated in favor of `request.data` '
'since version 3.0, and has been fully removed as of version 3.2.'
)
@property
def POST(self):
# Ensure that request.POST uses our request parsing.
if not _hasattr(self, '_data'):
self._load_data_and_files()
if is_form_media_type(self.content_type):
return self._data
return QueryDict('', encoding=self._request._encoding)
@property
def FILES(self):
# Leave this one alone for backwards compat with Django's request.FILES
# Different from the other two cases, which are not valid property
# names on the WSGIRequest class.
if not _hasattr(self, '_files'):
self._load_data_and_files()
return self._files
@property
def QUERY_PARAMS(self):
raise NotImplementedError(
'`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
'since version 3.0, and has been fully removed as of version 3.2.'
)
def force_plaintext_errors(self, value):
# Hack to allow our exception handler to force choice of
# plaintext or html error responses.
self._request.is_ajax = lambda: value
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
所以在使用drf的框架的requet的时候,会有一些不同,例如:
# Create your views here.
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
def get(self, request, *args, **kwargs):
# 通过对象的嵌套直接找到request,读取相关值
request._request.method
request._request.GET
request._request.POST
request._request.body
# 直接去读新request对象中的值,一般此处会对原始数据的进行处理,方便在视图函数里使用
request.query_params # 内部本质上就是 request._request.GET
request.data # 内部读取请求体中的数据,并进行处理,对请求者发送来的JSON会对JSON字符串进行反序列化
# 通过 __getattr__ 去访问reqeust._request 中的值
return Response({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
drf内部会根据请求头content-type
来进行内部处理数据然后组装完了扔到request里,方便我们进行使用。
__getattr__
什么时候触发?
当你调用了reqeust.属性
就会去调用
点击查看代码
源码
def __getattr__(self, attr):
"""
If an attribute does not exist on this instance, then we also attempt
to proxy it to the underlying HttpRequest object.
"""
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
2
3
4
5
6
7
8
9
request.method => self._request.method
# 版本管理
在restful规范中,后端的API需要体现版本
drf
框架中支持5种版本的设置
# GET请求获取
# views.py
from rest_framework.versioning import QueryParameterVersioning
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
versioning_class = QueryParameterVersioning
def get(self, request, *args, **kwargs):
print(request.version)
return Response({"code": 1000, "data": "xxx"})
2
3
4
5
6
7
8
9
10
11
12
13
14
# urls.py
from django.urls import path
from app01 import views
urlpatterns = [
# path('admin/', admin.site.urls),
# path("api/<str:version>/users", views.users),
# path("api/<str:version>/users/<int:pk>", views.users),
path('api/users/', views.UserView.as_view()),
]
2
3
4
5
6
7
8
9
10
11
12
13
在浏览器中输入地址:http://127.0.0.1:8000/api/users/?version=v1
此时终端会输出一个对应的版本号:v1
警告
注意,浏览器输入的version=v几
的version
是固定的
那么可以不可以修改呢,毕竟version
要输入这么一长串
看下`QueryParameterVersioning`的源码的父类`BaseVersioning`
class BaseVersioning:
default_version = api_settings.DEFAULT_VERSION
allowed_versions = api_settings.ALLOWED_VERSIONS
version_param = api_settings.VERSION_PARAM
2
3
4
其中的VERSION_PARAM
,我们可以拿出来放到配置文件里,我们重新设置一下内容
# settings.py
# 之前说的drf的框架的配置文件都放在这个里面
REST_FRAMEWORK = {
"VERSION_PARAM": "v"
}
2
3
4
5
6
写成这个时,后面浏览器写的时候传入的版本就得写:http://127.0.0.1:8000/api/users/?v=v1
警告
此时如果你还写成version
就不行
如果想设置一个默认值的版本,就是你不想写后面一串,如何设置一个默认值的版本?
还是看上面的源码部分,里面有一个DEFAULT_VERSION
看英文就知道是啥意思了。配置一下这个的默认值即可
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": "v",
"DEFAULT_VERSION": "v1", # 设置默认版本为v1
}
2
3
4
5
6
看到源码里还有一个
ALLOWED_VERSIONS
没用上,那肯定还是有用的啊,这个从字面上看就是可以允许的版本,我们可以进行限制一些版本名称
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": "v",
"DEFAULT_VERSION": "v1", # 设置默认版本为v1
"ALLOWED_VERSIONS": ["v1", "v2", "v3"],
}
2
3
4
5
6
7
警告
此时浏览器只能传入v1、v2、v3
,其余的就会出错
# 省事教程
此时,如果有多个视图,versioning_class = QueryParameterVersioning
这一句是不是要在很多个视图类里都要写一遍。是的,很麻烦,drf
还提供了一个解决办法。
全局配置
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": "v",
"DEFAULT_VERSION": "v1", # 设置默认版本为v1
"ALLOWED_VERSIONS": ["v1", "v2", "v3"],
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning"
}
2
3
4
5
6
7
8
9
查看源码位置
class APIView(View):
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS # 此处
2
3
4
5
6
7
8
9
10
11
# URL路径传递(*)
urls.py
path('api/<str:version>/users/', views.UserView.as_view()),
此时视图函数内需要变更配置
from rest_framework.versioning import URLPathVersioning
class UserView(APIView):
versioning_class = URLPathVersioning
def get(self, request, *args, **kwargs):
print(request.version)
return Response({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
2
3
4
5
6
7
8
9
10
11
12
13
使用此方法后,前面的配置的REST_FRAMEWORK
里的版本名称,也得和这里的url
参数传递的一样为version
,这也支持配置全局的地方进行使用。
# 请求头传递
去headers
请求头里获取版本信息
{
"Accept": "application/json;version=v3"
}
2
3
# Create your views here.
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning, AcceptHeaderVersioning
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
# versioning_class = QueryParameterVersioning
# versioning_class = URLPathVersioning
versioning_class = AcceptHeaderVersioning
def get(self, request, *args, **kwargs):
print(request.version)
return Response({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
查看源码
class AcceptHeaderVersioning(BaseVersioning):
"""
GET /something/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0
"""
invalid_version_message = _('Invalid version in "Accept" header.')
def determine_version(self, request, *args, **kwargs):
media_type = _MediaType(request.accepted_media_type)
version = media_type.params.get(self.version_param, self.default_version)
version = unicode_http_header(version)
if not self.is_allowed_version(version):
raise exceptions.NotAcceptable(self.invalid_version_message)
return version
# We don't need to implement `reverse`, as the versioning is based
# on the `Accept` header, not on the request URL.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 二级域名传递
效果就是 v1.xxx.com:8000/api/users
直接就在二级域名前加上版本 信息
视图函数修改配置
# Create your views here.
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning, AcceptHeaderVersioning, \
HostNameVersioning
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
# versioning_class = QueryParameterVersioning
# versioning_class = URLPathVersioning
# versioning_class = AcceptHeaderVersioning
versioning_class = HostNameVersioning
def get(self, request, *args, **kwargs):
print(request.version)
return Response({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
提示
自己如何想测试的话,windows
的可以去修改hosts添加配置项
127.0.0.1 v1.xxx.com
127.0.0.1 v2.xxx.com
2
同样mac
也是如此,但是mac
需要获取到root
权限才能去修改
su -
# 然后输入你的密码
vim /etc/hosts # 应该是这个地址的。然后去添加上面的2个二级域名
2
3
4
5
然后根据视图函数里的配置的信息,去测试请求获取的版本信息
# 路由的namespace传递
urls.py
需要做出修改
from django.contrib import admin
from django.urls import path, include
from app01 import views
urlpatterns = [
# path('admin/', admin.site.urls),
# path("api/<str:version>/users", views.users),
# path("api/<str:version>/users/<int:pk>", views.users),
# path('api/users/', views.UserView.as_view()),
path('api/v1', include('app01.urls', namespace='v1')),
path('api/v2', include('app01.urls', namespace='v2'))
]
2
3
4
5
6
7
8
9
10
11
12
13
14
视图函数对应的修改配置
# Create your views here.
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning, AcceptHeaderVersioning, \
HostNameVersioning, NamespaceVersioning
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
# versioning_class = QueryParameterVersioning
# versioning_class = URLPathVersioning
# versioning_class = AcceptHeaderVersioning
# versioning_class = HostNameVersioning
versioning_class = NamespaceVersioning
def get(self, request, *args, **kwargs):
print(request.version)
return Response({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 反向生成URL
在每个版本处理的类中还定义了reverse
方法, 它是用来反向生成URL并携带相关的版本信息用的。
from rest_framework.versioning import QueryParameterVersioning
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
versioning_class = QueryParameterVersioning
def get(self, request, *args, **kwargs):
print(request.version)
# 反向生成URL
url1 = request.versioning_scheme.reverse("u1", request=request)
print(url1)
return Response({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
某一个reverse源码
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
url = super().reverse(
viewname, args, kwargs, request, format, **extra
)
if request.version is not None:
return replace_query_param(url, self.version_param, request.version)
return url
2
3
4
5
6
7
# 总结
使用drf
框架开发后端的API
接口时:
- 首先创建Django项目
- 安装drf框架
- 创建一个app专门来处理用户请求
- 注册app
- 设置版本
- 编写视图类