|
4 | 4 | # filename : modelset |
5 | 5 | # author : ly_13 |
6 | 6 | # date : 6/2/2023 |
| 7 | +import itertools |
7 | 8 | import json |
| 9 | +import uuid |
8 | 10 | from hashlib import md5 |
9 | 11 | from typing import Callable |
10 | 12 |
|
| 13 | +import math |
11 | 14 | from django.conf import settings |
12 | 15 | from django.db import transaction |
13 | 16 | from django.forms.widgets import SelectMultiple, DateTimeInput |
|
29 | 32 | from common.core.config import SysConfig |
30 | 33 | from common.core.response import ApiResponse |
31 | 34 | from common.core.serializers import BasePrimaryKeyRelatedField |
32 | | -from common.core.utils import get_query_post_pks |
33 | 35 | from common.drf.renders.csv import CSVFileRenderer |
34 | 36 | from common.drf.renders.excel import ExcelFileRenderer |
35 | 37 | from common.swagger.utils import get_default_response_schema |
| 38 | +from common.tasks import background_task_view_set_job |
36 | 39 | from common.utils import get_logger |
37 | 40 |
|
38 | 41 | logger = get_logger(__name__) |
39 | 42 |
|
40 | 43 |
|
| 44 | +def run_view_by_celery_task(view, request, kwargs, list_data, batch_length=100): |
| 45 | + task = kwargs.get("task", request.query_params.get('task', 'true').lower() in ['true', '1', 'yes']) # 默认为任务异步导入 |
| 46 | + if task: |
| 47 | + view_str = f"{view.__class__.__module__}.{view.__class__.__name__}" |
| 48 | + meta = request.META |
| 49 | + task_id = uuid.uuid4() |
| 50 | + meta["task_count"] = math.ceil(len(list_data) / batch_length) |
| 51 | + meta["action"] = view.action |
| 52 | + for index, batch in enumerate(itertools.batched(list_data, batch_length)): |
| 53 | + meta["task_id"] = f"{task_id}_{index}" |
| 54 | + meta["task_index"] = index |
| 55 | + res = background_task_view_set_job.apply_async(args=(view_str, meta, json.dumps(batch), view.action_map), |
| 56 | + task_id=meta["task_id"]) |
| 57 | + logger.info(f"add {view_str} task success. {res}") |
| 58 | + return ApiResponse(detail=_("Task add success")) |
| 59 | + |
| 60 | + |
41 | 61 | class CacheDetailResponseMixin(object): |
42 | 62 | def get_cache_key(self, view_instance, view_method, request, args, kwargs): |
43 | 63 | func_name = f'{view_instance.__class__.__name__}_{view_method.__name__}' |
@@ -106,20 +126,14 @@ class RankAction(object): |
106 | 126 | get_queryset: Callable |
107 | 127 |
|
108 | 128 | @extend_schema( |
109 | | - request=OpenApiRequest( |
110 | | - build_object_type( |
111 | | - properties={'pks': build_array_type(build_basic_type(OpenApiTypes.STR))}, |
112 | | - required=['pks'], |
113 | | - description="主键列表" |
114 | | - ) |
115 | | - ), |
| 129 | + request=OpenApiRequest(build_array_type(build_basic_type(OpenApiTypes.STR))), |
116 | 130 | responses=get_default_response_schema() |
117 | 131 | ) |
118 | 132 | @action(methods=['post'], detail=False, url_path='rank') |
119 | 133 | def rank(self, request, *args, **kwargs): |
120 | 134 | """{cls}排序""" |
121 | 135 | rank = 1 |
122 | | - for pk in get_query_post_pks(request): |
| 136 | + for pk in request.data: |
123 | 137 | self.filter_queryset(self.get_queryset()).filter(pk=pk).update(rank=rank) |
124 | 138 | rank += 1 |
125 | 139 | return ApiResponse(detail=_("Sorting saved successfully")) |
@@ -336,7 +350,7 @@ def get_input_type(value, info): |
336 | 350 | return ApiResponse(data=results) |
337 | 351 |
|
338 | 352 |
|
339 | | -class BaseViewSet(GenericViewSet): |
| 353 | +class BaseViewSet(object): |
340 | 354 | action: Callable |
341 | 355 | extra_filter_class = [] |
342 | 356 |
|
@@ -373,30 +387,26 @@ class BatchDestroyAction(object): |
373 | 387 | perform_destroy: Callable |
374 | 388 |
|
375 | 389 | @extend_schema( |
376 | | - request=OpenApiRequest( |
377 | | - build_object_type( |
378 | | - properties={'pks': build_array_type(build_basic_type(OpenApiTypes.STR))}, |
379 | | - required=['pks'], |
380 | | - description="主键列表" |
381 | | - ) |
382 | | - ), |
| 390 | + request=OpenApiRequest(build_array_type(build_basic_type(OpenApiTypes.STR))), |
383 | 391 | responses=get_default_response_schema() |
384 | 392 | ) |
385 | 393 | @action(methods=['post'], detail=False, url_path='batch-destroy') |
386 | 394 | def batch_destroy(self, request, *args, **kwargs): |
387 | 395 | """批量删除{cls}""" |
388 | | - pks = get_query_post_pks(request) |
389 | | - if not pks: |
390 | | - return ApiResponse(code=1003, detail=_("Operation failed. Primary key list does not exist")) |
| 396 | + |
| 397 | + # response = run_view_by_celery_task(self, request, kwargs, request.data, batch_length=30) |
| 398 | + # if response: |
| 399 | + # return response |
| 400 | + |
391 | 401 | # queryset delete() 方法进行批量删除,并不调用模型上的任何 delete() 方法,需要通过循环对象进行删除 |
392 | 402 | count = 0 |
393 | | - for instance in self.filter_queryset(self.get_queryset()).filter(pk__in=pks): |
| 403 | + for instance in self.filter_queryset(self.get_queryset()).filter(pk__in=request.data): |
394 | 404 | try: |
395 | 405 | deleted, _rows_count = self.perform_destroy(instance) |
396 | 406 | if deleted: |
397 | 407 | count += 1 |
398 | | - except Exception: |
399 | | - pass |
| 408 | + except Exception as e: |
| 409 | + logger.error(f"failed to destroy instance {instance} with error {e}") |
400 | 410 | return ApiResponse(detail=_("Operation successful. Batch deleted {} data").format(count)) |
401 | 411 |
|
402 | 412 |
|
@@ -481,6 +491,11 @@ class ImportExportDataAction(CreateAction, UpdateAction, OnlyExportDataAction): |
481 | 491 | @transaction.atomic |
482 | 492 | def import_data(self, request, *args, **kwargs): |
483 | 493 | """导入{cls}数据""" |
| 494 | + |
| 495 | + response = run_view_by_celery_task(self, request, kwargs, request.data) |
| 496 | + if response: |
| 497 | + return response |
| 498 | + |
484 | 499 | act = request.query_params.get('action') |
485 | 500 | ignore_error = request.query_params.get('ignore_error', 'false') == 'true' |
486 | 501 | if act and request.data: |
@@ -509,25 +524,25 @@ def import_data(self, request, *args, **kwargs): |
509 | 524 | return ApiResponse(detail=_("Operation failed. Abnormal data"), code=1001) |
510 | 525 |
|
511 | 526 |
|
512 | | -class DetailUpdateModelSet(UpdateAction, DetailAction, BaseViewSet): |
| 527 | +class DetailUpdateModelSet(BaseViewSet, UpdateAction, DetailAction, GenericViewSet): |
513 | 528 | pass |
514 | 529 |
|
515 | 530 |
|
516 | | -class OnlyListModelSet(ListAction, SearchFieldsAction, SearchColumnsAction, BaseViewSet): |
| 531 | +class OnlyListModelSet(BaseViewSet, ListAction, SearchFieldsAction, SearchColumnsAction, GenericViewSet): |
517 | 532 | pass |
518 | 533 |
|
519 | 534 |
|
520 | 535 | # 全部 ViewSet 包含增删改查 |
521 | | -class BaseModelSet(CreateAction, DestroyAction, UpdateAction, ListAction, DetailAction, SearchFieldsAction, |
522 | | - SearchColumnsAction, BatchDestroyAction, BaseViewSet): |
| 536 | +class BaseModelSet(BaseViewSet, CreateAction, DestroyAction, UpdateAction, ListAction, DetailAction, SearchFieldsAction, |
| 537 | + SearchColumnsAction, BatchDestroyAction, GenericViewSet): |
523 | 538 | pass |
524 | 539 |
|
525 | 540 |
|
526 | 541 | # 只允许读和删除,不允许创建和修改 |
527 | | -class ListDeleteModelSet(DestroyAction, ListAction, DetailAction, SearchFieldsAction, SearchColumnsAction, |
528 | | - BatchDestroyAction, BaseViewSet): |
| 542 | +class ListDeleteModelSet(BaseViewSet, DestroyAction, ListAction, DetailAction, SearchFieldsAction, SearchColumnsAction, |
| 543 | + BatchDestroyAction, GenericViewSet): |
529 | 544 | pass |
530 | 545 |
|
531 | 546 |
|
532 | | -class NoDetailModelSet(UpdateAction, DetailAction, SearchColumnsAction, BaseViewSet): |
| 547 | +class NoDetailModelSet(BaseViewSet, UpdateAction, DetailAction, SearchColumnsAction, GenericViewSet): |
533 | 548 | pass |
0 commit comments