From 53ca8f17425290c1cdaeeebdfa9bf6c77221668b Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 14 Nov 2023 10:23:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20bidataset=E5=A2=9E=E5=8A=A0=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0003_dataset_default_param.py | 18 +++++++++ apps/bi/models.py | 6 ++- apps/bi/serializers.py | 7 +++- apps/bi/views.py | 39 ++++++++++++------- 4 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 apps/bi/migrations/0003_dataset_default_param.py diff --git a/apps/bi/migrations/0003_dataset_default_param.py b/apps/bi/migrations/0003_dataset_default_param.py new file mode 100644 index 00000000..5803ccb5 --- /dev/null +++ b/apps/bi/migrations/0003_dataset_default_param.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2023-11-14 02:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bi', '0002_dataset_cache_seconds'), + ] + + operations = [ + migrations.AddField( + model_name='dataset', + name='default_param', + field=models.JSONField(blank=True, default=dict, verbose_name='默认查询参数'), + ), + ] diff --git a/apps/bi/models.py b/apps/bi/models.py index 693478d7..b8f23fd4 100644 --- a/apps/bi/models.py +++ b/apps/bi/models.py @@ -2,12 +2,14 @@ from django.db import models from apps.utils.models import BaseModel, CommonADModel, CommonBDModel # Create your models here. + class Dataset(CommonBDModel): name = models.CharField('名称', max_length=100) code = models.CharField('标识', max_length=100, default='', blank=True) description = models.TextField('描述说明', default='', blank=True) sql_query = models.TextField('sql查询语句', default='', blank=True) - echart_options = models.TextField(default='', blank=True) + echart_options = models.TextField(default='', blank=True) + default_param = models.JSONField('默认查询参数', default=dict, blank=True) cache_seconds = models.PositiveIntegerField('缓存秒数', default=10, blank=True) @@ -15,4 +17,4 @@ class Dataset(CommonBDModel): # name = models.CharField('名称', max_length=100) # code = models.CharField('标识', max_length=100, default='', blank=True) # js_function = models.TextField('数据转化函数', default='', blank=True) -# datasets = models.ManyToManyField(Dataset, verbose_name='关联数据集', blank=True) \ No newline at end of file +# datasets = models.ManyToManyField(Dataset, verbose_name='关联数据集', blank=True) diff --git a/apps/bi/serializers.py b/apps/bi/serializers.py index 1225e89b..74338540 100644 --- a/apps/bi/serializers.py +++ b/apps/bi/serializers.py @@ -9,13 +9,14 @@ class DatasetCreateUpdateSerializer(CustomModelSerializer): class Meta: model = Dataset exclude = EXCLUDE_FIELDS - + def validate(self, attrs): sql_query = attrs.get('sql_query', '') if sql_query: check_sql_safe(sql_query) return attrs + class DatasetSerializer(CustomModelSerializer): class Meta: model = Dataset @@ -30,4 +31,6 @@ class DatasetSerializer(CustomModelSerializer): class DataExecSerializer(serializers.Serializer): - query = serializers.JSONField(label="查询字典参数", required=False, allow_null=True) \ No newline at end of file + query = serializers.JSONField( + label="查询字典参数", required=False, allow_null=True) + is_test = serializers.BooleanField(label='是否测试', default=False) diff --git a/apps/bi/views.py b/apps/bi/views.py index aafee7f6..2b35e507 100644 --- a/apps/bi/views.py +++ b/apps/bi/views.py @@ -14,13 +14,14 @@ from rest_framework.exceptions import ParseError from rest_framework.generics import get_object_or_404 # Create your views here. + class DatasetViewSet(CustomModelViewSet): queryset = Dataset.objects.all() serializer_class = DatasetSerializer create_serializer_class = DatasetCreateUpdateSerializer update_serializer_class = DatasetCreateUpdateSerializer search_fields = ['name', 'code'] - + def get_object(self): """ Returns the object the view is displaying. @@ -52,35 +53,45 @@ class DatasetViewSet(CustomModelViewSet): self.check_object_permissions(self.request, obj) return obj - + @action(methods=['post'], detail=True, perms_map={'post': 'dataset.exec'}, serializer_class=DataExecSerializer, cache_seconds=0) def exec(self, request, pk=None): """执行sql查询 执行sql查询支持code """ - dt = self.get_object() + dt: Dataset = self.get_object() rdata = DatasetSerializer(instance=dt).data query = request.data.get('query', {}) + is_test = request.data.get('is_test', False) query['r_user'] = request.user.id query['r_dept'] = request.user.belong_dept.id if request.user.belong_dept else '' results = {} results2 = {} can_cache = True - + if dt.sql_query: try: sql_f_ = check_sql_safe(dt.sql_query.format(**query)) except KeyError: + if is_test and dt.default_param: + new_query = dt.default_param + new_query.update(query) + try: + sql_f_ = check_sql_safe(dt.sql_query.format(**query)) + except KeyError: + raise ParseError('需指定查询参数') raise ParseError('需指定查询参数') - sql_f_l = sql_f_.strip(';').split(';') - hash_k = hash(sql_f_.strip(';')) + sql_f_strip = sql_f_.strip(';') + sql_f_l = sql_f_strip.split(';') + hash_k = hash(sql_f_strip) hash_v = cache.get(hash_k, None) if hash_v: return Response(hash_v) - with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: # 多线程运行并返回字典结果 + # 多线程运行并返回字典结果 + with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: fun_ps = [] - for ind, val in enumerate(sql_f_l): + for ind, val in enumerate(sql_f_l): fun_ps.append((f'ds{ind}', execute_raw_sql, val)) # 生成执行函数 futures = {executor.submit(i[1], i[2]): i for i in fun_ps} @@ -88,7 +99,8 @@ class DatasetViewSet(CustomModelViewSet): name, *_, sql_f = futures[future] # 获取对应的键 try: res = future.result() - results[name], results2[name]= format_sqldata(res[0], res[1]) + results[name], results2[name] = format_sqldata( + res[0], res[1]) except Exception as e: results[name] = 'error: ' + str(e) can_cache = False @@ -98,11 +110,12 @@ class DatasetViewSet(CustomModelViewSet): for key in results: if isinstance(results[key], str): raise ParseError(results[key]) - rdata['echart_options'] = format_json_with_placeholders(rdata['echart_options'], **results) + rdata['echart_options'] = format_json_with_placeholders( + rdata['echart_options'], **results) if results and can_cache: cache.set(hash_k, rdata, dt.cache_seconds) return Response(rdata) - + @action(methods=['get'], detail=False, perms_map={'get': '*'}) def base(self, request, pk=None): all_models = apps.get_models() @@ -116,7 +129,8 @@ class DatasetViewSet(CustomModelViewSet): # 获取字段信息 fields = model._meta.get_fields() for field in fields: - rdict[table_name].append({'name': field.name, 'type': field.get_internal_type()}) + rdict[table_name].append( + {'name': field.name, 'type': field.get_internal_type()}) return Response(rdict) @@ -166,4 +180,3 @@ class DatasetViewSet(CustomModelViewSet): # rdata['data'] = results # return Response(rdata) - \ No newline at end of file