diff --git a/apps/utils/mixins.py b/apps/utils/mixins.py index 9e4a277f..94703573 100755 --- a/apps/utils/mixins.py +++ b/apps/utils/mixins.py @@ -9,7 +9,6 @@ from django.utils.timezone import now from user_agents import parse import logging from rest_framework.response import Response -from django.db import transaction from rest_framework.exceptions import ParseError, ValidationError from apps.utils.errors import PKS_ERROR from rest_framework.generics import get_object_or_404 @@ -19,6 +18,7 @@ from apps.utils.serializers import PkSerializer from rest_framework.decorators import action from apps.utils.serializers import ComplexSerializer from django.db.models import F +from django.db import transaction # 实例化myLogger myLogger = logging.getLogger('log') @@ -81,7 +81,8 @@ class BulkCreateModelMixin(CreateModelMixin): def after_bulk_create(self, objs): pass - + + @transaction.atomic def create(self, request, *args, **kwargs): """创建(支持批量) @@ -91,10 +92,9 @@ class BulkCreateModelMixin(CreateModelMixin): many = False if isinstance(rdata, list): many = True - with transaction.atomic(): - sr = self.get_serializer(data=rdata, many=many) - sr.is_valid(raise_exception=True) - self.perform_create(sr) + sr = self.get_serializer(data=rdata, many=many) + sr.is_valid(raise_exception=True) + self.perform_create(sr) if many: self.after_bulk_create(sr.data) return Response(sr.data, status=201) @@ -105,6 +105,7 @@ class BulkUpdateModelMixin(UpdateModelMixin): def after_bulk_update(self, objs): pass + @transaction.atomic def partial_update(self, request, *args, **kwargs): """部分更新(支持批量) @@ -113,6 +114,7 @@ class BulkUpdateModelMixin(UpdateModelMixin): kwargs['partial'] = True return self.update(request, *args, **kwargs) + @transaction.atomic def update(self, request, *args, **kwargs): """更新(支持批量) @@ -124,16 +126,15 @@ class BulkUpdateModelMixin(UpdateModelMixin): queryset = self.filter_queryset(self.get_queryset()) objs = [] if isinstance(request.data, list): - with transaction.atomic(): - for ind, item in enumerate(request.data): - obj = get_object_or_404(queryset, id=item['id']) - sr = self.get_serializer(obj, data=item, partial=partial) - if not sr.is_valid(): - err_dict = { f'第{ind+1}': sr.errors} - raise ValidationError(err_dict) - self.perform_update(sr) # 用自带的更新,可能需要做其他操作 - objs.append(sr.data) - self.after_bulk_update(objs) + for ind, item in enumerate(request.data): + obj = get_object_or_404(queryset, id=item['id']) + sr = self.get_serializer(obj, data=item, partial=partial) + if not sr.is_valid(): + err_dict = { f'第{ind+1}': sr.errors} + raise ValidationError(err_dict) + self.perform_update(sr) # 用自带的更新,可能需要做其他操作 + objs.append(sr.data) + self.after_bulk_update(objs) else: raise ParseError('提交数据非列表') return Response(objs) @@ -148,6 +149,7 @@ class BulkUpdateModelMixin(UpdateModelMixin): class BulkDestroyModelMixin(DestroyModelMixin): @swagger_auto_schema(request_body=PkSerializer) + @transaction.atomic def destroy(self, request, *args, **kwargs): """删除(支持批量) diff --git a/apps/utils/viewsets.py b/apps/utils/viewsets.py index b33c973d..41588d26 100755 --- a/apps/utils/viewsets.py +++ b/apps/utils/viewsets.py @@ -1,6 +1,6 @@ from django.core.cache import cache -from django.http import StreamingHttpResponse +from django.http import StreamingHttpResponse, Http404 from rest_framework.decorators import action from rest_framework.exceptions import ParseError from rest_framework.mixins import RetrieveModelMixin @@ -18,7 +18,8 @@ from apps.utils.serializers import ComplexSerializer from rest_framework.throttling import UserRateThrottle from drf_yasg.utils import swagger_auto_schema import json - +from django.db import connection +from django.core.exceptions import ObjectDoesNotExist class CustomGenericViewSet(MyLoggingMixin, GenericViewSet): """ @@ -89,6 +90,34 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet): elif hash_v_e: return Response(hash_v_e) + def get_object(self, force_lock=False): + """ + 智能加锁的get_object + - 只读请求:普通查询 + - 非只读请求且在事务中:加锁查询 + - 非只读请求但不在事务中:普通查询(带警告) + """ + # 只读方法列表 + read_only_methods = ['GET', 'HEAD', 'OPTIONS'] + + if self.request.method not in read_only_methods and connection.in_atomic_block: + if force_lock: + raise ParseError("当前操作需要在事务中进行,请使用事务装饰器") + # 非只读请求且在事务中:加锁查询 + queryset = self.filter_queryset(self.get_queryset()) + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} + + try: + obj = queryset.select_for_update().get(**filter_kwargs) + self.check_object_permissions(self.request, obj) + return obj + except ObjectDoesNotExist: + raise Http404 + else: + # 其他情况:普通查询 + return super().get_object() + def get_serializer_class(self): action_serializer_name = f"{self.action}_serializer_class" action_serializer_class = getattr(self, action_serializer_name, None) @@ -193,5 +222,4 @@ class CustomModelViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomListM CustomRetrieveModelMixin, BulkDestroyModelMixin, ComplexQueryMixin, CustomGenericViewSet): """ 增强的ModelViewSet - """ - pass \ No newline at end of file + """ \ No newline at end of file diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 7baf2930..1840d37c 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -738,7 +738,6 @@ class BatchSt(BaseModel): # return ins, True @classmethod - @transaction.atomic def init_dag(cls, batch:str): """ 更新批次数据关系链(初步) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 09e4c6f1..b24c0ffc 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -885,7 +885,6 @@ class MlogbwCreateUpdateSerializer(CustomModelSerializer): ftest_sr.update(instance=ftest, validated_data=ftest_data) return mlogbw - @transaction.atomic def create(self, validated_data): wpr: Wpr = validated_data.get("wpr", None) if wpr: @@ -898,7 +897,6 @@ class MlogbwCreateUpdateSerializer(CustomModelSerializer): mlogbw = self.save_ftest(mlogbw, ftest_data) return mlogbw - @transaction.atomic def update(self, instance, validated_data): validated_data.pop("mlogb") ftest_data = validated_data.pop("ftest", None) @@ -1078,7 +1076,6 @@ class MlogbOutUpdateSerializer(CustomModelSerializer): # else: # raise ParseError("mlogbdefect仅支持批次件") # return ins - @transaction.atomic def update(self, instance, validated_data): mlogbdefect = validated_data.pop("mlogbdefect", None) with transaction.atomic(): diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 97c49e67..659fb340 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -37,14 +37,12 @@ from django.db.models import Q from apps.utils.tools import convert_ordereddict, update_dict from django.db.models import Count from datetime import datetime, timedelta -from apps.utils.lock import lock_model_record_d_method from apps.em.models import Equipment from django.db.models import Prefetch from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi - # Create your views here. @@ -155,7 +153,6 @@ class WMaterialViewSet(ListModelMixin, CustomGenericViewSet): return queryset.exclude(state=WMaterial.WM_SCRAP) @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=DeptBatchSerializer) - @transaction.atomic def batchs(self, request): """获取车间的批次号(废弃) @@ -282,7 +279,6 @@ class MlogViewSet(CustomModelViewSet): item["mlogbw_number_list"] = wpr_dict.get(item["id"], None) return data - @lock_model_record_d_method(Mlog) def perform_destroy(self, instance): if instance.submit_time is not None: raise ParseError('日志已提交不可变动') @@ -295,7 +291,6 @@ class MlogViewSet(CustomModelViewSet): instance.delete() Ftest.objects.filter(id__in=ftestIds).delete() - @transaction.atomic def perform_update(self, serializer): ins = serializer.instance if ins.ticket and ins.ticket.state != State.STATE_TYPE_START: @@ -318,7 +313,10 @@ class MlogViewSet(CustomModelViewSet): ins = sr.save() return Response(MlogSerializer(ins).data) - @action(methods=['post'], detail=True, perms_map={'post': 'mlog.update'}, serializer_class=MlogChangeSerializer) + @action(methods=['post'], detail=True, + perms_map={'post': 'mlog.update'}, + serializer_class=MlogChangeSerializer) + @transaction.atomic def change(self, request, *args, **kwargs): """修改日志 @@ -334,7 +332,9 @@ class MlogViewSet(CustomModelViewSet): sr.save() return Response(MlogSerializer(ins).data) - @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=Serializer) + @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, + serializer_class=Serializer) + @transaction.atomic def submit(self, request, *args, **kwargs): """日志提交(变动车间库存) @@ -349,20 +349,12 @@ class MlogViewSet(CustomModelViewSet): if p.mlog_need_ticket: raise ParseError('该日志需要审批!') mlog_submit_validate(ins) - with transaction.atomic(): - updated_count = Mlog.objects.filter(id=ins.id, submit_time__isnull=True).update( - submit_time=now, submit_user=request.user, update_by=request.user) - if updated_count == 1: - mlog_submit(ins, self.request.user, now) - else: - raise ParseError('记录正在处理中,请稍后再试') - - vdata_new = MlogSerializer(ins).data - # create_auditlog('submit', ins, vdata_new, - # vdata_old, now, self.request.user) + mlog_submit(ins, self.request.user, now) + vdata_new = MlogSerializer(ins).data return Response(vdata_new) @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=MlogRevertSerializer) + @transaction.atomic def revert(self, request, *args, **kwargs): """撤回日志提交 @@ -377,20 +369,10 @@ class MlogViewSet(CustomModelViewSet): if user != ins.submit_user: raise ParseError('非提交人不可撤销!') now = timezone.now() - with transaction.atomic(): - updated_count = Mlog.objects.filter(id=ins.id, submit_time__isnull=False).update( - submit_time=None, update_time=now, submit_user=None, update_by=request.user) - if updated_count == 1: - mlog_revert(ins, user, now) - else: - raise ParseError('记录正在处理中,请稍后再试') - - # create_auditlog('revert', ins, {}, {}, now, user, - # request.data.get('change_reason', '')) + mlog_revert(ins, user, now) return Response(MlogSerializer(instance=ins).data) @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MlogRelatedSerializer) - @transaction.atomic def related_first(self, request, *args, **kwargs): """获取相关任务的第一道工序日志 @@ -508,8 +490,7 @@ class HandoverViewSet(CustomModelViewSet): 'material__number', 'material__specification', 'batch', 'material__model', 'b_handover__batch', "new_batch", "wm__batch"] prefetch_related_fields = ["b_handover"] - @lock_model_record_d_method(Handover) - def perform_destroy(self, instance:Handover): + def perform_destroy(self, instance): user = self.request.user if instance.submit_time is not None: raise ParseError('该交接记录已提交不可删除') @@ -520,9 +501,8 @@ class HandoverViewSet(CustomModelViewSet): ticket.delete() instance.delete() - @transaction.atomic def perform_update(self, serializer): - ins:Handover = self.get_object() + ins:Handover = serializer.instance if ins.submit_time is not None: raise ParseError('该交接记录已提交!') ticket:Ticket = ins.ticket @@ -580,7 +560,6 @@ class HandoverViewSet(CustomModelViewSet): return Response() @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=HandoverMgroupSerializer) - @transaction.atomic def mgroups(self, request, *args, **kwargs): """获取可交接到的工段 @@ -708,7 +687,6 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust serializer_class = MlogbInSerializer update_serializer_class = MlogbInUpdateSerializer - @transaction.atomic def perform_destroy(self, instance): ins: Mlogb = instance if ins.mlog.submit_time is not None: @@ -716,7 +694,6 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust ins.delete() ins.mlog.cal_mlog_count_from_mlogb() - @transaction.atomic def perform_update(self, serializer): ins:Mlogb = serializer.save() ins.mlog.cal_mlog_count_from_mlogb() @@ -883,7 +860,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust raise ParseError("不支持生成产出物料!") mlog.cal_mlog_count_from_mlogb() - @transaction.atomic + def perform_create(self, serializer): mlogbin: Mlogb = serializer.save() MlogbInViewSet.p_create_after(mlogbin) @@ -925,7 +902,6 @@ class MlogbOutViewSet(UpdateModelMixin, CustomGenericViewSet): queryset = Mlogb.objects.filter(material_out__isnull=False) serializer_class = MlogbOutUpdateSerializer - @transaction.atomic def perform_update(self, serializer): material_out = serializer.validated_data.get('material_out') if material_out and material_out.tracking == Material.MA_TRACKING_SINGLE: @@ -985,7 +961,6 @@ class MlogbwViewSet(CustomModelViewSet): # raise ParseError('请指定所属消耗/产出明细') # return super().filter_queryset(queryset) - @transaction.atomic def perform_create(self, serializer): ins:Mlogbw = serializer.save() mlog:Mlog = None @@ -1061,7 +1036,6 @@ class MlogbwViewSet(CustomModelViewSet): raise ParseError("该个号不可产生该批") mlog.cal_mlog_count_from_mlogb() - @transaction.atomic def perform_update(self, serializer): mlogbw = serializer.save() if isinstance(mlogbw, list): @@ -1079,7 +1053,6 @@ class MlogbwViewSet(CustomModelViewSet): mlog = mlogb.mlog mlog.cal_mlog_count_from_mlogb() - @transaction.atomic def perform_destroy(self, instance:Mlogbw): mlogb:Mlogb = instance.mlogb if mlogb.material_out is not None and instance.wpr is not None: