From 412398d46174851d50de6f87bb754529ae7b54e9 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 12 Sep 2025 12:14:29 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20base=20CustomGenericViewSet=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8=E4=BA=8B=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/utils/mixins.py | 27 +++++++++---------- apps/utils/viewsets.py | 60 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/apps/utils/mixins.py b/apps/utils/mixins.py index 9e4a277f..dff0fd27 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 @@ -91,10 +90,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) @@ -124,16 +122,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) diff --git a/apps/utils/viewsets.py b/apps/utils/viewsets.py index b33c973d..1e2758d8 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,6 +18,9 @@ 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 +from django.db import transaction class CustomGenericViewSet(MyLoggingMixin, GenericViewSet): @@ -59,6 +62,30 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet): cls._initialized = True return super().__new__(cls) + def dispatch(self, request, *args, **kwargs): + # 判断是否需要事务 + if self._should_use_transaction(request): + with transaction.atomic(): + return super().dispatch(request, *args, **kwargs) + else: + return super().dispatch(request, *args, **kwargs) + + def _should_use_transaction(self, request): + """判断当前请求是否需要事务""" + # 标准的写操作需要事务 + if request.method in ('POST', 'PUT', 'PATCH', 'DELETE'): + # 但还要看具体是哪个action + action = self.action_map.get(request.method.lower(), {}).get(request.method.lower()) + if action in ['create', 'update', 'partial_update', 'destroy']: + return True + + # 自定义的action:可以通过在action方法上添加装饰器或特殊属性来判断 + action = getattr(self, self.action, None) if self.action else None + if action and getattr(action, 'requires_transaction', False): + return True + + return False + def finalize_response(self, request, response, *args, **kwargs): # 如果是流式响应,直接返回 if isinstance(response, StreamingHttpResponse): @@ -89,6 +116,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 +248,4 @@ class CustomModelViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomListM CustomRetrieveModelMixin, BulkDestroyModelMixin, ComplexQueryMixin, CustomGenericViewSet): """ 增强的ModelViewSet - """ - pass \ No newline at end of file + """ \ No newline at end of file From 527e6c0fc28ab611757ddc211064cd40c3753db9 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 12 Sep 2025 12:16:47 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20wpm=20=E5=8F=96=E6=B6=88=20transact?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/models.py | 1 - apps/wpm/serializers.py | 3 -- apps/wpm/views.py | 77 +++++++++++------------------------------ apps/wpmw/views.py | 3 +- 4 files changed, 21 insertions(+), 63 deletions(-) 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..2b2aefbe 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -37,7 +37,6 @@ 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 @@ -155,7 +154,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 +280,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 +292,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 +314,9 @@ 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, requires_transaction=True) def change(self, request, *args, **kwargs): """修改日志 @@ -334,7 +332,8 @@ 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, requires_transaction=True) def submit(self, request, *args, **kwargs): """日志提交(变动车间库存) @@ -349,20 +348,11 @@ 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) + @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=MlogRevertSerializer, requires_transaction=True) def revert(self, request, *args, **kwargs): """撤回日志提交 @@ -377,20 +367,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): """获取相关任务的第一道工序日志 @@ -453,8 +433,7 @@ class MlogViewSet(CustomModelViewSet): res[i] = 0 return Response(res) - @action(methods=['post'], detail=False, perms_map={'post': 'mlog.create'}, serializer_class=MlogQuickSerializer) - @transaction.atomic + @action(methods=['post'], detail=False, perms_map={'post': 'mlog.create'}, serializer_class=MlogQuickSerializer, requires_transaction=True) def quick(self, request, *args, **kwargs): """快速创建日志 @@ -508,8 +487,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 +498,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 @@ -530,8 +507,7 @@ class HandoverViewSet(CustomModelViewSet): raise ParseError("该交接记录存在审批,不可修改") serializer.save() - @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=HandoverSerializer) - @transaction.atomic + @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=HandoverSerializer, requires_transaction=True) def create_and_submit(self, request, *args, **kwargs): user = request.user sr = HandoverSerializer(data=request.data, context={'request': request}) @@ -546,8 +522,7 @@ class HandoverViewSet(CustomModelViewSet): handover_submit(ins, user, None) return Response({"id": ins.id}) - @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer) - @transaction.atomic + @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer, requires_transaction=True) def submit(self, request, *args, **kwargs): """交接记录提交(变动车间库存) @@ -567,8 +542,7 @@ class HandoverViewSet(CustomModelViewSet): handover_submit(ins, user, None) return Response() - @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer) - @transaction.atomic + @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer, requires_transaction=True) def revert(self, request, *args, **kwargs): """交接记录撤回(变动车间库存) @@ -580,7 +554,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): """获取可交接到的工段 @@ -602,8 +575,7 @@ class HandoverViewSet(CustomModelViewSet): m_qs = m_qs.filter(process=None) return Response(list(m_qs.values('id', 'name').distinct())) - @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverWmSerializer) - @transaction.atomic + @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverWmSerializer, requires_transaction=True) def gen_by_wm(self, request): """从车间库存生成交接记录(废弃) @@ -632,8 +604,7 @@ class HandoverViewSet(CustomModelViewSet): ) return Response({'handover': handover.id}) - @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverSerializer) - @transaction.atomic + @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverSerializer, requires_transaction=True) def gen_by_mlog(self, request): """从生产日志生成交接记录(废弃) @@ -708,7 +679,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 +686,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 +852,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 +894,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 +953,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 +1028,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 +1045,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: @@ -1109,8 +1074,7 @@ class MlogbwViewSet(CustomModelViewSet): mlog = mlogb.mlog mlog.cal_mlog_count_from_mlogb() - @action(methods=['post'], detail=False, perms_map={'post': 'mlog.update'}, serializer_class=MlogbwStartTestSerializer) - @transaction.atomic + @action(methods=['post'], detail=False, perms_map={'post': 'mlog.update'}, serializer_class=MlogbwStartTestSerializer, requires_transaction=True) def start_test(self, request, *args, **kwargs): sr = MlogbwStartTestSerializer(data=request.data) sr.is_valid(raise_exception=True) @@ -1143,8 +1107,7 @@ class BatchLogViewSet(ListModelMixin, CustomGenericViewSet): serializer_class = BatchLogSerializer select_related_fields = ["source", "target"] - @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer) - @transaction.atomic + @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer, requires_transaction=True) def dag(self, request): """ 获取该批次的DAG图数据 diff --git a/apps/wpmw/views.py b/apps/wpmw/views.py index 149d1759..6a7256b3 100644 --- a/apps/wpmw/views.py +++ b/apps/wpmw/views.py @@ -97,8 +97,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu return Response({"number_out_last": None}) - @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproutListSerializer) - @transaction.atomic + @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproutListSerializer, requires_transaction=True) def assgin_number_out(self, request, *args, **kwargs): """分配出库对外编号 From 674f62a05adc3c48202ac8b692a89e4e71856c30 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 12 Sep 2025 12:40:42 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20base=20=E4=BF=AE=E6=94=B9=5Fshould?= =?UTF-8?q?=5Fuse=5Ftransaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/utils/viewsets.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/utils/viewsets.py b/apps/utils/viewsets.py index 1e2758d8..ad6de925 100755 --- a/apps/utils/viewsets.py +++ b/apps/utils/viewsets.py @@ -22,6 +22,10 @@ from django.db import connection from django.core.exceptions import ObjectDoesNotExist from django.db import transaction +def enable_transaction(func): + """装饰器 标记这个action需要事务""" + func._enable_transaction = True + return func class CustomGenericViewSet(MyLoggingMixin, GenericViewSet): """ @@ -72,18 +76,14 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet): def _should_use_transaction(self, request): """判断当前请求是否需要事务""" - # 标准的写操作需要事务 - if request.method in ('POST', 'PUT', 'PATCH', 'DELETE'): - # 但还要看具体是哪个action - action = self.action_map.get(request.method.lower(), {}).get(request.method.lower()) - if action in ['create', 'update', 'partial_update', 'destroy']: - return True - - # 自定义的action:可以通过在action方法上添加装饰器或特殊属性来判断 - action = getattr(self, self.action, None) if self.action else None - if action and getattr(action, 'requires_transaction', False): + # 1. 标准写操作需要事务 + if self.action in ['create', 'update', 'partial_update', 'destroy']: + return True + action_method = getattr(self, self.action, None) + if not action_method: + return False + elif hasattr(action_method, '_enable_transaction'): return True - return False def finalize_response(self, request, response, *args, **kwargs): From 5e8e72cee9293a70da273da5bdb9a3e019db8bd0 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 12 Sep 2025 12:41:00 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9wpm=E4=BA=8B?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/views.py | 35 +++++++++++++++++++++++------------ apps/wpmw/views.py | 5 +++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 2b2aefbe..cd95d8c8 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -11,7 +11,7 @@ from django.utils import timezone from apps.system.models import User from apps.mtm.models import Material, Process, Route, Mgroup, RoutePack, RouteMat -from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet +from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, enable_transaction from apps.utils.mixins import CustomListModelMixin, BulkCreateModelMixin, ComplexQueryMixin from .filters import StLogFilter, SfLogFilter, WMaterialFilter, MlogFilter, HandoverFilter, MlogbFilter, BatchStFilter, MlogbwFilter @@ -316,7 +316,8 @@ class MlogViewSet(CustomModelViewSet): @action(methods=['post'], detail=True, perms_map={'post': 'mlog.update'}, - serializer_class=MlogChangeSerializer, requires_transaction=True) + serializer_class=MlogChangeSerializer) + @enable_transaction def change(self, request, *args, **kwargs): """修改日志 @@ -333,7 +334,8 @@ class MlogViewSet(CustomModelViewSet): return Response(MlogSerializer(ins).data) @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, - serializer_class=Serializer, requires_transaction=True) + serializer_class=Serializer) + @enable_transaction def submit(self, request, *args, **kwargs): """日志提交(变动车间库存) @@ -352,7 +354,8 @@ class MlogViewSet(CustomModelViewSet): vdata_new = MlogSerializer(ins).data return Response(vdata_new) - @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=MlogRevertSerializer, requires_transaction=True) + @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=MlogRevertSerializer) + @enable_transaction def revert(self, request, *args, **kwargs): """撤回日志提交 @@ -433,7 +436,8 @@ class MlogViewSet(CustomModelViewSet): res[i] = 0 return Response(res) - @action(methods=['post'], detail=False, perms_map={'post': 'mlog.create'}, serializer_class=MlogQuickSerializer, requires_transaction=True) + @action(methods=['post'], detail=False, perms_map={'post': 'mlog.create'}, serializer_class=MlogQuickSerializer) + @enable_transaction def quick(self, request, *args, **kwargs): """快速创建日志 @@ -507,7 +511,8 @@ class HandoverViewSet(CustomModelViewSet): raise ParseError("该交接记录存在审批,不可修改") serializer.save() - @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=HandoverSerializer, requires_transaction=True) + @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=HandoverSerializer) + @enable_transaction def create_and_submit(self, request, *args, **kwargs): user = request.user sr = HandoverSerializer(data=request.data, context={'request': request}) @@ -522,7 +527,8 @@ class HandoverViewSet(CustomModelViewSet): handover_submit(ins, user, None) return Response({"id": ins.id}) - @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer, requires_transaction=True) + @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer) + @enable_transaction def submit(self, request, *args, **kwargs): """交接记录提交(变动车间库存) @@ -542,7 +548,8 @@ class HandoverViewSet(CustomModelViewSet): handover_submit(ins, user, None) return Response() - @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer, requires_transaction=True) + @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer) + @enable_transaction def revert(self, request, *args, **kwargs): """交接记录撤回(变动车间库存) @@ -575,7 +582,8 @@ class HandoverViewSet(CustomModelViewSet): m_qs = m_qs.filter(process=None) return Response(list(m_qs.values('id', 'name').distinct())) - @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverWmSerializer, requires_transaction=True) + @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverWmSerializer) + @enable_transaction def gen_by_wm(self, request): """从车间库存生成交接记录(废弃) @@ -604,7 +612,8 @@ class HandoverViewSet(CustomModelViewSet): ) return Response({'handover': handover.id}) - @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverSerializer, requires_transaction=True) + @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverSerializer) + @enable_transaction def gen_by_mlog(self, request): """从生产日志生成交接记录(废弃) @@ -1074,7 +1083,8 @@ class MlogbwViewSet(CustomModelViewSet): mlog = mlogb.mlog mlog.cal_mlog_count_from_mlogb() - @action(methods=['post'], detail=False, perms_map={'post': 'mlog.update'}, serializer_class=MlogbwStartTestSerializer, requires_transaction=True) + @action(methods=['post'], detail=False, perms_map={'post': 'mlog.update'}, serializer_class=MlogbwStartTestSerializer) + @enable_transaction def start_test(self, request, *args, **kwargs): sr = MlogbwStartTestSerializer(data=request.data) sr.is_valid(raise_exception=True) @@ -1107,7 +1117,8 @@ class BatchLogViewSet(ListModelMixin, CustomGenericViewSet): serializer_class = BatchLogSerializer select_related_fields = ["source", "target"] - @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer, requires_transaction=True) + @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer) + @enable_transaction def dag(self, request): """ 获取该批次的DAG图数据 diff --git a/apps/wpmw/views.py b/apps/wpmw/views.py index 6a7256b3..b6985558 100644 --- a/apps/wpmw/views.py +++ b/apps/wpmw/views.py @@ -1,5 +1,5 @@ from rest_framework.decorators import action -from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet +from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet, enable_transaction from apps.utils.mixins import CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin from apps.wpmw.models import Wpr, WprDefect @@ -97,7 +97,8 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu return Response({"number_out_last": None}) - @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproutListSerializer, requires_transaction=True) + @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproutListSerializer) + @enable_transaction def assgin_number_out(self, request, *args, **kwargs): """分配出库对外编号 From e5008c84123aff493a1ae4cb9a883afcb9bc7aa0 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 12 Sep 2025 13:48:10 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20base=20=E5=9C=A8create=20update=20de?= =?UTF-8?q?stroy=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8=E4=BA=8B=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/utils/mixins.py | 7 ++++++- apps/utils/viewsets.py | 26 -------------------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/apps/utils/mixins.py b/apps/utils/mixins.py index dff0fd27..94703573 100755 --- a/apps/utils/mixins.py +++ b/apps/utils/mixins.py @@ -18,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') @@ -80,7 +81,8 @@ class BulkCreateModelMixin(CreateModelMixin): def after_bulk_create(self, objs): pass - + + @transaction.atomic def create(self, request, *args, **kwargs): """创建(支持批量) @@ -103,6 +105,7 @@ class BulkUpdateModelMixin(UpdateModelMixin): def after_bulk_update(self, objs): pass + @transaction.atomic def partial_update(self, request, *args, **kwargs): """部分更新(支持批量) @@ -111,6 +114,7 @@ class BulkUpdateModelMixin(UpdateModelMixin): kwargs['partial'] = True return self.update(request, *args, **kwargs) + @transaction.atomic def update(self, request, *args, **kwargs): """更新(支持批量) @@ -145,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 ad6de925..41588d26 100755 --- a/apps/utils/viewsets.py +++ b/apps/utils/viewsets.py @@ -20,12 +20,6 @@ from drf_yasg.utils import swagger_auto_schema import json from django.db import connection from django.core.exceptions import ObjectDoesNotExist -from django.db import transaction - -def enable_transaction(func): - """装饰器 标记这个action需要事务""" - func._enable_transaction = True - return func class CustomGenericViewSet(MyLoggingMixin, GenericViewSet): """ @@ -66,26 +60,6 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet): cls._initialized = True return super().__new__(cls) - def dispatch(self, request, *args, **kwargs): - # 判断是否需要事务 - if self._should_use_transaction(request): - with transaction.atomic(): - return super().dispatch(request, *args, **kwargs) - else: - return super().dispatch(request, *args, **kwargs) - - def _should_use_transaction(self, request): - """判断当前请求是否需要事务""" - # 1. 标准写操作需要事务 - if self.action in ['create', 'update', 'partial_update', 'destroy']: - return True - action_method = getattr(self, self.action, None) - if not action_method: - return False - elif hasattr(action_method, '_enable_transaction'): - return True - return False - def finalize_response(self, request, response, *args, **kwargs): # 如果是流式响应,直接返回 if isinstance(response, StreamingHttpResponse): From 57a61daa6695b25a9f40e0f50ab4de0ffad24896 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 12 Sep 2025 13:48:34 +0800 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20wpm=E4=BF=AE=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E4=BA=8B=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/views.py | 25 ++++++++++++------------- apps/wpmw/views.py | 4 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index cd95d8c8..659fb340 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -11,7 +11,7 @@ from django.utils import timezone from apps.system.models import User from apps.mtm.models import Material, Process, Route, Mgroup, RoutePack, RouteMat -from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, enable_transaction +from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet from apps.utils.mixins import CustomListModelMixin, BulkCreateModelMixin, ComplexQueryMixin from .filters import StLogFilter, SfLogFilter, WMaterialFilter, MlogFilter, HandoverFilter, MlogbFilter, BatchStFilter, MlogbwFilter @@ -43,7 +43,6 @@ from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi - # Create your views here. @@ -317,7 +316,7 @@ class MlogViewSet(CustomModelViewSet): @action(methods=['post'], detail=True, perms_map={'post': 'mlog.update'}, serializer_class=MlogChangeSerializer) - @enable_transaction + @transaction.atomic def change(self, request, *args, **kwargs): """修改日志 @@ -335,7 +334,7 @@ class MlogViewSet(CustomModelViewSet): @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=Serializer) - @enable_transaction + @transaction.atomic def submit(self, request, *args, **kwargs): """日志提交(变动车间库存) @@ -355,7 +354,7 @@ class MlogViewSet(CustomModelViewSet): return Response(vdata_new) @action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=MlogRevertSerializer) - @enable_transaction + @transaction.atomic def revert(self, request, *args, **kwargs): """撤回日志提交 @@ -437,7 +436,7 @@ class MlogViewSet(CustomModelViewSet): return Response(res) @action(methods=['post'], detail=False, perms_map={'post': 'mlog.create'}, serializer_class=MlogQuickSerializer) - @enable_transaction + @transaction.atomic def quick(self, request, *args, **kwargs): """快速创建日志 @@ -512,7 +511,7 @@ class HandoverViewSet(CustomModelViewSet): serializer.save() @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=HandoverSerializer) - @enable_transaction + @transaction.atomic def create_and_submit(self, request, *args, **kwargs): user = request.user sr = HandoverSerializer(data=request.data, context={'request': request}) @@ -528,7 +527,7 @@ class HandoverViewSet(CustomModelViewSet): return Response({"id": ins.id}) @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer) - @enable_transaction + @transaction.atomic def submit(self, request, *args, **kwargs): """交接记录提交(变动车间库存) @@ -549,7 +548,7 @@ class HandoverViewSet(CustomModelViewSet): return Response() @action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer) - @enable_transaction + @transaction.atomic def revert(self, request, *args, **kwargs): """交接记录撤回(变动车间库存) @@ -583,7 +582,7 @@ class HandoverViewSet(CustomModelViewSet): return Response(list(m_qs.values('id', 'name').distinct())) @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverWmSerializer) - @enable_transaction + @transaction.atomic def gen_by_wm(self, request): """从车间库存生成交接记录(废弃) @@ -613,7 +612,7 @@ class HandoverViewSet(CustomModelViewSet): return Response({'handover': handover.id}) @action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverSerializer) - @enable_transaction + @transaction.atomic def gen_by_mlog(self, request): """从生产日志生成交接记录(废弃) @@ -1084,7 +1083,7 @@ class MlogbwViewSet(CustomModelViewSet): mlog.cal_mlog_count_from_mlogb() @action(methods=['post'], detail=False, perms_map={'post': 'mlog.update'}, serializer_class=MlogbwStartTestSerializer) - @enable_transaction + @transaction.atomic def start_test(self, request, *args, **kwargs): sr = MlogbwStartTestSerializer(data=request.data) sr.is_valid(raise_exception=True) @@ -1118,7 +1117,7 @@ class BatchLogViewSet(ListModelMixin, CustomGenericViewSet): select_related_fields = ["source", "target"] @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer) - @enable_transaction + @transaction.atomic def dag(self, request): """ 获取该批次的DAG图数据 diff --git a/apps/wpmw/views.py b/apps/wpmw/views.py index b6985558..149d1759 100644 --- a/apps/wpmw/views.py +++ b/apps/wpmw/views.py @@ -1,5 +1,5 @@ from rest_framework.decorators import action -from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet, enable_transaction +from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet from apps.utils.mixins import CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin from apps.wpmw.models import Wpr, WprDefect @@ -98,7 +98,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproutListSerializer) - @enable_transaction + @transaction.atomic def assgin_number_out(self, request, *args, **kwargs): """分配出库对外编号