def client_func(genericAPIView, user, app, model, func_name, params): """云函数, 由客户端直接调用的服务函数""" func, options = find_func(app, model, func_name) if not func: raise exceptions.BusinessException( error_code=exceptions.FUNCTION_NOT_FOUNT, error_data=f"no such func: {func_name} found", ) if options.get("login_required", False): if not user.is_authenticated: raise PermissionDenied() view_context = {"view": genericAPIView} params["view_context"] = view_context result = func(user, **params) # TODO:考虑函数的返回结果类型。1. 实体,2.实体列表,3.字典,4.无返回,针对不同的结果给客户端反馈 if isinstance(result, requests.Response): return HttpResponse(result, result.headers.get("Content-Type", None)) if isinstance(result, (list, dict)): return success_response(result) if isinstance(result, genericAPIView.model): serializer = genericAPIView.get_serializer(result) return success_response(serializer.data) return success_response()
def _get_menu_from_autobuild(self): """根据模型自定义菜单""" export_apps = get_export_apps() if not export_apps: return success_response([]) try: result, id_index = [], 0 for app_name in export_apps: application = apps.get_app_config(app_name) for model_item in application.get_models(): id_index += 1 result.append( { "id": id_index, "name": model_item._meta.verbose_name, "icon": None, "parent": None, "page": "list", "permission": None, "model": f"{app_name}__{model_item._meta.model_name}", "sequence": 0, "menu": [], } ) return success_response(result) except Exception: return success_response([])
def manage_func(genericAPIView, user, app, model, func_name, params): """云函数, 由客户端直接调用的服务函数""" # import ipdb; ipdb.set_trace() func, options = find_func(app, model, func_name) if not func: raise exceptions.BusinessException( error_code=exceptions.FUNCTION_NOT_FOUNT, error_data=f"no such func: {func_name} found", ) if options.get("login_required", False): if not user.is_authenticated: raise PermissionDenied() if options.get("staff_required", False): if not user.is_staff: raise PermissionDenied() if options.get("superuser_required", False): if not user.is_superuser: raise PermissionDenied() view_context = {"view": genericAPIView} params["view_context"] = view_context result = func(user, **params) # TODO:考虑函数的返回结果类型。1. 实体,2.实体列表,3.字典,4.无返回,针对不同的结果给客户端反馈 if isinstance(result, requests.Response): response = HttpResponse(result, result.headers.get("Content-Type", None)) if "Content-disposition" in result.headers: response["Content-disposition"] = result.headers.get( "Content-disposition") return response if (isinstance(result, list) or isinstance(result, dict) or isinstance(result, str) or isinstance(result, bytes)): return success_response(result) return success_response()
def delete_by_conditon(genericAPIView): """按查询条件删除""" queryset = genericAPIView.filter_queryset(genericAPIView.get_queryset()) deleted, rows_count = queryset.delete() result = {"deleted": deleted} return success_response(result)
def update_sort(genericAPIView, request, data): if data.get("dragId") == data.get("hoverId"): return instance = genericAPIView.model admin = genericAPIView.get_bsm_model_admin() sort_key = admin.sort_key from django.db.models import F, Q with transaction.atomic(): dragItem = instance.objects.filter(id=data["dragId"]).first() hoverItem = instance.objects.filter(id=data["hoverId"]).first() dragIndex = getattr(dragItem, sort_key) hoveIndex = getattr(hoverItem, sort_key) isDownward = dragIndex < hoveIndex or (dragIndex == hoveIndex and dragItem.id < hoverItem.id) instance.objects.filter(id=data["dragId"]).update( **{ "parent": hoverItem.parent, sort_key: hoveIndex + 1 }) instance.objects.filter( Q(parent=hoverItem.parent), ~Q(id=dragItem.id), Q(**{f"{sort_key}__gt": hoveIndex}), ).update(**{f"{sort_key}": F(f"{sort_key}") + 2}) up = Q(id__gt=hoverItem.id) if isDownward else Q(id__gte=hoverItem.id) instance.objects.filter( Q(parent=hoverItem.parent), ~Q(id=dragItem.id), Q(**{f"{sort_key}": hoveIndex}), up, ).update(**{f"{sort_key}": F(f"{sort_key}") + 2}) return success_response(instance.objects.all().values())
def client_update(genericAPIView, request, partial, set_data): """全量更新数据""" with transaction.atomic(): client_user_pip.add_login_user_data(genericAPIView, set_data) forward_relation_hand(genericAPIView.model, set_data) # partial = kwargs.pop('partial', False) instance = genericAPIView.get_object() old_instance = copy(instance) serializer = genericAPIView.get_validate_form(genericAPIView.action)( instance, data=set_data, partial=partial) serializer.is_valid(raise_exception=True) instance = genericAPIView.perform_update(serializer) reverse_relation_hand(genericAPIView.model, set_data, instance) instance = genericAPIView.get_queryset().get(pk=instance.pk) # with transaction.atomic(): log.debug( "sending Post Update signal with: model: %s, instance: %s", genericAPIView.model, instance, ) post_bsm_create.send( sender=genericAPIView.model, instance=instance, create=False, request=genericAPIView.request, old_instance=old_instance, ) serializer = genericAPIView.get_serializer( genericAPIView.get_queryset().get(pk=instance.pk)) return success_response(serializer.data)
def token(self, request, *args, **kwargs): """ ## 生成对应的上传签名 **参数放在 query string 中,形式如 ?service=aliyun** 当前的服务只支持 (aliyun, 阿里云) ### Returns #### 阿里云的返回数据结构如下: ``` { "error_code": "0", "error_message": "", "result": { "accessid": "LTAIudMj4IZMpCCn", "host": "speedapi.oss-cn-shanghai.aliyuncs.com", "policy": "eyJleHBpcmF0aW9uIjogIjIwMTgV5IiwgIm1lZGlhLyJdXX0=", "signature": "9aOOMFzwwVQl0u/sFgdLKRSyeIw=", "expire": 1539770693, "dir": "media/" } } ``` #### 腾讯云 COS 返回的数据结构如下 { 'startTime': 1592561936 'expiredTime': 1592561966, 'expiration': '2020-06-19T10:19:26Z', 'requestId': '4332ced3-50a7-48fb-a35a-cb9efcec95d9', 'bucket': 'test-20188932', 'region': 'ap-guangzhou', 'credentials': { 'sessionToken': 'kg1Mg_UmDtAJ3wQA', 'tmpSecretId': 'AKIDy_RmF9qEg1geYsrJ_UwR4WWYcDGM2iy71R', 'tmpSecretKey': 'Q5FPMVsD=' }, } """ service = request.query_params.get("service", site_setting["upload_provider"]) result = {"provider": None} if service in ["aliyun", "oss"]: result = aliyun.get_token() result["provider"] = "oss" elif service in ["tencent", "cos"]: result = tencent.post_object_token() result["provider"] = "cos" elif service == "file_storage": result = { "provider": "file_storage", "policy": "", "dir": "", "host": "/basebone/storage/upload", } return success_response(result)
def _get_menu_from_custom(self): """从自定义的菜单配置中获取菜单""" menu_module = module.get_bsm_global_module(module.BSM_GLOBAL_MODULE_MENU) result = getattr(menu_module, module.BSM_GLOBAL_MODULE_MENU_MANAGE, None) by_role = getattr(settings, "BSM_MANAGE_MENU_BY_ROLE", False) if not by_role: return success_response(result["default"]) else: groups = {item.name for item in self.request.user.groups.all()} if not groups: return success_response([]) for item in groups: if item in result: return success_response(result[item]) return success_response([])
def manage_create(genericAPIView, request, set_data): """ 这里校验表单和序列化类分开创建 原因:序列化类有可能嵌套 """ many = isinstance(set_data, list) with transaction.atomic(): forward_relation_hand(genericAPIView.model, set_data) serializer = genericAPIView.get_validate_form(genericAPIView.action)( data=set_data, context=genericAPIView.get_serializer_context(), many=many) serializer.is_valid(raise_exception=True) instance = genericAPIView.perform_create(serializer) if many: # 如果是批量插入,则直接返回 return success_response() # 如果有联合查询,单个对象创建后并没有联合查询 instance = genericAPIView.get_queryset().filter(pk=instance.pk).first() serializer = genericAPIView.get_serializer(instance) reverse_relation_hand(genericAPIView.model, set_data, instance, detail=False) log.debug( "sending Post Save signal with: model: %s, instance: %s", genericAPIView.model, instance, ) post_bsm_create.send( sender=genericAPIView.model, instance=instance, create=True, request=genericAPIView.request, old_instance=None, scope="admin", ) return success_response(serializer.data)
def get_userinfo(self, request, *args, **kwargs): """ ## 检测是否是否登录 如果用户已登录,则直接返回此登录用户的数据结构 """ serializer = self.get_serializer(request.user) result = {} result.update(serializer.data) result["permissions"] = request.user.get_all_permissions() return success_response(result)
def get_all(self, request, *args, **kargs): """获取所有的客户端配置,包括schema, admin""" self._load_bsm_admin_module() data = {"schemas": get_app_field_schema(), "admins": get_app_admin_config()} json_object_schemas, json_array_item_schemas = get_app_json_field_schema() json_admin_configs = get_json_field_admin_config( json_object_schemas, json_array_item_schemas ) data["schemas"].update(json_object_schemas) data["schemas"].update(json_array_item_schemas) data["admins"].update(json_admin_configs) return success_response(data)
def destroy(genericAPIView, request, scope=""): """删除数据""" instance = genericAPIView.get_object() old_instance = copy(instance) genericAPIView.perform_destroy(instance) post_bsm_delete.send( sender=genericAPIView.model, instance=old_instance, request=genericAPIView.request, scope=scope, ) return success_response()
def get_chart(self, request, *args, **kwargs): log.debug(f"get_chart action: {self.action}") from chart.models import Chart from django.core.cache import cache id = request.data["id"] chart = cache.get(f"chart_config:{id}", None) log.debug(f"chart get from cache: {chart}") if chart is None: chart = Chart.objects.prefetch_related( "metrics", "dimensions", "chart_filters" ).get(id=id) cache.set(f"chart_config:{id}", chart, 600) log.debug("cached Chart") group = {} fields = {} for dimension in chart.dimensions.all(): field = { "field": dimension.field, "displayName": dimension.display_name, "expression": dimension.expression, } if dimension.method: field["method"] = dimension.method if dimension.name == "groupby": group[dimension.name] = field if dimension.name == "legend": group[dimension.name] = field for metric in chart.metrics.all(): field = { "field": metric.field, "method": metric.method, "expression": metric.expression, "displayName": metric.display_name, "format": metric.format, } fields[metric.name] = field group_kwargs = self.get_group_data(group) filters = [ {"field": ft.field, "operator": ft.operator, "value": ft.value} for ft in chart.chart_filters.all() ] data = self.group_statistics_data( fields, group_kwargs, sort_keys=chart.sort_keys, top_max=chart.top_max, filters=filters, ) return success_response(data)
def batch(self, request, app, model, **kwargs): """ ## 批量操作 ```python {action: 动作, data: 主键的列表} ``` """ serializer = batch_actions.BatchActionForm( data=request.data, context=self.get_serializer_context() ) serializer.is_valid(raise_exception=True) serializer.handle() return success_response()
def display(genericAPIView, display_fields): """查询操作,取名display,避免跟列表list冲突""" queryset = genericAPIView.filter_queryset(genericAPIView.get_queryset()) page = genericAPIView.paginate_queryset(queryset) if page is not None: """分页查询""" serializer = genericAPIView.get_serializer(page, many=True) result = filter_display_fields(serializer.data, display_fields) response = genericAPIView.get_paginated_response(result) result = response.data else: serializer = genericAPIView.get_serializer(queryset, many=True) result = filter_display_fields(serializer.data, display_fields) return success_response(result)
def login(self, request, *args, **kwargs): """ ## 用户登录 ``` Params: username string 用户名 password string 用户密码 Returns: object 用户数据结构 ``` """ serializer = forms.LoginForm( data=request.data, context=self.get_serializer_context() ) serializer.is_valid(raise_exception=True) instance = serializer.save() serializer = self.get_serializer(instance) return success_response(serializer.data)
def manage_update(genericAPIView, request, partial, set_data): """全量更新数据""" print("进入全量更新了吗?") with transaction.atomic(): forward_relation_hand(genericAPIView.model, set_data) # partial = kwargs.pop('partial', False) instance = genericAPIView.get_object() old_instance = copy(instance) serializer = genericAPIView.get_validate_form(genericAPIView.action)( instance, data=set_data, partial=partial, context=genericAPIView.get_serializer_context(), ) serializer.is_valid(raise_exception=True) instance = genericAPIView.perform_update(serializer) serializer = genericAPIView.get_serializer(instance) if getattr(instance, "_prefetched_objects_cache", None): instance._prefetched_objects_cache = {} reverse_relation_hand(genericAPIView.model, set_data, instance) with transaction.atomic(): log.debug( "sending Post Update signal with: model: %s, instance: %s", genericAPIView.model, instance, ) post_bsm_create.send( sender=genericAPIView.model, instance=instance, create=False, old_instance=old_instance, request=genericAPIView.request, scope="admin", ) return success_response(serializer.data)
def upload(request): key, policy, file = ( request.data["key"], request.data["policy"], request.data["file"], ) storage_path = site_setting["storage_path"] if not storage_path: raise BusinessException("storage support not enabled") file_path = Path(storage_path).joinpath(key) if not is_relative_to(file_path, storage_path): raise BusinessException("invalid file key: %s" % key) dirname = file_path.parent if not dirname.exists(): dirname.mkdir(parents=True) elif not dirname.is_dir(): raise BusinessException("dir exists: %s" % os.path.dirname(key)) elif file_path.exists(): raise BusinessException("file already exists: %s" % key) with file_path.open("wb+") as f: for chunk in file.chunks(): f.write(chunk) return success_response()
def _get_menu_from_database(self): """从数据库中获取菜单""" user = self.request.user # permissions = self.request.user.get_all_permissions() # permission_filter = (Q(permission=None) | Q(permission='') | Q(permission__in=permissions)) menus = ( Menu.objects.prefetch_related("parent").order_by("sequence", "id") if user.is_superuser else Menu.objects.filter( Q(groups__in=self.request.user.groups.all()) | Q(groups__isnull=True) ) .prefetch_related("parent") .order_by("sequence", "id") ) fields = {field.name for field in Menu._meta.fields} - { "id", "parent", "permission", "name", } menus_map = { menu.id: dict( {field: getattr(menu, field) for field in fields}, **{ "name": menu.display_name, "parent_id": menu.parent_id, "children": [], }, ) for menu in menus } for _, menu in menus_map.items(): parent_id = menu["parent_id"] if parent_id and parent_id in menus_map: menus_map[parent_id]["children"].append(menu) menus_data = [m for _, m in menus_map.items() if not m.get("parent_id")] return success_response(menus_data)
def client_create(genericAPIView, request, set_data): """ 这里校验表单和序列化类分开创建 原因:序列化类有可能嵌套 """ with transaction.atomic(): client_user_pip.add_login_user_data(genericAPIView, set_data) forward_relation_hand(genericAPIView.model, set_data) serializer = genericAPIView.get_validate_form( genericAPIView.action)(data=set_data) serializer.is_valid(raise_exception=True) instance = genericAPIView.perform_create(serializer) reverse_relation_hand(genericAPIView.model, set_data, instance, detail=False) instance = genericAPIView.get_queryset().get(pk=instance.pk) # with transaction.atomic(): log.debug( "sending Post Save signal with: model: %s, instance: %s", genericAPIView.model, instance, ) post_bsm_create.send( sender=genericAPIView.model, instance=instance, create=True, request=genericAPIView.request, old_instance=None, ) # 如果有联合查询,单个对象创建后并没有联合查询, 所以要多查一次? serializer = genericAPIView.get_serializer( genericAPIView.get_queryset().get(pk=instance.pk)) return success_response(serializer.data)
def get_manage_menu(self, request, *args, **kwargs): """获取管理端的菜单配置""" if hasattr(settings, "ADMIN_MENUS"): group_names = self.request.user.groups.values_list("name", flat=True) group_names = set(group_names) def map_menus(menus): return [ {**m, "children": map_menus(m.get("children", []))} for m in menus if "groups" not in m or self.request.user.is_superuser or set(m["groups"]) & group_names ] return success_response(map_menus(settings.ADMIN_MENUS)) menutype = request.query_params.get("menutype", "database") if menutype == "database": return self._get_menu_from_database() if menutype == "custom": return self._get_menu_from_custom() if menutype == "autobuild": return self._get_menu_from_autobuild()
def permissions(self, request, *args, **kwargs): """获取当前用户的权限""" return success_response(request.user.get_all_permissions())
def logout(self, request, *args, **kwargs): """退出登录""" logout(request) return success_response()
def move(request, block_id): parent = request.data.get("parent", None) index = request.data["index"] services.move(block_id, parent, index) return success_response()
def get_setting_config(self, request, *args, **kargs): settings = get_setting_config() return success_response(settings)
def group_statistics(self, request, *args, **kwargs): fields = request.data.get("fields") group_kwargs = self.get_group() data = self.group_statistics_data(fields, group_kwargs) return success_response(data)
def statistics(self, request, *args, **kwargs): """计算统计数据 请求的数据结构如下: { key: { method: 'count' field: 'xxxx', verbose_name: '可读名称', }, ... } """ log.debug(f"statistics action: {self.action}") configs = request.data.get("fields", None) if not configs: configs = self.basebone_get_statistics_config() if not configs: return success_response({}) queryset = self.get_queryset() method_map = {"sum": Sum, "count": Count} aggregates, relation_aggregates = {}, {} relation_fields = [item.name for item in get_all_relation_fields(self.model)] for key, value in configs.items(): if not isinstance(value, dict): continue method = value.get("method") if method not in method_map: continue field = value.get("field") if value.get("field") else key aggregate_param = method_map[value["method"]](field) if method == "count": aggregate_param = method_map[value["method"]](field, distinct=True) condition = Coalesce(aggregate_param, Value(0)) split_field = field.split("__")[0] if split_field in relation_fields: relation_aggregates[key] = condition else: aggregates[key] = condition if not aggregates and not relation_aggregates: return success_response({}) result = queryset.aggregate(**aggregates) self.basebone_origin_queryset.query.annotations.clear() relation_result = self.basebone_origin_queryset.aggregate(**relation_aggregates) result.update(relation_result) return success_response(result)
def update_by_conditon(genericAPIView, set_fields): queryset = genericAPIView.filter_queryset(genericAPIView.get_queryset()) count = queryset.update(**set_fields) result = {"count": count} return success_response(result)
def block_view(request, block_id): block = Block.objects.get(id=block_id) data = None if block.component in component_resolver_map: data = component_resolver_map[block.component](block) return success_response(data)
def retrieve(genericAPIView, display_fields): """获取数据详情""" instance = genericAPIView.get_object() serializer = genericAPIView.get_serializer(instance) result = filter_display_fields(serializer.data, display_fields) return success_response(result)