From 2be34d3caa402ebc200f5dad47390ceda8b20c04 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 27 Aug 2024 17:28:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20enm=E6=B7=BB=E5=8A=A0xscript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/enm/migrations/0043_xscript.py | 34 ++++++++++++++ apps/enm/models.py | 7 +++ apps/enm/serializers.py | 69 ++++++++++++++++++++++++++++- apps/enm/tasks.py | 15 ++++++- apps/enm/urls.py | 4 +- apps/enm/views.py | 35 ++++++++++++++- 6 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 apps/enm/migrations/0043_xscript.py diff --git a/apps/enm/migrations/0043_xscript.py b/apps/enm/migrations/0043_xscript.py new file mode 100644 index 00000000..4e8a689a --- /dev/null +++ b/apps/enm/migrations/0043_xscript.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.12 on 2024-08-27 04:20 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('system', '0005_alter_permission_type'), + ('django_celery_beat', '0016_alter_crontabschedule_timezone'), + ('enm', '0042_enstat2_out_cement_cost_unit'), + ] + + operations = [ + migrations.CreateModel( + name='Xscript', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=50, verbose_name='脚本名称')), + ('code', models.TextField(blank=True, null=True, verbose_name='脚本内容')), + ('base_data', models.JSONField(blank=True, default=dict, null=True, verbose_name='基础数据')), + ('myschedule', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.myschedule', verbose_name='周期')), + ('periodictask', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='xscript_periodictask', to='django_celery_beat.periodictask', verbose_name='关联定时任务')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/enm/models.py b/apps/enm/models.py index 7c672f9f..d778cb2f 100644 --- a/apps/enm/models.py +++ b/apps/enm/models.py @@ -2,7 +2,14 @@ from django.db import models from apps.utils.models import BaseModel, CommonBModel, CommonADModel, CommonBDModel from apps.wpm.models import SfLog from apps.mtm.models import Material, Mgroup, Team +from django_celery_beat.models import PeriodicTask +class Xscript(BaseModel): + name = models.CharField("脚本名称", max_length=50) + code = models.TextField("脚本内容", null=True, blank=True) + base_data = models.JSONField("基础数据", default=dict, null=True, blank=True) + myschedule = models.ForeignKey('system.myschedule', verbose_name='周期', on_delete=models.SET_NULL, null=True, blank=True) + periodictask = models.ForeignKey(PeriodicTask, verbose_name='关联定时任务', on_delete=models.CASCADE, related_name='xscript_periodictask', null=True, blank=True) class Mpoint(CommonBModel): """测点""" diff --git a/apps/enm/serializers.py b/apps/enm/serializers.py index 533a134d..049a806d 100644 --- a/apps/enm/serializers.py +++ b/apps/enm/serializers.py @@ -1,10 +1,14 @@ from apps.utils.serializers import CustomModelSerializer -from apps.enm.models import Mpoint, MpointStat, EnStat, EnStat2, MpLogx -from apps.utils.constants import EXCLUDE_FIELDS +from apps.enm.models import Mpoint, MpointStat, EnStat, EnStat2, MpLogx, Xscript +from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE from rest_framework import serializers from apps.mtm.models import Mgroup from rest_framework.exceptions import ParseError from django.core.cache import cache +from django.db import transaction +from django_celery_beat.models import PeriodicTask +import json +from apps.utils.tools import CodeAnalyzer class MpointSerializer(CustomModelSerializer): @@ -185,3 +189,64 @@ class MpointStatCorrectSerializer(CustomModelSerializer): class EnStatAnaSerializer(serializers.Serializer): start_date = serializers.DateField(label="开始日期") end_date = serializers.DateField(label="结束日期") + + +class XscriptSerializer(CustomModelSerializer): + myschedule_name = serializers.CharField(source="myschedule.name", read_only=True) + enabled = serializers.BooleanField(source='periodictask.enabled', read_only=True) + last_run_at = serializers.DateTimeField(source='periodictask.last_run_at', read_only=True) + total_run_count = serializers.IntegerField(source='periodictask.total_run_count', read_only=True) + class Meta: + model = Xscript + fields = "__all__" + read_only_fields = EXCLUDE_FIELDS_BASE + ['periodictask'] + + def validate(self, attrs): + code = attrs['code'] + analyzer = CodeAnalyzer() + analyzer.analyze(code) + if analyzer.errors: + raise ParseError(f'脚本错误:{analyzer.errors[0]}') + return super().validate(attrs) + + @transaction.atomic + def create(self, validated_data): + ins: Xscript = super().create(validated_data) + pobj = PeriodicTask() + pobj.name = f'{ins.name}_{str(ins.id)[-8:]}' + pobj.task = 'apps.enm.tasks.insert_mplogx_from_xscript' + sch = ins.myschedule + if sch.crontab: + pobj.crontab = sch.crontab + elif sch.interval: + pobj.interval = sch.interval + else: + raise ParseError('不支持的定时策略') + pobj.kwargs = json.dumps({'xscript_id': ins.id}) + pobj.enabled = False + pobj.save() + ins.periodictask = pobj + ins.save() + return ins + + @transaction.atomic + def update(self, instance, validated_data): + ins = super().update(instance, validated_data) + pobj = instance.periodictask + pobj.name = f'{ins.name}_{str(ins.id)[-8:]}' + sch = ins.myschedule + if sch.crontab: + pobj.crontab = sch.crontab + elif sch.interval: + pobj.interval = sch.interval + else: + raise ParseError('不支持的定时策略') + pobj.save() + return ins + + +class XscriptDetailSerializer(XscriptSerializer): + last_run_data = serializers.SerializerMethodField() + + def get_last_run_data(self, obj): + return {} \ No newline at end of file diff --git a/apps/enm/tasks.py b/apps/enm/tasks.py index 49473fcb..38f28433 100644 --- a/apps/enm/tasks.py +++ b/apps/enm/tasks.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals from apps.utils.tasks import CustomTask from celery import shared_task -from apps.enm.models import MpLogx, Mpoint, MpointStat, EnStat, EnStat2 +from apps.enm.models import MpLogx, Mpoint, MpointStat, EnStat, EnStat2, Xscript from apps.wpm.models import SfLog import datetime from django.db.models import Sum, Avg @@ -37,6 +37,19 @@ def get_current_and_previous_time(): pre = now - datetime.timedelta(hours=1) return now, pre +@shared_task(base=CustomTask) +def insert_mplogx_from_xscript(xscript_id): + xscript = Xscript.objects.get(id=xscript_id) + eval_code = """ + from apps.utils.sql import DbConnection + from apps.enm.tasks import db_insert_mplogx_batch + import datetime + from django.utils import timezone + + {code} + """.format(code=xscript.code) + exec(eval_code) + def db_insert_mplogx_batch(rows): for row in rows: diff --git a/apps/enm/urls.py b/apps/enm/urls.py index de4cca4d..18d16ac5 100644 --- a/apps/enm/urls.py +++ b/apps/enm/urls.py @@ -1,6 +1,7 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.enm.views import (MpointViewSet, MpLogxViewSet, MpointStatViewSet, EnStatViewSet, EnStat2ViewSet) +from apps.enm.views import (MpointViewSet, MpLogxViewSet, MpointStatViewSet, + EnStatViewSet, EnStat2ViewSet, XscriptViewSet) API_BASE_URL = 'api/enm/' HTML_BASE_URL = 'enm/' @@ -11,6 +12,7 @@ router.register('mplogx', MpLogxViewSet, basename='mplogx') router.register('mpointstat', MpointStatViewSet, basename='mpointstat') router.register('enstat', EnStatViewSet, basename='enstat') router.register('enstat2', EnStat2ViewSet, basename='enstat2') +router.register('xscript', XscriptViewSet, basename='xscript') urlpatterns = [ path(API_BASE_URL, include(router.urls)), ] \ No newline at end of file diff --git a/apps/enm/views.py b/apps/enm/views.py index f80f3edc..c9d6ceb3 100644 --- a/apps/enm/views.py +++ b/apps/enm/views.py @@ -1,8 +1,10 @@ from django.conf import settings -from apps.enm.models import Mpoint, MpointStat, EnStat, EnStat2, MpLogx +from apps.enm.models import Mpoint, MpointStat, EnStat, EnStat2, MpLogx, Xscript from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin -from apps.enm.serializers import (MpointSerializer, MpLogxSerializer, MpointStatSerializer, EnStatSerializer, EnStat2Serializer, ReCalSerializer, MpointStatCorrectSerializer, EnStatAnaSerializer) +from apps.enm.serializers import (MpointSerializer, MpLogxSerializer, MpointStatSerializer, + EnStatSerializer, EnStat2Serializer, ReCalSerializer, + MpointStatCorrectSerializer, EnStatAnaSerializer, XscriptSerializer) from apps.enm.filters import MpointStatFilter, EnStatFilter, EnStat2Filter from apps.enm.tasks import cal_mpointstat_manual from rest_framework.response import Response @@ -66,6 +68,35 @@ class MpointViewSet(CustomModelViewSet): return Response() +class XscriptViewSet(CustomModelViewSet): + """ + list:执行脚本 + + 执行脚本 + """ + queryset = Xscript.objects.all() + serializer_class = XscriptSerializer + select_related_fields = ["myschedule", "periodictask"] + search_fields = ['name'] + + @action(methods=['put'], detail=True, perms_map={'put': 'xscript.update'}) + def toggle(self, request, pk=None): + """修改启用禁用状态 + + 修改启用禁用状态 + """ + obj = self.get_object() + periodictask = obj.periodictask + periodictask.enabled = False if periodictask.enabled else True + periodictask.save() + return Response() + + @transaction.atomic + def perform_destroy(self, instance): + periodictask = instance.periodictask + instance.delete() + periodictask.delete() + # class MpLogViewSet(ListModelMixin, CustomGenericViewSet): # """ # list:测点原始记录