-
Notifications
You must be signed in to change notification settings - Fork 0
/
views.py
383 lines (332 loc) · 13.6 KB
/
views.py
1
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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
"""
@Author: WangYuXiang
@E-mile: [email protected]
@CreateTime: 2021/1/19 15:44
@DependencyLibrary:
@MainFunction:
@FileDoc:
login.py
基础视图文件
BaseView 只实现路由分发的基础视图
GeneralView 通用视图,可以基于其实现增删改查,提供权限套件
ViewSetView 视图集视图,可以配合Mixin实现复杂的视图集,
数据来源基于模型查询集,可以配合Route组件实现便捷的路由管理
"""
import inspect
from datetime import datetime
from sanic.log import logger
from sanic.response import json, HTTPResponse
from simplejson import dumps
from tortoise.exceptions import IntegrityError
from tortoise.transactions import in_transaction
from sanic_rest_framework import mixins
from sanic_rest_framework.constant import ALL_METHOD, DEFAULT_METHOD_MAP
from sanic_rest_framework.exceptions import APIException, ValidationException
from sanic_rest_framework.filters import ORMAndFilter
from sanic_rest_framework.status import RuleStatus, HttpStatus
__all__ = ['BaseView', 'GeneralView', 'ViewSetView', 'ModelViewSet']
from sanic_rest_framework.utils import run_awaitable
class BaseView:
"""只实现路由分发的基础视图
在使用时应当开放全部路由 ALL_METHOD
app.add_route('/test', BaseView.as_view(), 'test', ALL_METHOD)
如需限制路由则在其他地方注明
app.add_route('/test', BaseView.as_view(), 'test', ALL_METHOD)
注意以上方法的报错是不可控的
"""
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
async def dispatch(self, request, *args, **kwargs):
"""分发路由"""
request.user = None
method = request.method
if method.lower() not in self.method_map:
return HTTPResponse('405请求方法错误', status=405)
handler = getattr(self, method.lower(), None)
response = handler(request, *args, **kwargs)
if inspect.isawaitable(response):
response = await response
return response
@classmethod
def get_method_map(cls):
methods = {}
for method in ALL_METHOD:
method = method.lower()
if hasattr(cls, method):
methods[method] = method
return methods
@classmethod
def as_view(cls, method_map=DEFAULT_METHOD_MAP, *class_args, **class_kwargs):
# 返回的响应方法闭包
def view(request, *args, **kwargs):
self = view.base_class(*class_args, **class_kwargs)
view_method_map = {}
for method, action in method_map.items():
handler = getattr(self, action, None)
if handler:
setattr(self, method, handler)
view_method_map[method] = action
self.method_map = view_method_map
self.methods = list(view_method_map.keys())
self.request = request
self.args = args
self.kwargs = kwargs
self.app = request.app
return self.dispatch(request, *args, **kwargs)
view.base_class = cls
view.methods = list(method_map.keys())
view.API_DOC_CONFIG = class_kwargs.get('API_DOC_CONFIG') # 未来的API文档配置属性+
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.__name__ = cls.__name__
return view
class GeneralView(BaseView):
"""通用视图,可以基于其实现增删改查,提供权限套件"""
authentication_classes = ()
permission_classes = ()
is_transaction = True
async def dispatch(self, request, *args, **kwargs):
"""分发路由"""
request.user = None
method = request.method
if method.lower() not in self.methods:
return self.json_response(msg='发生错误:未找到%s方法' % method, status=RuleStatus.STATUS_0_FAIL,
http_status=HttpStatus.HTTP_405_METHOD_NOT_ALLOWED)
handler = getattr(self, method.lower(), None)
try:
await self.initial(request, *args, **kwargs)
if self.is_transaction:
async with in_transaction():
response = await run_awaitable(handler, request=request, *args, **kwargs)
else:
response = await run_awaitable(handler, request=request, *args, **kwargs)
except APIException as exc:
response = self.handle_exception(exc)
except ValidationException as exc:
response = self.error_json_response(exc.error_detail, '数据验证失败')
except AssertionError as exc:
raise exc
except IntegrityError as exc:
response = self.json_response(msg=str(exc), status=RuleStatus.STATUS_0_FAIL)
except Exception as exc:
logger.error('--捕获未知错误--', exc)
msg = '发生致命的未知错误,请在服务器查看时间为{}的日志'.format(datetime.now().strftime('%F %T'))
response = self.json_response(msg=msg, status=RuleStatus.STATUS_0_FAIL,
http_status=HttpStatus.HTTP_500_INTERNAL_SERVER_ERROR)
return response
def handle_exception(self, exc: APIException):
return self.json_response(**exc.response_data())
def json_response(self, data=None, msg="OK", status=RuleStatus.STATUS_1_SUCCESS,
http_status=HttpStatus.HTTP_200_OK):
"""
Json 相应体
:param data: 返回的数据主题
:param msg: 前台提示字符串
:param status: 前台约定状态,供前台判断是否成功
:param http_status: Http响应数据
:return:
"""
if data is None:
data = {}
response_body = {
'data': data,
'message': msg,
'status': status
}
return json(body=response_body, status=http_status, dumps=dumps)
def success_json_response(self, data=None, msg="Success", **kwargs):
"""
快捷的成功的json响应体
:param data: 返回的数据主题
:param msg: 前台提示字符串
:return: json
"""
status = kwargs.pop('status', RuleStatus.STATUS_1_SUCCESS)
http_status = kwargs.pop('http_status', HttpStatus.HTTP_200_OK)
return self.json_response(data=data, msg=msg, status=status, http_status=http_status)
def error_json_response(self, data=None, msg="Fail", **kwargs):
"""
快捷的失败的json响应体
:param data: 返回的数据主题
:param msg: 前台提示字符串
:return: json
"""
status = kwargs.pop('status', RuleStatus.STATUS_0_FAIL)
http_status = kwargs.pop('http_status', HttpStatus.HTTP_400_BAD_REQUEST)
return self.json_response(data=data, msg=msg, status=status, http_status=http_status)
def get_authenticators(self):
"""
实例化并返回此视图可以使用的身份验证器列表
"""
return [auth() for auth in self.authentication_classes]
async def check_authentication(self, request):
"""
检查权限 查看是否拥有权限,并在此处为Request.User 赋值
:param request: 请求
:return:
"""
for authenticators in self.get_authenticators():
await authenticators.authenticate(request)
def get_permissions(self):
"""
实例化并返回此视图所需的权限列表
"""
return [permission() for permission in self.permission_classes]
async def check_permissions(self, request):
"""
检查是否应允许该请求,如果不允许该请求,
则在 has_permission 中引发一个适当的异常。
:param request: 当前请求
:return:
"""
for permission in self.get_permissions():
await permission.has_permission(request, self)
async def check_object_permissions(self, request, obj):
"""
检查是否应允许给定对象的请求, 如果不允许该请求,
则在 has_object_permission 中引发一个适当的异常。
常用于 get_object() 方法
:param request: 当前请求
:param obj: 需要鉴权的模型对象
:return:
"""
for permission in self.get_permissions():
await permission.has_object_permission(request, self, obj)
async def check_throttles(self, request):
"""
检查范围频率。
则引发一个 APIException 异常。
:param request:
:return:
"""
pass
async def initial(self, request, *args, **kwargs):
"""
在请求分发之前执行初始化操作,用于检查权限及检查基础内容
"""
await self.check_authentication(request)
await self.check_permissions(request)
await self.check_throttles(request)
class ViewSetView(GeneralView):
"""
视图集视图,可以配合Mixin实现复杂的视图集,
数据来源基于模型查询集,可以配合Route组件实现便捷的路由管理
"""
queryset = None
lookup_field = 'pk'
serializer_class = None
pagination_class = None
filter_class = ORMAndFilter
search_fields = None
async def get_object(self):
"""
返回视图显示的对象。
如果您需要提供非标准的内容,则可能要覆盖此设置
queryset查找。
"""
queryset = await self.get_queryset()
lookup_field = self.lookup_field
assert lookup_field in self.kwargs, (
'%s 不存在于 %s 的 Url配置中的关键词内 ' %
(lookup_field, self.__class__.__name__,)
)
filter_kwargs = {lookup_field: self.kwargs[lookup_field]}
obj = await queryset.get_or_none(**filter_kwargs)
if obj is None:
raise APIException('不存在%s为%s的数据' % (lookup_field, self.kwargs[lookup_field]),
http_status=HttpStatus.HTTP_200_OK)
# May raise a permission denied
await self.check_object_permissions(self.request, obj)
return obj
async def get_queryset(self):
assert self.queryset is not None, (
"'%s'应该包含一个'queryset'属性,"
"或重写`get_queryset()`方法。"
% self.__class__.__name__
)
queryset = self.queryset
filter_orm = await self.filter_orm()
queryset = queryset.filter(filter_orm)
return queryset
async def filter_orm(self):
"""得到ORM过滤参数"""
return self.filter_class(self.request, self).orm_filter
def get_serializer(self, *args, **kwargs):
"""
返回应该用于验证和验证的序列化程序实例
对输入进行反序列化,并对输出进行序列化。
"""
serializer_class = self.get_serializer_class()
kwargs.setdefault('context', self.get_serializer_context())
return serializer_class(*args, **kwargs)
def get_serializer_class(self):
"""
返回用于序列化器的类。
默认使用`self.serializer_class`。
如果您需要提供其他信息,则可能要覆盖此设置
序列化取决于传入的请求。
(例如,管理员获得完整的序列化,其他获得基本的序列化)
"""
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
def get_serializer_context(self):
"""
提供给序列化程序类的额外上下文。
"""
return {
'request': self.request,
'view': self
}
@property
def paginator(self):
"""
与视图关联的分页器实例,或“None”。
"""
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class(self.request, self)
return self._paginator
async def get_paginator_count(self, queryset):
"""
获取记录总数
:param queryset:
:return:
"""
return await queryset.count()
async def paginate_queryset(self, queryset):
"""
返回单页结果,如果禁用了分页,则返回“无”。
"""
if self.paginator is None:
return None
offset = (self.paginator.query_page - 1) * self.paginator.query_page_size
return queryset.limit(self.paginator.query_page_size).offset(offset)
def get_paginated_response(self, data):
"""
返回给定输出数据的分页样式`Response`对象。
"""
return {
'count': self.paginator.count,
'next': self.paginator.next_link,
'next_page_num': self.paginator.next_page,
'previous': self.paginator.previous_link,
'previous_num': self.paginator.previous_page,
'results': data
}
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
ViewSetView):
"""
`create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()`, `list()` actions.
"""
pass