diff --git a/backend/docker_start.sh b/backend/docker_start.sh index 1285d98..d0237cc 100755 --- a/backend/docker_start.sh +++ b/backend/docker_start.sh @@ -2,4 +2,5 @@ # python manage.py makemigrations # python manage.py migrate # python manage.py init -y -gunicorn -c gunicorn_conf.py application.asgi:application +#gunicorn -c gunicorn_conf.py application.asgi:application +uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 4 diff --git a/backend/dvadmin/system/models.py b/backend/dvadmin/system/models.py index 6e9101a..488ebe3 100644 --- a/backend/dvadmin/system/models.py +++ b/backend/dvadmin/system/models.py @@ -318,33 +318,33 @@ class FileList(CoreModel): # 保存到File model中 instance = FileList() instance.name = file_name - instance.engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local' + instance.engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local' instance.file_url = os.path.join(file_path, file_name) instance.mime_type = mime_type instance.creator = request.user instance.modifier = request.user.id instance.dept_belong_id = request.user.dept_id - file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup") - file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local' + file_backup = dispatch.get_system_config_values("file_storage.file_backup") + file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local' if file_backup: instance.url = os.path.join(file_path.replace('media/', ''), file_name) if file_engine == 'oss': from dvadmin_cloud_storage.views.aliyun import ali_oss_upload - file = File(open(os.path.join(BASE_DIR, file_path, file_name))) - file_path = ali_oss_upload(file) - if file_path: - instance.file_url = file_path - else: - raise ValueError("上传失败") + with open(os.path.join(BASE_DIR, file_path, file_name), 'rb') as file: + file_path = ali_oss_upload(file, file_name=os.path.join(file_path.replace('media/', ''), file_name)) + if file_path: + instance.file_url = file_path + else: + raise ValueError("上传失败") elif file_engine == 'cos': from dvadmin_cloud_storage.views.tencent import tencent_cos_upload - file = File(open(os.path.join(BASE_DIR, file_path, file_name))) - file_path = tencent_cos_upload(file) - if file_path: - instance.file_url = file_path - else: - raise ValueError("上传失败") + with open(os.path.join(BASE_DIR, file_path, file_name), 'rb') as file: + file_path = tencent_cos_upload(file, file_name=os.path.join(file_path.replace('media/', ''), file_name)) + if file_path: + instance.file_url = file_path + else: + raise ValueError("上传失败") else: instance.url = os.path.join(file_path.replace('media/', ''), file_name) instance.save() diff --git a/backend/dvadmin/system/views/datav.py b/backend/dvadmin/system/views/datav.py index a43cf7c..1c96684 100644 --- a/backend/dvadmin/system/views/datav.py +++ b/backend/dvadmin/system/views/datav.py @@ -215,38 +215,39 @@ class DataVViewSet(GenericViewSet): CHINA_PROVINCES = [ {'name': '北京', 'code': '110000'}, {'name': '天津', 'code': '120000'}, - {'name': '河北', 'code': '130000'}, - {'name': '山西', 'code': '140000'}, + {'name': '河北省', 'code': '130000'}, + {'name': '山西省', 'code': '140000'}, {'name': '内蒙古', 'code': '150000'}, - {'name': '辽宁', 'code': '210000'}, - {'name': '吉林', 'code': '220000'}, - {'name': '黑龙江', 'code': '230000'}, + {'name': '辽宁省', 'code': '210000'}, + {'name': '吉林省', 'code': '220000'}, + {'name': '黑龙江省', 'code': '230000'}, {'name': '上海', 'code': '310000'}, - {'name': '江苏', 'code': '320000'}, - {'name': '浙江', 'code': '330000'}, - {'name': '安徽', 'code': '340000'}, - {'name': '福建', 'code': '350000'}, - {'name': '江西', 'code': '360000'}, - {'name': '山东', 'code': '370000'}, - {'name': '河南', 'code': '410000'}, - {'name': '湖北', 'code': '420000'}, - {'name': '湖南', 'code': '430000'}, - {'name': '广东', 'code': '440000'}, + {'name': '江苏省', 'code': '320000'}, + {'name': '浙江省', 'code': '330000'}, + {'name': '安徽省', 'code': '340000'}, + {'name': '福建省', 'code': '350000'}, + {'name': '江西省', 'code': '360000'}, + {'name': '山东省', 'code': '370000'}, + {'name': '河南省', 'code': '410000'}, + {'name': '湖北省', 'code': '420000'}, + {'name': '湖南省', 'code': '430000'}, + {'name': '广东省', 'code': '440000'}, {'name': '广西', 'code': '450000'}, - {'name': '海南', 'code': '460000'}, + {'name': '海南省', 'code': '460000'}, {'name': '重庆', 'code': '500000'}, - {'name': '四川', 'code': '510000'}, - {'name': '贵州', 'code': '520000'}, - {'name': '云南', 'code': '530000'}, + {'name': '四川省', 'code': '510000'}, + {'name': '贵州省', 'code': '520000'}, + {'name': '云南省', 'code': '530000'}, {'name': '西藏', 'code': '540000'}, - {'name': '陕西', 'code': '610000'}, - {'name': '甘肃', 'code': '620000'}, - {'name': '青海', 'code': '630000'}, + {'name': '陕西省', 'code': '610000'}, + {'name': '甘肃省', 'code': '620000'}, + {'name': '青海省', 'code': '630000'}, {'name': '宁夏', 'code': '640000'}, {'name': '新疆', 'code': '650000'}, {'name': '台湾', 'code': '710000'}, {'name': '香港', 'code': '810000'}, {'name': '澳门', 'code': '820000'}, + {'name': '钓鱼岛', 'code': '900000'}, {'name': '未知区域', 'code': '000000'}, ] provinces = [x['name'] for x in CHINA_PROVINCES] diff --git a/backend/dvadmin/system/views/file_list.py b/backend/dvadmin/system/views/file_list.py index 7a2947f..01f222c 100644 --- a/backend/dvadmin/system/views/file_list.py +++ b/backend/dvadmin/system/views/file_list.py @@ -1,17 +1,20 @@ +import base64 import datetime import hashlib import json import os import random +from pathlib import PurePosixPath from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from rest_framework import serializers from rest_framework.decorators import action from application.settings import BASE_DIR -from application import dispatch -from dvadmin.system.models import FileList +from application import dispatch, settings +from dvadmin.system.models import FileList, media_file_name from dvadmin.system.views.ueditor_settings import ueditor_upload_settings, ueditor_settings +from dvadmin.utils.json_response import DetailResponse from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.string_util import format_bytes from dvadmin.utils.viewset import CustomModelViewSet @@ -21,6 +24,16 @@ class FileSerializer(CustomModelSerializer): url = serializers.SerializerMethodField(read_only=True) def get_url(self, instance): + if self.request.query_params.get('prefix'): + if settings.ENVIRONMENT in ['local']: + prefix = 'http://127.0.0.1:8000' + elif settings.ENVIRONMENT in ['test']: + prefix = 'http://{host}/api'.format(host=self.request.get_host()) + else: + prefix = 'https://{host}/api'.format(host=self.request.get_host()) + if instance.file_url: + return instance.file_url if instance.file_url.startswith('http') else f"{prefix}/{instance.file_url}" + return (f'{prefix}/media/{str(instance.url)}') return instance.file_url or (f'media/{str(instance.url)}') class Meta: @@ -28,8 +41,8 @@ class FileSerializer(CustomModelSerializer): fields = "__all__" def create(self, validated_data): - file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local' - file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup") + file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local' + file_backup = dispatch.get_system_config_values("file_storage.file_backup") file = self.initial_data.get('file') file_size = file.size validated_data['name'] = file.name @@ -44,14 +57,18 @@ class FileSerializer(CustomModelSerializer): validated_data['url'] = file if file_engine == 'oss': from dvadmin_cloud_storage.views.aliyun import ali_oss_upload - file_path = ali_oss_upload(file) + h = validated_data['md5sum'] + basename, ext = os.path.splitext(file.name) + file_path = ali_oss_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower())) if file_path: validated_data['file_url'] = file_path else: raise ValueError("上传失败") elif file_engine == 'cos': from dvadmin_cloud_storage.views.tencent import tencent_cos_upload - file_path = tencent_cos_upload(file) + h = validated_data['md5sum'] + basename, ext = os.path.splitext(file.name) + file_path = tencent_cos_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower())) if file_path: validated_data['file_url'] = file_path else: @@ -83,6 +100,12 @@ class FileViewSet(CustomModelViewSet): filter_fields = ['name', ] permission_classes = [] + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data, request=request) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + return DetailResponse(data=serializer.data, msg="新增成功") + @csrf_exempt @action(methods=["GET", "POST"], detail=False, permission_classes=[]) def ueditor(self, request): @@ -138,16 +161,17 @@ class FileViewSet(CustomModelViewSet): # 涂鸦功能上传处理 def save_scrawl_file(self, request, file_path, file_name): import base64 + instance = None try: content = request.data.get(ueditor_upload_settings.get("scrawlFieldName", "upfile")) f = open(os.path.join(BASE_DIR, file_path, file_name), 'wb') f.write(base64.b64decode(content)) f.close() state = "SUCCESS" - FileList.save_file(request, file_path, file_name,mime_type='image/png') + instance = FileList.save_file(request, file_path, file_name, mime_type='image/png') except Exception as e: state = f"写入图片文件错误:{e}" - return state + return state, instance def upload_file(self, request): """上传文件""" @@ -213,20 +237,21 @@ class FileViewSet(CustomModelViewSet): # 取得输出文件的路径 format_file_name, output_path = self.get_output_path(path_format_var) # 所有检测完成后写入文件 + file_instance = None if state == "SUCCESS": if action == "uploadscrawl": - state = self.save_scrawl_file(request, file_path=output_path, - file_name=format_file_name) + state, file_instance = self.save_scrawl_file(request, file_path=output_path, + file_name=format_file_name) else: file = request.FILES.get(upload_field_name, None) # 保存到文件中,如果保存错误,需要返回ERROR state = self.save_upload_file(file, os.path.join(BASE_DIR, output_path, format_file_name)) # 保存到附件管理中 - FileList.save_file(request, output_path, format_file_name, mime_type=file.content_type) + file_instance = FileList.save_file(request, output_path, format_file_name, mime_type=file.content_type) # 返回数据 return_info = { - 'url': os.path.join(output_path, format_file_name), # 保存后的文件名称 + 'url': file_instance.file_url if file_instance else os.path.join(output_path, format_file_name), # 保存后的文件名称 'original': upload_file_name, # 原始文件名 'type': upload_original_ext, 'state': state, # 上传状态,成功时返回SUCCESS,其他任何值将原样返回至图片上传框中 diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index 2f91674..ad1bc32 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -249,16 +249,16 @@ class UserViewSet(CustomModelViewSet): serializer_class = UserSerializer create_serializer_class = UserCreateSerializer update_serializer_class = UserUpdateSerializer - # filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"] - filter_fields = { - "name": ["icontains"], - "mobile": ["icontains"], - "username": ["icontains"], - "gender": ["icontains"], - "is_active": ["icontains"], - "dept": ["exact"], - "user_type": ["exact"], - } + filter_fields = ["^name", "~username", "^mobile", "is_active", "dept", "user_type", "$dept__name"] + # filter_fields = { + # "name": ["icontains"], + # "mobile": ["iregex"], + # "username": ["icontains"], + # "is_active": ["icontains"], + # "dept": ["exact"], + # "user_type": ["exact"], + # "dept__name": ["icontains"], + # } search_fields = ["username", "name", "gender", "dept__name", "role__name"] # 导出 export_field_label = { diff --git a/backend/dvadmin/utils/filters.py b/backend/dvadmin/utils/filters.py index 13beff2..ad3d056 100644 --- a/backend/dvadmin/utils/filters.py +++ b/backend/dvadmin/utils/filters.py @@ -150,6 +150,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): "$": "iregex", "~": "icontains", } + filter_fields = "__all__" def construct_search(self, field_name, lookup_expr=None): lookup = self.lookup_prefixes.get(field_name[0]) @@ -157,14 +158,16 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): field_name = field_name[1:] else: lookup = lookup_expr - if field_name.endswith(lookup): - return field_name - return LOOKUP_SEP.join([field_name, lookup]) + if lookup: + if field_name.endswith(lookup): + return field_name + return LOOKUP_SEP.join([field_name, lookup]) + return field_name def find_filter_lookups(self, orm_lookups, search_term_key): for lookup in orm_lookups: # if lookup.find(search_term_key) >= 0: - new_lookup = lookup.split("__")[0] + new_lookup = LOOKUP_SEP.join(lookup.split(LOOKUP_SEP)[:-1]) if len(lookup.split(LOOKUP_SEP)) > 1 else lookup # 修复条件搜索错误 bug if new_lookup == search_term_key: return lookup @@ -189,7 +192,13 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): utils.deprecate( "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__ ) - filterset_fields = getattr(view, "filter_fields", None) + self.filter_fields = getattr(view, "filter_fields", None) + if isinstance(self.filter_fields, (list, tuple)): + filterset_fields = [ + field[1:] if field[0] in self.lookup_prefixes.keys() else field for field in self.filter_fields + ] + else: + filterset_fields = self.filter_fields if filterset_class: filterset_model = filterset_class._meta.model @@ -327,18 +336,22 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): return queryset if filterset.__class__.__name__ == "AutoFilterSet": queryset = filterset.queryset - orm_lookups = [] - for search_field in filterset.filters: - if isinstance(filterset.filters[search_field], CharFilter): - orm_lookups.append( - self.construct_search(six.text_type(search_field), filterset.filters[search_field].lookup_expr) - ) - else: - orm_lookups.append(search_field) + filter_fields = filterset.filters if self.filter_fields == "__all__" else self.filter_fields + orm_lookup_dict = dict( + zip( + [field for field in filter_fields], + [filterset.filters[lookup].lookup_expr for lookup in filterset.filters.keys()], + ) + ) + orm_lookups = [ + self.construct_search(lookup, lookup_expr) for lookup, lookup_expr in orm_lookup_dict.items() + ] + # print(orm_lookups) conditions = [] queries = [] for search_term_key in filterset.data.keys(): orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key) + # print(search_term_key, orm_lookup) if not orm_lookup: continue query = Q(**{orm_lookup: filterset.data[search_term_key]}) diff --git a/backend/dvadmin/utils/models.py b/backend/dvadmin/utils/models.py index 9ba4f33..403ccf9 100644 --- a/backend/dvadmin/utils/models.py +++ b/backend/dvadmin/utils/models.py @@ -118,7 +118,6 @@ class SoftDeleteModel(models.Model): ] relations["self"] = f"{tree_model_field[0]}_id" if len(tree_model_field) == 1 else None relations["foreign"] = related_fields - print(f"{relations=}", flush=True) return relations def _is_cascade(self, relation): diff --git a/backend/requirements.txt b/backend/requirements.txt index d07e365..82b3acd 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,7 +3,7 @@ certifi==2021.5.30 chardet==4.0.0 coreapi==2.3.3 coreschema==0.0.4 -Django==3.2.12 +Django==3.2.19 django-comment-migrate==0.1.7 django-cors-headers==3.10.1 django-filter==22.1 @@ -11,8 +11,8 @@ django-ranged-response==0.2.0 django-redis==5.2.0 django-restql==0.15.3 django-simple-captcha==0.5.17 -django-tenants==3.4.8 -django-timezone-field==4.2.3 +django-tenants==3.5.0 +django-timezone-field==5.0 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 packaging==23.0 @@ -29,7 +29,7 @@ pyparsing==2.4.7 pyPEG2==2.15.2 pypinyin==0.48.0 pytz==2021.1 -requests==2.28.2 +requests==2.28.0 ruamel.yaml==0.17.10 ruamel.yaml.clib==0.2.4 six==1.16.0 diff --git a/web/.env.development b/web/.env.development index 2b35a9a..dc35c7f 100644 --- a/web/.env.development +++ b/web/.env.development @@ -6,4 +6,6 @@ VUE_APP_TITLE=企业级后台管理系统 VUE_APP_PM_ENABLED = true # 后端接口地址及端口(域名) VUE_APP_API = "http://127.0.0.1:8000" - +VUE_APP_VERSION = '2.0.4' +# 文件存储引擎 +VUE_APP_FILE_ENGINE = 'local' # oss、cos、local diff --git a/web/.env.preview b/web/.env.preview index 1855d20..854e006 100644 --- a/web/.env.preview +++ b/web/.env.preview @@ -13,3 +13,5 @@ VUE_APP_SCOURCE_LINK=FALSE VUE_APP_PUBLIC_PATH=/ # 启用权限管理 VUE_APP_PM_ENABLED = true +# 文件存储引擎 +VUE_APP_FILE_ENGINE = 'local' # oss、cos、local diff --git a/web/.env.production b/web/.env.production index 8b3fa56..d6ba836 100644 --- a/web/.env.production +++ b/web/.env.production @@ -13,3 +13,5 @@ VUE_APP_SCOURCE_LINK=FALSE VUE_APP_PUBLIC_PATH=/ # 启用权限管理 VUE_APP_PM_ENABLED = true +# 文件存储引擎 +VUE_APP_FILE_ENGINE = 'local' # oss、cos、local diff --git a/web/src/components/file-uploader/lib/file-uploader.vue b/web/src/components/file-uploader/lib/file-uploader.vue index 7579d83..250e8ed 100644 --- a/web/src/components/file-uploader/lib/file-uploader.vue +++ b/web/src/components/file-uploader/lib/file-uploader.vue @@ -56,6 +56,9 @@
+
+ +
diff --git a/web/src/components/foreign-key-crud-form/foreign-key-crud-form.vue b/web/src/components/foreign-key-crud-form/foreign-key-crud-form.vue index 98270da..b3b96b5 100644 --- a/web/src/components/foreign-key-crud-form/foreign-key-crud-form.vue +++ b/web/src/components/foreign-key-crud-form/foreign-key-crud-form.vue @@ -15,7 +15,7 @@
- {{index+1}} + {{ index + 1 }}
@@ -34,20 +34,23 @@ :value="item.value"> - -
- + +
+
- + @@ -56,9 +59,11 @@ - - - + + + @@ -102,6 +107,7 @@ - +