diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml
index 6fa3c7e08..296d43abb 100644
--- a/.github/workflows/django.yml
+++ b/.github/workflows/django.yml
@@ -5,7 +5,7 @@ jobs:
runs-on: ubuntu-20.04
env:
OS: ubuntu-20.04
- PYTHON: "3.6"
+ PYTHON: "3.11.10"
RUN_ENV: "open"
APP_CODE: "bk_itsm"
APP_ID: "bk_itsm"
@@ -51,7 +51,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@master
with:
- python-version: 3.6
+ python-version: 3.11.10
- name: Setup Mysql
run: |
sudo systemctl start mysql.service
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3689787cf..b70e7a06f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,20 +1,22 @@
-default_stages: [commit]
+default_stages: [pre-commit]
+default_language_version:
+ python: python3.11.10
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v2.1.0
+ rev: v5.0.0
hooks:
- id: check-merge-conflict
- repo: https://github.com/psf/black
- rev: 22.3.0
+ rev: 24.10.0
hooks:
- id: black
- language_version: python3.6
+ language_version: python3.11.10
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.1.0
hooks:
- id: flake8
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
- rev: v2.2.0
+ rev: v9.19.0
hooks:
- id: commitlint
stages: [commit-msg]
diff --git a/VERSION b/VERSION
index 2c9b4ef42..dbe590065 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.7.3
+2.8.1
diff --git a/app.yml b/app.yml
index 61b27d249..f4a50d777 100644
--- a/app.yml
+++ b/app.yml
@@ -5,7 +5,7 @@ author: 蓝鲸智云
category: 办公应用
introduction: 流程服务是蓝鲸推出的轻量级ITSM,通过可自定义设计的流程模块,覆盖IT服务中的不同管理活动或应用场景。帮助企业用户规范内部管理流程,提升沟通及管理效率。
introduction_en: bk_itsm is a lightweight ITSM created by Blueking. It covers different application scenarios in IT services through customizable workflows and help enterprise users to implement standardize IT workflow, improve communication and management efficiency.
-version: 2.7.3
+version: 2.8.1
language: python
is_use_celery: True
is_use_celery_with_gevent: False
diff --git a/app_desc.yaml b/app_desc.yaml
index 75f70afb5..7f3625c43 100644
--- a/app_desc.yaml
+++ b/app_desc.yaml
@@ -1,5 +1,5 @@
spec_version: 2
-app_version: "2.7.3"
+app_version: "2.8.1"
app:
region: default
bk_app_code: bk_itsm
@@ -35,6 +35,9 @@ modules:
- key: GUNICORN_THREAD_NUM
value: 10
description: GunicornThread数量
+ - key: C_FORCE_ROOT
+ value: 1
+ description: celery5 fit
scripts:
pre_release_hook: "bash ./bin/pre-release"
processes:
@@ -47,11 +50,11 @@ modules:
plan: 4C1G5R
replicas: 1
pworker:
- command: python manage.py celery worker -n prefork@%h -P threads -c 10 -l info --maxtasksperchild=100
+ command: python manage.py celery worker -n prefork@%h -P threads -c 10 -l info --max-tasks-per-child=100
plan: 4C2G5R
replicas: 5
gworker:
- command: python manage.py celery worker -P gevent -n gevent@%h -c 4 -l info --maxtasksperchild=100
+ command: python manage.py celery worker -P gevent -n gevent@%h -c 4 -l info --max-tasks-per-child=100
plan: 4C2G5R
replicas: 5
svc_discovery:
diff --git a/blueking/component/open/utils.py b/blueking/component/open/utils.py
index 28d438293..503d0a57d 100644
--- a/blueking/component/open/utils.py
+++ b/blueking/component/open/utils.py
@@ -30,22 +30,18 @@
def get_signature(method, path, app_secret, params=None, data=None):
- """generate signature
- """
+ """generate signature"""
kwargs = {}
if params:
kwargs.update(params)
if data:
data = json.dumps(data) if isinstance(data, dict) else data
- kwargs['data'] = data
- kwargs = '&'.join([
- '%s=%s' % (k, v)
- for k, v in sorted(iter(kwargs.items()), key=lambda x: x[0])
- ])
- orignal = '%s%s?%s' % (method, path, kwargs)
+ kwargs["data"] = data
+ kwargs = "&".join(
+ ["%s=%s" % (k, v) for k, v in sorted(iter(kwargs.items()), key=lambda x: x[0])]
+ )
+ orignal = "%s%s?%s" % (method, path, kwargs)
signature = base64.b64encode(
- hmac.new(
- str(app_secret),
- orignal,
- hashlib.sha1).digest())
+ hmac.new(str(app_secret), orignal, hashlib.sha256).digest()
+ )
return signature
diff --git a/config/default.py b/config/default.py
index 082dbbc00..cc128a395 100644
--- a/config/default.py
+++ b/config/default.py
@@ -33,6 +33,9 @@
from blueapps.opentelemetry.utils import inject_logging_trace_info
from django.http import HttpResponseRedirect
from django.urls import reverse
+from django.db.backends.mysql.features import DatabaseFeatures
+from django.utils.functional import cached_property
+
from config import (
APP_CODE,
@@ -376,7 +379,9 @@ def _(s):
if IS_USE_REDIS:
CACHE_BACKEND_TYPE = os.environ.get("CACHE_BACKEND_TYPE", "RedisCache")
REDIS_PORT = os.environ.get("BKAPP_REDIS_PORT", 6379)
- REDIS_PASSWORD = os.environ.get("BKAPP_REDIS_PASSWORD", "") # 密码中不能包括敏感字符,例如":"
+ REDIS_PASSWORD = os.environ.get(
+ "BKAPP_REDIS_PASSWORD", ""
+ ) # 密码中不能包括敏感字符,例如":"
REDIS_SERVICE_NAME = os.environ.get("BKAPP_REDIS_SERVICE_NAME", "mymaster")
REDIS_MODE = os.environ.get("BKAPP_REDIS_MODE", "single")
REDIS_DB = os.environ.get("BKAPP_REDIS_DB", 0)
@@ -983,3 +988,16 @@ def redirect_func(request):
"BKAPP_QW_WEB_HOOK_URL",
"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={}",
)
+
+
+class PatchFeatures:
+ @cached_property
+ def minimum_database_version(self):
+ if self.connection.mysql_is_mariadb:
+ return (10, 4)
+ else:
+ return (5, 7)
+
+
+# 将补丁应用到 DatabaseFeatures 中
+DatabaseFeatures.minimum_database_version = PatchFeatures.minimum_database_version
diff --git a/django_signal_valve/models.py b/django_signal_valve/models.py
index e679ab9f7..0e0c7e417 100644
--- a/django_signal_valve/models.py
+++ b/django_signal_valve/models.py
@@ -26,7 +26,7 @@
import zlib
from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
try:
import pickle as pickle
@@ -34,7 +34,6 @@
import pickle
-
class IOField(models.BinaryField):
def __init__(self, compress_level=6, *args, **kwargs):
super(IOField, self).__init__(*args, **kwargs)
diff --git a/iam/contrib/django/response.py b/iam/contrib/django/response.py
index 2a870d9cf..bf4648c74 100644
--- a/iam/contrib/django/response.py
+++ b/iam/contrib/django/response.py
@@ -12,7 +12,7 @@
"""
from django.http.response import JsonResponse
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from iam.contrib.http import HTTP_AUTH_FORBIDDEN_CODE
@@ -26,5 +26,5 @@ def __init__(self, exc, *args, **kwargs):
"data": None,
"permission": exc.perms_apply_data(),
}
- kwargs['status'] = kwargs.get("status", 499)
+ kwargs["status"] = kwargs.get("status", 499)
super(IAMAuthFailedResponse, self).__init__(*args, **kwargs)
diff --git a/itsm/api/v1.py b/itsm/api/v1.py
index 0a86b0a92..986e73b55 100644
--- a/itsm/api/v1.py
+++ b/itsm/api/v1.py
@@ -24,7 +24,7 @@
"""
-from django.conf.urls import include, url
+from django.urls import include, re_path
__author__ = "蓝鲸智云"
@@ -38,35 +38,35 @@
urlpatterns = [
# 流程管理模块
- url(r"^workflow/", include("itsm.workflow.urls")),
+ re_path(r"^workflow/", include("itsm.workflow.urls")),
# 单据模块
- url(r"^ticket/", include("itsm.ticket.urls")),
+ re_path(r"^ticket/", include("itsm.ticket.urls")),
# 任务模块
- url(r"^task/", include("itsm.task.urls")),
+ re_path(r"^task/", include("itsm.task.urls")),
# 服务模块
- url(r"^service/", include("itsm.service.urls")),
+ re_path(r"^service/", include("itsm.service.urls")),
# sla模块
- url(r"^sla/", include("itsm.sla.urls")),
+ re_path(r"^sla/", include("itsm.sla.urls")),
# postman
- url(r"^postman/", include("itsm.postman.urls")),
+ re_path(r"^postman/", include("itsm.postman.urls")),
# 角色模块
- url(r"^role/", include("itsm.role.urls")),
+ re_path(r"^role/", include("itsm.role.urls")),
# iadmin
- url(r"^iadmin/", include("itsm.iadmin.urls")),
+ re_path(r"^iadmin/", include("itsm.iadmin.urls")),
# 网关转发模块,目前主要用于转发esb侧的接口调用
- url(r"^gateway/", include("itsm.gateway.urls")),
+ re_path(r"^gateway/", include("itsm.gateway.urls")),
# "杂种"模块,没有model,且不知道放哪里合适,就放到这个模块吧!
- url(r"^misc/", include("itsm.misc.urls")),
+ re_path(r"^misc/", include("itsm.misc.urls")),
# 单据状态模块
- url(r"^ticket_status/", include("itsm.ticket_status.urls")),
+ re_path(r"^ticket_status/", include("itsm.ticket_status.urls")),
# Trigger Module
- url(r"^trigger/", include("itsm.trigger.urls")),
+ re_path(r"^trigger/", include("itsm.trigger.urls")),
# iam
- url(r"^iam/", include("itsm.auth_iam.urls")),
+ re_path(r"^iam/", include("itsm.auth_iam.urls")),
# iam
- url(r"^project/", include("itsm.project.urls")),
+ re_path(r"^project/", include("itsm.project.urls")),
# 人员选择器
- url(r"^c/compapi/v2/usermanage/fs_list_users/$", get_batch_users),
+ re_path(r"^c/compapi/v2/usermanage/fs_list_users/$", get_batch_users),
# 蓝鲸插件服务
- url(r"^plugin_service/", include("itsm.plugin_service.urls")),
+ re_path(r"^plugin_service/", include("itsm.plugin_service.urls")),
]
diff --git a/itsm/auth_iam/models.py b/itsm/auth_iam/models.py
index 6b2496de4..1713cbea0 100644
--- a/itsm/auth_iam/models.py
+++ b/itsm/auth_iam/models.py
@@ -24,7 +24,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import LEN_NORMAL
diff --git a/itsm/auth_iam/urls.py b/itsm/auth_iam/urls.py
index f3d10cbf2..caa93ce5c 100644
--- a/itsm/auth_iam/urls.py
+++ b/itsm/auth_iam/urls.py
@@ -24,7 +24,7 @@
"""
from django.conf import settings
-from django.conf.urls import url
+from django.urls import re_path
from rest_framework.routers import DefaultRouter
from iam import IAM
@@ -46,7 +46,12 @@
from itsm.auth_iam.views import ResourceViewSet, PermissionViewSet
from itsm.auth_iam.resources import ProjectResourceProvider
-iam = IAM(settings.APP_CODE, settings.SECRET_KEY, settings.BK_IAM_INNER_HOST, settings.BK_PAAS_HOST)
+iam = IAM(
+ settings.APP_CODE,
+ settings.SECRET_KEY,
+ settings.BK_IAM_INNER_HOST,
+ settings.BK_PAAS_HOST,
+)
routers = DefaultRouter(trailing_slash=True)
@@ -70,5 +75,5 @@
dispatcher.register("task_template", TaskSchemaResourceProvider())
dispatcher.register("public_api", PublicApiResourceProvider())
urlpatterns = routers.urls + [
- url(r'^resources/v1/$', dispatcher.as_view([login_exempt])),
+ re_path(r"^resources/v1/$", dispatcher.as_view([login_exempt])),
]
diff --git a/itsm/component/auto_register/strategy.py b/itsm/component/auto_register/strategy.py
index 152d38688..82af1fdbb 100644
--- a/itsm/component/auto_register/strategy.py
+++ b/itsm/component/auto_register/strategy.py
@@ -26,22 +26,34 @@
from distutils.version import StrictVersion
import django
-from django.db.models import *
+from django.db.models import * # noqa: F403
from django.conf import settings
def is_boolean(field):
- return isinstance(field, (BooleanField, NullBooleanField))
+ return isinstance(field, (BooleanField))
def is_string(field):
- return isinstance(field, (CharField, EmailField, IPAddressField, SlugField, URLField))
+ return isinstance(
+ field, (CharField, EmailField, IPAddressField, SlugField, URLField)
+ )
def is_number(field):
- return isinstance(field, (IntegerField, SmallIntegerField, PositiveIntegerField,
- PositiveSmallIntegerField, BigIntegerField,
- CommaSeparatedIntegerField, DecimalField, FloatField))
+ return isinstance(
+ field,
+ (
+ IntegerField,
+ SmallIntegerField,
+ PositiveIntegerField,
+ PositiveSmallIntegerField,
+ BigIntegerField,
+ CommaSeparatedIntegerField,
+ DecimalField,
+ FloatField,
+ ),
+ )
def is_datetime(field):
@@ -53,7 +65,7 @@ def is_file(field):
def is_binary(self, field):
- if self.django_greater_than('1.6'):
+ if self.django_greater_than("1.6"):
return isinstance(field, (BinaryField))
else:
return False
@@ -69,6 +81,7 @@ class BaseStrategy:
"""
基础策略, 封装了策略所需要的字段判定方法
"""
+
type = None
def get_value(self, field_list: list) -> list:
@@ -101,24 +114,36 @@ class ListDisplayStrategy(BaseStrategy):
type = "list_display"
def is_matched(self, field: Field):
- return is_string(field) or is_boolean(field) or \
- is_number(field) or is_datetime(field)
+ return (
+ is_string(field)
+ or is_boolean(field)
+ or is_number(field)
+ or is_datetime(field)
+ )
class ListDisplayLinksStrategy(BaseStrategy):
type = "list_display_links"
def is_matched(self, field: Field):
- return is_string(field) or is_boolean(field) \
- or is_number(field) or is_datetime(field)
+ return (
+ is_string(field)
+ or is_boolean(field)
+ or is_number(field)
+ or is_datetime(field)
+ )
class ListFilterStrategy(BaseStrategy):
type = "list_filter"
def is_matched(self, field: Field):
- return is_string(field) or is_boolean(field) \
- or is_number(field) or is_datetime(field)
+ return (
+ is_string(field)
+ or is_boolean(field)
+ or is_number(field)
+ or is_datetime(field)
+ )
class SearchFieldsStrategy(BaseStrategy):
@@ -132,21 +157,29 @@ class ListPerPageStrategy(BaseStrategy):
type = "list_per_page"
def get_value(self, field_list):
- return int(settings.DSA_LIST_PER_PAGE) if hasattr(settings, 'DSA_LIST_PER_PAGE') else 5
+ return (
+ int(settings.DSA_LIST_PER_PAGE)
+ if hasattr(settings, "DSA_LIST_PER_PAGE")
+ else 5
+ )
class ListMaxShowAllStrategy(BaseStrategy):
type = "list_max_show_all"
def get_value(self, field_list):
- return int(settings.DSA_LIST_MAX_SHOW_ALL) if hasattr(settings,
- 'DSA_LIST_MAX_SHOW_ALL') else 50
+ return (
+ int(settings.DSA_LIST_MAX_SHOW_ALL)
+ if hasattr(settings, "DSA_LIST_MAX_SHOW_ALL")
+ else 50
+ )
class StrategyDispatcher(object):
"""
StrategyDispatcher 负责将不同的方法分派到不同的类中去处理
"""
+
STRATEGY_CLASS = [
ListRawIdFieldsStrategy,
ListDisplayStrategy,
@@ -155,15 +188,16 @@ class StrategyDispatcher(object):
SearchFieldsStrategy,
ListPerPageStrategy,
ListMaxShowAllStrategy,
- FilterHorizontalStrategy
+ FilterHorizontalStrategy,
]
- STRATEGY_DICT = dict(
- [(_object.type, _object()) for _object in STRATEGY_CLASS])
+ STRATEGY_DICT = dict([(_object.type, _object()) for _object in STRATEGY_CLASS])
def __init__(self, strategy_type):
if strategy_type not in self.STRATEGY_DICT:
- raise Exception("The strategy corresponding to Strategy_type does not exist")
+ raise Exception(
+ "The strategy corresponding to Strategy_type does not exist"
+ )
self.strategy_type = strategy_type
diff --git a/itsm/component/constants/basic.py b/itsm/component/constants/basic.py
index ed83c8e00..06a429c10 100644
--- a/itsm/component/constants/basic.py
+++ b/itsm/component/constants/basic.py
@@ -27,7 +27,7 @@
from calendar import FRIDAY, MONDAY, SATURDAY, SUNDAY, THURSDAY, TUESDAY, WEDNESDAY
from django.conf import settings
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.utils.basic import choices_to_namedtuple, tuple_choices
@@ -240,7 +240,12 @@ def update(self, **kwargs):
"source": "ticket",
"type": "STRING",
},
- {"key": "ticket_creator", "name": _("提单人"), "source": "ticket", "type": "MEMBER"},
+ {
+ "key": "ticket_creator",
+ "name": _("提单人"),
+ "source": "ticket",
+ "type": "MEMBER",
+ },
{
"key": "ticket_create_at",
"name": _("提单时间"),
@@ -302,8 +307,18 @@ def update(self, **kwargs):
"source": "task",
"type": "STRING",
},
- {"key": "task_creator", "name": _("任务创建人"), "source": "task", "type": "MEMBER"},
- {"key": "task_operator", "name": _("任务处理人"), "source": "task", "type": "MEMBER"},
+ {
+ "key": "task_creator",
+ "name": _("任务创建人"),
+ "source": "task",
+ "type": "MEMBER",
+ },
+ {
+ "key": "task_operator",
+ "name": _("任务处理人"),
+ "source": "task",
+ "type": "MEMBER",
+ },
{
"key": "task_create_at",
"name": _("任务创建时间"),
diff --git a/itsm/component/constants/task.py b/itsm/component/constants/task.py
index fcb5f4dad..61dea616b 100644
--- a/itsm/component/constants/task.py
+++ b/itsm/component/constants/task.py
@@ -24,7 +24,7 @@
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.enum import enum
NEW = "NEW"
@@ -36,34 +36,40 @@
FAILED = "FAILED"
FINISHED = "FINISHED"
RUNNING = "RUNNING"
-REVOKED = 'REVOKED'
-SUSPENDED = 'SUSPENDED'
+REVOKED = "REVOKED"
+SUSPENDED = "SUSPENDED"
DELETED = "DELETED"
SUCCEED = "SUCCEED"
CANCELED = "CANCELED"
TERMINATE = "TERMINATE"
-ACTIVE_TASK_STATUS = [QUEUE, WAITING_FOR_OPERATE, WAITING_FOR_CONFIRM, WAITING_FOR_BACKEND, RUNNING]
+ACTIVE_TASK_STATUS = [
+ QUEUE,
+ WAITING_FOR_OPERATE,
+ WAITING_FOR_CONFIRM,
+ WAITING_FOR_BACKEND,
+ RUNNING,
+]
NEED_UPDATE_TASK_STATUS = [REVOKED, FAILED, SUSPENDED, DELETED, RUNNING]
END_TASK_STATUS = [REVOKED, FINISHED, DELETED]
NEED_SYNC_STATUS = [NEW, QUEUE, FAILED, SUSPENDED, RUNNING, WAITING_FOR_OPERATE]
-NOT_CREATED = 'NOT_CREATED'
-CREATE_FAILED = 'CREATE_FAILED'
-CREATED = 'CREATED'
-START_FAILED = 'START_FAILED'
+NOT_CREATED = "NOT_CREATED"
+CREATE_FAILED = "CREATE_FAILED"
+CREATED = "CREATED"
+START_FAILED = "START_FAILED"
SOPS_TASK_STATE_CHOICE = [
- (NOT_CREATED, '未创建'),
- (CREATE_FAILED, '创建失败'),
- (CREATED, '创建成功'),
- (START_FAILED, '启动失败'),
- (RUNNING, '执行中'),
- (FAILED, '执行失败'),
- (FINISHED, '执行结束'),
- (REVOKED, '被撤销'),
- (SUSPENDED, '被挂起'),
- (DELETED, '被删除'),
+ (NOT_CREATED, "未创建"),
+ (CREATE_FAILED, "创建失败"),
+ (CREATED, "创建成功"),
+ (START_FAILED, "启动失败"),
+ (RUNNING, "执行中"),
+ (FAILED, "执行失败"),
+ (FINISHED, "执行结束"),
+ (REVOKED, "被撤销"),
+ (SUSPENDED, "被挂起"),
+ (DELETED, "被删除"),
]
SOPS_TASK_STARTED_STATUS = [START_FAILED, RUNNING, REVOKED, FINISHED, FAILED, SUSPENDED]
@@ -94,7 +100,11 @@
SOPS_TASK = "SOPS"
NORMAL_TASK = "NORMAL"
DEVOPS_TASK = "DEVOPS"
-TASK_COMPONENT_CHOICE = [(NORMAL_TASK, _("常规")), (SOPS_TASK, _("标准运维")), (DEVOPS_TASK, _("蓝盾"))]
+TASK_COMPONENT_CHOICE = [
+ (NORMAL_TASK, _("常规")),
+ (SOPS_TASK, _("标准运维")),
+ (DEVOPS_TASK, _("蓝盾")),
+]
TASK_NAME_KEY = "task_name" # 字段`任务名称`的key
TASK_PROCESSOR_KEY = "processors" # 字段`处理人`的key
SOPS_TEMPLATE_KEY = "sops_templates" # 字段`流程模板`的key
@@ -141,225 +151,229 @@
"regex": "EMPTY",
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'STRING',
- 'key': 'executeTime',
- 'name': '执行时长(分钟)',
- 'layout': 'COL_12',
- 'validate_type': 'REQUIRE',
- 'regex': 'NON_NEGATIVE',
- 'desc': '运维执行时长 (浮点数,单位是分钟)',
- 'tips': '运维执行时长 (浮点数,单位是分钟)',
- 'is_tips': True,
- 'default': '',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 1,
+ "is_builtin": True,
+ "display": True,
+ "type": "STRING",
+ "key": "executeTime",
+ "name": "执行时长(分钟)",
+ "layout": "COL_12",
+ "validate_type": "REQUIRE",
+ "regex": "NON_NEGATIVE",
+ "desc": "运维执行时长 (浮点数,单位是分钟)",
+ "tips": "运维执行时长 (浮点数,单位是分钟)",
+ "is_tips": True,
+ "default": "",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 1,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'STRING',
- 'key': 'reviewNumerator',
- 'name': '停机比例',
- 'layout': 'COL_12',
- 'validate_type': 'OPTION',
- 'regex': 'NON_NEGATIVE',
- 'desc': '停机比例(整型 0-100)',
- 'tips': '停机比例(整型 0-100)',
- 'is_tips': True,
- 'default': '',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 2,
+ "is_builtin": True,
+ "display": True,
+ "type": "STRING",
+ "key": "reviewNumerator",
+ "name": "停机比例",
+ "layout": "COL_12",
+ "validate_type": "OPTION",
+ "regex": "NON_NEGATIVE",
+ "desc": "停机比例(整型 0-100)",
+ "tips": "停机比例(整型 0-100)",
+ "is_tips": True,
+ "default": "",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 2,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'RADIO',
- 'key': 'reviewIsShutdown',
- 'name': '是否停机发布',
- 'layout': 'COL_12',
- 'validate_type': 'REQUIRE',
- 'regex': 'EMPTY',
- 'desc': '是否停机发布 (整型:0, 1)',
- 'tips': '是否停机发布 (整型:0, 1)',
- 'is_tips': True,
- 'default': '0',
- 'choice': [{'key': '1', 'name': '是'}, {'key': '0', 'name': '否'}],
- 'stage': CONFIRM,
- 'sequence': 3,
+ "is_builtin": True,
+ "display": True,
+ "type": "RADIO",
+ "key": "reviewIsShutdown",
+ "name": "是否停机发布",
+ "layout": "COL_12",
+ "validate_type": "REQUIRE",
+ "regex": "EMPTY",
+ "desc": "是否停机发布 (整型:0, 1)",
+ "tips": "是否停机发布 (整型:0, 1)",
+ "is_tips": True,
+ "default": "0",
+ "choice": [{"key": "1", "name": "是"}, {"key": "0", "name": "否"}],
+ "stage": CONFIRM,
+ "sequence": 3,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'STRING',
- 'key': 'reviewShutdownTime',
- 'name': '停机耗费时长(分钟)',
- 'layout': 'COL_12',
- 'validate_type': 'OPTION',
- 'regex': 'NON_NEGATIVE',
- 'desc': '停机耗费时长 (浮点数)',
- 'tips': '停机耗费时长 (浮点数)',
- 'is_tips': True,
- 'default': '0',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 4,
+ "is_builtin": True,
+ "display": True,
+ "type": "STRING",
+ "key": "reviewShutdownTime",
+ "name": "停机耗费时长(分钟)",
+ "layout": "COL_12",
+ "validate_type": "OPTION",
+ "regex": "NON_NEGATIVE",
+ "desc": "停机耗费时长 (浮点数)",
+ "tips": "停机耗费时长 (浮点数)",
+ "is_tips": True,
+ "default": "0",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 4,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'STRING',
- 'key': 'prepareTime',
- 'name': '任务准备时长(分钟)',
- 'layout': 'COL_12',
- 'validate_type': 'REQUIRE',
- 'regex': 'NON_NEGATIVE',
- 'desc': '任务准备时长 (浮点数,单位是分钟)',
- 'tips': '任务准备时长 (浮点数,单位是分钟)',
- 'is_tips': True,
- 'default': '0',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 5,
+ "is_builtin": True,
+ "display": True,
+ "type": "STRING",
+ "key": "prepareTime",
+ "name": "任务准备时长(分钟)",
+ "layout": "COL_12",
+ "validate_type": "REQUIRE",
+ "regex": "NON_NEGATIVE",
+ "desc": "任务准备时长 (浮点数,单位是分钟)",
+ "tips": "任务准备时长 (浮点数,单位是分钟)",
+ "is_tips": True,
+ "default": "0",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 5,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'STRING',
- 'key': 'testTime',
- 'name': '现网测试时长(分钟)',
- 'layout': 'COL_12',
- 'validate_type': 'REQUIRE',
- 'regex': 'NON_NEGATIVE',
- 'desc': '现网测试时长 (浮点数,单位是分钟)',
- 'tips': '现网测试时长 (浮点数,单位是分钟)',
- 'is_tips': True,
- 'default': '0',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 6,
+ "is_builtin": True,
+ "display": True,
+ "type": "STRING",
+ "key": "testTime",
+ "name": "现网测试时长(分钟)",
+ "layout": "COL_12",
+ "validate_type": "REQUIRE",
+ "regex": "NON_NEGATIVE",
+ "desc": "现网测试时长 (浮点数,单位是分钟)",
+ "tips": "现网测试时长 (浮点数,单位是分钟)",
+ "is_tips": True,
+ "default": "0",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 6,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'SELECT',
- 'key': 'isSuccess',
- 'name': '发布实施结论',
- 'layout': 'COL_12',
- 'validate_type': 'REQUIRE',
- 'regex': 'EMPTY',
- 'desc': '发布实施结论',
- 'tips': '发布实施结论',
- 'is_tips': True,
- 'default': '1',
- 'choice': [{'key': '1', 'name': '完全成功'}, {'key': '2', 'name': '成功但有问题'}, {'key': '3', 'name': '发布失败'}],
- 'stage': CONFIRM,
- 'sequence': 7,
+ "is_builtin": True,
+ "display": True,
+ "type": "SELECT",
+ "key": "isSuccess",
+ "name": "发布实施结论",
+ "layout": "COL_12",
+ "validate_type": "REQUIRE",
+ "regex": "EMPTY",
+ "desc": "发布实施结论",
+ "tips": "发布实施结论",
+ "is_tips": True,
+ "default": "1",
+ "choice": [
+ {"key": "1", "name": "完全成功"},
+ {"key": "2", "name": "成功但有问题"},
+ {"key": "3", "name": "发布失败"},
+ ],
+ "stage": CONFIRM,
+ "sequence": 7,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'DATETIME',
- 'key': 'actualEndTime',
- 'name': '实际结束时间',
- 'layout': 'COL_12',
- 'validate_type': 'REQUIRE',
- 'regex': 'EMPTY',
- 'desc': "实际结束时间,日期格式 'yyyy-MM-dd hh:mm:ss'",
- 'tips': "实际结束时间,日期格式 'yyyy-MM-dd hh:mm:ss'",
- 'is_tips': True,
- 'default': '',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 8,
+ "is_builtin": True,
+ "display": True,
+ "type": "DATETIME",
+ "key": "actualEndTime",
+ "name": "实际结束时间",
+ "layout": "COL_12",
+ "validate_type": "REQUIRE",
+ "regex": "EMPTY",
+ "desc": "实际结束时间,日期格式 'yyyy-MM-dd hh:mm:ss'",
+ "tips": "实际结束时间,日期格式 'yyyy-MM-dd hh:mm:ss'",
+ "is_tips": True,
+ "default": "",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 8,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'DATETIME',
- 'key': 'actualBeginTime',
- 'name': '实际开始时间',
- 'layout': 'COL_12',
- 'validate_type': 'REQUIRE',
- 'regex': 'EMPTY',
- 'desc': "实际开始时间,日期格式 'yyyy-MM-dd hh:mm:ss'",
- 'tips': "实际开始时间,日期格式 'yyyy-MM-dd hh:mm:ss'",
- 'is_tips': True,
- 'default': '',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 9,
+ "is_builtin": True,
+ "display": True,
+ "type": "DATETIME",
+ "key": "actualBeginTime",
+ "name": "实际开始时间",
+ "layout": "COL_12",
+ "validate_type": "REQUIRE",
+ "regex": "EMPTY",
+ "desc": "实际开始时间,日期格式 'yyyy-MM-dd hh:mm:ss'",
+ "tips": "实际开始时间,日期格式 'yyyy-MM-dd hh:mm:ss'",
+ "is_tips": True,
+ "default": "",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 9,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'RADIO',
- 'key': 'reviewIsDbChange',
- 'name': '是否DB发布',
- 'layout': 'COL_12',
- 'validate_type': 'REQUIRE',
- 'regex': 'EMPTY',
- 'desc': '是否DB发布',
- 'tips': '是否DB发布',
- 'is_tips': True,
- 'default': '0',
- 'choice': [{'key': '1', 'name': '是'}, {'key': '0', 'name': '否'}],
- 'stage': CONFIRM,
- 'sequence': 10,
+ "is_builtin": True,
+ "display": True,
+ "type": "RADIO",
+ "key": "reviewIsDbChange",
+ "name": "是否DB发布",
+ "layout": "COL_12",
+ "validate_type": "REQUIRE",
+ "regex": "EMPTY",
+ "desc": "是否DB发布",
+ "tips": "是否DB发布",
+ "is_tips": True,
+ "default": "0",
+ "choice": [{"key": "1", "name": "是"}, {"key": "0", "name": "否"}],
+ "stage": CONFIRM,
+ "sequence": 10,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'STRING',
- 'key': 'reviewDbChangeTime',
- 'name': 'DB耗费时长(分钟)',
- 'layout': 'COL_12',
- 'validate_type': 'OPTION',
- 'regex': 'NON_NEGATIVE',
- 'desc': 'DB耗费时长 (浮点数)',
- 'tips': 'DB耗费时长 (浮点数)',
- 'is_tips': True,
- 'default': '0',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 11,
+ "is_builtin": True,
+ "display": True,
+ "type": "STRING",
+ "key": "reviewDbChangeTime",
+ "name": "DB耗费时长(分钟)",
+ "layout": "COL_12",
+ "validate_type": "OPTION",
+ "regex": "NON_NEGATIVE",
+ "desc": "DB耗费时长 (浮点数)",
+ "tips": "DB耗费时长 (浮点数)",
+ "is_tips": True,
+ "default": "0",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 11,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'TEXT',
- 'key': 'conclusion',
- 'name': '发布经验总结',
- 'layout': 'COL_12',
- 'validate_type': 'OPTION',
- 'regex': 'EMPTY',
- 'desc': '发布经验总结',
- 'tips': '',
- 'is_tips': False,
- 'default': '',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 12,
+ "is_builtin": True,
+ "display": True,
+ "type": "TEXT",
+ "key": "conclusion",
+ "name": "发布经验总结",
+ "layout": "COL_12",
+ "validate_type": "OPTION",
+ "regex": "EMPTY",
+ "desc": "发布经验总结",
+ "tips": "",
+ "is_tips": False,
+ "default": "",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 12,
},
{
- 'is_builtin': True,
- 'display': True,
- 'type': 'STRING',
- 'key': 'dbBackupTime',
- 'name': 'DB备份时长(分钟)',
- 'layout': 'COL_12',
- 'validate_type': 'OPTION',
- 'regex': 'NON_NEGATIVE',
- 'desc': '',
- 'tips': '',
- 'is_tips': False,
- 'default': '',
- 'choice': [],
- 'stage': CONFIRM,
- 'sequence': 13,
+ "is_builtin": True,
+ "display": True,
+ "type": "STRING",
+ "key": "dbBackupTime",
+ "name": "DB备份时长(分钟)",
+ "layout": "COL_12",
+ "validate_type": "OPTION",
+ "regex": "NON_NEGATIVE",
+ "desc": "",
+ "tips": "",
+ "is_tips": False,
+ "default": "",
+ "choice": [],
+ "stage": CONFIRM,
+ "sequence": 13,
},
]
@@ -420,12 +434,12 @@
]
DEVOPS_TASK_STATE_CHOICE = [
- (NOT_CREATED, '未创建'),
- (DEVOPS_STATUS.SUCCEED, '执行成功'),
- (DEVOPS_STATUS.FAILED, '执行失败'),
- (DEVOPS_STATUS.CANCELED, '已取消'),
- (DEVOPS_STATUS.TERMINATE, '已终止'),
- (DEVOPS_STATUS.RUNNING, '执行中'),
+ (NOT_CREATED, "未创建"),
+ (DEVOPS_STATUS.SUCCEED, "执行成功"),
+ (DEVOPS_STATUS.FAILED, "执行失败"),
+ (DEVOPS_STATUS.CANCELED, "已取消"),
+ (DEVOPS_STATUS.TERMINATE, "已终止"),
+ (DEVOPS_STATUS.RUNNING, "执行中"),
]
TASK_STATUS_CHOICE = [
@@ -434,7 +448,7 @@
(QUEUE, _("待处理")),
# 下发到后台队列中,等待执行
(WAITING_FOR_OPERATE, _("待处理")),
- (WAITING_FOR_BACKEND, '后台处理中'),
+ (WAITING_FOR_BACKEND, "后台处理中"),
(RUNNING, _("执行中")),
(WAITING_FOR_CONFIRM, _("待总结")),
# 任务无法正常结束,可设置为忽略状态
@@ -442,10 +456,10 @@
# 自动任务执行失败
(FAILED, _("失败")),
(FINISHED, _("完成")),
- (REVOKED, _('被撤销')),
- (SUSPENDED, _('被挂起')),
- (DELETED, _('被删除')),
- (SUCCEED, _('执行成功')),
- (TERMINATE, _('已终止')),
- (CANCELED, _('已取消')),
+ (REVOKED, _("被撤销")),
+ (SUSPENDED, _("被挂起")),
+ (DELETED, _("被删除")),
+ (SUCCEED, _("执行成功")),
+ (TERMINATE, _("已终止")),
+ (CANCELED, _("已取消")),
]
diff --git a/itsm/component/constants/trigger.py b/itsm/component/constants/trigger.py
index 4f2997548..57e55275f 100644
--- a/itsm/component/constants/trigger.py
+++ b/itsm/component/constants/trigger.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
ACTION_STATUS_CREATED = "CREATED"
ACTION_STATUS_RUNNING = "RUNNING"
@@ -90,7 +90,7 @@
RECOVERY_TICKET: _("恢复单据"),
DELETE_TICKET: _("撤销单据"),
GLOBAL_ENTER_STATE: _("进入节点"),
- GLOBAL_LEAVE_STATE: _("离开节点")
+ GLOBAL_LEAVE_STATE: _("离开节点"),
# CREATE_RELATE_TICKET: _("创建关联单"),
# CREATE_PARENTChILD_TICKET: _("创建母子单"),
# DISSOLVE_PARENTChILD_TICKET: _("解除母子单"),
diff --git a/itsm/component/db/models.py b/itsm/component/db/models.py
index 10d38aeac..873b402fa 100644
--- a/itsm/component/db/models.py
+++ b/itsm/component/db/models.py
@@ -27,7 +27,7 @@
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mptt.managers import TreeManager
from mptt.models import MPTTModel
from itsm.component.db import managers
diff --git a/itsm/component/decorators.py b/itsm/component/decorators.py
index 19bcfe51e..961c74255 100644
--- a/itsm/component/decorators.py
+++ b/itsm/component/decorators.py
@@ -31,7 +31,7 @@
from django.core.exceptions import ValidationError
from rest_framework.exceptions import ValidationError as RrfValidationError
from django.http import JsonResponse, HttpResponse
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.bkoauth.jwt_client import JWTClient, jwt_invalid_view
from itsm.component.exceptions import ServerError, ParamError
@@ -119,7 +119,9 @@ def __wrapper(request, *args, **kwargs):
try:
is_file_name_valid(file_name)
except ValidationError as error:
- return Fail(_("文件上传失败:{}").format(str(error)), "FILE_NAME_INVALID").json()
+ return Fail(
+ _("文件上传失败:{}").format(str(error)), "FILE_NAME_INVALID"
+ ).json()
except RrfValidationError as error:
return Fail(
_("文件上传失败:{}").format(error.detail[0]), "FILE_NAME_INVALID"
@@ -199,7 +201,8 @@ def _wrapped_view(request, *args, **kwargs):
{
"code": "FILE_NOT_ALLOWED",
"result": False,
- "message": _("上传文件类型仅支持:%s") % ", ".join(content_types),
+ "message": _("上传文件类型仅支持:%s")
+ % ", ".join(content_types),
}
)
@@ -232,9 +235,13 @@ def __wrapper(request, *args, **kwargs):
try:
system_file_path = SystemSettings.objects.get(key="SYS_FILE_PATH").value
if not os.path.exists(system_file_path):
- return Fail(_("请检查系统配置:附件存储目录不存在"), "SYS_FILE_PATH_INVALID").json()
+ return Fail(
+ _("请检查系统配置:附件存储目录不存在"), "SYS_FILE_PATH_INVALID"
+ ).json()
except SystemSettings.DoesNotExist:
- return Fail(_("请检查系统配置:附件存储的目录配置无效"), "SYS_FILE_PATH_EMPTY").json()
+ return Fail(
+ _("请检查系统配置:附件存储的目录配置无效"), "SYS_FILE_PATH_EMPTY"
+ ).json()
except Exception as e:
return Fail(_("附件路径生成异常:%s") % e, "FILE_PATH_EXCEPTION").json()
diff --git a/itsm/component/drf/permissions.py b/itsm/component/drf/permissions.py
index a7e44f686..221212d39 100644
--- a/itsm/component/drf/permissions.py
+++ b/itsm/component/drf/permissions.py
@@ -24,7 +24,7 @@
"""
import copy
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import permissions
from iam import Subject, Action, Resource
@@ -94,7 +94,7 @@ def has_permission(self, request, view):
return True
apply_actions = self.get_view_iam_actions(view)
-
+
# 项目下创建资源
if view.action in ["create", "imports"]:
if not apply_actions:
@@ -110,11 +110,11 @@ def has_object_permission(self, request, view, obj, **kwargs):
# 关联实例的请求,需要针对对象进行鉴权
if view.action in getattr(view, "permission_free_actions", []):
return True
-
+
# 获取视图权限action
apply_actions = self.get_view_iam_actions(view)
return self.iam_auth(request, apply_actions, obj)
-
+
@staticmethod
def get_view_iam_actions(view):
# 获取视图权限action
@@ -131,7 +131,7 @@ def get_view_iam_actions(view):
else:
apply_actions = copy.deepcopy(apply_actions)
return apply_actions
-
+
def iam_auth(self, request, apply_actions, obj=None):
resources = []
@@ -185,9 +185,11 @@ def iam_auth(self, request, apply_actions, obj=None):
str(resource["resource_id"]),
{
"iam_resource_owner": resource.get("creator", ""),
- "_bk_iam_path_": "/project,{}/".format(project_key)
- if resource["resource_type"] != "project"
- else "",
+ "_bk_iam_path_": (
+ "/project,{}/".format(project_key)
+ if resource["resource_type"] != "project"
+ else ""
+ ),
"name": resource.get("resource_name", ""),
},
)
@@ -235,9 +237,9 @@ def iam_create_auth(self, request, apply_actions):
str(resource["resource_id"]),
{
"iam_resource_owner": resource.get("creator", ""),
- "_bk_iam_path_": bk_iam_path
- if resource["resource_type"] != "project"
- else "",
+ "_bk_iam_path_": (
+ bk_iam_path if resource["resource_type"] != "project" else ""
+ ),
"name": resource.get("resource_name", ""),
},
)
@@ -301,12 +303,12 @@ def has_object_permission(self, request, view, obj):
class IamAuthProjectViewPermit(IamAuthPermit):
def has_object_permission(self, request, view, obj):
apply_actions = self.get_view_iam_actions(view)
-
+
if hasattr(obj, "project_key"):
project_key = obj.project_key
if not apply_actions and view.action in ["create", "update", "destroy"]:
apply_actions = ["system_settings_manage"]
-
+
# 项目管理必须有查看权限
apply_actions.append("project_view")
return self.has_project_view_permission(request, project_key, apply_actions)
@@ -342,9 +344,9 @@ def has_project_view_permission(self, request, project_key, apply_actions):
str(resource["resource_id"]),
{
"iam_resource_owner": resource.get("creator", ""),
- "_bk_iam_path_": bk_iam_path
- if resource["resource_type"] != "project"
- else "",
+ "_bk_iam_path_": (
+ bk_iam_path if resource["resource_type"] != "project" else ""
+ ),
"name": resource.get("resource_name", ""),
},
)
diff --git a/itsm/component/esb/esbclient.py b/itsm/component/esb/esbclient.py
index e837c9fef..d57f23227 100644
--- a/itsm/component/esb/esbclient.py
+++ b/itsm/component/esb/esbclient.py
@@ -25,18 +25,18 @@
# 全平台 esb-sdk 封装,依赖于 esb-sdk 包,但不依赖 sdk 的版本。
# sdk 中有封装好 cc.get_app_by_user 方法时,可直接按以前 sdk 的习惯调用
-#
+#
# from blueapps.utils import client
# client.cc.get_app_by_user()
-#
+#
# from blueapps.utils import backend_client
# b_client = backend_client(access_token="SfgcGlBHmPWttwlGd7nOLAbOP3TAOG")
# b_client.cc.get_app_by_user()
-#
+#
# 当前版本 sdk 中未封装好,但 api 已经有 get_app_by_user 的时候。需要指定请求方法
# client.cc.get_app_by_user.get()
-import collections
+from collections.abc import Callable
import logging
from django.contrib.auth import get_user_model
@@ -50,11 +50,11 @@
from itsm.component.constants import API_PERMISSION_ERROR_CODE
__all__ = [
- 'client',
- 'backend_client',
- 'get_client_by_user',
- 'get_client_by_request',
- 'CustomComponentAPI',
+ "client",
+ "backend_client",
+ "get_client_by_user",
+ "get_client_by_request",
+ "CustomComponentAPI",
"client_backend",
]
@@ -79,7 +79,7 @@ def get_api_prefix():
if not ESB_SDK_NAME:
raise AttributeError
except AttributeError:
- ESB_SDK_NAME = 'blueking.component.{platform}'.format(platform=settings.RUN_VER)
+ ESB_SDK_NAME = "blueking.component.{platform}".format(platform=settings.RUN_VER)
class SDKClient(object):
@@ -96,7 +96,7 @@ def __backend__(self):
def __new__(cls, **kwargs):
if cls.sdk_package is None:
try:
- cls.sdk_package = __import__(ESB_SDK_NAME, fromlist=['shortcuts'])
+ cls.sdk_package = __import__(ESB_SDK_NAME, fromlist=["shortcuts"])
except ImportError as e:
raise ImportError("%s is not installed: %s" % (ESB_SDK_NAME, e))
return super(SDKClient, cls).__new__(cls)
@@ -104,7 +104,7 @@ def __new__(cls, **kwargs):
def __init__(self, **kwargs):
self.mod_name = ""
self.sdk_mod = None
- for ignored_field in ['app_code', 'app_secret']:
+ for ignored_field in ["app_code", "app_secret"]:
if ignored_field in kwargs:
kwargs.pop(ignored_field)
self.common_args = kwargs
@@ -114,7 +114,7 @@ def __getattr__(self, item):
ret = SDKClient(**self.common_args)
ret.mod_name = item
ret.setup_modules()
- if isinstance(ret.sdk_mod, collections.Callable):
+ if isinstance(ret.sdk_mod, Callable):
return ret.sdk_mod
return ret
else:
@@ -123,7 +123,7 @@ def __getattr__(self, item):
if ret is None:
ret = ComponentAPICollection(self).add_api(item)
- if not isinstance(ret, collections.Callable):
+ if not isinstance(ret, Callable):
ret = self
return _wrap_data_handler(ret)
@@ -134,20 +134,26 @@ def setup_modules(self):
@property
def sdk_client(self):
- is_backend = self.common_args.pop('__backend__', False)
+ is_backend = self.common_args.pop("__backend__", False)
username = self.common_args.get("username", settings.SYSTEM_CALL_USER)
if is_backend:
try:
- return self.load_sdk_class("shortcuts", "get_client_by_user")(username, **self.common_args)
+ return self.load_sdk_class("shortcuts", "get_client_by_user")(
+ username, **self.common_args
+ )
except Exception as err:
- logger.exception("client call get_client_by_user failed, msg is {}".format(err))
+ logger.exception(
+ "client call get_client_by_user failed, msg is {}".format(err)
+ )
if settings.RUN_MODE != "DEVELOP":
if self.common_args:
return self.load_sdk_class("shortcuts", "get_client_by_user")(
settings.SYSTEM_CALL_USER, **self.common_args
)
else:
- raise AccessForbidden("sdk can only be called through the Web request")
+ raise AccessForbidden(
+ "sdk can only be called through the Web request"
+ )
else:
# develop mode
# 根据RUN_VER获得get_component_client_common_args函数
@@ -163,14 +169,22 @@ def sdk_client(self):
request = get_request()
try:
# 调用sdk方法获取sdk client
- return self.load_sdk_class("shortcuts", "get_client_by_request")(request)
+ return self.load_sdk_class("shortcuts", "get_client_by_request")(
+ request
+ )
except Exception as err:
- logger.exception("client call get_client_by_request failed, msg is {}".format(err))
+ logger.exception(
+ "client call get_client_by_request failed, msg is {}".format(err)
+ )
if settings.RUN_MODE != "DEVELOP":
if self.common_args:
- return self.load_sdk_class("shortcuts", "get_client_by_request")(request, **self.common_args)
+ return self.load_sdk_class(
+ "shortcuts", "get_client_by_request"
+ )(request, **self.common_args)
else:
- raise AccessForbidden("sdk can only be called through the Web request")
+ raise AccessForbidden(
+ "sdk can only be called through the Web request"
+ )
else:
# develop mode
# 根据RUN_VER获得get_component_client_common_args函数
@@ -189,7 +203,7 @@ def load_sdk_class(self, mod, attr_or_class):
def patch_sdk_component_api_class(self):
def patch_get_item(self, item):
- if item.startswith('__'):
+ if item.startswith("__"):
# make client can be pickled
raise AttributeError()
@@ -209,7 +223,9 @@ class ComponentAPICollection(object):
def __new__(cls, sdk_client, *args, **kwargs):
if sdk_client.mod_name not in cls.mod_map:
- cls.mod_map[sdk_client.mod_name] = super(ComponentAPICollection, cls).__new__(cls)
+ cls.mod_map[sdk_client.mod_name] = super(
+ ComponentAPICollection, cls
+ ).__new__(cls)
return cls.mod_map[sdk_client.mod_name]
def __init__(self, sdk_client):
@@ -240,14 +256,18 @@ def __getattr__(self, method):
return api_cls(
client=SDKClient(**self.collection.client.common_args),
method=method,
- path='{api_prefix}{collection}/{action}/'.format(
- api_prefix=ESB_API_PREFIX, collection=self.collection.client.mod_name, action=self.action
+ path="{api_prefix}{collection}/{action}/".format(
+ api_prefix=ESB_API_PREFIX,
+ collection=self.collection.client.mod_name,
+ action=self.action,
),
- description='custom api(%s)' % self.action,
+ description="custom api(%s)" % self.action,
)
def __call__(self, *args, **kwargs):
- raise NotImplementedError('custom api `%s` must specify the request method' % self.action)
+ raise NotImplementedError(
+ "custom api `%s` must specify the request method" % self.action
+ )
client = SDKClient()
@@ -264,7 +284,9 @@ def get_client_by_user(user_or_username):
username = user_or_username.username
else:
username = user_or_username
- get_client_by_user = import_string(".".join([ESB_SDK_NAME, 'shortcuts', 'get_client_by_user']))
+ get_client_by_user = import_string(
+ ".".join([ESB_SDK_NAME, "shortcuts", "get_client_by_user"])
+ )
return get_client_by_user(username)
@@ -282,17 +304,20 @@ def _wrap(*args, **kwargs):
response = sdk_method(*args, **kwargs)
if __raw:
return response
-
- if "get_batch_users" in sdk_method.url:
- logger.info("get_batch_users is execute, args={}, kwargs={}, response={}"
- .format(args, kwargs, response))
- if not response['result'] and not __ignore_err:
- if response['code'] == API_PERMISSION_ERROR_CODE:
+ if "get_batch_users" in sdk_method.url:
+ logger.info(
+ "get_batch_users is execute, args={}, kwargs={}, response={}".format(
+ args, kwargs, response
+ )
+ )
+
+ if not response["result"] and not __ignore_err:
+ if response["code"] == API_PERMISSION_ERROR_CODE:
"""接口返回无权限的时候,直接抛出权限不够"""
raise IamPermissionDenied(data=response.get("permission", []))
raise ComponentCallError(response)
- return response['data']
+ return response["data"]
return _wrap
diff --git a/itsm/component/exceptions.py b/itsm/component/exceptions.py
index 7442359fa..272bfd3ec 100644
--- a/itsm/component/exceptions.py
+++ b/itsm/component/exceptions.py
@@ -24,7 +24,7 @@
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework.exceptions import PermissionDenied
diff --git a/itsm/component/generics.py b/itsm/component/generics.py
index 911fbfae5..bbc96eef6 100644
--- a/itsm/component/generics.py
+++ b/itsm/component/generics.py
@@ -29,7 +29,7 @@
import traceback
from django.http import Http404
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import status
from rest_framework.exceptions import (
AuthenticationFailed,
@@ -51,29 +51,29 @@
def exception_handler(exc, context):
"""
- 分类:
- rest_framework框架内异常
- app自定义异常
+ 分类:
+ rest_framework框架内异常
+ app自定义异常
"""
- data = {'result': False, 'data': None}
+ data = {"result": False, "data": None}
if isinstance(exc, AuthFailedException):
# 权限中心校验异常, 直接抛出
raise exc
if isinstance(exc, (NotAuthenticated, AuthenticationFailed)):
data = {
- 'result': False,
- 'code': ResponseCodeStatus.UNAUTHORIZED,
- 'detail': _("用户未登录或登录态失效,请使用登录链接重新登录"),
- 'login_url': '',
+ "result": False,
+ "code": ResponseCodeStatus.UNAUTHORIZED,
+ "detail": _("用户未登录或登录态失效,请使用登录链接重新登录"),
+ "login_url": "",
}
return Response(data, status=status.HTTP_403_FORBIDDEN)
if isinstance(exc, IamPermissionDenied):
data = {
- 'result': False,
- 'code': ResponseCodeStatus.PERMISSION_DENIED,
- 'message': exc.detail,
+ "result": False,
+ "code": ResponseCodeStatus.PERMISSION_DENIED,
+ "message": exc.detail,
"data": [],
"permission": exc.data, # 具体的权限信息
}
@@ -81,9 +81,9 @@ def exception_handler(exc, context):
if isinstance(exc, PermissionDenied):
data = {
- 'result': False,
- 'code': ResponseCodeStatus.PERMISSION_DENIED,
- 'message': exc.detail,
+ "result": False,
+ "code": ResponseCodeStatus.PERMISSION_DENIED,
+ "message": exc.detail,
}
return Response(data, status=status.HTTP_403_FORBIDDEN)
@@ -91,30 +91,42 @@ def exception_handler(exc, context):
if isinstance(exc, ValidationError):
data.update(
{
- 'code': ResponseCodeStatus.VALIDATE_ERROR,
- 'messages': exc.detail,
- 'message': format_validation_message(exc),
+ "code": ResponseCodeStatus.VALIDATE_ERROR,
+ "messages": exc.detail,
+ "message": format_validation_message(exc),
}
)
elif isinstance(exc, MethodNotAllowed):
data.update(
- {'code': ResponseCodeStatus.METHOD_NOT_ALLOWED, 'message': exc.detail, }
+ {
+ "code": ResponseCodeStatus.METHOD_NOT_ALLOWED,
+ "message": exc.detail,
+ }
)
elif isinstance(exc, PermissionDenied):
data.update(
- {'code': ResponseCodeStatus.PERMISSION_DENIED, 'message': exc.detail, }
+ {
+ "code": ResponseCodeStatus.PERMISSION_DENIED,
+ "message": exc.detail,
+ }
)
elif isinstance(exc, ServerError):
# 更改返回的状态为为自定义错误类型的状态码
data.update(
- {'code': exc.code, 'message': exc.message, }
+ {
+ "code": exc.code,
+ "message": exc.message,
+ }
)
elif isinstance(exc, Http404):
# 更改返回的状态为为自定义错误类型的状态码
data.update(
- {'code': ResponseCodeStatus.OBJECT_NOT_EXIST, 'message': _("当前操作的对象不存在"), }
+ {
+ "code": ResponseCodeStatus.OBJECT_NOT_EXIST,
+ "message": _("当前操作的对象不存在"),
+ }
)
else:
# 调试模式
@@ -122,8 +134,8 @@ def exception_handler(exc, context):
# 正式环境,屏蔽500
data.update(
{
- 'code': ResponseCodeStatus.SERVER_500_ERROR,
- 'message': getattr(exc, "message", str(exc))
+ "code": ResponseCodeStatus.SERVER_500_ERROR,
+ "message": getattr(exc, "message", str(exc)),
}
)
diff --git a/itsm/component/middlewares.py b/itsm/component/middlewares.py
index f9c70e044..b337d1056 100644
--- a/itsm/component/middlewares.py
+++ b/itsm/component/middlewares.py
@@ -28,9 +28,9 @@
# ESB封装类,拷贝自`数据平台`,修改自
# https://github.com/LLK/django-request-provider/blob/master/request_provider/signals.py
-#
+#
# 建议进一步考察TODO项,涉及线程安全问题
-#
+#
# since each thread has its own greenlet we can just use those as identifiers
# for the context. If greenlets are not available we fall back to the
# current thread ident depending on where it is.
@@ -44,7 +44,7 @@
from _thread import get_ident
from django.dispatch import Signal
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
class UnauthorizedSignalReceiver(Exception):
@@ -56,7 +56,7 @@ class SingleHandlerSignal(Signal):
与 RequestProvider 中间件搭配使用
"""
- allowed_receiver = 'itsm.component.middlewares.RequestProvider'
+ allowed_receiver = "itsm.component.middlewares.RequestProvider"
def __init__(self, providing_args=None):
Signal.__init__(self, providing_args)
@@ -69,9 +69,13 @@ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
if self.bind_times >= 1:
return
- receiver_name = '.'.join([receiver.__class__.__module__, receiver.__class__.__name__])
+ receiver_name = ".".join(
+ [receiver.__class__.__module__, receiver.__class__.__name__]
+ )
if receiver_name != self.allowed_receiver:
- raise UnauthorizedSignalReceiver(_("%s is not allowed to connect") % receiver_name)
+ raise UnauthorizedSignalReceiver(
+ _("%s is not allowed to connect") % receiver_name
+ )
Signal.connect(self, receiver, sender, weak, dispatch_uid)
self.bind_times += 1
@@ -100,7 +104,7 @@ def process_response(self, request, response):
def __call__(self, *args, **kwargs):
# TODO 仅对信号反馈?
- from_signal = kwargs.get('from_signal', False)
+ from_signal = kwargs.get("from_signal", False)
if from_signal:
return self.get_request(**kwargs)
else:
@@ -112,16 +116,20 @@ def get_request(self, **kwargs):
if sender is None:
sender = get_ident()
if sender not in self._request_pool:
- raise UnauthorizedSignalReceiver(_("get_request can't be called in a new thread."))
+ raise UnauthorizedSignalReceiver(
+ _("get_request can't be called in a new thread.")
+ )
return self._request_pool[sender]
def get_x_request_id():
- x_request_id = ''
+ x_request_id = ""
http_request = get_request()
- if hasattr(http_request, 'META'):
+ if hasattr(http_request, "META"):
meta = http_request.META
- x_request_id = meta.get('HTTP_X_REQUEST_ID', '') if isinstance(meta, dict) else ''
+ x_request_id = (
+ meta.get("HTTP_X_REQUEST_ID", "") if isinstance(meta, dict) else ""
+ )
return x_request_id
diff --git a/itsm/component/misc_middlewares.py b/itsm/component/misc_middlewares.py
index cc63df56d..6b838a675 100644
--- a/itsm/component/misc_middlewares.py
+++ b/itsm/component/misc_middlewares.py
@@ -243,7 +243,7 @@ class WikiIamAuthMiddleware(MiddlewareMixin):
def process_view(self, request, view, args, kwargs):
"""process_view."""
- if request.user.username and "/wiki/" in request.path:
+ if request.user and request.user.username and "/wiki/" in request.path:
apply_actions = ["knowledge_manage"]
iam_client = IamRequest(request)
auth_actions = iam_client.resource_multi_actions_allowed(apply_actions, [])
@@ -264,7 +264,7 @@ class HttpsMiddleware(MiddlewareMixin):
def process_request(self, request):
if settings.ENVIRONMENT == "dev":
return None
-
+
if settings.RUN_VER == "ieod":
# 对于openapi 跳转豁免
if request.path.startswith(EXEMPT_HTTPS_REDIRECT):
diff --git a/itsm/component/notify.py b/itsm/component/notify.py
index 0299c884a..9179450cf 100644
--- a/itsm/component/notify.py
+++ b/itsm/component/notify.py
@@ -26,7 +26,7 @@
from datetime import datetime
from django.conf import settings
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.constants import GENERAL_NOTICE
diff --git a/itsm/component/request_middlewares.py b/itsm/component/request_middlewares.py
index b0acc113e..7a0c9615e 100644
--- a/itsm/component/request_middlewares.py
+++ b/itsm/component/request_middlewares.py
@@ -25,19 +25,21 @@
from django.dispatch import Signal
from django.utils.deprecation import MiddlewareMixin
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.utils.local import local
class AccessorSignal(Signal):
- allowed_receiver = 'itsm.component.request_middlewares.RequestProvider'
+ allowed_receiver = "itsm.component.request_middlewares.RequestProvider"
def __init__(self, providing_args=None):
Signal.__init__(self, providing_args)
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
- receiver_name = '.'.join([receiver.__class__.__module__, receiver.__class__.__name__])
+ receiver_name = ".".join(
+ [receiver.__class__.__module__, receiver.__class__.__name__]
+ )
if receiver_name != self.allowed_receiver:
raise Exception(_("%s is not allowed to connect") % receiver_name)
if not self.receivers:
@@ -60,29 +62,31 @@ def process_request(self, request):
return None
def process_response(self, request, response):
- if hasattr(local, 'current_request'):
+ if hasattr(local, "current_request"):
assert request is local.current_request
del local.current_request
return response
def __call__(self, **kwargs):
- if not hasattr(local, 'current_request'):
+ if not hasattr(local, "current_request"):
raise Exception(_("get_request can't be called in a new thread."))
return local.current_request
def get_request():
- if hasattr(local, 'current_request'):
+ if hasattr(local, "current_request"):
return local.current_request
else:
raise Exception(_("get_request: current thread hasn't request."))
def get_x_request_id():
- x_request_id = ''
+ x_request_id = ""
http_request = get_request()
- if hasattr(http_request, 'META'):
+ if hasattr(http_request, "META"):
meta = http_request.META
- x_request_id = meta.get('HTTP_X_REQUEST_ID', '') if isinstance(meta, dict) else ''
+ x_request_id = (
+ meta.get("HTTP_X_REQUEST_ID", "") if isinstance(meta, dict) else ""
+ )
return x_request_id
diff --git a/itsm/component/tasks.py b/itsm/component/tasks.py
index 21c094fef..3e0579c6b 100644
--- a/itsm/component/tasks.py
+++ b/itsm/component/tasks.py
@@ -23,9 +23,9 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from celery import task
+from celery import shared_task
from celery.schedules import crontab
-from celery.task import periodic_task
+from blueapps.contrib.celery_tools.periodic import periodic_task
from django.core.cache import cache
from django.conf import settings
from itsm.component.constants import CACHE_10MIN, CACHE_5MIN
@@ -36,7 +36,7 @@
adapter_api = settings.ADAPTER_API
-@task
+@shared_task
def update_user_cache(cache_key, ret_type="list", name_type="bk_username", users=None):
"""更新用户缓存"""
bk_users = None
@@ -56,7 +56,7 @@ def update_user_cache(cache_key, ret_type="list", name_type="bk_username", users
return bk_users
-@task
+@shared_task
def update_bk_business(cache_key, bk_biz_id, role_type):
"""更新CMDB缓存"""
@@ -81,7 +81,7 @@ def update():
return result if result else []
-@task
+@shared_task
def update_user_departments(cache_key, username, id_only):
"""更新组织缓存"""
diff --git a/itsm/component/utils/basic.py b/itsm/component/utils/basic.py
index 5d7dac90c..f0be6f0e2 100644
--- a/itsm/component/utils/basic.py
+++ b/itsm/component/utils/basic.py
@@ -41,7 +41,7 @@
from django.db.models.fields.reverse_related import ManyToManyRel
from django.utils import timezone
from django.utils.crypto import get_random_string
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from jsonschema import validate
from pypinyin import lazy_pinyin
from rest_framework.exceptions import ValidationError
@@ -53,7 +53,9 @@
# 清理终端颜色
COLOR_REMOVE = re.compile(r"\x1b[^m]*m")
-CLEAR_COLOR_RE = re.compile(r"\\u001b\[\D{1}|\[\d{1,2}\D?|\\u001b\[\d{1,2}\D?~?", re.I | re.U)
+CLEAR_COLOR_RE = re.compile(
+ r"\\u001b\[\D{1}|\[\d{1,2}\D?|\\u001b\[\d{1,2}\D?~?", re.I | re.U
+)
# 换行转换
LINE_BREAK_RE = re.compile(r"\r\n|\r|\n", re.I | re.U)
# ip地址v4版本
@@ -61,8 +63,7 @@
def merge_dict_list(dict_list):
- """合并字典列表为单个字典,后面的覆盖前面的
- """
+ """合并字典列表为单个字典,后面的覆盖前面的"""
merged_dict = {}
for dict_item in dict_list:
@@ -113,17 +114,26 @@ class ComplexRegexField(object):
end_with:以什么类型的字符结束 type:list
"""
- def __init__(self, validate_type=None, min_match_count=1, start_with=None, end_with=None, special_char=""):
-
+ def __init__(
+ self,
+ validate_type=None,
+ min_match_count=1,
+ start_with=None,
+ end_with=None,
+ special_char="",
+ ):
+
if end_with is None:
end_with = []
if start_with is None:
start_with = []
if validate_type is None:
validate_type = []
-
+
self.validate_type = validate_type
- self.min_match_count = min_match_count if min_match_count else len(validate_type)
+ self.min_match_count = (
+ min_match_count if min_match_count else len(validate_type)
+ )
self.start_with = start_with
self.end_with = end_with
self.regex_dict = {
@@ -163,7 +173,9 @@ def __init__(self, validate_type=None, min_match_count=1, start_with=None, end_w
"start_en": _("包含中英文,数字,以英文字符开头"),
}
self.regex_error_display = [
- str(value) for key, value in list(self.regex_display_dict.items()) if key in self.validate_type
+ str(value)
+ for key, value in list(self.regex_display_dict.items())
+ if key in self.validate_type
]
if special_char:
self.regex_dict["special"] = special_char
@@ -173,20 +185,33 @@ def __init__(self, validate_type=None, min_match_count=1, start_with=None, end_w
def get_regex(self):
regex_list = list(
combinations(
- ["(?=.*[%s])" % self.regex_dict.get(type_key, "") for type_key in self.validate_type],
+ [
+ "(?=.*[%s])" % self.regex_dict.get(type_key, "")
+ for type_key in self.validate_type
+ ],
self.min_match_count,
)
)
start_pattern = (
- "[%s]" % "".join([self.regex_dict.get(type_key, "") for type_key in self.start_with])
+ "[%s]"
+ % "".join(
+ [self.regex_dict.get(type_key, "") for type_key in self.start_with]
+ )
if self.start_with
else ""
)
end_pattern = (
- "[%s]" % "".join([self.regex_dict.get(type_key, "") for type_key in self.end_with]) if self.end_with else ""
+ "[%s]"
+ % "".join([self.regex_dict.get(type_key, "") for type_key in self.end_with])
+ if self.end_with
+ else ""
+ )
+ include_rules = "".join(
+ [self.regex_dict.get(type_key, "") for type_key in self.validate_type]
+ )
+ include_pattern = "^{}[{}]*{}$".format(
+ start_pattern, include_rules, end_pattern
)
- include_rules = "".join([self.regex_dict.get(type_key, "") for type_key in self.validate_type])
- include_pattern = "^{}[{}]*{}$".format(start_pattern, include_rules, end_pattern)
end_pattern = "^.*%s$" % end_pattern
start_pattern = "^%s.*$" % start_pattern
least_pattern = "^%s.*$" % "|".join(["".join(item) for item in regex_list])
@@ -197,20 +222,35 @@ def validate(self, value):
return
include_pattern, least_pattern, start_pattern, end_pattern = self.get_regex()
if list(set(self.start_with).difference(self.validate_type)):
- raise ValidationError(_("包含了指定字符【{}】以外的内容").format(",".join(self.regex_error_display)), code="not-matched")
+ raise ValidationError(
+ _("包含了指定字符【{}】以外的内容").format(
+ ",".join(self.regex_error_display)
+ ),
+ code="not-matched",
+ )
if list(set(self.end_with).difference(self.validate_type)):
- raise ValidationError(_("包含了指定字符【{}】以外的内容").format(",".join(self.regex_error_display)), code="not-matched")
+ raise ValidationError(
+ _("包含了指定字符【{}】以外的内容").format(
+ ",".join(self.regex_error_display)
+ ),
+ code="not-matched",
+ )
if not re.match(start_pattern, value):
raise ValidationError(_("开头格式不正确"), code="not-matched")
if not re.match(end_pattern, value):
raise ValidationError(_("结尾格式不正确"), code="not-matched")
if not re.match(include_pattern, value):
raise ValidationError(
- _("输入格式不正确:包含了指定字符【{}】以外的内容").format(",".join(self.regex_error_display)), code="not-matched"
+ _("输入格式不正确:包含了指定字符【{}】以外的内容").format(
+ ",".join(self.regex_error_display)
+ ),
+ code="not-matched",
)
if not re.match(least_pattern, value):
raise ValidationError(
- _("至少需要匹配%s种字符(%s)") % (self.min_match_count, ",".join(self.regex_error_display)), code="not-matched"
+ _("至少需要匹配%s种字符(%s)")
+ % (self.min_match_count, ",".join(self.regex_error_display)),
+ code="not-matched",
)
@@ -263,7 +303,9 @@ def __init__(self, validate_type=""):
"start_en": _("包含中英文,数字,以英文字符开头"),
}
self.regex_error_display = [
- value for key, value in list(self.regex_display_dict.items()) if key == self.validate_type
+ value
+ for key, value in list(self.regex_display_dict.items())
+ if key == self.validate_type
]
def validate(self, value):
@@ -272,7 +314,10 @@ def validate(self, value):
pattern = self.regex_dict.get(self.validate_type, "")
if not re.match(pattern, value):
raise ValidationError(
- _("输入格式不正确:包含了指定字符【{}】以外的内容").format(",".join(self.regex_error_display)), code="not-matched"
+ _("输入格式不正确:包含了指定字符【{}】以外的内容").format(
+ ",".join(self.regex_error_display)
+ ),
+ code="not-matched",
)
@@ -340,7 +385,9 @@ def deep_getattr(obj, attr):
return reduce(getattr, attr.split("."), obj)
-def group_by(item_list, key_or_index_tuple, dict_result=False, aggregate=None, as_key=None):
+def group_by(
+ item_list, key_or_index_tuple, dict_result=False, aggregate=None, as_key=None
+):
"""
对列表中的字典元素进行groupby操作,依据为可排序的某个key
:param item_list: 待分组字典列表或元组列表
@@ -423,7 +470,16 @@ def parse_color(content):
},
{"pattern": [], "class": "agent-color-gray"},
{
- "pattern": [_("返回码"), _("执行完毕"), _("作业参数"), _("curl"), _("status"), _("agent状态"), _("yum"), _("apt-get")],
+ "pattern": [
+ _("返回码"),
+ _("执行完毕"),
+ _("作业参数"),
+ _("curl"),
+ _("status"),
+ _("agent状态"),
+ _("yum"),
+ _("apt-get"),
+ ],
"class": "agent-color-black",
},
{
@@ -555,17 +611,26 @@ def generate_random_sn(service_type):
from itsm.component.data import incr_expireat, exists
from itsm.component.constants import PREFIX_KEY
- prefix_mapping = {"event": "INC", "request": "REQ", "change": "CRQ", "question": "PBI"}
+ prefix_mapping = {
+ "event": "INC",
+ "request": "REQ",
+ "change": "CRQ",
+ "question": "PBI",
+ }
key = PREFIX_KEY + service_type
when = None
now_time = now()
if not exists(key):
# 设置第二天的0:00:00过期
- when = datetime.datetime(year=now_time.year, month=now_time.month, day=now_time.day) + datetime.timedelta(
- days=1
- )
+ when = datetime.datetime(
+ year=now_time.year, month=now_time.month, day=now_time.day
+ ) + datetime.timedelta(days=1)
num = incr_expireat(key, when=when)
- sn = prefix_mapping[service_type] + now_time.strftime("%Y%m%d") + "{:0>6}".format(num)
+ sn = (
+ prefix_mapping[service_type]
+ + now_time.strftime("%Y%m%d")
+ + "{:0>6}".format(num)
+ )
return sn
@@ -637,7 +702,11 @@ def dotted_property(instance, name):
'' -> ''
',aaa,bbb,ccc', -> 'aaa,bbb,ccc'
"""
- property = instance.get(name, "") if isinstance(instance, dict) else getattr(instance, name, "")
+ property = (
+ instance.get(name, "")
+ if isinstance(instance, dict)
+ else getattr(instance, name, "")
+ )
return ",".join(list_by_separator(property))
@@ -661,10 +730,17 @@ def __init__(self, signal, receiver, sender, dispatch_uid=None):
self.dispatch_uid = dispatch_uid
def __enter__(self):
- self.signal.disconnect(receiver=self.receiver, sender=self.sender, dispatch_uid=self.dispatch_uid)
+ self.signal.disconnect(
+ receiver=self.receiver, sender=self.sender, dispatch_uid=self.dispatch_uid
+ )
def __exit__(self, exc_type, exc_val, exc_tb):
- self.signal.connect(receiver=self.receiver, sender=self.sender, weak=False, dispatch_uid=self.dispatch_uid)
+ self.signal.connect(
+ receiver=self.receiver,
+ sender=self.sender,
+ weak=False,
+ dispatch_uid=self.dispatch_uid,
+ )
def fill_tree_route(pure_tree, pre_routes=None):
@@ -676,7 +752,9 @@ def fill_tree_route(pure_tree, pre_routes=None):
pure_tree["route"] = pre_routes
for child in pure_tree.get("children", []):
- fill_tree_route(child, pre_routes + [{"id": pure_tree["id"], "name": pure_tree["name"]}])
+ fill_tree_route(
+ child, pre_routes + [{"id": pure_tree["id"], "name": pure_tree["name"]}]
+ )
def build_tree(raw_nodes, parent_name, empty_parent=None, need_route=False):
@@ -718,7 +796,7 @@ def jsonschema_validate(schema, instance):
def walk(node):
- """ iterate tree in pre-order depth-first search order """
+ """iterate tree in pre-order depth-first search order"""
yield node
for child in node.get("children", []):
for item in walk(child):
@@ -815,7 +893,7 @@ def _convert(obj, converted):
def namedtuplefetchall(cursor):
"Return all rows from a cursor as a namedtuple"
desc = cursor.description
- nt_result = namedtuple('Result', [col[0] for col in desc])
+ nt_result = namedtuple("Result", [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]
diff --git a/itsm/component/utils/batch.py b/itsm/component/utils/batch.py
index a4fa8985a..8ec1e52da 100644
--- a/itsm/component/utils/batch.py
+++ b/itsm/component/utils/batch.py
@@ -28,19 +28,19 @@
# AttributeError: 'Thread' object has no attribute '_children'
# This probably happens due to a bug in multiprocessing.dummy (see here and here) that existed
# before python 2.7.5 and 3.3.2.
-#
+#
# Solution A - Upgrade Python
-#
+#
# Solution B - Modify dummy
# multiprocessing/dummy/__init__.py, edit the start method within the DummyProcess class as follows
# if hasattr(self._parent, '_children'): # add this line
# self._parent._children[self] = None # indent this existing line
-#
-#
+#
+#
# Solution C - Monkey Patch
# Let's make it available in our namespace:
# from multiprocessing import dummy as __mp_dummy
-#
+#
# Now we can define a replacement and patch DummyProcess:
# def __DummyProcess_start_patch(self): # pulled from an updated version of Python
# assert self._parent is __mp_dummy.current_process() # modified to avoid further imports
@@ -53,7 +53,7 @@
from multiprocessing.dummy import Pool as ThreadPool
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
# 线程池容量上限
diff --git a/itsm/component/utils/client_backend_query.py b/itsm/component/utils/client_backend_query.py
index c16a78ff5..19acfe4cd 100644
--- a/itsm/component/utils/client_backend_query.py
+++ b/itsm/component/utils/client_backend_query.py
@@ -28,7 +28,7 @@
from math import ceil
from django.conf import settings
from django.core.cache import cache
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.constants import CACHE_5MIN, CACHE_30MIN, PREFIX_KEY
@@ -52,7 +52,11 @@ def get_biz_choices():
apps = get_all_apps()
app_list = [
- {"key": item["bk_biz_id"], "name": item["bk_biz_name"], "desc": _("请选择关联业务")}
+ {
+ "key": item["bk_biz_id"],
+ "name": item["bk_biz_name"],
+ "desc": _("请选择关联业务"),
+ }
for item in apps
]
@@ -80,7 +84,11 @@ def get_group_app_list(apps, group_apps, group_other, biz_group_conf):
if apps:
group_other["items"].extend(
[
- {"key": a["bk_biz_id"], "name": a["bk_biz_name"], "desc": _("请选择关联业务")}
+ {
+ "key": a["bk_biz_id"],
+ "name": a["bk_biz_name"],
+ "desc": _("请选择关联业务"),
+ }
for a in list(apps.values())
]
)
@@ -304,7 +312,8 @@ def get_department_users(department_id, recursive=False, detail=False):
cache.set(cache_key, users, CACHE_5MIN)
except ComponentCallError as e:
logger.error(
- "获取组织架构用户失败:department_id=%s, error=%s" % (department_id, str(e))
+ "获取组织架构用户失败:department_id=%s, error=%s"
+ % (department_id, str(e))
)
return []
@@ -338,7 +347,9 @@ def get_department_info(department_id):
try:
res = client_backend.usermanage.retrieve_department({"id": department_id})
except ComponentCallError as e:
- logger.error("获取组织架构详情失败:department_id=%s, error=%s" % (department_id, str(e)))
+ logger.error(
+ "获取组织架构详情失败:department_id=%s, error=%s" % (department_id, str(e))
+ )
return []
return res
@@ -395,7 +406,9 @@ def get_components(system_names):
)
return res.get("data", [])
except Exception as e:
- logger.error("获取指定系统的组件列表: system_names=%s, error=%s" % (system_names, str(e)))
+ logger.error(
+ "获取指定系统的组件列表: system_names=%s, error=%s" % (system_names, str(e))
+ )
return []
diff --git a/itsm/component/utils/conversion.py b/itsm/component/utils/conversion.py
index 6b2b6b16c..5d48eae21 100644
--- a/itsm/component/utils/conversion.py
+++ b/itsm/component/utils/conversion.py
@@ -26,7 +26,7 @@
import json
import re
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mako.template import Template
from itsm.component.exceptions import ParamError
diff --git a/itsm/gateway/urls.py b/itsm/gateway/urls.py
index dc640dc3e..b4ae38628 100644
--- a/itsm/gateway/urls.py
+++ b/itsm/gateway/urls.py
@@ -26,56 +26,58 @@
__author__ = "蓝鲸智云"
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
-from django.conf.urls import url
+from django.urls import re_path
from itsm.gateway import views
urlpatterns = [
- url(r"^test/token/$", views.get_token),
- url(r"^bk_login/get_batch_users/$", views.get_batch_users),
- url(r"^bk_login/get_all_users/$", views.get_all_users),
- url(r"^cmdb/get_app_list/$", views.get_app_list),
- url(r"^usermanage/get_departments/$", views.get_departments),
- url(
+ re_path(r"^test/token/$", views.get_token),
+ re_path(r"^bk_login/get_batch_users/$", views.get_batch_users),
+ re_path(r"^bk_login/get_all_users/$", views.get_all_users),
+ re_path(r"^cmdb/get_app_list/$", views.get_app_list),
+ re_path(r"^usermanage/get_departments/$", views.get_departments),
+ re_path(
r"^usermanage/get_first_level_departments/$", views.get_first_level_departments
),
- url(r"^usermanage/get_department_info/$", views.get_department_info),
- url(r"^usermanage/get_department_users/$", views.get_department_users),
- url(r"^usermanage/get_department_users_count/$", views.get_department_users_count),
- url(r"^usermanage/get_user_info/$", views.get_user_info),
- url(r"^sops/get_user_project_list/$", views.get_user_project_list),
- url(r"^sops/get_template_list/$", views.get_template_list),
- url(r"^sops/get_template_detail/$", views.get_template_detail),
- url(r"^sops/get_unfinished_sops_tasks/$", views.get_unfinished_sops_tasks),
- url(r"^sops/get_sops_tasks/$", views.get_sops_tasks),
- url(r"^sops/get_sops_tasks_detail/$", views.get_sops_tasks_detail),
- url(r"^sops/get_sops_template_schemes/$", views.get_sops_template_schemes),
- url(r"^sops/get_sops_preview_task_tree/$", views.get_sops_preview_task_tree),
- url(
+ re_path(r"^usermanage/get_department_info/$", views.get_department_info),
+ re_path(r"^usermanage/get_department_users/$", views.get_department_users),
+ re_path(
+ r"^usermanage/get_department_users_count/$", views.get_department_users_count
+ ),
+ re_path(r"^usermanage/get_user_info/$", views.get_user_info),
+ re_path(r"^sops/get_user_project_list/$", views.get_user_project_list),
+ re_path(r"^sops/get_template_list/$", views.get_template_list),
+ re_path(r"^sops/get_template_detail/$", views.get_template_detail),
+ re_path(r"^sops/get_unfinished_sops_tasks/$", views.get_unfinished_sops_tasks),
+ re_path(r"^sops/get_sops_tasks/$", views.get_sops_tasks),
+ re_path(r"^sops/get_sops_tasks_detail/$", views.get_sops_tasks_detail),
+ re_path(r"^sops/get_sops_template_schemes/$", views.get_sops_template_schemes),
+ re_path(r"^sops/get_sops_preview_task_tree/$", views.get_sops_preview_task_tree),
+ re_path(
r"^sops/get_sops_preview_common_task_tree/$",
views.get_sops_preview_common_task_tree,
),
- url(r"^devops/get_user_pipeline_list/$", views.get_user_pipeline_list),
- url(r"^devops/get_user_projects/$", views.get_user_projects),
- url(
+ re_path(r"^devops/get_user_pipeline_list/$", views.get_user_pipeline_list),
+ re_path(r"^devops/get_user_projects/$", views.get_user_projects),
+ re_path(
r"^devops/get_pipeline_build_start_info/$", views.get_pipeline_build_start_info
),
- url(r"^devops/get_user_pipeline_detail/$", views.get_user_pipeline_detail),
- url(r"^devops/get_pipeline_build_list/$", views.get_pipeline_build_list),
- url(r"^devops/start_user_pipeline/$", views.start_user_pipeline),
- url(
+ re_path(r"^devops/get_user_pipeline_detail/$", views.get_user_pipeline_detail),
+ re_path(r"^devops/get_pipeline_build_list/$", views.get_pipeline_build_list),
+ re_path(r"^devops/start_user_pipeline/$", views.start_user_pipeline),
+ re_path(
r"^devops/get_user_pipeline_build_status/$",
views.get_user_pipeline_build_status,
),
- url(
+ re_path(
r"^devops/get_user_pipeline_build_detail/$",
views.get_user_pipeline_build_detail,
),
- url(
+ re_path(
r"^devops/get_pipeline_build_artifactory/$",
views.get_pipeline_build_artifactory,
),
- url(
+ re_path(
r"^devops/get_pipeline_build_artifactory_download_url/$",
views.get_pipeline_build_artifactory_download_url,
),
diff --git a/itsm/gateway/views.py b/itsm/gateway/views.py
index 122fbc079..05c5decba 100644
--- a/itsm/gateway/views.py
+++ b/itsm/gateway/views.py
@@ -26,7 +26,7 @@
import json
from django.core.cache import cache
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django.views.decorators.cache import cache_page
from django.http import JsonResponse, HttpResponse
@@ -116,7 +116,9 @@ def get_batch_users(request):
return Success(res).json()
except Exception as error:
logger.warning(_("批量获取用户信息出错,%s"), str(error))
- return Fail(_("批量获取用户信息出错,%s") % str(error), "BK_LOGIN.GET_BATCH_USERS").json()
+ return Fail(
+ _("批量获取用户信息出错,%s") % str(error), "BK_LOGIN.GET_BATCH_USERS"
+ ).json()
@fbv_exception_handler
@@ -513,7 +515,9 @@ def get_user_pipeline_list(request):
pipeline_list.extend(batch_process(get_user_pipeline_singel_page, kwarg_list))
return Success(pipeline_list).json()
except Exception as e:
- return Fail(_("批量获取流水线出错:{}".format(str(e))), "BK_LOGIN.GET_BATCH_USERS").json()
+ return Fail(
+ _("批量获取流水线出错:{}".format(str(e))), "BK_LOGIN.GET_BATCH_USERS"
+ ).json()
@fbv_exception_handler
diff --git a/itsm/helper/tasks.py b/itsm/helper/tasks.py
index 7e41c326c..d18438ff1 100644
--- a/itsm/helper/tasks.py
+++ b/itsm/helper/tasks.py
@@ -33,7 +33,7 @@
import json
import os
-from celery import task
+from celery import shared_task
from django.conf import settings
from django.db import connection
from django.db.models import F
@@ -71,7 +71,7 @@
from itsm.workflow.models import DefaultField, Field, State, Workflow, WorkflowVersion
-@task
+@shared_task
def _db_fix_for_blueapps_after_2_6_0():
"""
blueapps的数据升级
@@ -79,7 +79,9 @@ def _db_fix_for_blueapps_after_2_6_0():
migrations = (("account", "0002_init_superuser"), ("account", "0003_verifyinfo"))
if settings.RUN_VER != "open":
logger.Exception(
- "当前运行环境为:{},不支持db_fix_for_blueapps_after_2_6_0方法".format(settings.RUN_VER)
+ "当前运行环境为:{},不支持db_fix_for_blueapps_after_2_6_0方法".format(
+ settings.RUN_VER
+ )
)
return
try:
@@ -98,7 +100,7 @@ def _db_fix_for_blueapps_after_2_6_0():
logger.Exception(str(err))
-@task
+@shared_task
def _db_fix_for_workflow_to_2_5_9():
"""
流程任务的数据升级
@@ -135,7 +137,7 @@ def create_task(instances):
create_task(WorkflowVersion.objects.all())
-@task
+@shared_task
def _db_fix_for_service_catalog():
"""服务目录添加前置路径"""
print("start execute _db_fix_for_service_catalog")
@@ -146,17 +148,17 @@ def _db_fix_for_service_catalog():
print("finish execute _db_fix_for_service_catalog")
-@task
+@shared_task
def _db_fix_default_value_for_field():
fix_default_value_for_field()
-@task
+@shared_task
def _db_fix_for_ticket_processors():
migrate_processors_for_ticket()
-@task
+@shared_task
def _db_fix_for_attachments():
"""附件升级方案"""
@@ -248,7 +250,7 @@ def update_ticket_fields():
update_ticket_fields()
-@task
+@shared_task
def _db_fix_from_2_1_x_to_2_2_1():
"""
流程引擎版本升级迁移:
@@ -298,7 +300,9 @@ def _db_fix_from_2_1_x_to_2_2_1():
source_type="CUSTOM"
)
- TicketEventLog.objects.filter(message__in=["流程开始", "单据流程结束"]).update(source="SYS")
+ TicketEventLog.objects.filter(message__in=["流程开始", "单据流程结束"]).update(
+ source="SYS"
+ )
task_end = datetime.datetime.now()
SystemSettings.objects.filter(key="_db_fix_from_2_1_x_to_2_2_1").update(
@@ -310,7 +314,7 @@ def _db_fix_from_2_1_x_to_2_2_1():
)
-@task
+@shared_task
def _db_fix_from_1_1_22_to_2_1_x():
"""V1.1.x到V2.1.x的数据升级接口(建议提前做好数据备份)"""
@@ -343,7 +347,7 @@ def _db_fix_from_1_1_22_to_2_1_x():
logger.info("_db_fix_from_1_1_22_to_2_1_x fail: %s" % str(e))
-@task
+@shared_task
def _db_fix_after_2_0_3():
"""
修复数据库数据:
@@ -377,7 +381,7 @@ def _db_fix_after_2_0_3():
logger.error("db_fix_after_2_0_3 fail! error: %s" % str(e))
-@task
+@shared_task
def _db_fix_after_2_0_7():
"""
日志新增处理人员快照
@@ -405,7 +409,7 @@ def _db_fix_after_2_0_7():
)
-@task
+@shared_task
def _db_fix_after_2_0_9():
try:
cnt = 0
@@ -442,7 +446,7 @@ def _db_fix_after_2_0_9():
logger.error("db_fix_after_2_0_9 fail! error: %s" % str(e))
-@task
+@shared_task
def _db_fix_after_2_1_x():
"""
第二次数据迁移:
@@ -473,7 +477,7 @@ def _db_fix_after_2_1_x():
).delete()
-@task
+@shared_task
def _db_fix_after_2_0_14():
try:
Ticket.objects.filter(
@@ -484,7 +488,7 @@ def _db_fix_after_2_0_14():
logger.error("db_fix_after_2_0_14 fail! error: %s" % str(e))
-@task
+@shared_task
def _db_fix_after_2_1_1():
try:
TicketEventLog.objects.filter(message__contains="驳回").update(
@@ -495,7 +499,7 @@ def _db_fix_after_2_1_1():
logger.error("db_fix_after_2_1_1 fail! error: %s" % str(e))
-@task
+@shared_task
def _fix_ticket_title():
tickets = Ticket.objects.filter(is_deleted=False, is_draft=False)
try:
@@ -509,7 +513,7 @@ def _fix_ticket_title():
logger.error("fix_ticket_title fail! error: %s" % str(e))
-@task
+@shared_task
def _update_logs_type():
try:
TicketEventLog.objects.filter(message__contains="】终止,原因:【").update(
@@ -520,7 +524,7 @@ def _update_logs_type():
logger.error("update_logs_type fail! error: %s" % str(e))
-@task
+@shared_task
def _db_fix_sla():
try:
choices = OldSla.objects.values(
@@ -539,7 +543,7 @@ def _db_fix_sla():
logger.error("_db_fix_sla fail! error: %s" % str(e))
-@task
+@shared_task
def _db_fix_after_2_1_9():
try:
TicketField.objects.update(related_fields={})
@@ -551,7 +555,7 @@ def _db_fix_after_2_1_9():
logger.error("_db_fix_after_2_1_9 fail!, error: %s" % str(e))
-@task
+@shared_task
def _db_fix_ticket_end_at_after_2_0_5():
try:
Ticket.objects.filter(
@@ -562,7 +566,7 @@ def _db_fix_ticket_end_at_after_2_0_5():
logger.error("_db_fix_ticket_end_at_after_2_0_5 fail!, error: %s" % str(e))
-@task
+@shared_task
def _db_fix_deal_time_after_2_0_5():
try:
for log in TicketEventLog.objects.filter(type="CLAIM", deal_time=0):
diff --git a/itsm/helper/urls.py b/itsm/helper/urls.py
index 5d92b79eb..873e0eeb6 100644
--- a/itsm/helper/urls.py
+++ b/itsm/helper/urls.py
@@ -26,39 +26,45 @@
__author__ = "蓝鲸智云"
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
-from django.conf.urls import url
+from django.urls import re_path
from itsm.helper import views
urlpatterns = [
# 统一的升级接口
- url(r'^db_fix_from_1_1_22_to_2_1_16/$', views.db_fix_from_1_1_22_to_2_1_16),
- url(r'^db_fix_from_2_1_x_to_2_2_1/$', views.db_fix_from_2_1_x_to_2_2_1),
- url(r'^db_fix_after_2_2_17/$', views.db_fix_after_2_2_17),
- url(r'^db_fix_after_2_3_1/$', views.db_fix_after_2_3_1),
+ re_path(r"^db_fix_from_1_1_22_to_2_1_16/$", views.db_fix_from_1_1_22_to_2_1_16),
+ re_path(r"^db_fix_from_2_1_x_to_2_2_1/$", views.db_fix_from_2_1_x_to_2_2_1),
+ re_path(r"^db_fix_after_2_2_17/$", views.db_fix_after_2_2_17),
+ re_path(r"^db_fix_after_2_3_1/$", views.db_fix_after_2_3_1),
# 杂乱的升级接口
- url(r'^fix_ticket_title/$', views.fix_ticket_title),
- url(r'^update_logs_type/$', views.update_logs_type),
- url(r'^db_fix_after_2_0_3/$', views.db_fix_after_2_0_3),
- url(r'^db_fix_ticket_end_at_after_2_0_5/$', views.db_fix_ticket_end_at_after_2_0_5),
- url(r'^db_fix_deal_time_after_2_0_5/$', views.db_fix_deal_time_after_2_0_5),
- url(r'^db_fix_after_2_0_7/$', views.db_fix_after_2_0_7),
- url(r'^db_fix_after_2_0_9/$', views.db_fix_after_2_0_9),
- url(r'^db_fix_after_2_0_14/$', views.db_fix_after_2_0_14),
- url(r'^db_fix_after_2_1_x/$', views.db_fix_after_2_1_x),
- url(r'^db_fix_after_2_1_1/$', views.db_fix_after_2_1_1),
- url(r'^db_fix_sla/$', views.db_fix_sla),
- url(r'^db_fix_after_2_1_9/$', views.db_fix_after_2_1_9),
- url(r'^export_api_system/$', views.export_api_system),
- url(r'^db_fix_for_attachments/$', views.db_fix_for_attachments),
- url(r'^db_fix_for_service_catalog/$', views.db_fix_for_service_catalog),
- url(r'^weekly_statical/$', views.weekly_statical),
- url(r'^db_fix_for_workflow_after_2_5_9/$', views.db_fix_for_workflow_after_2_5_9),
- url(r'^db_fix_for_blueapps_after_2_6_0/$', views.db_fix_for_blueapps_after_2_6_0),
+ re_path(r"^fix_ticket_title/$", views.fix_ticket_title),
+ re_path(r"^update_logs_type/$", views.update_logs_type),
+ re_path(r"^db_fix_after_2_0_3/$", views.db_fix_after_2_0_3),
+ re_path(
+ r"^db_fix_ticket_end_at_after_2_0_5/$", views.db_fix_ticket_end_at_after_2_0_5
+ ),
+ re_path(r"^db_fix_deal_time_after_2_0_5/$", views.db_fix_deal_time_after_2_0_5),
+ re_path(r"^db_fix_after_2_0_7/$", views.db_fix_after_2_0_7),
+ re_path(r"^db_fix_after_2_0_9/$", views.db_fix_after_2_0_9),
+ re_path(r"^db_fix_after_2_0_14/$", views.db_fix_after_2_0_14),
+ re_path(r"^db_fix_after_2_1_x/$", views.db_fix_after_2_1_x),
+ re_path(r"^db_fix_after_2_1_1/$", views.db_fix_after_2_1_1),
+ re_path(r"^db_fix_sla/$", views.db_fix_sla),
+ re_path(r"^db_fix_after_2_1_9/$", views.db_fix_after_2_1_9),
+ re_path(r"^export_api_system/$", views.export_api_system),
+ re_path(r"^db_fix_for_attachments/$", views.db_fix_for_attachments),
+ re_path(r"^db_fix_for_service_catalog/$", views.db_fix_for_service_catalog),
+ re_path(r"^weekly_statical/$", views.weekly_statical),
+ re_path(
+ r"^db_fix_for_workflow_after_2_5_9/$", views.db_fix_for_workflow_after_2_5_9
+ ),
+ re_path(
+ r"^db_fix_for_blueapps_after_2_6_0/$", views.db_fix_for_blueapps_after_2_6_0
+ ),
# 获取settings内容
]
# urlpatterns += [
-# url(r'^dump_db/$', views_common.dump_db),
-# url(r'^drop_table/$', views_common.drop_table),
+# re_path(r'^dump_db/$', views_common.dump_db),
+# re_path(r'^drop_table/$', views_common.drop_table),
# ]
diff --git a/itsm/helper/views_common.py b/itsm/helper/views_common.py
index d9b2862bd..93617401b 100644
--- a/itsm/helper/views_common.py
+++ b/itsm/helper/views_common.py
@@ -33,55 +33,57 @@
from django.contrib.auth.decorators import permission_required
from django.db import connection
from django.http import HttpResponse
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
-@permission_required('is_superuser')
+@permission_required("is_superuser")
def dump_db(request):
"""
dbdump操作
"""
- db = settings.DATABASES['default']
- dbfile = 'static/{}.sql'.format(db.get('NAME'))
- dumpcmd = 'mysqldump'
+ db = settings.DATABASES["default"]
+ dbfile = "static/{}.sql".format(db.get("NAME"))
+ dumpcmd = "mysqldump"
dumpdb = (
- '{dumpcmd} --user={user} '
- '--password={password} '
- '--host={host} '
- '--port={port} '
- '--single-transaction '
- '{dbname} > {dbfile}'.format(
+ "{dumpcmd} --user={user} "
+ "--password={password} "
+ "--host={host} "
+ "--port={port} "
+ "--single-transaction "
+ "{dbname} > {dbfile}".format(
dumpcmd=dumpcmd,
- user=db.get('USER'),
- password=db.get('PASSWORD'),
- host=db.get('HOST'),
- port=db.get('HOST'),
- dbname=db.get('NAME'),
+ user=db.get("USER"),
+ password=db.get("PASSWORD"),
+ host=db.get("HOST"),
+ port=db.get("HOST"),
+ dbname=db.get("NAME"),
dbfile=dbfile,
)
)
- p = subprocess.Popen(dumpdb, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ p = subprocess.Popen(
+ dumpdb, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+ )
out, err = p.communicate()
try:
- with open(dbfile, 'rb') as fd:
+ with open(dbfile, "rb") as fd:
file_content = fd.read()
response = HttpResponse(file_content)
- response['Content-Type'] = 'application/octet-stream'
- response['Content-Disposition'] = 'attachment;filename="%s_%s.sql"' % (
- db.get('NAME'),
+ response["Content-Type"] = "application/octet-stream"
+ response["Content-Disposition"] = 'attachment;filename="%s_%s.sql"' % (
+ db.get("NAME"),
datetime.datetime.now(),
)
except IOError:
- return HttpResponse(_('
磁盘中不存在该文件!
'))
+ return HttpResponse(_("磁盘中不存在该文件!
"))
except Exception as e:
- return HttpResponse(_('系统异常!
%s
') % e)
+ return HttpResponse(_("系统异常!
%s
") % e)
return response
-@permission_required('is_superuser')
+@permission_required("is_superuser")
def drop_table(request):
"""
清空表,危险操作
@@ -100,6 +102,6 @@ def drop_table(request):
drop_table_sql = "drop table " + table_name # +" if exists "+table_name
cursor.execute(drop_table_sql)
- return HttpResponse(_('命令执行成功'))
+ return HttpResponse(_("命令执行成功"))
except Exception as e:
- return HttpResponse(_('命令执行异常:%s') % e)
+ return HttpResponse(_("命令执行异常:%s") % e)
diff --git a/itsm/iadmin/contants.py b/itsm/iadmin/contants.py
index 0f7994861..b037c225e 100644
--- a/itsm/iadmin/contants.py
+++ b/itsm/iadmin/contants.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
EMAIL,
@@ -392,7 +392,9 @@
"""
-TASK_GENERAL_CONTENT_COMMON = TASK_SMS_CONTENT_COMMON = TASK_WEIXIN_CONTENT_COMMON = """
+TASK_GENERAL_CONTENT_COMMON = TASK_SMS_CONTENT_COMMON = (
+ TASK_WEIXIN_CONTENT_COMMON
+) = """
标题:${title}
单号:${sn}
任务名称:${task_name}
@@ -402,18 +404,24 @@
任务创建时间:${task_create_at}
${message}"""
-SMS_CONTENT_COMMON = WEIXIN_CONTENT_COMMON = """
+SMS_CONTENT_COMMON = (
+ WEIXIN_CONTENT_COMMON
+) = """
标题:${title}
单号:${sn}
${message}"""
-GENERAL_CONTENT_DONE = SMS_CONTENT_DONE = WEIXIN_CONTENT_DONE = """您的需求(${title})已经处理完成,
+GENERAL_CONTENT_DONE = SMS_CONTENT_DONE = (
+ WEIXIN_CONTENT_DONE
+) = """您的需求(${title})已经处理完成,
现邀请您为我们的服务进行评价。
您的反馈对我们非常重要!
感谢回复与建议,祝您工作愉快!
${ticket_url}""" # noqa
-GENERAL_CONTENT_FOLLOW = SMS_CONTENT_FOLLOW = WEIXIN_CONTENT_FOLLOW = """你有一条${service_type_name}工单需要关注
+GENERAL_CONTENT_FOLLOW = SMS_CONTENT_FOLLOW = (
+ WEIXIN_CONTENT_FOLLOW
+) = """你有一条${service_type_name}工单需要关注
标题:${title}
单号:${sn}
服务目录:${catalog_service_name}
@@ -425,21 +433,27 @@
服务目录:${catalog_service_name}
当前环节:${running_status}"""
-SMS_CONTENT_FAILED = WEIXIN_CONTENT_FAILED = """
+SMS_CONTENT_FAILED = (
+ WEIXIN_CONTENT_FAILED
+) = """
节点自动执行失败
标题:${title}
单号:${sn}
当前环节:${running_status}
失败信息:${message}"""
-GENERAL_CONTENT_OPERATE = WEIXIN_CONTENT_OPERATE = """
+GENERAL_CONTENT_OPERATE = (
+ WEIXIN_CONTENT_OPERATE
+) = """
标题:${title}
单号:${sn}
服务目录:${catalog_service_name}
当前环节:${running_status}
"""
-GENERAL_CONTENT_COMMON = ATTENTION_SMS_CONTENT_COMMON = ATTENTION_WEIXIN_CONTENT_COMMON = """
+GENERAL_CONTENT_COMMON = ATTENTION_SMS_CONTENT_COMMON = (
+ ATTENTION_WEIXIN_CONTENT_COMMON
+) = """
标题:${title}
单号:${sn}
服务:${catalog_service_name}
diff --git a/itsm/iadmin/forms.py b/itsm/iadmin/forms.py
index e615a7c07..c25ff1f3f 100644
--- a/itsm/iadmin/forms.py
+++ b/itsm/iadmin/forms.py
@@ -25,14 +25,18 @@
from django import forms
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import LEN_LONG
class CustomNotifyForm(forms.Form):
title_template = forms.CharField(
- max_length=LEN_LONG, label=_("标题模板"), help_text=_("工单字段的值可以作为参数写到模板中,格式如:【ITSM】${service}管理单【${action}】提醒")
+ max_length=LEN_LONG,
+ label=_("标题模板"),
+ help_text=_(
+ "工单字段的值可以作为参数写到模板中,格式如:【ITSM】${service}管理单【${action}】提醒"
+ ),
)
content_template = forms.CharField(
widget=forms.Textarea(attrs={"cols": "120", "rows": "20"}),
diff --git a/itsm/iadmin/models.py b/itsm/iadmin/models.py
index 24075942d..164479459 100644
--- a/itsm/iadmin/models.py
+++ b/itsm/iadmin/models.py
@@ -30,7 +30,7 @@
import mistune
from django.db import models
from django.db.models import QuerySet
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from config.default import PROJECT_ROOT
from itsm.component.constants import (
@@ -69,7 +69,10 @@ class Model(models.Model):
_objects = models.Manager()
- auth_resource = {"resource_type": "system_settings", "resource_type_name": "系统配置"}
+ auth_resource = {
+ "resource_type": "system_settings",
+ "resource_type_name": "系统配置",
+ }
resource_operations = ["system_settings_manage"]
class Meta:
@@ -87,7 +90,9 @@ def hard_delete(self, using=None):
class SystemSettings(Model):
type = models.CharField(_("类型"), max_length=LEN_NORMAL)
key = models.CharField(_("关键字唯一标识"), max_length=LEN_NORMAL, unique=True)
- value = models.TextField(_("系统设置值"), default=EMPTY_STRING, null=True, blank=True)
+ value = models.TextField(
+ _("系统设置值"), default=EMPTY_STRING, null=True, blank=True
+ )
objects = managers.Manager()
@@ -122,7 +127,9 @@ class CustomNotice(models.Model):
default="",
null=True,
blank=True,
- help_text=_("工单字段的值可以作为参数写到模板中,格式如:【ITSM】${service}管理单【${action}】提醒"),
+ help_text=_(
+ "工单字段的值可以作为参数写到模板中,格式如:【ITSM】${service}管理单【${action}】提醒"
+ ),
)
content_template = models.TextField(
_("内容模板"),
@@ -132,7 +139,10 @@ class CustomNotice(models.Model):
help_text=_("工单字段的值可以作为参数写到模板中,格式如:单号:${sn}"),
)
action = models.CharField(
- _("通知模板类型"), max_length=LEN_SHORT, choices=ACTION_CHOICES, default="default"
+ _("通知模板类型"),
+ max_length=LEN_SHORT,
+ choices=ACTION_CHOICES,
+ default="default",
)
notify_type = models.CharField(_("通知方式"), max_length=LEN_SHORT, default="EMAIL")
create_at = models.DateTimeField(_("创建时间"), auto_now_add=True)
@@ -142,7 +152,10 @@ class CustomNotice(models.Model):
version = models.CharField(_("版本"), max_length=LEN_SHORT, default="V1")
project_key = models.CharField(
- _("项目key"), max_length=LEN_SHORT, null=False, default=PUBLIC_PROJECT_PROJECT_KEY
+ _("项目key"),
+ max_length=LEN_SHORT,
+ null=False,
+ default=PUBLIC_PROJECT_PROJECT_KEY,
)
auth_resource = {"resource_type": "project", "resource_type_name": "项目"}
@@ -331,7 +344,9 @@ class Data(models.Model):
type = models.CharField(
_("类型"), choices=TYPE_CHOICES, default="string", max_length=LEN_SHORT
)
- expire_at = models.DateTimeField(_("过期时间"), null=True, blank=True, db_index=True)
+ expire_at = models.DateTimeField(
+ _("过期时间"), null=True, blank=True, db_index=True
+ )
objects = DataManager()
diff --git a/itsm/iadmin/permissions.py b/itsm/iadmin/permissions.py
index 405b94893..30379acc4 100644
--- a/itsm/iadmin/permissions.py
+++ b/itsm/iadmin/permissions.py
@@ -25,7 +25,7 @@
from functools import wraps
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import permissions
from rest_framework.exceptions import MethodNotAllowed
@@ -138,16 +138,18 @@ class CustomNotifyPermit(IamAuthPermit):
def has_permission(self, request, view):
if view.action in getattr(view, "permission_free_actions", []):
return True
-
+
# 获取项目标识
if view.action in ["list"]:
- project_key = request.query_params.get("project_key", PUBLIC_PROJECT_PROJECT_KEY)
+ project_key = request.query_params.get(
+ "project_key", PUBLIC_PROJECT_PROJECT_KEY
+ )
elif view.action in ["destroy"]:
instance = view.get_object()
project_key = instance.project_key
else:
project_key = request.data.get("project_key", PUBLIC_PROJECT_PROJECT_KEY)
-
+
# 平台管理
if project_key == PUBLIC_PROJECT_PROJECT_KEY:
# 平台管理限制创建新通知规则
@@ -155,7 +157,7 @@ def has_permission(self, request, view):
raise MethodNotAllowed(request.method)
apply_actions = ["notification_view", "platform_manage_access"]
return self.iam_auth(request, apply_actions)
-
+
# 项目管理
project = Project.objects.get(pk=project_key)
apply_actions = ["system_settings_manage"]
@@ -167,12 +169,12 @@ def has_object_permission(self, request, view, obj, **kwargs):
# 平台管理限制删除
if view.action in ["destroy"]:
raise MethodNotAllowed(request.method)
-
+
apply_actions = ["notification_view", "platform_manage_access"]
if view.action in ["update"]:
apply_actions.append("notification_manage")
return self.iam_auth(request, apply_actions)
-
+
# 项目:通知配置
project = Project.objects.filter(pk=obj.project_key).first()
return super().has_object_permission(request, view, project, **kwargs)
diff --git a/itsm/iadmin/serializers.py b/itsm/iadmin/serializers.py
index e473babd7..1fa12b394 100644
--- a/itsm/iadmin/serializers.py
+++ b/itsm/iadmin/serializers.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import empty
@@ -134,7 +134,9 @@ def create(self, validated_data):
project_key=project_key,
used_by=used_by,
).exists():
- raise serializers.ValidationError(_("该项目下已存在相同的通知配置,不能重复添加"))
+ raise serializers.ValidationError(
+ _("该项目下已存在相同的通知配置,不能重复添加")
+ )
return super(CustomNotifySerializer, self).create(validated_data=validated_data)
diff --git a/itsm/iadmin/tasks.py b/itsm/iadmin/tasks.py
index b3d1d1433..a1dbc7e2d 100644
--- a/itsm/iadmin/tasks.py
+++ b/itsm/iadmin/tasks.py
@@ -23,13 +23,13 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from celery import task
+from celery import shared_task
from common.log import logger
from itsm.iadmin.models import MigrateLogs
-@task
+@shared_task
def db_fix_by_version_list(need_exe_func_list, migrate_id):
"""执行需要执行的函数"""
migrate_log = MigrateLogs.objects.filter(id=migrate_id)
@@ -38,8 +38,8 @@ def db_fix_by_version_list(need_exe_func_list, migrate_id):
try:
for item in need_exe_func_list:
item()
- migrate_log.update(is_finished=True, is_success=True, note='升级成功')
+ migrate_log.update(is_finished=True, is_success=True, note="升级成功")
logger.info("db_fix_by_version_list success!")
except Exception as e:
- migrate_log.update(is_finished=True, is_success=False, note='升级失败')
- logger.error('db_fix_by_version_list fail!, error: %s' % str(e))
+ migrate_log.update(is_finished=True, is_success=False, note="升级失败")
+ logger.error("db_fix_by_version_list fail!, error: %s" % str(e))
diff --git a/itsm/iadmin/validators.py b/itsm/iadmin/validators.py
index 2532dc35b..415233b69 100644
--- a/itsm/iadmin/validators.py
+++ b/itsm/iadmin/validators.py
@@ -28,7 +28,7 @@
from functools import reduce
from django.conf import settings
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.esb.esbclient import client_backend
@@ -129,7 +129,9 @@ def validate_is_organization(value):
).values_list("name", flat=True)
if service_values.exists():
raise OrganizationStructureFunctionSwitchValidateError(
- _("以下服务正在使用组织架构功能,请更改再关闭:{}").format(",".join(service_values))
+ _("以下服务正在使用组织架构功能,请更改再关闭:{}").format(
+ ",".join(service_values)
+ )
)
@staticmethod
@@ -149,14 +151,18 @@ def validate_child_ticket_switch(value):
id__in=all_ticket_ids, current_status=PROCESS_RUNNING
).exists()
if active_ticket:
- raise ChildTicketSwitchValidateError(_("存在未完成的含有母子单的单据,请处理后再关闭"))
+ raise ChildTicketSwitchValidateError(
+ _("存在未完成的含有母子单的单据,请处理后再关闭")
+ )
@staticmethod
def validate_task_switch(value):
if value.get("value") == SWITCH_OFF:
active_task = Task.objects.filter(status__in=ACTIVE_TASK_STATUS).exists()
if active_task:
- raise TaskSwitchValidateError(_("存在含有未完成任务的单据,请处理后再关闭"))
+ raise TaskSwitchValidateError(
+ _("存在含有未完成任务的单据,请处理后再关闭")
+ )
@staticmethod
def validate_trigger_switch(value):
@@ -166,7 +172,9 @@ def validate_trigger_switch(value):
source_type__in=quoted_status
).exists()
if active_trigger:
- raise TriggerSwitchValidateError(_("存在被引用的触发器,请处理后再关闭"))
+ raise TriggerSwitchValidateError(
+ _("存在被引用的触发器,请处理后再关闭")
+ )
@staticmethod
def validate_other(value):
diff --git a/itsm/iadmin/views.py b/itsm/iadmin/views.py
index 2039c3da0..b7909f5fd 100644
--- a/itsm/iadmin/views.py
+++ b/itsm/iadmin/views.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework.decorators import action
from rest_framework.response import Response
@@ -95,7 +95,7 @@ class CustomNotifyViewSet(ModelViewSet):
serializer_class = CustomNotifySerializer
queryset = CustomNotice.objects.all()
pagination_class = None
-
+
permission_classes = (CustomNotifyPermit,)
permission_free_actions = ["variable_list", "action_type"]
permission_action_default = "system_settings_manage"
diff --git a/itsm/misc/urls.py b/itsm/misc/urls.py
index becbdbb6c..d387183f4 100644
--- a/itsm/misc/urls.py
+++ b/itsm/misc/urls.py
@@ -23,12 +23,12 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.conf.urls import url
+from django.urls import re_path
from itsm.misc import views
urlpatterns = [
- url(r"^upload_file/$", views.upload),
- url(r"^download_file/$", views.download),
- url(r"^clean_cache/$", views.clean_cache),
+ re_path(r"^upload_file/$", views.upload),
+ re_path(r"^download_file/$", views.download),
+ re_path(r"^clean_cache/$", views.clean_cache),
]
diff --git a/itsm/misc/views.py b/itsm/misc/views.py
index 204d45bb5..0079d3a45 100644
--- a/itsm/misc/views.py
+++ b/itsm/misc/views.py
@@ -36,7 +36,7 @@
from django.db import connection
from django.http import StreamingHttpResponse
from django.utils.encoding import escape_uri_path
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET, require_POST
@@ -66,7 +66,9 @@ def clean_cache(request):
# 更新用户角色表的更新时间,达到清理缓存目的
from itsm.role.models import BKUserRole
- BKUserRole.objects.update(update_at=datetime.datetime.now() - datetime.timedelta(minutes=30))
+ BKUserRole.objects.update(
+ update_at=datetime.datetime.now() - datetime.timedelta(minutes=30)
+ )
return Success(message=_("缓存更新成功")).json()
except Exception as e:
@@ -82,9 +84,15 @@ def compile_file_path(request):
tmp_key = request.GET.get("key") or ("tmp_%s" % int(time.time()))
system_file_path = SystemSettings.objects.get(key="SYS_FILE_PATH").value
- file_prefix = request.GET.get("ticket_id") or "workflow_%s" % request.GET.get("workflow_id")
+ file_prefix = request.GET.get("ticket_id") or "workflow_%s" % request.GET.get(
+ "workflow_id"
+ )
- file_path = os.path.join(system_file_path, "%s_%s" % (file_prefix, request.GET.get("state_id", "")), tmp_key)
+ file_path = os.path.join(
+ system_file_path,
+ "%s_%s" % (file_prefix, request.GET.get("state_id", "")),
+ tmp_key,
+ )
return file_path, tmp_key
@@ -140,6 +148,8 @@ def download(request):
response = StreamingHttpResponse(FileWrapper(store.open(file_path, "rb"), 512))
response["Content-Type"] = "application/octet-stream"
- response["Content-Disposition"] = "attachment; filename* = UTF-8''%s" % format(escape_uri_path(file_name))
+ response["Content-Disposition"] = "attachment; filename* = UTF-8''%s" % format(
+ escape_uri_path(file_name)
+ )
return response
diff --git a/itsm/monitor/urls.py b/itsm/monitor/urls.py
index 3970f35ed..45a439be9 100644
--- a/itsm/monitor/urls.py
+++ b/itsm/monitor/urls.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
-from django.conf.urls import url
+from django.urls import re_path
from itsm.monitor.views import healthz, ping
urlpatterns = [
# main
- url(r"^healthz/$", healthz),
- url(r"ping/$", ping),
+ re_path(r"^healthz/$", healthz),
+ re_path(r"ping/$", ping),
]
diff --git a/itsm/openapi/base_service/serializers.py b/itsm/openapi/base_service/serializers.py
index 687b094cb..63d399274 100644
--- a/itsm/openapi/base_service/serializers.py
+++ b/itsm/openapi/base_service/serializers.py
@@ -3,7 +3,7 @@
from django.db import transaction
from django.db.models.signals import post_save
from rest_framework import serializers
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import DEFAULT_BK_BIZ_ID
from itsm.component.exceptions import ParamError
@@ -52,7 +52,11 @@ def create(self, validated_data):
workflow_meta = validated_data.pop("workflow_meta")
if Workflow.objects.filter(name=workflow_meta["name"]).exists():
raise serializers.ValidationError(
- {str(_("参数校验失败")): _("系统中已存在同名流程,请尝试换个流程名称")}
+ {
+ str(_("参数校验失败")): _(
+ "系统中已存在同名流程,请尝试换个流程名称"
+ )
+ }
)
with TempDisableSignal(post_save, init_after_workflow_created, Workflow):
work_flow_instance = Workflow.objects.create(
@@ -163,7 +167,11 @@ def key_validate(self, value):
flow_id=value.get("workflow").id, key=value.get("key")
).exists()
):
- raise ParamError(_("当前流程已存在唯一标识【{}】,请重新输入").format(value.get("key")))
+ raise ParamError(
+ _("当前流程已存在唯一标识【{}】,请重新输入").format(
+ value.get("key")
+ )
+ )
class BatchSaveFieldSerializer(FieldSerializer):
@@ -267,7 +275,9 @@ def run_validation(self, data):
service_type=service.key, is_start=True
).key
except TicketStatus.DoesNotExist:
- raise serializers.ValidationError({_("工单状态"): _("工单状态不存在,请检查")})
+ raise serializers.ValidationError(
+ {_("工单状态"): _("工单状态不存在,请检查")}
+ )
# 创建单据时,若没有传入creator参数,则采用request的当前用户
creator = data.get("creator", self.context["request"].user.username)
diff --git a/itsm/openapi/base_service/validator.py b/itsm/openapi/base_service/validator.py
index 598a9de1d..ccf4fc130 100644
--- a/itsm/openapi/base_service/validator.py
+++ b/itsm/openapi/base_service/validator.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.ticket.validators import (
derive_validate,
@@ -41,12 +41,20 @@ def create_validate(self, value, fields, username, **kwargs):
flow = WorkflowVersion.objects.get(id=flow_id)
except WorkflowVersion.DoesNotExist:
raise CreateTicketError(
- _("单据创建失败,flow_id 对应的流程版本不存在, flow_id={}".format(flow_id))
+ _(
+ "单据创建失败,flow_id 对应的流程版本不存在, flow_id={}".format(
+ flow_id
+ )
+ )
)
if flow.workflow_id != service.workflow.workflow_id:
raise CreateTicketError(
- _("单据创建失败,flow_id对应的流程与该服务绑定的流程不一致,flow_id:{}".format(flow_id))
+ _(
+ "单据创建失败,flow_id对应的流程与该服务绑定的流程不一致,flow_id:{}".format(
+ flow_id
+ )
+ )
)
state_id = str(flow.first_state["id"])
@@ -68,7 +76,9 @@ def create_validate(self, value, fields, username, **kwargs):
lost_keys = required_keys - field_keys
if lost_keys:
- raise CreateTicketError(_("单据创建失败,缺少参数:{}".format(list(lost_keys))))
+ raise CreateTicketError(
+ _("单据创建失败,缺少参数:{}".format(list(lost_keys)))
+ )
first_state_permission(fields, state, username)
diff --git a/itsm/openapi/base_service/views/transition.py b/itsm/openapi/base_service/views/transition.py
index cb7a3b887..d768c613a 100644
--- a/itsm/openapi/base_service/views/transition.py
+++ b/itsm/openapi/base_service/views/transition.py
@@ -2,7 +2,7 @@
from rest_framework.decorators import action
from rest_framework.response import Response
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import START_STATE, TICKET_GLOBAL_VARIABLES
diff --git a/itsm/openapi/base_service/views/workflow.py b/itsm/openapi/base_service/views/workflow.py
index 92536ad4d..296e652a6 100644
--- a/itsm/openapi/base_service/views/workflow.py
+++ b/itsm/openapi/base_service/views/workflow.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from rest_framework.decorators import action
from rest_framework.response import Response
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from business_rules.operators import * # noqa
from itsm.component.constants import * # noqa
diff --git a/itsm/openapi/decorators.py b/itsm/openapi/decorators.py
index 1432070b2..77c796971 100644
--- a/itsm/openapi/decorators.py
+++ b/itsm/openapi/decorators.py
@@ -3,7 +3,7 @@
from functools import wraps
from rest_framework.response import Response
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.drf.exception import ValidationError
diff --git a/itsm/openapi/devops_plugin/urls.py b/itsm/openapi/devops_plugin/urls.py
index d49b2ae6f..b9c904be8 100644
--- a/itsm/openapi/devops_plugin/urls.py
+++ b/itsm/openapi/devops_plugin/urls.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
-from django.conf.urls import url
+from django.urls import re_path
from itsm.openapi.devops_plugin import views
urlpatterns = [
# main
- url(r"^devops_plugin/services/$", views.services),
- url(r"^devops_plugin/fields/$", views.service_fields),
+ re_path(r"^devops_plugin/services/$", views.services),
+ re_path(r"^devops_plugin/fields/$", views.service_fields),
]
diff --git a/itsm/openapi/management/commands/sync_saas_apigw.py b/itsm/openapi/management/commands/sync_saas_apigw.py
index 325ae0366..233486ff2 100644
--- a/itsm/openapi/management/commands/sync_saas_apigw.py
+++ b/itsm/openapi/management/commands/sync_saas_apigw.py
@@ -26,24 +26,6 @@ def handle(self, *args, **kwargs):
print("[bk-itsm]current version is not open v3,skip sync_saas_apigw")
return
- print("[bk-itsm]call fetch_apigw_public_key")
- try:
- call_command("fetch_apigw_public_key")
- except Exception:
- print(
- "[bk-itsm]this env has not bk-itsm esb api,skip fetch_apigw_public_key "
- )
- traceback.print_exc()
-
- print("[bk-itsm]call fetch_esb_public_key")
- try:
- call_command("fetch_esb_public_key")
- except Exception:
- print(
- "[bk-itsm]this env has not bk-itsm esb api,skip fetch_esb_public_key "
- )
- traceback.print_exc()
-
if (
settings.IS_OPEN_V3
and settings.ENGINE_REGION == "default"
@@ -90,3 +72,21 @@ def handle(self, *args, **kwargs):
)
print("[bk-itsm] migrate apigw success")
+
+ print("[bk-itsm]call fetch_apigw_public_key")
+ try:
+ call_command("fetch_apigw_public_key")
+ except Exception:
+ print(
+ "[bk-itsm]this env has not bk-itsm esb api,skip fetch_apigw_public_key "
+ )
+ traceback.print_exc()
+
+ print("[bk-itsm]call fetch_esb_public_key")
+ try:
+ call_command("fetch_esb_public_key")
+ except Exception:
+ print(
+ "[bk-itsm]this env has not bk-itsm esb api,skip fetch_esb_public_key "
+ )
+ traceback.print_exc()
diff --git a/itsm/openapi/service/views.py b/itsm/openapi/service/views.py
index 55a54849c..3224b669d 100644
--- a/itsm/openapi/service/views.py
+++ b/itsm/openapi/service/views.py
@@ -25,7 +25,7 @@
from django.db import transaction
from django.db.models import Q
from django.utils.decorators import method_decorator
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework.decorators import action
from rest_framework.response import Response
diff --git a/itsm/openapi/tasks.py b/itsm/openapi/tasks.py
index ed684d428..e60e6d1e4 100644
--- a/itsm/openapi/tasks.py
+++ b/itsm/openapi/tasks.py
@@ -25,7 +25,7 @@
import time
-from celery.task import periodic_task
+from blueapps.contrib.celery_tools.periodic import periodic_task
from celery.schedules import crontab
from common.redis import Cache
diff --git a/itsm/openapi/ticket/serializers.py b/itsm/openapi/ticket/serializers.py
index 4de4e3bc6..a0993f778 100644
--- a/itsm/openapi/ticket/serializers.py
+++ b/itsm/openapi/ticket/serializers.py
@@ -25,7 +25,7 @@
import random
import string
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from itsm.component.constants import (
diff --git a/itsm/openapi/ticket/tasks.py b/itsm/openapi/ticket/tasks.py
index 35ce5ac4a..7c6a4c8c6 100644
--- a/itsm/openapi/ticket/tasks.py
+++ b/itsm/openapi/ticket/tasks.py
@@ -1,8 +1,8 @@
-from celery.task import task
+from celery import shared_task
from common.log import logger
-@task
+@shared_task
def openapi_start_ticket(ticket, fields, from_ticket_id=None):
try:
logger.info(
diff --git a/itsm/openapi/ticket/validators.py b/itsm/openapi/ticket/validators.py
index a31aaccb7..d5397d393 100644
--- a/itsm/openapi/ticket/validators.py
+++ b/itsm/openapi/ticket/validators.py
@@ -25,7 +25,7 @@
from __future__ import absolute_import, unicode_literals
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from itsm.component.exceptions import ParamError
@@ -52,16 +52,22 @@ def openapi_operate_validate(username, ticket, state_id=None, action_type=None):
status = ticket.status(state_id)
if not status:
- raise serializers.ValidationError(_("抱歉,没有找到该节点:{}").format(state_id))
+ raise serializers.ValidationError(
+ _("抱歉,没有找到该节点:{}").format(state_id)
+ )
if status.status == "FINISHED":
- raise serializers.ValidationError(_("抱歉,当前节点:{} 已经结束").format(state_id))
+ raise serializers.ValidationError(
+ _("抱歉,当前节点:{} 已经结束").format(state_id)
+ )
if not status.can_operate(username, action_type):
raise serializers.ValidationError(
_("抱歉,{}不能操作该节点(没有权限或操作类型不支持)").format(username)
)
else:
if not ticket.can_operate(username):
- raise serializers.ValidationError(_("抱歉,{}无权操作该单据").format(username))
+ raise serializers.ValidationError(
+ _("抱歉,{}无权操作该单据").format(username)
+ )
def openapi_suspend_validate(ticket):
@@ -90,7 +96,9 @@ def edit_field_validate(ticket, field, **kwargs):
field_obj.key in ["title", "impact", "urgency", "priority"]
or field_obj.state_id == ticket.first_state_id
):
- raise ParamError(_("只允许修改提单节点的字段和内置字段, key={}".format(field["key"])))
+ raise ParamError(
+ _("只允许修改提单节点的字段和内置字段, key={}".format(field["key"]))
+ )
key_value = {
"params_%s" % field["key"]: format_exp_value(field["type"], field["_value"])
diff --git a/itsm/pipeline_plugins/components/collections/bk_plugin.py b/itsm/pipeline_plugins/components/collections/bk_plugin.py
index 7fed27f7f..ba5506355 100644
--- a/itsm/pipeline_plugins/components/collections/bk_plugin.py
+++ b/itsm/pipeline_plugins/components/collections/bk_plugin.py
@@ -8,7 +8,7 @@
from itsm.pipeline_plugins.components.collections.webhook import ParamsBuilder
from itsm.plugin_service.plugin_client import PluginServiceApiClient
from pipeline.component_framework.component import Component
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
TRANSITION_OPERATE,
@@ -157,7 +157,9 @@ def execute(self, data, parent_data):
state = ticket.flow.get_state(state_id)
variables = state["variables"].get("outputs", [])
- error_message_template = "蓝鲸插件调用失败【{name}】执行失败,失败信息 {detail_message}"
+ error_message_template = (
+ "蓝鲸插件调用失败【{name}】执行失败,失败信息 {detail_message}"
+ )
processors = ticket.current_processors[1:-1]
current_node = ticket.node_status.get(state_id=state_id)
@@ -216,8 +218,10 @@ def execute(self, data, parent_data):
return False
if not result:
- err_message = "bk_plugin_info 请求失败,返回值非 true, message = {}".format(
- resp.get("message")
+ err_message = (
+ "bk_plugin_info 请求失败,返回值非 true, message = {}".format(
+ resp.get("message")
+ )
)
self.do_exit_plugins(
ticket,
@@ -276,7 +280,9 @@ def execute(self, data, parent_data):
def schedule(self, data, parent_data, callback_data=None):
- error_message_template = "蓝鲸插件调用失败【{name}】执行失败,失败信息 {detail_message}"
+ error_message_template = (
+ "蓝鲸插件调用失败【{name}】执行失败,失败信息 {detail_message}"
+ )
ticket = Ticket.objects.get(id=parent_data.inputs.ticket_id)
state_id = data.inputs.state_id
diff --git a/itsm/pipeline_plugins/components/collections/itsm_auto.py b/itsm/pipeline_plugins/components/collections/itsm_auto.py
index 73b031ce0..e79a1269c 100644
--- a/itsm/pipeline_plugins/components/collections/itsm_auto.py
+++ b/itsm/pipeline_plugins/components/collections/itsm_auto.py
@@ -33,7 +33,7 @@
from blueapps.utils.logger import logger_celery as logger
from django.core.cache import cache
from django.db import transaction
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
ACTION_DICT,
@@ -128,22 +128,22 @@ def build_query_params(ticket, query_params, schema, method="POST"):
for name in params_list:
if name not in params:
params[name] = ""
-
+
logger.info(
- f"[auto_state][{ticket.id}] build_query begin:" +
- f"query_params=>{query_params}, params=>{params}"
+ f"[auto_state][{ticket.id}] build_query begin:"
+ + f"query_params=>{query_params}, params=>{params}"
)
result, build_query_params = build_params_by_mako_template(query_params, params)
if not result:
logger.exception(
- f"[auto_state][{ticket.id}] build_query exception:" +
- f"result=>{result}, build_query_params=>{build_query_params}"
+ f"[auto_state][{ticket.id}] build_query exception:"
+ + f"result=>{result}, build_query_params=>{build_query_params}"
)
return False, _("请求参数构造异常,详细信息: %s") % str(build_query_params)
-
+
logger.info(
- f"[auto_state][{ticket.id}] build_query done:" +
- f"result=>{result}, build_query_params=>{build_query_params}"
+ f"[auto_state][{ticket.id}] build_query done:"
+ + f"result=>{result}, build_query_params=>{build_query_params}"
)
# 引用变量的类型转换及参数整体schema校验
@@ -152,8 +152,8 @@ def build_query_params(ticket, query_params, schema, method="POST"):
params_type_conversion(build_query_params, schema)
except BaseException as e:
logger.exception(
- f"[auto_state][{ticket.id}] params_type_conversion exception:" +
- f"build_query_params=>{build_query_params}, schema=>{schema}, e=>{e}"
+ f"[auto_state][{ticket.id}] params_type_conversion exception:"
+ + f"build_query_params=>{build_query_params}, schema=>{schema}, e=>{e}"
)
return False, _("请求参数转换异常,详细信息: %s") % str(str(e))
@@ -161,8 +161,8 @@ def build_query_params(ticket, query_params, schema, method="POST"):
jsonschema.validate(build_query_params, schema)
except Exception as e:
logger.exception(
- f"[auto_state][{ticket.id}] validate exception:" +
- f"build_query_params=>{build_query_params}, schema=>{schema}, e=>{e}"
+ f"[auto_state][{ticket.id}] validate exception:"
+ + f"build_query_params=>{build_query_params}, schema=>{schema}, e=>{e}"
)
return False, _("请求参数校验异常,详细信息: %s") % str(str(e))
@@ -174,8 +174,8 @@ def get_rsp_content(
if operate_info and json.loads(operate_info)["action"] == "MANUAL":
ignore_params = ticket.node_status.get(state_id=state_id).ignore_params
logger.info(
- f"[auto_state][{ticket.id}][{state_id}] get_rsp_content" +
- f"ignore_params=>{ignore_params}"
+ f"[auto_state][{ticket.id}][{state_id}] get_rsp_content"
+ + f"ignore_params=>{ignore_params}"
)
return True, {"data": ignore_params}
else:
@@ -207,8 +207,8 @@ def do_exit_plugins(
for field in ticket.get_output_fields(state_id):
data.set_outputs("params_%s" % field["key"], field["value"])
logger.info(
- f"[auto_state][{ticket.id}][{state_id}] do_exit_plugins::set_output" +
- "key=>{}, value=>{}".format(field["key"], field["value"])
+ f"[auto_state][{ticket.id}][{state_id}] do_exit_plugins::set_output"
+ + "key=>{}, value=>{}".format(field["key"], field["value"])
)
if not operator_info:
@@ -226,9 +226,7 @@ def do_exit_plugins(
operate_type = detail["action"].upper()
action = API_DICT.get(detail["action"].upper())
if state_status == FAILED:
- log_message = (
- "{operator}{action}单据任务【{name}】执行失败:({detail_message})."
- )
+ log_message = "{operator}{action}单据任务【{name}】执行失败:({detail_message})."
else:
log_message = "{operator}{action}单据任务【{name}】执行成功."
node_status = ticket.status(state_id)
@@ -272,8 +270,8 @@ def do_exit_plugins(
self.update_status(ticket, state_id, state_status, ex_data)
if state_status == FAILED:
logger.exception(
- f"[auto_state][{ticket.id}][{state_id}] do_exit_plugins failed:" +
- f"state_status=>{state_status}, ex_data={ex_data}"
+ f"[auto_state][{ticket.id}][{state_id}] do_exit_plugins failed:"
+ + f"state_status=>{state_status}, ex_data={ex_data}"
)
ticket.node_status.filter(state_id=state_id).update(
action_type=TRANSITION_OPERATE
@@ -302,12 +300,12 @@ def execute(self, data, parent_data):
return True
ticket_id = parent_data.inputs.ticket_id
state_id = data.inputs.state_id
-
+
logger.info(
- f"[auto_state][{ticket_id}][{state_id}] execute init:" +
- f"data=>{data.inputs}, parent_data=>{parent_data.inputs}"
+ f"[auto_state][{ticket_id}][{state_id}] execute init:"
+ + f"data=>{data.inputs}, parent_data=>{parent_data.inputs}"
)
-
+
ticket = Ticket.objects.get(id=ticket_id)
state = ticket.flow.get_state(state_id)
variables = state["variables"].get("outputs", [])
@@ -316,7 +314,13 @@ def execute(self, data, parent_data):
api_instance = RemoteApiInstance.objects.get(id=state["api_instance_id"])
except RemoteApiInstance.DoesNotExist:
self.do_exit_plugins(
- ticket, state_id, FAILED, _("对应的api配置不存在,请查询"), {}, variables, data
+ ticket,
+ state_id,
+ FAILED,
+ _("对应的api配置不存在,请查询"),
+ {},
+ variables,
+ data,
)
return True
# 更新单据状态
@@ -359,12 +363,12 @@ def execute(self, data, parent_data):
result, query_params = self.build_query_params(
ticket, schedule_query_params, schema, remote_api.method
)
-
+
logger.info(
- f"[auto_state]y[{ticket_id}][{state_id}] execute build_query_params:" +
- f"result=>{result}, query_params=>{query_params}"
+ f"[auto_state]y[{ticket_id}][{state_id}] execute build_query_params:"
+ + f"result=>{result}, query_params=>{query_params}"
)
-
+
node_status.query_params = query_params
node_status.save()
if not result:
@@ -409,12 +413,12 @@ def schedule(self, data, parent_data, callback_data=None):
ticket = Ticket.objects.get(id=ticket_id)
variables = data.outputs.get("variables")
operate_info = cache.get("node_retry_{}_{}".format(ticket_id, state_id))
-
+
logger.info(
- f"[auto_state][{ticket_id}][{state_id}]schedule init:" +
- f"operate_info=>{operate_info}"
+ f"[auto_state][{ticket_id}][{state_id}]schedule init:"
+ + f"operate_info=>{operate_info}"
)
-
+
# 补充ticket/state/api_instance_id信息
api_config.update(
ticket_id=ticket_id,
@@ -434,8 +438,8 @@ def schedule(self, data, parent_data, callback_data=None):
return True
logger.info(
- f"[auto_state][{ticket_id}][{state_id}] schedule polling:" +
- f"poll_times=>{poll_time}, latest_poll_time=>{latest_poll_time}"
+ f"[auto_state][{ticket_id}][{state_id}] schedule polling:"
+ + f"poll_times=>{poll_time}, latest_poll_time=>{latest_poll_time}"
)
# 如果为轮询并且时间超过上一次的轮询时间
@@ -446,12 +450,12 @@ def schedule(self, data, parent_data, callback_data=None):
success_conditions,
operate_info,
)
-
+
logger.info(
- f"[auto_state][{ticket_id}][{state_id}] schedule curl: " +
- f"api_config=>{api_config}, p_result=>{p_result}, rsp=>{p_rsp}"
+ f"[auto_state][{ticket_id}][{state_id}] schedule curl: "
+ + f"api_config=>{api_config}, p_result=>{p_result}, rsp=>{p_rsp}"
)
-
+
poll_time -= 1
if p_result:
# 返回为True的时候,直接结束
@@ -468,8 +472,8 @@ def schedule(self, data, parent_data, callback_data=None):
return True
if poll_time <= 0:
logger.error(
- f"[auto_state][{ticket_id}][{state_id}] schedule polling error:" +
- f"response={p_rsp}"
+ f"[auto_state][{ticket_id}][{state_id}] schedule polling error:"
+ + f"response={p_rsp}"
)
self.do_exit_plugins(
ticket=ticket,
@@ -487,7 +491,9 @@ def schedule(self, data, parent_data, callback_data=None):
data.set_outputs("latest_poll_time", datetime.now())
return True
except Exception as e:
- logger.exception(f"[auto_state] data=>{data}, callback=>{callback_data}, e=>{e}")
+ logger.exception(
+ f"[auto_state] data=>{data}, callback=>{callback_data}, e=>{e}"
+ )
raise e
def outputs_format(self):
diff --git a/itsm/pipeline_plugins/components/collections/tasks.py b/itsm/pipeline_plugins/components/collections/tasks.py
index cce8fd5ce..81c3c980b 100644
--- a/itsm/pipeline_plugins/components/collections/tasks.py
+++ b/itsm/pipeline_plugins/components/collections/tasks.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from celery.task import task
+from celery import shared_task
from itsm.ticket.models import SignTask
from pipeline.engine.api import activity_callback
@@ -31,13 +31,17 @@
from common.log import logger
-@task
+@shared_task
def auto_approve(node_status_id, creator, activity_id, callback_data):
try:
# 如果存在这条任务,证明有其他用户在页面或者api执行了审批任务,无需自动过单
SignTask.objects.get(status_id=node_status_id)
except SignTask.DoesNotExist:
- logger.info("正在创建自动过单任务, node_status_id={}, creator={}".format(node_status_id, creator))
+ logger.info(
+ "正在创建自动过单任务, node_status_id={}, creator={}".format(
+ node_status_id, creator
+ )
+ )
SignTask.objects.update_or_create(
status_id=node_status_id, processor=creator, defaults={"status": "RUNNING"}
)
diff --git a/itsm/pipeline_plugins/components/collections/webhook.py b/itsm/pipeline_plugins/components/collections/webhook.py
index d74a8cf9f..26e3a93a3 100644
--- a/itsm/pipeline_plugins/components/collections/webhook.py
+++ b/itsm/pipeline_plugins/components/collections/webhook.py
@@ -32,7 +32,7 @@
from jinja2 import Template
from pipeline.utils.boolrule import BoolRule
from pipeline.component_framework.component import Component
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
TRANSITION_OPERATE,
@@ -222,7 +222,9 @@ def execute(self, data, parent_data):
state = ticket.flow.get_state(state_id)
variables = state["variables"].get("outputs", [])
- error_message_template = "WebHook任务【{name}】执行失败,失败信息 {detail_message}"
+ error_message_template = (
+ "WebHook任务【{name}】执行失败,失败信息 {detail_message}"
+ )
processors = ticket.current_processors[1:-1]
current_node = ticket.node_status.get(state_id=state_id)
diff --git a/itsm/plugin_service/README.md b/itsm/plugin_service/README.md
index 50940eaba..f31f78f8c 100644
--- a/itsm/plugin_service/README.md
+++ b/itsm/plugin_service/README.md
@@ -22,7 +22,7 @@ INSTALLED_APPS += (
``` python
urlpatterns = [
...,
- url(r"^plugin_service/", include("plugin_service.urls")),
+ re_path(r"^plugin_service/", include("plugin_service.urls")),
...,
]
```
diff --git a/itsm/plugin_service/docs/openapi_config.md b/itsm/plugin_service/docs/openapi_config.md
index 72abbd542..6295c249e 100644
--- a/itsm/plugin_service/docs/openapi_config.md
+++ b/itsm/plugin_service/docs/openapi_config.md
@@ -16,9 +16,9 @@
)
urlpatterns += [
- url(r"^swagger(?P\.json|\.yaml)$", schema_view.without_ui(cache_timeout=0), name="schema-json"),
- url(r"^swagger/$", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"),
- url(r"^redoc/$", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
+ re_path(r"^swagger(?P\.json|\.yaml)$", schema_view.without_ui(cache_timeout=0), name="schema-json"),
+ re_path(r"^swagger/$", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"),
+ re_path(r"^redoc/$", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
]
```
diff --git a/itsm/plugin_service/urls.py b/itsm/plugin_service/urls.py
index 7f3d4ced3..305b35604 100644
--- a/itsm/plugin_service/urls.py
+++ b/itsm/plugin_service/urls.py
@@ -11,19 +11,19 @@
specific language governing permissions and limitations under the License.
"""
-from django.conf.urls import url
+from django.urls import re_path
from . import api
urlpatterns = [
- url(r"^list/$", api.get_plugin_list),
- url(r"^detail_list/$", api.get_plugin_detail_list),
- url(r"^meta/$", api.get_meta),
- url(r"^detail/$", api.get_plugin_detail),
- url(r"^logs/$", api.get_logs),
- url(r"^app_detail/$", api.get_plugin_app_detail),
- url(
+ re_path(r"^list/$", api.get_plugin_list),
+ re_path(r"^detail_list/$", api.get_plugin_detail_list),
+ re_path(r"^meta/$", api.get_meta),
+ re_path(r"^detail/$", api.get_plugin_detail),
+ re_path(r"^logs/$", api.get_logs),
+ re_path(r"^app_detail/$", api.get_plugin_app_detail),
+ re_path(
r"^data_api/(?P.+?)/(?P.+)$",
api.get_plugin_api_data,
),
diff --git a/itsm/postman/models.py b/itsm/postman/models.py
index 8a367db8c..d9b736b98 100644
--- a/itsm/postman/models.py
+++ b/itsm/postman/models.py
@@ -31,7 +31,7 @@
from django.conf import settings
from django.db import models
from django.forms import model_to_dict
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.constants import (
@@ -103,14 +103,22 @@ class RemoteSystem(Model):
_("系统描述"), max_length=LEN_LONG, default=EMPTY_STRING, null=True, blank=True
)
owners = models.CharField(
- _("系统责任人"), max_length=LEN_NORMAL, default=EMPTY_STRING, null=True, blank=True
+ _("系统责任人"),
+ max_length=LEN_NORMAL,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
contact_information = models.TextField(_("联系方式"), blank=True)
is_builtin = models.BooleanField(_("是否内置系统"), default=False)
# 公共配置信息
domain = models.CharField(
- _("系统域名"), max_length=LEN_XX_LONG, default=EMPTY_STRING, null=True, blank=True
+ _("系统域名"),
+ max_length=LEN_XX_LONG,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
is_activated = models.BooleanField(_("是否启用"), default=False)
headers = jsonfield.JSONField(
@@ -279,7 +287,9 @@ def init_default_remote_api(cls):
try:
cls.restore_api(api, "system", True)
except Exception as e:
- logger.info("创建默认api失败:%s api name %s" % (str(e), api["name"]))
+ logger.info(
+ "创建默认api失败:%s api name %s" % (str(e), api["name"])
+ )
def tag_data(self):
"""Api数据"""
@@ -413,9 +423,9 @@ def get_config(self):
"rsp_data": self.rsp_data,
"map_code": self.map_code,
"before_req": self.before_req,
- "query_params": self.req_body
- if remote_api.method == "POST"
- else self.req_params,
+ "query_params": (
+ self.req_body if remote_api.method == "POST" else self.req_params
+ ),
}
def get_api_choice(self, kv_relation, params):
@@ -467,7 +477,9 @@ def get_api_choice(self, kv_relation, params):
return {
"result": False,
"code": ResponseCodeStatus.OK,
- "message": _("接口返回协议不符合规范,请确保接口协议符合蓝鲸规范。详见: Github->API功能使用说明"),
+ "message": _(
+ "接口返回协议不符合规范,请确保接口协议符合蓝鲸规范。详见: Github->API功能使用说明"
+ ),
"data": [],
}
@@ -475,7 +487,10 @@ def get_api_choice(self, kv_relation, params):
if rsp.get("code") == API_PERMISSION_ERROR_CODE:
raise IamPermissionDenied(
data=rsp["permission"],
- detail=_("用户没有对应的第三方系统接口【%s】权限" % api_config.get("path")),
+ detail=_(
+ "用户没有对应的第三方系统接口【%s】权限"
+ % api_config.get("path")
+ ),
)
return rsp
diff --git a/itsm/postman/permissions.py b/itsm/postman/permissions.py
index 741ccfdbb..25f2e1ea6 100644
--- a/itsm/postman/permissions.py
+++ b/itsm/postman/permissions.py
@@ -27,7 +27,7 @@
from itsm.component.drf import permissions as perm
from itsm.component.exceptions import ValidateError
from itsm.postman.models import RemoteSystem, RemoteApi
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.project.models import Project
from itsm.workflow.permissions import WorkflowElementManagePermission
@@ -50,12 +50,12 @@ def has_permission(self, request, view):
# 平台公共API管理
if project_key == PUBLIC_PROJECT_PROJECT_KEY:
return self.iam_auth(request, ["public_apis_manage"])
-
+
# 项目管理
apply_actions = ["system_settings_manage"]
project = Project.objects.get(pk=project_key)
return self.iam_auth(request, apply_actions, project)
-
+
if view.action == "batch_delete":
api_ids = request.data["id"].split(",")
api_instances = RemoteApi.objects.filter(pk__in=api_ids)
@@ -63,15 +63,15 @@ def has_permission(self, request, view):
if len(project_keys) != 1:
raise ValidateError(_("API 所属项目异常"))
project_key = project_keys.pop()
-
+
# 平台公共API管理
if project_key == PUBLIC_PROJECT_PROJECT_KEY:
return self.iam_auth(request, ["public_apis_manage"])
-
+
# 项目
project = Project.objects.get(pk=project_key)
return self.iam_auth(request, ["system_settings_manage"], project)
-
+
return True
def has_object_permission(self, request, view, obj, **kwargs):
@@ -84,7 +84,7 @@ def has_object_permission(self, request, view, obj, **kwargs):
if view.action == "retrieve":
return True
return self.iam_auth(request, ["public_apis_manage"])
-
+
# 项目管理
project_key = obj.remote_system.project_key
project = Project.objects.get(pk=project_key)
diff --git a/itsm/postman/rpc/components/demo.py b/itsm/postman/rpc/components/demo.py
index cd35b88d7..528c2fb4b 100644
--- a/itsm/postman/rpc/components/demo.py
+++ b/itsm/postman/rpc/components/demo.py
@@ -24,7 +24,7 @@
"""
from django import forms
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.dlls.component import BaseComponentForm
from itsm.postman.rpc.core.component import BaseComponent
diff --git a/itsm/postman/rpc/components/service_catalog.py b/itsm/postman/rpc/components/service_catalog.py
index 401b41d06..ecc78edd3 100644
--- a/itsm/postman/rpc/components/service_catalog.py
+++ b/itsm/postman/rpc/components/service_catalog.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.dlls.component import BaseComponentForm
from itsm.component.exceptions import RpcAPIError
diff --git a/itsm/postman/rpc/components/state_fields.py b/itsm/postman/rpc/components/state_fields.py
index 2a7ecfc90..27d980f1b 100644
--- a/itsm/postman/rpc/components/state_fields.py
+++ b/itsm/postman/rpc/components/state_fields.py
@@ -24,7 +24,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django import forms
diff --git a/itsm/postman/rpc/components/ticket_fields.py b/itsm/postman/rpc/components/ticket_fields.py
index ed5b152b7..ba623bb02 100644
--- a/itsm/postman/rpc/components/ticket_fields.py
+++ b/itsm/postman/rpc/components/ticket_fields.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django import forms
@@ -39,8 +39,12 @@ class GetTicketFields(BaseComponent):
code = TABLE_FIELDS
class Form(BaseComponentForm):
- trigger_source_id = forms.IntegerField(label=_("触发器源id"), required=True, initial="trigger source id")
- trigger_source_type = forms.CharField(label=_("触发器源类型"), required=True, initial="trigger source type")
+ trigger_source_id = forms.IntegerField(
+ label=_("触发器源id"), required=True, initial="trigger source id"
+ )
+ trigger_source_type = forms.CharField(
+ label=_("触发器源类型"), required=True, initial="trigger source type"
+ )
def clean(self):
"""数据清理"""
@@ -49,14 +53,18 @@ def clean(self):
def handle(self):
payload = []
- if self.form_data.get("trigger_source_type") != 'workflow':
+ if self.form_data.get("trigger_source_type") != "workflow":
self.response.payload = payload
return
try:
- current_workflow = Workflow.objects.get(id=self.form_data.get("trigger_source_id"))
+ current_workflow = Workflow.objects.get(
+ id=self.form_data.get("trigger_source_id")
+ )
except Workflow.DoesNotExist:
raise
- payload = TemplateFieldSerializer(current_workflow.public_table_fields, many=True).data
+ payload = TemplateFieldSerializer(
+ current_workflow.public_table_fields, many=True
+ ).data
self.response.payload = payload
diff --git a/itsm/postman/rpc/components/ticket_states.py b/itsm/postman/rpc/components/ticket_states.py
index e86772dfc..224df39a8 100644
--- a/itsm/postman/rpc/components/ticket_states.py
+++ b/itsm/postman/rpc/components/ticket_states.py
@@ -23,11 +23,17 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django import forms
-from itsm.component.constants import FLOW_STATES, NORMAL_STATE, TASK_STATE, TASK_SOPS_STATE, SIGN_STATE
+from itsm.component.constants import (
+ FLOW_STATES,
+ NORMAL_STATE,
+ TASK_STATE,
+ TASK_SOPS_STATE,
+ SIGN_STATE,
+)
from itsm.component.dlls.component import BaseComponentForm
from itsm.postman.rpc.core.component import BaseComponent
from itsm.workflow.models import Workflow
@@ -39,8 +45,12 @@ class GetTicketFields(BaseComponent):
code = FLOW_STATES
class Form(BaseComponentForm):
- trigger_source_id = forms.IntegerField(label=_("触发器源id"), required=True, initial="trigger source id")
- trigger_source_type = forms.CharField(label=_("触发器源类型"), required=True, initial="trigger source type")
+ trigger_source_id = forms.IntegerField(
+ label=_("触发器源id"), required=True, initial="trigger source id"
+ )
+ trigger_source_type = forms.CharField(
+ label=_("触发器源类型"), required=True, initial="trigger source type"
+ )
def clean(self):
"""数据清理"""
@@ -49,17 +59,20 @@ def clean(self):
def handle(self):
payload = []
- if self.form_data.get("trigger_source_type") != 'workflow':
+ if self.form_data.get("trigger_source_type") != "workflow":
self.response.payload = payload
return
try:
- current_workflow = Workflow.objects.get(id=self.form_data.get("trigger_source_id"))
+ current_workflow = Workflow.objects.get(
+ id=self.form_data.get("trigger_source_id")
+ )
except Workflow.DoesNotExist:
raise
payload = StateSerializer(
current_workflow.states.filter(
- type__in=[NORMAL_STATE, TASK_STATE, TASK_SOPS_STATE, SIGN_STATE], is_builtin=False
+ type__in=[NORMAL_STATE, TASK_STATE, TASK_SOPS_STATE, SIGN_STATE],
+ is_builtin=False,
),
many=True,
).data
diff --git a/itsm/postman/rpc/components/ticket_status.py b/itsm/postman/rpc/components/ticket_status.py
index 118e1ba16..111c3a7c9 100644
--- a/itsm/postman/rpc/components/ticket_status.py
+++ b/itsm/postman/rpc/components/ticket_status.py
@@ -25,7 +25,7 @@
from collections import OrderedDict, defaultdict
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import STATUS, SERVICE_CATEGORY
from itsm.component.dlls.component import BaseComponentForm
diff --git a/itsm/postman/serializers.py b/itsm/postman/serializers.py
index b3a02034f..ebfc9fe88 100644
--- a/itsm/postman/serializers.py
+++ b/itsm/postman/serializers.py
@@ -25,7 +25,7 @@
import json
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mako.template import Template
from rest_framework import serializers
from rest_framework.fields import JSONField
@@ -39,7 +39,10 @@
LEN_XX_LONG,
PUBLIC_PROJECT_PROJECT_KEY,
)
-from itsm.component.drf.serializers import DynamicFieldsModelSerializer, BaseModelSerializer
+from itsm.component.drf.serializers import (
+ DynamicFieldsModelSerializer,
+ BaseModelSerializer,
+)
from itsm.component.exceptions import ParamError
from itsm.component.utils.basic import normal_name, dotted_name
from itsm.postman.models import RemoteApi, RemoteApiInstance, RemoteSystem
@@ -50,10 +53,14 @@ class RemoteSystemSerializer(BaseModelSerializer):
"""API系统序列化"""
name = serializers.CharField(
- max_length=LEN_NORMAL, required=True, error_messages={"blank": _("名称不能为空")}
+ max_length=LEN_NORMAL,
+ required=True,
+ error_messages={"blank": _("名称不能为空")},
)
code = serializers.CharField(
- max_length=LEN_NORMAL, required=True, error_messages={"blank": _("编码不能为空")}
+ max_length=LEN_NORMAL,
+ required=True,
+ error_messages={"blank": _("编码不能为空")},
)
system_id = serializers.IntegerField(required=False)
desc = serializers.CharField(max_length=LEN_LONG, required=False, allow_blank=True)
@@ -124,14 +131,20 @@ class RemoteApiSerializer(DynamicFieldsModelSerializer):
"""API序列化"""
name = serializers.CharField(
- required=True, error_messages={"blank": _("名称不能为空")}, max_length=LEN_NORMAL
+ required=True,
+ error_messages={"blank": _("名称不能为空")},
+ max_length=LEN_NORMAL,
)
path = serializers.CharField(
- required=True, error_messages={"blank": _("路径不能为空")}, max_length=LEN_X_LONG
+ required=True,
+ error_messages={"blank": _("路径不能为空")},
+ max_length=LEN_X_LONG,
)
version = serializers.CharField(required=False, max_length=LEN_SHORT)
func_name = serializers.CharField(
- required=True, error_messages={"blank": _("调用函数不能为空")}, max_length=LEN_NORMAL
+ required=True,
+ error_messages={"blank": _("调用函数不能为空")},
+ max_length=LEN_NORMAL,
)
method = serializers.ChoiceField(
choices=[("GET", "GET"), ("POST", "POST")], default="GET"
diff --git a/itsm/postman/urls.py b/itsm/postman/urls.py
index df87d3a1e..daaa09732 100644
--- a/itsm/postman/urls.py
+++ b/itsm/postman/urls.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.conf.urls import url
+from django.urls import re_path
from rest_framework.routers import DefaultRouter
from itsm.postman.views import (
@@ -36,13 +36,13 @@
routers = DefaultRouter(trailing_slash=True)
-routers.register(r'api_instance', ApiInstanceViewsSet, basename="api_instance")
+routers.register(r"api_instance", ApiInstanceViewsSet, basename="api_instance")
-routers.register(r'remote_system', RemoteSystemViewSet, basename='remote_system')
+routers.register(r"remote_system", RemoteSystemViewSet, basename="remote_system")
-routers.register(r'remote_api', RemoteApiViewSet, basename='remote_api')
+routers.register(r"remote_api", RemoteApiViewSet, basename="remote_api")
# APIView不能通过routers.register()的方式注入路由
urlpatterns = routers.urls + [
- url(r'^rpc_api/$', RpcApiViewSet.as_view()),
+ re_path(r"^rpc_api/$", RpcApiViewSet.as_view()),
]
diff --git a/itsm/postman/views.py b/itsm/postman/views.py
index 3bbd2b19e..1f85118bf 100644
--- a/itsm/postman/views.py
+++ b/itsm/postman/views.py
@@ -29,7 +29,7 @@
from django.db.models import Q
from django.forms.forms import DeclarativeFieldsMetaclass
from django.http import HttpResponse
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework.decorators import action
from rest_framework.response import Response
@@ -99,7 +99,7 @@ class RemoteSystemViewSet(ModelViewSet):
permission_action_default = "system_settings_manage"
permission_resource_is_project = True
permission_free_actions = ["list", "all", "get_systems", "get_components"]
-
+
pagination_class = None
filter_fields = {
@@ -187,7 +187,7 @@ class RemoteApiViewSet(DynamicListModelMixin, ModelViewSet):
serializer_class = RemoteApiSerializer
queryset = RemoteApi.objects.all()
-
+
permission_classes = (RemoteApiPermit,)
permission_resource_is_project = True
permission_create_action = ["create", "imports"]
@@ -254,11 +254,11 @@ def batch_delete(self, request, *args, **kwargs):
will_deleted = self.queryset.filter(id__in=id_list)
real_deleted = list(will_deleted.values_list("id", flat=True))
-
+
# 判断输入的接口实例
if will_deleted.count() != len(real_deleted):
raise ValidationError(_("接口数量异常,请刷新后重试"))
-
+
# 检测实例所属项目是否一致
project_keys = [i.remote_system.project_key for i in will_deleted]
if len(set(project_keys)) != 1:
@@ -277,10 +277,10 @@ def exports(self, request, pk=None):
data = api.tag_data()
response = HttpResponse(content_type="application/octet-stream; charset=utf-8")
- response[
- "Content-Disposition"
- ] = "attachment; filename=bk_itsm_api_{}_{}.json".format(
- api.func_name, datetime.datetime.now().strftime("%Y%m%d%H%M")
+ response["Content-Disposition"] = (
+ "attachment; filename=bk_itsm_api_{}_{}.json".format(
+ api.func_name, datetime.datetime.now().strftime("%Y%m%d%H%M")
+ )
)
# 统一导入导出格式为列表数据
diff --git a/itsm/project/handler/migration_handler.py b/itsm/project/handler/migration_handler.py
index c5fc2d1ce..29444de01 100644
--- a/itsm/project/handler/migration_handler.py
+++ b/itsm/project/handler/migration_handler.py
@@ -26,10 +26,14 @@
from abc import abstractmethod
from django.db import transaction
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import DEFAULT_PROJECT_PROJECT_KEY
-from itsm.component.exceptions import ResourceTypeNotFound, ProjectNotFound, NoMigratePermission
+from itsm.component.exceptions import (
+ ResourceTypeNotFound,
+ ProjectNotFound,
+ NoMigratePermission,
+)
from itsm.project.handler.utils import MigrateIamRequest
from itsm.project.models import Project
from itsm.service.models import Service, CatalogService, ServiceCatalog
@@ -50,20 +54,27 @@ def handler(self, resource_id, old_project_key, new_project_key, request):
def grant_or_revoke_instance_permission(self, request, actions, obj, operate):
iam = MigrateIamRequest(request)
- resources = [{
- "resource_id": obj.id,
- "resource_name": obj.name,
- "resource_type": obj.auth_resource.get("resource_type"),
- "resource_type_name": obj.auth_resource.get("resource_type_name"),
- }]
- iam.grant_or_revoke_instance_permission(actions, resources, operate=operate,
- project_key=obj.project_key)
-
- def grant_or_revoke_permit_with_project(self, request, actions, project_key, operate):
+ resources = [
+ {
+ "resource_id": obj.id,
+ "resource_name": obj.name,
+ "resource_type": obj.auth_resource.get("resource_type"),
+ "resource_type_name": obj.auth_resource.get("resource_type_name"),
+ }
+ ]
+ iam.grant_or_revoke_instance_permission(
+ actions, resources, operate=operate, project_key=obj.project_key
+ )
+
+ def grant_or_revoke_permit_with_project(
+ self, request, actions, project_key, operate
+ ):
iam = MigrateIamRequest(request)
iam.grant_or_revoke_permit_with_project(actions, operate, project_key)
- def iam_auth(self, request, apply_actions, obj=None, project_key=DEFAULT_PROJECT_PROJECT_KEY):
+ def iam_auth(
+ self, request, apply_actions, obj=None, project_key=DEFAULT_PROJECT_PROJECT_KEY
+ ):
resources = []
if obj:
@@ -71,17 +82,17 @@ def iam_auth(self, request, apply_actions, obj=None, project_key=DEFAULT_PROJECT
{
"resource_id": obj.id,
"resource_name": getattr(obj, "name"),
- "resource_type": obj.auth_resource['resource_type'],
+ "resource_type": obj.auth_resource["resource_type"],
"creator": obj.creator,
}
)
iam_client = MigrateIamRequest(request)
if resources:
- auth_actions = iam_client.batch_resource_multi_actions_allowed(set(apply_actions),
- resources,
- project_key=project_key)
- auth_actions = auth_actions.get(resources[0]['resource_id'], {})
+ auth_actions = iam_client.batch_resource_multi_actions_allowed(
+ set(apply_actions), resources, project_key=project_key
+ )
+ auth_actions = auth_actions.get(resources[0]["resource_id"], {})
else:
auth_actions = iam_client.resource_multi_actions_allowed(apply_actions, [])
@@ -106,8 +117,9 @@ class ServiceMigrationHandler(MigrationHandlerBase):
resource_type = "service"
def handler(self, resource_id, old_project_key, new_project_key, request):
- ready_migrate_service = Service.objects.filter(project_key=old_project_key,
- id=resource_id).first()
+ ready_migrate_service = Service.objects.filter(
+ project_key=old_project_key, id=resource_id
+ ).first()
# 如果在默认项目下搜索不到该服务,则证明已经被迁移过了或者数据有问题,此时不进行迁移
if ready_migrate_service is None:
@@ -116,24 +128,32 @@ def handler(self, resource_id, old_project_key, new_project_key, request):
# 鉴权,用户是否有该资源的service_manage权限,有的话才可以进行迁移
if not self.iam_auth(request, actions, ready_migrate_service, old_project_key):
- raise NoMigratePermission(_("您当前没有权限迁移该服务,您在权限中心没有该服务的权限,service_name={}".
- format(ready_migrate_service.name)))
+ raise NoMigratePermission(
+ _(
+ "您当前没有权限迁移该服务,您在权限中心没有该服务的权限,service_name={}".format(
+ ready_migrate_service.name
+ )
+ )
+ )
# 先回收权限
- self.grant_or_revoke_instance_permission(request, actions,
- ready_migrate_service, REVOKE)
+ self.grant_or_revoke_instance_permission(
+ request, actions, ready_migrate_service, REVOKE
+ )
# 获取该项目获取的catalog名称
catalog_name = ready_migrate_service.bounded_catalogs[0]
# 如果对应的服务目录本身就有
- new_project_catalog = ServiceCatalog.objects.filter(project_key=new_project_key,
- name=catalog_name).first()
+ new_project_catalog = ServiceCatalog.objects.filter(
+ project_key=new_project_key, name=catalog_name
+ ).first()
if new_project_catalog is None:
# 同步相关目录
service_catalog = ServiceCatalog.objects.filter(
- id=ready_migrate_service.catalog_id).first()
+ id=ready_migrate_service.catalog_id
+ ).first()
path_list = list(service_catalog.get_ancestors())
path_list.append(service_catalog)
# 新生成的节点树的最后一个节点
@@ -141,7 +161,8 @@ def handler(self, resource_id, old_project_key, new_project_key, request):
with transaction.atomic():
catalog_service = CatalogService.objects.filter(
- service=ready_migrate_service).first()
+ service=ready_migrate_service
+ ).first()
catalog_service.catalog = new_project_catalog
catalog_service.save()
ready_migrate_service.project_key = new_project_key
@@ -156,18 +177,21 @@ def handler(self, resource_id, old_project_key, new_project_key, request):
# 权限中心授权
actions = ["service_manage", "service_view", "ticket_view"]
- self.grant_or_revoke_instance_permission(request, actions, ready_migrate_service, "grant")
+ self.grant_or_revoke_instance_permission(
+ request, actions, ready_migrate_service, "grant"
+ )
def sync_catalog_tree(self, path_list, new_project_key):
# 同步该目录
same_catalog = self.find_same_node(path_list, new_project_key)
same_catalog_index = path_list.index(same_catalog)
- service_catalog = ServiceCatalog.objects.filter(project_key=new_project_key,
- name=same_catalog.name).first()
- for catalog in path_list[same_catalog_index + 1:]:
- service_catalog = ServiceCatalog.create_catalog(name=catalog.name,
- parent=service_catalog,
- project_key=new_project_key)
+ service_catalog = ServiceCatalog.objects.filter(
+ project_key=new_project_key, name=same_catalog.name
+ ).first()
+ for catalog in path_list[same_catalog_index + 1 :]:
+ service_catalog = ServiceCatalog.create_catalog(
+ name=catalog.name, parent=service_catalog, project_key=new_project_key
+ )
return service_catalog
def find_same_node(self, path_list, new_project_key):
@@ -175,8 +199,9 @@ def find_same_node(self, path_list, new_project_key):
找到两个项目 服务目录的第一个共同的目录
"""
for catalog in reversed(path_list):
- if ServiceCatalog.objects.filter(project_key=new_project_key,
- name=catalog.name).exists():
+ if ServiceCatalog.objects.filter(
+ project_key=new_project_key, name=catalog.name
+ ).exists():
return catalog
@@ -185,8 +210,9 @@ class UserGroupMigrationHandler(MigrationHandlerBase):
def handler(self, resource_id, old_project_key, new_project_key, request):
- user_role = UserRole.objects.filter(project_key=old_project_key,
- id=resource_id).first()
+ user_role = UserRole.objects.filter(
+ project_key=old_project_key, id=resource_id
+ ).first()
if user_role is None:
return
@@ -194,15 +220,21 @@ def handler(self, resource_id, old_project_key, new_project_key, request):
if not request.user.username == user_role.creator:
raise NoMigratePermission("权限迁移失败,请联系该用户组创建者进行迁移")
actions = ["user_group_view", "user_group_edit", "user_group_delete"]
- user_role.auth_resource = {"resource_type": "user_group", "resource_type_name": "用户组"}
+ user_role.auth_resource = {
+ "resource_type": "user_group",
+ "resource_type_name": "用户组",
+ }
# 取消实例级别的授权
self.grant_or_revoke_instance_permission(request, actions, user_role, REVOKE)
with transaction.atomic():
user_role.project_key = new_project_key
user_role.save()
-
- user_role.auth_resource = {"resource_type": "user_group", "resource_type_name": "用户组"}
+
+ user_role.auth_resource = {
+ "resource_type": "user_group",
+ "resource_type_name": "用户组",
+ }
actions = ["user_group_view", "user_group_edit", "user_group_delete"]
self.grant_or_revoke_instance_permission(request, actions, user_role, GRANT)
@@ -215,15 +247,14 @@ class MigrationHandlerDispatcher(object):
# 保留映射变量,便于直接从 object_class 找到对象定义
MIGRATIONS_HANDLER_CLASS_DICT = dict(
- [(_object.resource_type, _object()) for _object in MIGRATIONS_HANDLER_CLASS])
+ [(_object.resource_type, _object()) for _object in MIGRATIONS_HANDLER_CLASS]
+ )
def __init__(self, resource_type):
self.resource_type = resource_type
def migrate(self, resource_id, old_project_key, new_project_key, request):
- """
-
- """
+ """ """
if not Project.objects.filter(key=old_project_key).exists():
raise ProjectNotFound()
@@ -233,7 +264,6 @@ def migrate(self, resource_id, old_project_key, new_project_key, request):
if self.resource_type not in self.MIGRATIONS_HANDLER_CLASS_DICT:
raise ResourceTypeNotFound()
- self.MIGRATIONS_HANDLER_CLASS_DICT[self.resource_type].handler(resource_id,
- old_project_key,
- new_project_key,
- request)
+ self.MIGRATIONS_HANDLER_CLASS_DICT[self.resource_type].handler(
+ resource_id, old_project_key, new_project_key, request
+ )
diff --git a/itsm/project/models/base.py b/itsm/project/models/base.py
index 747edf57e..598edc95a 100644
--- a/itsm/project/models/base.py
+++ b/itsm/project/models/base.py
@@ -25,18 +25,22 @@
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import LEN_NORMAL
class Model(models.Model):
- FIELDS = ('creator', 'create_at', 'updated_by', 'update_at')
- creator = models.CharField(_("创建人"), max_length=LEN_NORMAL, null=True, blank=True)
+ FIELDS = ("creator", "create_at", "updated_by", "update_at")
+ creator = models.CharField(
+ _("创建人"), max_length=LEN_NORMAL, null=True, blank=True
+ )
create_at = models.DateTimeField(_("创建时间"), auto_now_add=True)
update_at = models.DateTimeField(_("更新时间"), auto_now=True)
- updated_by = models.CharField(_("修改人"), max_length=LEN_NORMAL, null=True, blank=True)
-
+ updated_by = models.CharField(
+ _("修改人"), max_length=LEN_NORMAL, null=True, blank=True
+ )
+
class Meta:
- app_label = 'project'
+ app_label = "project"
abstract = True
diff --git a/itsm/project/models/project.py b/itsm/project/models/project.py
index 98465f73d..dcba3da86 100644
--- a/itsm/project/models/project.py
+++ b/itsm/project/models/project.py
@@ -40,7 +40,7 @@
)
from itsm.iadmin.contants import PROJECT_SETTING
from itsm.project.models.base import Model
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.service.models import ServiceCatalog
from itsm.sla.models import Sla, Schedule
@@ -67,7 +67,7 @@ class Project(Model):
"catalog_create",
"catalog_edit",
"catalog_delete",
- "system_settings_manage"
+ "system_settings_manage",
]
auth_resource = {"resource_type": "project", "resource_type_name": "项目"}
@@ -180,8 +180,12 @@ def init_lesscode_project(cls):
class ProjectSettings(Model):
type = models.CharField(_("类型"), max_length=LEN_NORMAL, default="FUNCTION")
key = models.CharField(_("关键字唯一标识"), max_length=LEN_NORMAL, unique=False)
- value = models.TextField(_("系统设置值"), default=EMPTY_STRING, null=True, blank=True)
- project = models.ForeignKey("Project", help_text=_("项目"), on_delete=models.CASCADE)
+ value = models.TextField(
+ _("系统设置值"), default=EMPTY_STRING, null=True, blank=True
+ )
+ project = models.ForeignKey(
+ "Project", help_text=_("项目"), on_delete=models.CASCADE
+ )
class UserProjectAccessRecord(Model):
diff --git a/itsm/project/views.py b/itsm/project/views.py
index f7b248a2f..823011459 100644
--- a/itsm/project/views.py
+++ b/itsm/project/views.py
@@ -25,7 +25,7 @@
# 当前提供给前端获取用户权限链接使用
from django.db.models import Q
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import status
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
@@ -66,7 +66,7 @@ class ProjectViewSet(component_viewsets.AuthModelViewSet):
permission_action_default = ["project_edit"]
permission_action_mapping = {
"retrieve": ["project_view"],
- "update_project_record": ["project_view"]
+ "update_project_record": ["project_view"],
}
def list(self, request, *args, **kwargs):
diff --git a/itsm/role/models.py b/itsm/role/models.py
index 297fd4734..54a0f8219 100644
--- a/itsm/role/models.py
+++ b/itsm/role/models.py
@@ -29,7 +29,7 @@
from django.conf import settings
from django.core.cache import cache
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.constants import (
@@ -96,12 +96,19 @@ class RoleType(Model):
is_processor = models.BooleanField(_("可否操作单据"), default=True)
is_display = models.BooleanField(_("是否显示"), default=True)
desc = models.CharField(
- _("角色描述"), max_length=LEN_MIDDLE, default=EMPTY_STRING, null=True, blank=True
+ _("角色描述"),
+ max_length=LEN_MIDDLE,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
objects = managers.Manager()
- auth_resource = {"resource_type": "system_settings", "resource_type_name": "系统配置"}
+ auth_resource = {
+ "resource_type": "system_settings",
+ "resource_type_name": "系统配置",
+ }
resource_operations = ["system_settings_manage"]
class Meta:
@@ -151,7 +158,11 @@ class UserRole(ObjectManagerMixin, Model):
owners = models.CharField(_("负责人"), max_length=LEN_XX_LONG, default=EMPTY_STRING)
access = models.CharField(_("对应服务"), max_length=LEN_MIDDLE)
desc = models.CharField(
- _("用户角色描述"), max_length=LEN_MIDDLE, default=EMPTY_STRING, null=True, blank=True
+ _("用户角色描述"),
+ max_length=LEN_MIDDLE,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
is_builtin = models.BooleanField(_("是否内置"), default=False)
project_key = models.CharField(
@@ -442,7 +453,9 @@ class BKUserRole(models.Model):
username = models.CharField(
_("蓝鲸用户username"), max_length=LEN_NORMAL, default=EMPTY_STRING
)
- roles = jsonfield.JSONField(_("用户角色"), default=roles_dict, null=True, blank=True)
+ roles = jsonfield.JSONField(
+ _("用户角色"), default=roles_dict, null=True, blank=True
+ )
uid = models.CharField(
_("用户uid"), max_length=LEN_NORMAL, default=EMPTY_STRING, null=True, blank=True
)
diff --git a/itsm/role/permissions.py b/itsm/role/permissions.py
index 07949e930..ee6b94024 100644
--- a/itsm/role/permissions.py
+++ b/itsm/role/permissions.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.drf import permissions as perm
from itsm.component.drf.permissions import IamAuthPermit
@@ -37,12 +37,12 @@ class IsUserRoleManager(perm.IsManager):
class UserGroupPermission(IamAuthPermit):
-
+
def has_object_permission(self, request, view, obj, **kwargs):
# 关联实例的请求,需要针对对象进行鉴权
if view.action in getattr(view, "permission_free_actions", []):
return True
-
+
if view.action in ["retrieve"]:
apply_actions = ["user_group_view"]
elif view.action in ["destroy"]:
diff --git a/itsm/role/serializers.py b/itsm/role/serializers.py
index 5c56f73c8..5c8e2563b 100644
--- a/itsm/role/serializers.py
+++ b/itsm/role/serializers.py
@@ -25,7 +25,7 @@
from django.contrib.auth import get_user_model
from django.db import transaction
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from itsm.component.constants import (
@@ -33,7 +33,8 @@
LEN_MIDDLE,
LEN_NORMAL,
LEN_XX_LONG,
- WIKI_ADMIN_SUPERUSER_KEY, LEN_SHORT,
+ WIKI_ADMIN_SUPERUSER_KEY,
+ LEN_SHORT,
)
from itsm.component.drf.serializers import DynamicFieldsModelSerializer
from itsm.component.utils.basic import dotted_name, list_by_separator, normal_name
@@ -49,8 +50,11 @@ class RoleTypeSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
type = serializers.CharField(required=True, max_length=LEN_NORMAL)
- name = serializers.CharField(required=True, max_length=LEN_NORMAL,
- error_messages={"blank": _("请输入角色名!")})
+ name = serializers.CharField(
+ required=True,
+ max_length=LEN_NORMAL,
+ error_messages={"blank": _("请输入角色名!")},
+ )
desc = serializers.CharField(required=False, max_length=LEN_MIDDLE)
class Meta:
@@ -69,26 +73,48 @@ class UserRoleSerializer(DynamicFieldsModelSerializer):
id = serializers.IntegerField(required=False)
role_type = serializers.CharField(required=True, max_length=LEN_NORMAL)
- name = serializers.CharField(required=True, max_length=LEN_NORMAL,
- error_messages={"blank": _("请输入自定义角色名")})
- members = serializers.CharField(required=True, max_length=LEN_XX_LONG,
- error_messages={"blank": _("请指定角色下的人员")})
- owners = serializers.CharField(required=False, max_length=LEN_XX_LONG, allow_blank=True)
- access = serializers.CharField(required=False, allow_null=True, allow_blank=True,
- max_length=LEN_MIDDLE)
- creator = serializers.CharField(required=False, allow_null=True, allow_blank=True,
- max_length=LEN_NORMAL)
- role_key = serializers.CharField(required=False, allow_null=True, allow_blank=True,
- max_length=LEN_MIDDLE)
- desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,
- max_length=LEN_MIDDLE)
+ name = serializers.CharField(
+ required=True,
+ max_length=LEN_NORMAL,
+ error_messages={"blank": _("请输入自定义角色名")},
+ )
+ members = serializers.CharField(
+ required=True,
+ max_length=LEN_XX_LONG,
+ error_messages={"blank": _("请指定角色下的人员")},
+ )
+ owners = serializers.CharField(
+ required=False, max_length=LEN_XX_LONG, allow_blank=True
+ )
+ access = serializers.CharField(
+ required=False, allow_null=True, allow_blank=True, max_length=LEN_MIDDLE
+ )
+ creator = serializers.CharField(
+ required=False, allow_null=True, allow_blank=True, max_length=LEN_NORMAL
+ )
+ role_key = serializers.CharField(
+ required=False, allow_null=True, allow_blank=True, max_length=LEN_MIDDLE
+ )
+ desc = serializers.CharField(
+ required=False, allow_null=True, allow_blank=True, max_length=LEN_MIDDLE
+ )
project_key = serializers.CharField(required=True, max_length=LEN_SHORT)
class Meta:
model = UserRole
fields = (
- "id", "role_type", "name", "members", "project_key", "owners", "access",
- "desc", "role_key", "creator", "is_builtin")
+ "id",
+ "role_type",
+ "name",
+ "members",
+ "project_key",
+ "owners",
+ "access",
+ "desc",
+ "role_key",
+ "creator",
+ "is_builtin",
+ )
create_only_fields = ("project_key", "is_builtin", "creator")
def __init__(self, *args, **kwargs):
@@ -121,8 +147,12 @@ def to_representation(self, instance):
if "access" in data:
data["access_name"] = _(
- ",".join([_(ACCESS_NAMES.get(key, "")) for key in
- list_by_separator(data.get("access"), ",")])
+ ",".join(
+ [
+ _(ACCESS_NAMES.get(key, ""))
+ for key in list_by_separator(data.get("access"), ",")
+ ]
+ )
)
return self.update_auth_actions(instance, data)
@@ -139,7 +169,9 @@ def update(self, instance, validated_data):
if cancel_wiki_admins:
# 去掉权限的用户
- for not_wiki_admin_user in BKUser.objects.filter(username__in=cancel_wiki_admins):
+ for not_wiki_admin_user in BKUser.objects.filter(
+ username__in=cancel_wiki_admins
+ ):
not_wiki_admin_user.set_property("is_wiki_superuser", 0)
# 加权限的用户,如果用户不存在就创建一个
diff --git a/itsm/role/utils.py b/itsm/role/utils.py
index 17cfe9461..851ec57a0 100644
--- a/itsm/role/utils.py
+++ b/itsm/role/utils.py
@@ -24,7 +24,7 @@
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
def translate_constant_2(constant):
diff --git a/itsm/role/validators.py b/itsm/role/validators.py
index 5bac700e8..fb4c48179 100644
--- a/itsm/role/validators.py
+++ b/itsm/role/validators.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from itsm.component.constants import DEFAULT_PROJECT_PROJECT_KEY
@@ -44,7 +44,7 @@ def __call__(self, value):
access = value.get("access", "")
project_key = value.get("project_key", DEFAULT_PROJECT_PROJECT_KEY)
members = list_by_separator(value.get("members", ""))
-
+
if getattr(self.role, "id", None) != value.get("id"):
raise serializers.ValidationError(_("角色 ID 异常"))
diff --git a/itsm/service/api.py b/itsm/service/api.py
index d2571846a..270e37d61 100644
--- a/itsm/service/api.py
+++ b/itsm/service/api.py
@@ -23,11 +23,11 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-__author__ = u"蓝鲸智云"
+__author__ = "蓝鲸智云"
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
from django.core.cache import cache
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import PREFIX_KEY
from .models import Service, ServiceCatalog
@@ -36,14 +36,16 @@
def get_catalog_fullname(catalog_id):
"""
获取服务目录全名
- :param catalog_id:
- :return:
+ :param catalog_id:
+ :return:
"""
cache_key = "%scatalog_fullname_%s" % (PREFIX_KEY, catalog_id)
catalog_fullname = cache.get(cache_key)
if catalog_fullname:
return _(catalog_fullname)
- catalog_fullname = ServiceCatalog._objects.get(id=catalog_id).link_parent_name_ex_root
+ catalog_fullname = ServiceCatalog._objects.get(
+ id=catalog_id
+ ).link_parent_name_ex_root
cache.set(cache_key, catalog_fullname, 30)
return _(catalog_fullname)
diff --git a/itsm/service/managers.py b/itsm/service/managers.py
index e1e601b3e..486fefaf2 100644
--- a/itsm/service/managers.py
+++ b/itsm/service/managers.py
@@ -29,7 +29,7 @@
from django.conf import settings
from django.db import transaction
from django.db.models.query import QuerySet
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.constants import (
@@ -210,7 +210,6 @@ def insert_services(self, services, catalog=None):
return {"result": True, "message": "success"}
def upgrade_services_flow(self, **kwargs):
-
"""更新现有服务绑定的流程版本"""
print("-------------------upgrade_services_flow------------------------\n")
@@ -249,7 +248,6 @@ def upgrade_services_flow(self, **kwargs):
WorkflowVersion.objects.upgrade_version(non_bind_flow.id, **kwargs)
def get_or_create_service_and_catalog_from_version(self, *args, **kwargs):
-
"""创建服务条目并绑定到服务目录
排除草稿流程,仅创建有效流程的服务项
"""
diff --git a/itsm/service/models.py b/itsm/service/models.py
index de6062334..582affdae 100644
--- a/itsm/service/models.py
+++ b/itsm/service/models.py
@@ -32,7 +32,7 @@
from django.db import models, transaction
from django.db.models import Q, Count
from django.utils.functional import cached_property
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mptt.models import TreeForeignKey
from multiselectfield import MultiSelectField
@@ -626,7 +626,9 @@ def subtree(node, catalogs=None, show_deleted=False, catalog_count=None):
data = {
"id": node.id,
"key": node.key,
- "name": _("{} (已删除)").format(node.name) if node.is_deleted else node.name,
+ "name": (
+ _("{} (已删除)").format(node.name) if node.is_deleted else node.name
+ ),
"is_deleted": node.is_deleted,
"level": node.level,
"desc": node.desc,
@@ -1167,8 +1169,13 @@ class ServiceProperty(Model):
related_name="properties",
on_delete=models.CASCADE,
)
- key = models.CharField(_("默认为名称拼音,唯一存在,如果有一样的,则通过拼音+随机字符匹配"), max_length=LEN_SHORT)
- pk_key = models.CharField(_("fields中的主键的key"), max_length=LEN_SHORT, default="")
+ key = models.CharField(
+ _("默认为名称拼音,唯一存在,如果有一样的,则通过拼音+随机字符匹配"),
+ max_length=LEN_SHORT,
+ )
+ pk_key = models.CharField(
+ _("fields中的主键的key"), max_length=LEN_SHORT, default=""
+ )
cascade_key = models.CharField(
_("fields中的级联外键的key"), max_length=LEN_SHORT, default=""
)
@@ -1274,7 +1281,10 @@ class PropertyRecord(Model):
)
data = jsonfield.JSONField(_("对应属性字段的值"), null=True, blank=True)
display_role = MultiSelectField(
- _("可展示的用户"), default=["all"], max_length=LEN_NORMAL, choices=display_role_choice
+ _("可展示的用户"),
+ default=["all"],
+ max_length=LEN_NORMAL,
+ choices=display_role_choice,
)
# objects = managers.Manager()
diff --git a/itsm/service/permissions.py b/itsm/service/permissions.py
index 733475947..592b7305d 100644
--- a/itsm/service/permissions.py
+++ b/itsm/service/permissions.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import permissions
from itsm.auth_iam.utils import IamRequest
@@ -50,7 +50,7 @@ class IsDictDataManager(permissions.BasePermission):
from itsm.service.models import SysDict
- SAFE_METHODS = permissions.SAFE_METHODS + ('DELETE',)
+ SAFE_METHODS = permissions.SAFE_METHODS + ("DELETE",)
def has_permission(self, request, view):
if request.method in self.SAFE_METHODS:
@@ -59,7 +59,7 @@ def has_permission(self, request, view):
if UserRole.is_itsm_superuser(request.user.username):
return True
- dict_table = request.data.get('dict_table')
+ dict_table = request.data.get("dict_table")
try:
sys_dict = self.SysDict.objects.get(id=dict_table)
return sys_dict.is_obj_manager(request.user.username)
@@ -87,16 +87,18 @@ def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
- if view.action == 'batch_delete':
- id_list = [i for i in request.data.get('id').split(',') if i.isdigit()]
+ if view.action == "batch_delete":
+ id_list = [i for i in request.data.get("id").split(",") if i.isdigit()]
return not CatalogService.objects.filter(service_id__in=id_list).exists()
return True
def has_object_permission(self, request, view, obj):
- if view.action == 'destroy':
- return not CatalogService.objects.filter(service_id=request.parser_context['kwargs'].get('pk')).exists()
+ if view.action == "destroy":
+ return not CatalogService.objects.filter(
+ service_id=request.parser_context["kwargs"].get("pk")
+ ).exists()
return True
@@ -105,6 +107,7 @@ class ServicePermit(IamAuthPermit):
"""
服务鉴权
"""
+
service_clone_action = ["clone", "import_from_service", "import_from_template"]
def has_permission(self, request, view):
@@ -113,7 +116,7 @@ def has_permission(self, request, view):
obj = view.get_object()
project = Project.objects.filter(pk=obj.project_key).first()
return super().has_object_permission(request, view, project)
-
+
# 批量删除
if view.action == "batch_delete":
id_list = [i for i in request.data.get("id").split(",") if i.isdigit()]
@@ -128,23 +131,22 @@ def has_permission(self, request, view):
elif service.project_key != project_key:
raise ValidationError(_("服务所属项目不一致"))
- resources.append({
- "resource_id": service.id,
- "resource_type": "service",
- "creator": getattr(service, "creator", ""),
- })
-
+ resources.append(
+ {
+ "resource_id": service.id,
+ "resource_type": "service",
+ "creator": getattr(service, "creator", ""),
+ }
+ )
+
iam_client = IamRequest(request)
allowed = iam_client.batch_resource_multi_actions_allowed(
- actions=["service_manage"],
- resources=resources,
- project_key=project_key
-
+ actions=["service_manage"], resources=resources, project_key=project_key
)
return all([i["service_manage"] for i in allowed.values()])
-
+
return super().has_permission(request, view)
-
+
def has_object_permission(self, request, view, obj, **kwargs):
if view.action in self.service_clone_action:
"""针对 clone 类操作,不需要检测实例对象权限"""
diff --git a/itsm/service/serializers.py b/itsm/service/serializers.py
index 2f17802b9..8b3902f24 100644
--- a/itsm/service/serializers.py
+++ b/itsm/service/serializers.py
@@ -24,7 +24,7 @@
"""
from django.db import transaction
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.fields import JSONField
@@ -184,7 +184,9 @@ def validate(self, attrs):
.filter(is_deleted=False, name=attrs["name"])
.exists()
):
- raise ServiceCatalogValidateError(_("同级下目录名不能重复,请修改后提交"))
+ raise ServiceCatalogValidateError(
+ _("同级下目录名不能重复,请修改后提交")
+ )
if self.context["view"].action == "update":
if (
parent_object
@@ -193,7 +195,9 @@ def validate(self, attrs):
.exclude(id=self.instance.id)
.exists()
):
- raise ServiceCatalogValidateError(_("同级下目录名不能重复,请修改后提交"))
+ raise ServiceCatalogValidateError(
+ _("同级下目录名不能重复,请修改后提交")
+ )
attrs["parent"] = parent_object
@@ -253,7 +257,9 @@ class SlaSerializer(serializers.ModelSerializer):
error_messages={"blank": _("名称为必填项")},
max_length=8,
validators=[
- UniqueValidator(queryset=OldSla.objects.all(), message=_("服务级别名已存在,请重新输入")),
+ UniqueValidator(
+ queryset=OldSla.objects.all(), message=_("服务级别名已存在,请重新输入")
+ ),
name_validator,
],
)
@@ -307,7 +313,9 @@ class ServiceSlaSerializer(serializers.ModelSerializer):
"""服务与SLA关联表序列化"""
name = serializers.CharField(
- required=True, error_messages={"blank": _("协议名称不能为空")}, max_length=LEN_LONG
+ required=True,
+ error_messages={"blank": _("协议名称不能为空")},
+ max_length=LEN_LONG,
)
service_id = serializers.IntegerField(required=False, allow_null=True)
lines = JSONField(required=False, initial=EMPTY_LIST)
@@ -326,7 +334,9 @@ class ServiceSerializer(AuthModelSerializer):
error_messages={"blank": _("名称不能为空")},
max_length=LEN_MIDDLE,
validators=[
- UniqueValidator(queryset=Service.objects.all(), message=_("服务名已存在,请重新输入")),
+ UniqueValidator(
+ queryset=Service.objects.all(), message=_("服务名已存在,请重新输入")
+ ),
# name_validator
],
)
@@ -386,9 +396,7 @@ def get_favorite_users(self):
services = (
[self.instance]
if isinstance(self.instance, Service)
- else []
- if self.instance is None
- else self.instance
+ else [] if self.instance is None else self.instance
)
service_ids = [service.id for service in services]
users = FavoriteService.objects.filter(service_id__in=service_ids).values(
@@ -476,7 +484,9 @@ def to_representation(self, instance):
try:
workflow_instance = Workflow.objects.get(id=instance.workflow.workflow_id)
except Workflow.DoesNotExist:
- raise ServerError("当前服务绑定的流程已经被删除, service_name={}".format(instance.name))
+ raise ServerError(
+ "当前服务绑定的流程已经被删除, service_name={}".format(instance.name)
+ )
username = self.context["request"].user.username
data["creator"] = transform_single_username(data["creator"])
@@ -508,7 +518,9 @@ class ServiceListSerializer(serializers.ModelSerializer):
error_messages={"blank": _("名称不能为空")},
max_length=LEN_MIDDLE,
validators=[
- UniqueValidator(queryset=Service.objects.all(), message=_("服务名已存在,请重新输入")),
+ UniqueValidator(
+ queryset=Service.objects.all(), message=_("服务名已存在,请重新输入")
+ ),
# name_validator
],
)
@@ -533,9 +545,7 @@ def get_service_ids(self):
services = (
[self.instance]
if isinstance(self.instance, Service)
- else []
- if self.instance is None
- else self.instance
+ else [] if self.instance is None else self.instance
)
return [service["id"] for service in services]
@@ -617,7 +627,9 @@ def validate_catalog_id(self, value):
try:
catalog = ServiceCatalog.objects.get(id=value)
if catalog.level == 0:
- raise serializers.ValidationError(_("根目录不允许添加服务,选择其他目录"))
+ raise serializers.ValidationError(
+ _("根目录不允许添加服务,选择其他目录")
+ )
except ServiceCatalog.DoesNotExist:
raise serializers.ValidationError(_("指定的服务目录不存在"))
@@ -674,7 +686,9 @@ class SysDictSerializer(DynamicFieldsModelSerializer):
error_messages={"blank": _("编码不能为空")},
max_length=LEN_MIDDLE,
validators=[
- UniqueValidator(queryset=SysDict.objects.all(), message=_("编码已存在,请重新输入")),
+ UniqueValidator(
+ queryset=SysDict.objects.all(), message=_("编码已存在,请重新输入")
+ ),
key_validator,
],
)
@@ -768,7 +782,10 @@ class WorkflowImportSerializer(serializers.Serializer):
name = serializers.CharField(
required=True,
max_length=LEN_MIDDLE,
- error_messages={"blank": _("请输入流程名称!"), "max_length": _("流程名称长度不能大于120个字符")},
+ error_messages={
+ "blank": _("请输入流程名称!"),
+ "max_length": _("流程名称长度不能大于120个字符"),
+ },
)
flow_type = serializers.CharField(required=True, max_length=LEN_NORMAL)
desc = serializers.CharField(
@@ -840,7 +857,9 @@ def validate(self, attrs):
project_key = attrs["project_key"]
if not Project.objects.filter(key=project_key).exists():
- raise serializers.ValidationError(_("导入失败,project_key 对应的项目不存在"))
+ raise serializers.ValidationError(
+ _("导入失败,project_key 对应的项目不存在")
+ )
catalog_id = attrs.get("catalog_id", None)
if catalog_id is not None:
diff --git a/itsm/service/validators.py b/itsm/service/validators.py
index 2317745df..8bd8a221b 100644
--- a/itsm/service/validators.py
+++ b/itsm/service/validators.py
@@ -26,7 +26,7 @@
import re
from django.core.validators import RegexValidator
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from itsm.service.models import CatalogService, Service, ServiceCategory
@@ -39,20 +39,26 @@
def time_validator(value):
time = value[-1]
if time not in ["m", "h", "d"]:
- raise serializers.ValidationError(_('时间单位不正确'))
+ raise serializers.ValidationError(_("时间单位不正确"))
try:
number = int(value[:-1])
if number > 65536 or number <= 0:
- raise serializers.ValidationError(_('时间超出了设置范围(1~65536)'))
+ raise serializers.ValidationError(_("时间超出了设置范围(1~65536)"))
except ValueError:
- raise serializers.ValidationError(_('数据类型错误,不是合法的时间'))
+ raise serializers.ValidationError(_("数据类型错误,不是合法的时间"))
-key_validator = RegexValidator(re.compile('^[_a-zA-Z0-9]+$'), message=_('请输入合法编码:英文数字及下划线'), code='invalid',)
+key_validator = RegexValidator(
+ re.compile("^[_a-zA-Z0-9]+$"),
+ message=_("请输入合法编码:英文数字及下划线"),
+ code="invalid",
+)
# 正则表达式带中文一定要要带上u,否则校验不通过
name_validator = RegexValidator(
- re.compile(r'^[a-zA-Z0-9_\s()()\u4e00-\u9fa5]+$'), message=_('请输入合法名称:中英文、中英文括号、数字、空格及下划线'), code='invalid',
+ re.compile(r"^[a-zA-Z0-9_\s()()\u4e00-\u9fa5]+$"),
+ message=_("请输入合法名称:中英文、中英文括号、数字、空格及下划线"),
+ code="invalid",
)
@@ -66,12 +72,16 @@ def service_validate(service_id):
try:
service = Service.objects.get(id=service_id)
if not service.is_valid:
- raise serializers.ValidationError({_("服务"): _("服务未启用,请联系管理员!")})
+ raise serializers.ValidationError(
+ {_("服务"): _("服务未启用,请联系管理员!")}
+ )
except Service.DoesNotExist:
raise serializers.ValidationError({_("服务"): _("服务不存在,请联系管理员!")})
try:
- catalog_services = CatalogService.objects.get(service_id=service_id, is_deleted=False)
+ catalog_services = CatalogService.objects.get(
+ service_id=service_id, is_deleted=False
+ )
except CatalogService.DoesNotExist:
raise serializers.ValidationError({_("服务"): _("服务对应的服务目录不存在")})
diff --git a/itsm/service/views.py b/itsm/service/views.py
index 526c7917b..13db17edf 100644
--- a/itsm/service/views.py
+++ b/itsm/service/views.py
@@ -28,7 +28,7 @@
from django.http import FileResponse
from django.utils.encoding import escape_uri_path
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django_bulk_update.helper import bulk_update
from rest_framework import serializers
from rest_framework.decorators import action
@@ -155,7 +155,7 @@ class ServiceCatalogViewSet(component_viewsets.ModelViewSet):
"update": "catalog_edit",
"destroy": "catalog_delete",
}
-
+
filter_fields = {
"id": ["exact", "in"],
"key": ["exact", "in"],
@@ -515,7 +515,9 @@ def import_from_service(self, request, *args, **kwargs):
raise ParamError("service_id 不能为空")
from_service = Service.objects.get(id=service_id)
if from_service is None:
- raise ServiceNotExist("未找到相对应的服务, service_id={}".format(service_id))
+ raise ServiceNotExist(
+ "未找到相对应的服务, service_id={}".format(service_id)
+ )
with transaction.atomic():
self.copy_fields_from_service(from_service, service)
@@ -658,11 +660,11 @@ def export(self, request, *args, **kwargs):
response = FileResponse(json.dumps(data, cls=JsonEncoder, indent=2))
response["Content-Type"] = "application/octet-stream"
# 中文文件名乱码问题
- response[
- "Content-Disposition"
- ] = "attachment; filename*=UTF-8''bk_itsm_{}_{}.json".format(
- escape_uri_path(instance.name),
- create_version_number(),
+ response["Content-Disposition"] = (
+ "attachment; filename*=UTF-8''bk_itsm_{}_{}.json".format(
+ escape_uri_path(instance.name),
+ create_version_number(),
+ )
)
return response
@@ -683,7 +685,9 @@ def imports(self, request, *args, **kwargs):
project_key = request.data.get("project_key", data.get("project_key"))
data["project_key"] = project_key
if isinstance(data, list):
- raise ParamError(_("2.5.9 版本之前的流程无法导入,请转换后在看,详情请看github"))
+ raise ParamError(
+ _("2.5.9 版本之前的流程无法导入,请转换后在看,详情请看github")
+ )
ServiceImportSerializer(data=data).is_valid(raise_exception=True)
catalog_id = request.data.get("catalog_id")
service = Service.objects.clone(
@@ -794,7 +798,11 @@ def perform_destroy(self, instance):
dict_table__key__in=tables, key=instance.key
).exists():
raise serializers.ValidationError(
- _("[{}] 已经被勾选绑定,请先到优先级管理中解绑".format(instance.name))
+ _(
+ "[{}] 已经被勾选绑定,请先到优先级管理中解绑".format(
+ instance.name
+ )
+ )
)
instance.delete()
diff --git a/itsm/sites/urls.py b/itsm/sites/urls.py
index 17063dfc8..451bbdf4c 100644
--- a/itsm/sites/urls.py
+++ b/itsm/sites/urls.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.conf.urls import include, url
+from django.urls import include, re_path
from django_nyt.urls import get_pattern as get_nyt_pattern
from itsm.sites.views import index, get_footer, init
@@ -31,17 +31,17 @@
urlpatterns = [
# main
- url(r"^$", index),
- url(r"^init/$", init),
+ re_path(r"^$", index),
+ re_path(r"^init/$", init),
# flower, celery monitor
- url(r"^o/bk_sops/", include("sops_proxy.urls")),
+ re_path(r"^o/bk_sops/", include("sops_proxy.urls")),
# helper, fix database
- url(r"^helper/", include("itsm.helper.urls")),
+ re_path(r"^helper/", include("itsm.helper.urls")),
# weixin
- url(r"^weixin/$", weixin_views.index),
- url(r"^weixin/login/", include("weixin.core.urls")),
- url(r"^weixin/api/", include("weixin.urls")),
+ re_path(r"^weixin/$", weixin_views.index),
+ re_path(r"^weixin/login/", include("weixin.core.urls")),
+ re_path(r"^weixin/api/", include("weixin.urls")),
# wiki
- url(r"^notifications/", get_nyt_pattern()),
- url(r"^core/footer/$", get_footer),
+ re_path(r"^notifications/", get_nyt_pattern()),
+ re_path(r"^core/footer/$", get_footer),
]
diff --git a/itsm/sites/views.py b/itsm/sites/views.py
index 81ea8269c..0998de1f0 100644
--- a/itsm/sites/views.py
+++ b/itsm/sites/views.py
@@ -30,7 +30,7 @@
from django.conf import settings
from django.http import JsonResponse, HttpResponseRedirect
from django.shortcuts import render
-from django.utils.translation import ugettext as _, get_language
+from django.utils.translation import gettext as _, get_language
from django.views.decorators.http import require_GET
from mako.template import Template
@@ -71,9 +71,9 @@ def init(request):
"chname": request.user.get_property("chname"),
"username": request.user.username,
"all_access": UserRole.get_access_by_user(request.user.username),
- "IS_ITSM_ADMIN": 1
- if UserRole.is_itsm_superuser(request.user.username)
- else 0,
+ "IS_ITSM_ADMIN": (
+ 1 if UserRole.is_itsm_superuser(request.user.username) else 0
+ ),
"need_target": False, # 不需要强制跳转无权限页
"location": "",
},
@@ -116,15 +116,17 @@ def index(request):
).value
except SystemSettings.DoesNotExist:
notice_center_switch_value = "off"
-
+
# 文档地址转换
doc_lang = "EN"
lang = get_language()
if lang in ["zh-cn", "zh-hans"]:
doc_lang = "ZH"
-
+
version = get_version()
- doc_url = settings.BK_DOC_URL.format(lang=doc_lang, version=get_major_minor_version(version))
+ doc_url = settings.BK_DOC_URL.format(
+ lang=doc_lang, version=get_major_minor_version(version)
+ )
return render(
request,
@@ -178,16 +180,16 @@ def get_version():
"""
# 读取文件内容
app_desc = os.path.join(settings.PROJECT_ROOT, "VERSION")
- with open(app_desc, 'r') as file:
+ with open(app_desc, "r") as file:
content = file.read()
return content.strip()
def get_major_minor_version(version_string):
# 使用 split() 方法分割字符串
- parts = version_string.split('.')
+ parts = version_string.split(".")
# 取前两个部分并用 '.' 连接
- major_minor = '.'.join(parts[:2])
+ major_minor = ".".join(parts[:2])
return major_minor
diff --git a/itsm/sla/models/basic.py b/itsm/sla/models/basic.py
index 2053a681b..8c508a883 100644
--- a/itsm/sla/models/basic.py
+++ b/itsm/sla/models/basic.py
@@ -24,7 +24,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import LEN_NORMAL
from itsm.component.utils.basic import get_random_key
@@ -35,10 +35,10 @@ class Model(models.Model):
"""基础字段"""
DISPLAY_FIELDS = (
- 'creator',
- 'create_at',
- 'updated_by',
- 'update_at',
+ "creator",
+ "create_at",
+ "updated_by",
+ "update_at",
)
creator = models.CharField(_("创建人"), max_length=LEN_NORMAL)
@@ -79,6 +79,6 @@ def get_unique_key(cls, name):
retry += 1
else:
# 尝试60次一直重复,则放弃生成key
- return '##Err##'
+ return "##Err##"
return key
diff --git a/itsm/sla/models/policy.py b/itsm/sla/models/policy.py
index 50ba26f3e..72eaa3d25 100644
--- a/itsm/sla/models/policy.py
+++ b/itsm/sla/models/policy.py
@@ -26,7 +26,7 @@
import copy
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from jsonfield import JSONField
from mako.template import Template
@@ -353,7 +353,9 @@ class ActionPolicy(Model):
"""
name = models.CharField(_("策略名称"), max_length=LEN_LONG)
- type = models.IntegerField(_("升级事件类型"), choices=ACTION_POLICY_TYPES, default=1)
+ type = models.IntegerField(
+ _("升级事件类型"), choices=ACTION_POLICY_TYPES, default=1
+ )
order = models.IntegerField(_("策略顺序"), default=-1)
condition = JSONField("升级条件", help_text="当达到条件的时候,可以触发不同的动作")
actions = models.ManyToManyField(Action, help_text=_("处理事件"))
diff --git a/itsm/sla/models/schedule.py b/itsm/sla/models/schedule.py
index 95cfef376..ce537a392 100644
--- a/itsm/sla/models/schedule.py
+++ b/itsm/sla/models/schedule.py
@@ -26,7 +26,7 @@
from calendar import FRIDAY, MONDAY, SATURDAY, SUNDAY, THURSDAY, TUESDAY, WEDNESDAY
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
DAY_TYPE_CHOICES,
@@ -49,7 +49,7 @@ class Duration(Model):
end_time = models.TimeField(_("结束时间"))
class Meta:
- app_label = 'sla'
+ app_label = "sla"
verbose_name = _("工作时间段")
verbose_name_plural = _("工作时间段")
@@ -62,15 +62,22 @@ class Day(Model):
name = models.CharField(_("假期名称"), max_length=LEN_LONG)
day_of_week = models.CharField(_("星期几"), max_length=LEN_SHORT, default=-1)
- type_of_day = models.CharField(_("日期类型"), max_length=LEN_SHORT, choices=DAY_TYPE_CHOICES, default=NORMAL_DAY)
+ type_of_day = models.CharField(
+ _("日期类型"),
+ max_length=LEN_SHORT,
+ choices=DAY_TYPE_CHOICES,
+ default=NORMAL_DAY,
+ )
# 比如十一假期范围
start_date = models.DateField(_("开始日期"), null=True)
end_date = models.DateField(_("结束日期"), null=True)
- duration = models.ManyToManyField(to='Duration', help_text=_("工作时间段,没有配置的情况下,默认从0:00 -23:59"))
+ duration = models.ManyToManyField(
+ to="Duration", help_text=_("工作时间段,没有配置的情况下,默认从0:00 -23:59")
+ )
class Meta:
- app_label = 'sla'
+ app_label = "sla"
verbose_name = _("工作日和节假日")
verbose_name_plural = _("工作日和节假日")
@@ -82,23 +89,42 @@ class Schedule(Model):
"""服务时间策略"""
name = models.CharField(_("名称"), max_length=LEN_LONG)
- is_enabled = models.BooleanField("配置是否生效", help_text="是:启用当前工作日历配置, 否:默认采用7*24小时", default=True)
- days = models.ManyToManyField(help_text=_("常规日历"), to='Day', related_name='days')
- workdays = models.ManyToManyField(help_text=_("加班日"), to='Day', related_name='workdays', default=None)
- holidays = models.ManyToManyField(help_text=_("假期"), to='Day', related_name='holidays', default=None)
+ is_enabled = models.BooleanField(
+ "配置是否生效",
+ help_text="是:启用当前工作日历配置, 否:默认采用7*24小时",
+ default=True,
+ )
+ days = models.ManyToManyField(
+ help_text=_("常规日历"), to="Day", related_name="days"
+ )
+ workdays = models.ManyToManyField(
+ help_text=_("加班日"), to="Day", related_name="workdays", default=None
+ )
+ holidays = models.ManyToManyField(
+ help_text=_("假期"), to="Day", related_name="holidays", default=None
+ )
is_builtin = models.BooleanField(_("是否内置"), default=False)
- project_key = models.CharField(_("项目key"), max_length=LEN_SHORT, null=False, default=0)
-
- auth_resource = {"resource_type": "sla_calendar", "resource_type_name": "SLA 服务模式"}
- resource_operations = ["sla_calendar_view", "sla_calendar_edit", "sla_calendar_delete"]
+ project_key = models.CharField(
+ _("项目key"), max_length=LEN_SHORT, null=False, default=0
+ )
+
+ auth_resource = {
+ "resource_type": "sla_calendar",
+ "resource_type_name": "SLA 服务模式",
+ }
+ resource_operations = [
+ "sla_calendar_view",
+ "sla_calendar_edit",
+ "sla_calendar_delete",
+ ]
need_auth_grant = True
class Meta:
- app_label = 'sla'
+ app_label = "sla"
verbose_name = _("服务运营时间")
verbose_name_plural = _("服务运营时间")
- ordering = ['-id']
+ ordering = ["-id"]
def __unicode__(self):
return self.name
@@ -106,22 +132,29 @@ def __unicode__(self):
@classmethod
def init_schedule(cls, project_key="0"):
"""初始化默认的服务模式"""
- default_schedule_name = ['5*8', '7*24']
+ default_schedule_name = ["5*8", "7*24"]
default_day_of_week = [
- ','.join(map(str, [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY])),
- ','.join(map(str, [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY])),
+ ",".join(map(str, [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY])),
+ ",".join(
+ map(
+ str,
+ [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY],
+ )
+ ),
]
default_durations = [
[
- {'name': _('上午'), 'start_time': '08:00:00', 'end_time': '12:00:00'},
- {'name': _('下午'), 'start_time': '14:00:00', 'end_time': '18:00:00'},
+ {"name": _("上午"), "start_time": "08:00:00", "end_time": "12:00:00"},
+ {"name": _("下午"), "start_time": "14:00:00", "end_time": "18:00:00"},
],
- [{'name': _('全天'), 'start_time': '00:00:00', 'end_time': '23:59:59'}],
+ [{"name": _("全天"), "start_time": "00:00:00", "end_time": "23:59:59"}],
]
schedules = []
for index, schedule_name in enumerate(default_schedule_name):
- schedule = cls.objects.filter(name=schedule_name, project_key=project_key).first()
+ schedule = cls.objects.filter(
+ name=schedule_name, project_key=project_key
+ ).first()
if schedule:
schedules.append(schedule)
@@ -133,9 +166,11 @@ def init_schedule(cls, project_key="0"):
for duration_data in durations_data:
duration = Duration.objects.create(**duration_data)
durations.append(duration)
- day = Day.objects.create(day_of_week=day_of_week, type_of_day='NORMAL')
+ day = Day.objects.create(day_of_week=day_of_week, type_of_day="NORMAL")
day.duration.add(*durations)
- schedule = cls.objects.create(name=schedule_name, is_builtin=True, project_key=project_key)
+ schedule = cls.objects.create(
+ name=schedule_name, is_builtin=True, project_key=project_key
+ )
schedule.days.add(day)
schedules.append(schedule)
diff --git a/itsm/sla/permissions.py b/itsm/sla/permissions.py
index 52e53573f..c5dd2a190 100644
--- a/itsm/sla/permissions.py
+++ b/itsm/sla/permissions.py
@@ -26,7 +26,7 @@
__author__ = "蓝鲸智云"
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import permissions
from itsm.component.drf.permissions import IamAuthPermit, IamAuthWithoutResourcePermit
@@ -50,7 +50,7 @@ def has_permission(self, request, view):
return True
# 提单环节拉取优先级
- if view.action in ['priority_value']:
+ if view.action in ["priority_value"]:
return True
return False
@@ -81,10 +81,10 @@ def has_object_permission(self, request, view, obj, **kwargs):
apply_actions = ["sla_agreement_edit"]
return self.iam_auth(request, apply_actions, obj)
-
-
+
+
class SchedulePermit(IamAuthPermit):
-
+
def has_object_permission(self, request, view, obj, **kwargs):
# 关联实例的请求,需要针对对象进行鉴权
if view.action in getattr(view, "permission_free_actions", []):
@@ -98,18 +98,13 @@ def has_object_permission(self, request, view, obj, **kwargs):
apply_actions = ["sla_calendar_edit"]
return self.iam_auth(request, apply_actions, obj)
-
-
+
+
class SlaMatrixPermit(IamAuthWithoutResourcePermit):
def has_permission(self, request, view):
if view.action == "matrix_of_service_type":
apply_actions = ["sla_priority_view", "platform_manage_access"]
else:
apply_actions = ["sla_priority_manage"]
-
+
return self.iam_auth(request, apply_actions)
-
-
-
-
-
diff --git a/itsm/sla/serializers/matrix.py b/itsm/sla/serializers/matrix.py
index a4f857cc5..339e6a5ae 100644
--- a/itsm/sla/serializers/matrix.py
+++ b/itsm/sla/serializers/matrix.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from bulk_update.helper import bulk_update
@@ -37,7 +37,13 @@ class PriorityMatrixSerializer(serializers.ModelSerializer):
class Meta:
model = PriorityMatrix
- fields = ('id', 'service_type', 'urgency', 'impact', 'priority',) + model.DISPLAY_FIELDS
+ fields = (
+ "id",
+ "service_type",
+ "urgency",
+ "impact",
+ "priority",
+ ) + model.DISPLAY_FIELDS
read_only_fields = model.DISPLAY_FIELDS
@@ -48,22 +54,24 @@ class MatrixListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data, other_types=None):
"""批量更新,返回更新的数量"""
- item_hash = {item['id']: item for item in validated_data}
+ item_hash = {item["id"]: item for item in validated_data}
priorities = []
for p in PriorityMatrix.objects.filter(pk__in=list(item_hash.keys())):
- p.priority = item_hash[p.id]['priority']
+ p.priority = item_hash[p.id]["priority"]
priorities.append(p)
# 其他类型更新
other_types = [] if other_types is None else other_types
for other_type in other_types:
for i in validated_data:
- p = PriorityMatrix.objects.get(service_type=other_type, urgency=i["urgency"], impact=i["impact"])
- p.priority = i['priority']
+ p = PriorityMatrix.objects.get(
+ service_type=other_type, urgency=i["urgency"], impact=i["impact"]
+ )
+ p.priority = i["priority"]
priorities.append(p)
- return bulk_update(priorities, update_fields=['priority'])
+ return bulk_update(priorities, update_fields=["priority"])
class MatrixUpdateSerializer(serializers.ModelSerializer):
@@ -76,15 +84,10 @@ class MatrixUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = PriorityMatrix
list_serializer_class = MatrixListSerializer
- fields = (
- 'id',
- 'priority',
- 'impact',
- 'urgency'
- )
+ fields = ("id", "priority", "impact", "urgency")
def validate_priority(self, value):
- priority_set = SysDict.get_data_by_key(PRIORITY, 'sets')
+ priority_set = SysDict.get_data_by_key(PRIORITY, "sets")
priority_set.add(EMPTY_STRING)
if value not in priority_set:
diff --git a/itsm/sla/serializers/policy.py b/itsm/sla/serializers/policy.py
index c9c76b6db..275e6de7d 100644
--- a/itsm/sla/serializers/policy.py
+++ b/itsm/sla/serializers/policy.py
@@ -23,13 +23,20 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import empty
from itsm.component.constants import LEN_MIDDLE, LEN_SHORT
from itsm.component.drf.serializers import AuthModelSerializer
-from itsm.sla.models import Action, ActionPolicy, PriorityPolicy, Sla, SlaTimerRule, SlaTicketHighlight
+from itsm.sla.models import (
+ Action,
+ ActionPolicy,
+ PriorityPolicy,
+ Sla,
+ SlaTimerRule,
+ SlaTicketHighlight,
+)
from itsm.sla.serializers import ModelSerializer
from itsm.sla.validators import SlaTimerRuleValidator, SlaValidator
@@ -59,11 +66,11 @@ class Meta:
) + model.DISPLAY_FIELDS
read_only_fields = model.DISPLAY_FIELDS
-
+
def to_internal_value(self, data):
if not data["reply_time"]:
data["reply_time"] = None
-
+
return super(PriorityPolicySerializer, self).to_internal_value(data)
@@ -76,7 +83,13 @@ class SlaTimerRuleSerializer(serializers.ModelSerializer):
class Meta:
model = SlaTimerRule
- fields = ('id', 'name', 'service_type', 'condition_type', 'condition') + model.DISPLAY_FIELDS
+ fields = (
+ "id",
+ "name",
+ "service_type",
+ "condition_type",
+ "condition",
+ ) + model.DISPLAY_FIELDS
read_only_fields = model.DISPLAY_FIELDS
@@ -95,7 +108,11 @@ class ActionSerializer(serializers.ModelSerializer):
class Meta:
model = Action
- fields = ('id', 'action_type', 'config',) + model.DISPLAY_FIELDS
+ fields = (
+ "id",
+ "action_type",
+ "config",
+ ) + model.DISPLAY_FIELDS
read_only_fields = model.DISPLAY_FIELDS
@@ -112,7 +129,7 @@ class ActionPolicySerializer(ModelSerializer):
class Meta:
model = ActionPolicy
- fields = ('id', 'name', 'condition', 'actions', 'type') + model.DISPLAY_FIELDS
+ fields = ("id", "name", "condition", "actions", "type") + model.DISPLAY_FIELDS
read_only_fields = model.DISPLAY_FIELDS
@@ -122,7 +139,9 @@ def create(self, validated_data):
if not actions:
return instance
- actions = [self.fields.fields['actions'].child.create(action) for action in actions]
+ actions = [
+ self.fields.fields["actions"].child.create(action) for action in actions
+ ]
instance.actions.set(actions)
instance.save()
return instance
@@ -154,19 +173,19 @@ class SlaSerializer(AuthModelSerializer, ModelSerializer):
class Meta:
model = Sla
fields = (
- 'id',
- 'name',
- 'is_enabled',
- 'is_builtin',
- 'policies',
- 'action_policies',
- 'service_count',
- 'service_names',
- 'is_reply_need',
- 'project_key'
+ "id",
+ "name",
+ "is_enabled",
+ "is_builtin",
+ "policies",
+ "action_policies",
+ "service_count",
+ "service_names",
+ "is_reply_need",
+ "project_key",
) + model.DISPLAY_FIELDS
- related_fields = ('policies', 'action_policies')
+ related_fields = ("policies", "action_policies")
read_only_fields = model.DISPLAY_FIELDS
@@ -175,15 +194,22 @@ def create(self, validated_data):
action_policies = validated_data.pop("action_policies", [])
policies = validated_data.pop("policies", [])
instance = super(SlaSerializer, self).create(validated_data)
- instance.action_policies.set([
- self.fields.fields['action_policies'].child.create(a_data) for a_data in action_policies
- ])
- instance.policies.set([self.fields.fields['policies'].child.create(p_data) for p_data in policies])
+ instance.action_policies.set(
+ [
+ self.fields.fields["action_policies"].child.create(a_data)
+ for a_data in action_policies
+ ]
+ )
+ instance.policies.set(
+ [self.fields.fields["policies"].child.create(p_data) for p_data in policies]
+ )
instance.save()
return instance
def update(self, instance, validated_data):
- rel_fields = {key: validated_data.pop(key, []) for key in self.Meta.related_fields}
+ rel_fields = {
+ key: validated_data.pop(key, []) for key in self.Meta.related_fields
+ }
instance = super(SlaSerializer, self).update(instance, validated_data)
instance = self.update_many_to_many_relation(instance, rel_fields)
@@ -192,7 +218,7 @@ def update(self, instance, validated_data):
def run_validation(self, data=empty):
return super(SlaSerializer, self).run_validation(data)
-
+
def to_representation(self, instance):
data = super(SlaSerializer, self).to_representation(instance)
return self.update_auth_actions(instance, data)
@@ -203,8 +229,4 @@ class TicketHighlightSerializer(ModelSerializer):
class Meta:
model = SlaTicketHighlight
- fields = (
- 'id',
- 'reply_timeout_color',
- 'handle_timeout_color'
- )
+ fields = ("id", "reply_timeout_color", "handle_timeout_color")
diff --git a/itsm/sla/serializers/schedule.py b/itsm/sla/serializers/schedule.py
index ccdf6f3fa..741ec1d93 100644
--- a/itsm/sla/serializers/schedule.py
+++ b/itsm/sla/serializers/schedule.py
@@ -23,13 +23,18 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import empty
from itsm.component.constants import DAY_TYPE_CHOICES, LEN_MIDDLE, LEN_SHORT
from itsm.sla.models import Day, Duration, Schedule
-from itsm.sla.validators import DayValidator, DurationValidator, ScheduleValidator, name_validator
+from itsm.sla.validators import (
+ DayValidator,
+ DurationValidator,
+ ScheduleValidator,
+ name_validator,
+)
from .basic import ModelSerializer
from ...component.drf.serializers import AuthModelSerializer
@@ -42,7 +47,7 @@ class DurationSerializer(ModelSerializer):
class Meta:
model = Duration
- fields = ('id', 'name', 'start_time', 'end_time')
+ fields = ("id", "name", "start_time", "end_time")
def run_validation(self, data=empty):
self.validators = [DurationValidator(self.instance)]
@@ -54,7 +59,9 @@ class DaySerializer(ModelSerializer):
id = serializers.CharField(required=False, max_length=LEN_SHORT, allow_null=True)
- day_of_week = serializers.CharField(required=False, max_length=LEN_SHORT, allow_null=True)
+ day_of_week = serializers.CharField(
+ required=False, max_length=LEN_SHORT, allow_null=True
+ )
start_date = serializers.DateField(required=False, allow_null=True)
@@ -62,7 +69,7 @@ class DaySerializer(ModelSerializer):
type_of_day = serializers.CharField(
required=True,
- error_messages={'blank': _("日期类型不能为空")},
+ error_messages={"blank": _("日期类型不能为空")},
max_length=LEN_SHORT,
# choices=DAY_TYPE_CHOICES
)
@@ -78,12 +85,20 @@ class DaySerializer(ModelSerializer):
class Meta:
model = Day
- fields = ('id', 'name', 'type_of_day', 'day_of_week', 'start_date', 'end_date', 'duration')
+ fields = (
+ "id",
+ "name",
+ "type_of_day",
+ "day_of_week",
+ "start_date",
+ "end_date",
+ "duration",
+ )
def create(self, validated_data):
duration = [
- Duration.objects.create(**duration_params) for duration_params in
- validated_data.pop("duration", [])
+ Duration.objects.create(**duration_params)
+ for duration_params in validated_data.pop("duration", [])
]
day = super(DaySerializer, self).create(validated_data)
if duration:
@@ -97,7 +112,9 @@ def update(self, instance, validated_data):
if not duration:
return instance
- duration = [Duration.objects.create(**duration_params) for duration_params in duration]
+ duration = [
+ Duration.objects.create(**duration_params) for duration_params in duration
+ ]
instance.duration.set(duration)
instance.save()
return instance
@@ -112,7 +129,7 @@ class ScheduleSerializer(AuthModelSerializer, ModelSerializer):
name = serializers.CharField(
required=True,
- error_messages={'blank': _("服务模式名称不能为空")},
+ error_messages={"blank": _("服务模式名称不能为空")},
# validators=[name_validator],
max_length=LEN_MIDDLE,
)
@@ -126,13 +143,21 @@ class Meta:
model = Schedule
related_fields = ("days", "workdays", "holidays")
fields = (
- 'id', 'name', 'is_enabled', 'is_builtin', 'days', 'workdays', 'holidays', 'project_key')
+ "id",
+ "name",
+ "is_enabled",
+ "is_builtin",
+ "days",
+ "workdays",
+ "holidays",
+ "project_key",
+ )
def create(self, validated_data):
ScheduleValidator(self.instance)(validated_data)
- days = validated_data.pop('days', [])
- workdays = validated_data.pop('workdays', [])
- holidays = validated_data.pop('holidays', [])
+ days = validated_data.pop("days", [])
+ workdays = validated_data.pop("workdays", [])
+ holidays = validated_data.pop("holidays", [])
schedule = super(ScheduleSerializer, self).create(validated_data)
@@ -148,7 +173,9 @@ def create(self, validated_data):
def update(self, instance, validated_data):
- rel_fields = {key: validated_data.pop(key, []) for key in self.Meta.related_fields}
+ rel_fields = {
+ key: validated_data.pop(key, []) for key in self.Meta.related_fields
+ }
instance = super(ScheduleSerializer, self).update(instance, validated_data)
@@ -158,22 +185,28 @@ def update(self, instance, validated_data):
def set_days(self, days, schedule):
if not days:
return
- days = [self.fields.fields['days'].child.create(day_params) for day_params in days]
- schedule.days.set(days)
+ days = [
+ self.fields.fields["days"].child.create(day_params) for day_params in days
+ ]
+ schedule.days.set(days)
def set_workdays(self, workdays, schedule):
if not workdays:
return
- workdays = [self.fields.fields['workdays'].child.create(day_params) for day_params in
- workdays]
+ workdays = [
+ self.fields.fields["workdays"].child.create(day_params)
+ for day_params in workdays
+ ]
schedule.workdays.set(workdays)
def set_holidays(self, holidays, schedule):
if not holidays:
return
- holidays = [self.fields.fields['holidays'].child.create(day_params) for day_params in
- holidays]
+ holidays = [
+ self.fields.fields["holidays"].child.create(day_params)
+ for day_params in holidays
+ ]
schedule.holidays.set(holidays)
def run_validation(self, data=empty):
@@ -194,7 +227,9 @@ class ScheduleDayRelationSerializer(serializers.Serializer):
def to_internal_value(self, data):
days = data.pop("days", [])
- data['days'] = [self.fields.fields['days'].child.to_internal_value(day) for day in days]
+ data["days"] = [
+ self.fields.fields["days"].child.to_internal_value(day) for day in days
+ ]
return data
def to_representation(self, instance):
@@ -203,7 +238,9 @@ def to_representation(self, instance):
def update(self, instance, validated_data):
days = validated_data.pop("days", [])
try:
- add_method = getattr(self, "add_{}s".format(validated_data['type_of_day'].lower()))
+ add_method = getattr(
+ self, "add_{}s".format(validated_data["type_of_day"].lower())
+ )
except AttributeError:
raise serializers.ValidationError(_("不存在的日期类型"))
add_method(days, instance)
@@ -211,11 +248,11 @@ def update(self, instance, validated_data):
def add_workdays(self, workdays, schedule):
for day_params in workdays:
- schedule.workdays.add(self.fields.fields['days'].child.create(day_params))
+ schedule.workdays.add(self.fields.fields["days"].child.create(day_params))
def add_holidays(self, holidays, schedule):
for day_params in holidays:
- schedule.holidays.add(self.fields.fields['days'].child.create(day_params))
+ schedule.holidays.add(self.fields.fields["days"].child.create(day_params))
def create(self, validated_data):
pass
diff --git a/itsm/sla/validators.py b/itsm/sla/validators.py
index 6f6951a69..c9945a041 100644
--- a/itsm/sla/validators.py
+++ b/itsm/sla/validators.py
@@ -28,7 +28,7 @@
from six.moves import map, range
from django.core.validators import RegexValidator
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from itsm.component.constants import (
@@ -97,15 +97,21 @@ def matrix_data_validator(matrix_data, matrix_type):
matrix_type_msg = "影响范围"
for data in matrix_data:
if data.get("id") is None:
- raise serializers.ValidationError(_("参数错误,%s数据缺少参数id") % matrix_type_msg)
+ raise serializers.ValidationError(
+ _("参数错误,%s数据缺少参数id") % matrix_type_msg
+ )
if data.get("is_enabled") is None:
raise serializers.ValidationError(
_("参数错误,%s数据缺少参数is_enabled") % matrix_type_msg
)
if data.get("name") is None:
- raise serializers.ValidationError(_("参数错误,%s数据缺少参数name") % matrix_type_msg)
+ raise serializers.ValidationError(
+ _("参数错误,%s数据缺少参数name") % matrix_type_msg
+ )
if data.get("key") is None:
- raise serializers.ValidationError(_("参数错误,%s数据缺少参数key") % matrix_type_msg)
+ raise serializers.ValidationError(
+ _("参数错误,%s数据缺少参数key") % matrix_type_msg
+ )
def priority_matrix_validator(priority_matrix):
@@ -116,7 +122,9 @@ def priority_matrix_validator(priority_matrix):
if priority.get("priority") is None:
raise ParamError(_("参数错误,优先级数据缺少参数priority"))
if not PriorityMatrix.objects.filter(id=priority.get("id")).exists():
- raise ParamError(_("参数错误,不存在id为[%s]的优先级对象") % priority.get("id"))
+ raise ParamError(
+ _("参数错误,不存在id为[%s]的优先级对象") % priority.get("id")
+ )
def priority_validate(impact_data, urgency_data, priority_matrix):
@@ -182,7 +190,9 @@ def name_validate(self, value):
if self.instance:
# 如果是更新,内置的名称不能更新
if self.instance.is_builtin and value.get("name") != self.instance.name:
- raise ParamError(_("内置服务模式:[%s] 的名称不能修改") % self.instance.name)
+ raise ParamError(
+ _("内置服务模式:[%s] 的名称不能修改") % self.instance.name
+ )
schedule_obj = schedule_obj.exclude(id=self.instance.id)
if schedule_obj.filter(
@@ -203,7 +213,9 @@ def date_compare(dates, day_type_msg):
if max(day.get("start_date"), other_day.get("start_date")) < min(
day.get("end_date"), other_day.get("end_date")
):
- raise ParamError(_("{}时间段设置有冲突,请检查").format(day_type_msg))
+ raise ParamError(
+ _("{}时间段设置有冲突,请检查").format(day_type_msg)
+ )
holidays = value.get("holidays")
workdays = value.get("workdays")
@@ -254,7 +266,9 @@ def holiday_validate(holiday):
if not holiday.get("name"):
raise serializers.ValidationError(_("请输入节假日名称"))
if holiday.get("start_date") > holiday.get("end_date"):
- raise ParamError(_("节假日期:[%s]的时间范围设置错误") % holiday.get("name"))
+ raise ParamError(
+ _("节假日期:[%s]的时间范围设置错误") % holiday.get("name")
+ )
def workday_validate(self, workday):
"""特定工作日校验"""
@@ -322,7 +336,9 @@ def name_validate(self, value):
if self.instance:
# 如果是更新,内置的名称不能更新
if self.instance.is_builtin and value.get("name") != self.instance.name:
- raise ParamError(_("内置服务协议:[%s] 的名称不能修改" % self.instance.name))
+ raise ParamError(
+ _("内置服务协议:[%s] 的名称不能修改" % self.instance.name)
+ )
sla_obj = sla_obj.exclude(id=self.instance.id)
if sla_obj.filter(name=value.get("name"), project_key=project_key).exists():
@@ -349,8 +365,14 @@ def condition_validate(value):
if expressions:
for expression in expressions:
if expression.get("operator") is None:
- raise serializers.ValidationError(_("参数错误,计时规则条件表达式缺少operator"))
+ raise serializers.ValidationError(
+ _("参数错误,计时规则条件表达式缺少operator")
+ )
if expression.get("name") is None:
- raise serializers.ValidationError(_("参数错误,计时规则条件表达式缺少name"))
+ raise serializers.ValidationError(
+ _("参数错误,计时规则条件表达式缺少name")
+ )
if expression.get("value") is None:
- raise serializers.ValidationError(_("参数错误,计时规则条件表达式缺少value"))
+ raise serializers.ValidationError(
+ _("参数错误,计时规则条件表达式缺少value")
+ )
diff --git a/itsm/sla/views/policy.py b/itsm/sla/views/policy.py
index 97e6d2702..caf09ba10 100644
--- a/itsm/sla/views/policy.py
+++ b/itsm/sla/views/policy.py
@@ -24,20 +24,26 @@
"""
from django.db.models import Q
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from itsm.component.drf.exception import ValidationError
from itsm.component.drf.viewsets import NormalModelViewSet, AuthModelViewSet
-from itsm.sla.models import ActionPolicy, PriorityPolicy, Sla, SlaTimerRule, SlaTicketHighlight
+from itsm.sla.models import (
+ ActionPolicy,
+ PriorityPolicy,
+ Sla,
+ SlaTimerRule,
+ SlaTicketHighlight,
+)
from itsm.sla.serializers import (
ActionPolicySerializer,
PriorityPolicySerializer,
SlaSerializer,
SlaTimerRuleSerializer,
- TicketHighlightSerializer
+ TicketHighlightSerializer,
)
from itsm.sla.validators import sla_can_destroy
from itsm.ticket_status.models import TicketStatus, TicketStatusConfig
@@ -69,9 +75,9 @@ def batch_create(self, request, *args, **kwargs):
批量创建
"""
data = request.data
- basic_info = {"name": data['name'], "service_type": data['service_type']}
+ basic_info = {"name": data["name"], "service_type": data["service_type"]}
rules = []
- for rule in data['rules']:
+ for rule in data["rules"]:
rule.update(basic_info)
serializer = self.get_serializer(data=rule)
serializer.is_valid(raise_exception=True)
@@ -80,20 +86,20 @@ def batch_create(self, request, *args, **kwargs):
rules.append(serializer.data)
# 创建完后,修改工单状态配置为已配置状态
- TicketStatusConfig.update_config(data['service_type'], request.user, True)
+ TicketStatusConfig.update_config(data["service_type"], request.user, True)
return Response(rules, status=status.HTTP_201_CREATED, headers=headers)
- @action(detail=False, methods=['post'])
+ @action(detail=False, methods=["post"])
def batch_update(self, request, *args, **kwargs):
"""
批量修改
"""
data = request.data
- basic_info = {"name": data['name'], "service_type": data['service_type']}
+ basic_info = {"name": data["name"], "service_type": data["service_type"]}
rules = []
- for rule in data['rules']:
+ for rule in data["rules"]:
rule.update(basic_info)
try:
instance = self.queryset.get(id=rule.pop("id", 0))
@@ -108,7 +114,7 @@ def batch_update(self, request, *args, **kwargs):
self.perform_update(serializer)
rules.append(serializer.data)
# 修改完后,修改工单状态配置为已配置状态
- TicketStatusConfig.update_config(data['service_type'], request.user, True)
+ TicketStatusConfig.update_config(data["service_type"], request.user, True)
return Response(rules)
@@ -145,7 +151,7 @@ class SlaViewSet(AuthModelViewSet):
serializer_class = SlaSerializer
queryset = Sla.objects.all()
- permission_classes = (SlaPermit, )
+ permission_classes = (SlaPermit,)
permission_free_actions = ["list"]
filter_fields = {
"name": ["exact", "contains", "icontains"],
@@ -157,10 +163,14 @@ def get_queryset(self):
if not self.request.query_params.get("page_size"):
self.pagination_class = None
return super(SlaViewSet, self).get_queryset().filter()
-
+
def list(self, request, *args, **kwargs):
- project_key = self.request.query_params.get("project_key", DEFAULT_PROJECT_PROJECT_KEY)
- queryset = self.filter_queryset(self.get_queryset().filter(project_key=project_key))
+ project_key = self.request.query_params.get(
+ "project_key", DEFAULT_PROJECT_PROJECT_KEY
+ )
+ queryset = self.filter_queryset(
+ self.get_queryset().filter(project_key=project_key)
+ )
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
@@ -191,7 +201,9 @@ def perform_update(self, serializer):
for service_type, statuses in is_over_statuses.items():
sub_q = Q()
sub_q.connector = "AND"
- sub_q.children.extend([("service_type", service_type), ("current_status__in", statuses)])
+ sub_q.children.extend(
+ [("service_type", service_type), ("current_status__in", statuses)]
+ )
is_over_q.add(sub_q, "OR")
if serializer.instance.get_tickets().exclude(is_over_q).exists():
diff --git a/itsm/sla_engine/models.py b/itsm/sla_engine/models.py
index 3bf6a9f77..65a5c56d2 100644
--- a/itsm/sla_engine/models.py
+++ b/itsm/sla_engine/models.py
@@ -28,7 +28,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from jsonfield import JSONField
from django.db import transaction
from common.redis import Cache
@@ -72,8 +72,12 @@ class SlaTask(models.Model):
end_at = models.DateTimeField(_("任务结束计时的时间"), null=True)
upgrade_at = models.DateTimeField(_("任务升级时间"), null=True)
last_settlement_time = models.DateTimeField(_("上次结算时间"), null=True)
- sla_status = models.IntegerField(_("sla状态"), choices=SLA_TIMING_STATUS, default=NORMAL)
- task_status = models.IntegerField(_("任务状态"), choices=SLA_TASK_STATUS, default=UNACTIVATED)
+ sla_status = models.IntegerField(
+ _("sla状态"), choices=SLA_TIMING_STATUS, default=NORMAL
+ )
+ task_status = models.IntegerField(
+ _("任务状态"), choices=SLA_TASK_STATUS, default=UNACTIVATED
+ )
is_reply_need = models.BooleanField(_("是否需要响应"), default=False)
is_replied = models.BooleanField(_("是否已响应"), default=False)
reply_deadline = models.DateTimeField(_("响应截至时间"), null=True)
@@ -182,13 +186,17 @@ def reply_timeout_time(self, start_time=None):
"""
响应超时时间
"""
- return action_time(self.reply_time, self.sla_id, self.ticket.priority, start_time)
+ return action_time(
+ self.reply_time, self.sla_id, self.ticket.priority, start_time
+ )
def handle_timeout_time(self, start_time=None):
"""
处理超时时间
"""
- return action_time(self.handle_time, self.sla_id, self.ticket.priority, start_time)
+ return action_time(
+ self.handle_time, self.sla_id, self.ticket.priority, start_time
+ )
@property
def protocol_name(self):
@@ -221,8 +229,9 @@ def get_cost_time(self, current_time=None):
if not start_time:
start_time = self.begin_at
- new_duration = action_time_delta(start_time, current_time, self.sla_id,
- self.ticket.priority)
+ new_duration = action_time_delta(
+ start_time, current_time, self.sla_id, self.ticket.priority
+ )
cost_time = self.cost_time + new_duration
return cost_time
@@ -246,7 +255,11 @@ def update_sla_status(self, current_time):
return
# 响应超时
- if self.is_reply_need and not self.is_replied and current_time > self.reply_deadline:
+ if (
+ self.is_reply_need
+ and not self.is_replied
+ and current_time > self.reply_deadline
+ ):
self.sla_status = REPLY_TIMEOUT
self.save()
return
@@ -276,10 +289,12 @@ def start(self, begin_at):
ac_time = action_trigger_time.strftime("%Y-%m-%d %H:%M")
ac_key = "{ticket_id}-{sla_task_id}-{action_policy_type}".format(
- ticket_id=self.ticket_id, sla_task_id=self.id, action_policy_type=action_policy.type
+ ticket_id=self.ticket_id,
+ sla_task_id=self.id,
+ action_policy_type=action_policy.type,
)
# 任务用到的必要参数
-
+
ac_value = json.dumps([action.id for action in action_policy.actions.all()])
# 插入数据到redis
@@ -299,7 +314,9 @@ def reply(self, replied_at):
self.reply_cost = self.cost_time
self.save()
- action_policies = self.action_policies.filter(type__in=[REPLY_WARING, REPLY_TIMEOUT])
+ action_policies = self.action_policies.filter(
+ type__in=[REPLY_WARING, REPLY_TIMEOUT]
+ )
self.delete_redis_task(action_policies)
@_frozen_check
@@ -319,7 +336,9 @@ def resume(self, resume_at):
action_policies = self.action_policies
# 已响应的去除响应策略
if self.is_reply_need and self.is_replied:
- action_policies = action_policies.exclude(type__in=[REPLY_WARING, REPLY_TIMEOUT])
+ action_policies = action_policies.exclude(
+ type__in=[REPLY_WARING, REPLY_TIMEOUT]
+ )
self._refresh_redis_task(action_policies, resume_at)
@@ -361,7 +380,9 @@ def refresh(self, refresh_at):
action_policies = self.action_policies
# 已响应的去除响应策略
if self.is_reply_need and self.is_replied:
- action_policies = action_policies.exclude(type__in=[REPLY_WARING, REPLY_TIMEOUT])
+ action_policies = action_policies.exclude(
+ type__in=[REPLY_WARING, REPLY_TIMEOUT]
+ )
self.delete_redis_task(action_policies)
self._refresh_redis_task(action_policies, refresh_at)
@@ -377,11 +398,14 @@ def _refresh_redis_task(self, action_policies, refresh_at):
# 策略触发时间大于恢复时间,重新入库redis
if action_trigger_time > refresh_at:
ac_key = "{ticket_id}-{sla_task_id}-{action_policy_type}".format(
- ticket_id=self.ticket_id, sla_task_id=self.id,
- action_policy_type=action_policy.type
+ ticket_id=self.ticket_id,
+ sla_task_id=self.id,
+ action_policy_type=action_policy.type,
)
# 任务用到的必要参数
- ac_value = json.dumps([action.id for action in action_policy.actions.all()])
+ ac_value = json.dumps(
+ [action.id for action in action_policy.actions.all()]
+ )
# 插入数据到redis
# 按时间记录任务
sla_redis_inst.hsetnx(name=ac_time, key=ac_key, value=ac_value)
@@ -390,8 +414,9 @@ def _refresh_redis_task(self, action_policies, refresh_at):
sla_redis_inst.sadd(SLA_ACTION_TIME, ac_time)
else:
for action in action_policy.actions.all():
- sla_task_action = SlaTaskAction(action, self.ticket, self, action_policy.type,
- ac_time)
+ sla_task_action = SlaTaskAction(
+ action, self.ticket, self, action_policy.type, ac_time
+ )
sla_task_action.alert()
def delete_redis_task(self, action_policies):
@@ -403,7 +428,9 @@ def delete_redis_task(self, action_policies):
def get_ac_keys(self, action_policies):
ac_keys = [
"{ticket_id}-{sla_task_id}-{action_policy_type}".format(
- ticket_id=self.ticket_id, sla_task_id=self.id, action_policy_type=action_policy.type
+ ticket_id=self.ticket_id,
+ sla_task_id=self.id,
+ action_policy_type=action_policy.type,
)
for action_policy in action_policies
]
@@ -428,31 +455,51 @@ def frozen(self):
class SlaEventLogManager(models.Manager):
def get_last_start_event(self, sla_task_id):
"""获取最后一次启动计时的事件"""
- return self.filter(sla_task_id=sla_task_id, tick_flag='START', is_archived=False).last()
+ return self.filter(
+ sla_task_id=sla_task_id, tick_flag="START", is_archived=False
+ ).last()
def get_last_stop_event(self, sla_task_id):
"""获取最后一次停止计时的事件"""
- return self.filter(sla_task_id=sla_task_id, tick_flag='END', is_archived=False).last()
+ return self.filter(
+ sla_task_id=sla_task_id, tick_flag="END", is_archived=False
+ ).last()
def create_start_event(self, sla_task_id, priority):
"""创建开始事件"""
- return self.create(sla_task_id=sla_task_id, priority=priority, event_type='START',
- tick_flag='START', )
+ return self.create(
+ sla_task_id=sla_task_id,
+ priority=priority,
+ event_type="START",
+ tick_flag="START",
+ )
def create_pause_event(self, sla_task_id, priority):
"""创建暂停事件"""
- return self.create(sla_task_id=sla_task_id, priority=priority, event_type='PAUSE',
- tick_flag='END', )
+ return self.create(
+ sla_task_id=sla_task_id,
+ priority=priority,
+ event_type="PAUSE",
+ tick_flag="END",
+ )
def create_resume_event(self, sla_task_id, priority):
"""创建恢复事件"""
- return self.create(sla_task_id=sla_task_id, priority=priority, event_type='RESUME',
- tick_flag='START', )
+ return self.create(
+ sla_task_id=sla_task_id,
+ priority=priority,
+ event_type="RESUME",
+ tick_flag="START",
+ )
def create_stop_event(self, sla_task_id, priority):
"""创建停止事件"""
- return self.create(sla_task_id=sla_task_id, priority=priority, event_type='STOP',
- tick_flag='END', )
+ return self.create(
+ sla_task_id=sla_task_id,
+ priority=priority,
+ event_type="STOP",
+ tick_flag="END",
+ )
class SlaEventLog(models.Model):
@@ -461,16 +508,30 @@ class SlaEventLog(models.Model):
目前主要有:启动、停止、暂停、恢复
"""
- sla_task_id = models.IntegerField(_("SLA TASK ID"), db_index=True, default=EMPTY_INT)
+ sla_task_id = models.IntegerField(
+ _("SLA TASK ID"), db_index=True, default=EMPTY_INT
+ )
priority = models.CharField(_("优先级"), max_length=LEN_LONG)
event_type = models.CharField(
- _("事件类型"), max_length=LEN_LONG,
- choices=[('PAUSE', "暂停"), ('RESUME', "恢复"), ('STOP', "停止"), ('START', "启动"), ]
+ _("事件类型"),
+ max_length=LEN_LONG,
+ choices=[
+ ("PAUSE", "暂停"),
+ ("RESUME", "恢复"),
+ ("STOP", "停止"),
+ ("START", "启动"),
+ ],
)
is_archived = models.BooleanField(_("是否已归档"), default=False)
tick_flag = models.CharField(
- _("计时标志"), max_length=LEN_LONG,
- choices=[('START', "开始计时"), ('END', "结束计时"), ('KEEP', "保持"), ], default='KEEP'
+ _("计时标志"),
+ max_length=LEN_LONG,
+ choices=[
+ ("START", "开始计时"),
+ ("END", "结束计时"),
+ ("KEEP", "保持"),
+ ],
+ default="KEEP",
)
create_time = models.DateTimeField(_("事件发生时间"), auto_now_add=True)
@@ -494,15 +555,20 @@ def mark_archived(self):
class SlaActionHistoryManager(models.Manager):
def get_last_success_action(self, action_id, action_type):
"""获取最后一次成功执行的sla行为"""
- return self.filter(action_id=action_id, action_type=action_type, status="SUCCESS").first()
+ return self.filter(
+ action_id=action_id, action_type=action_type, status="SUCCESS"
+ ).first()
class SlaActionHistory(models.Model):
"""sla行为历史记录"""
action_id = models.IntegerField(_("任务ID"), db_index=True, default=EMPTY_INT)
- status = models.CharField(_("结果状态"), max_length=LEN_LONG,
- choices=[("SUCCESS", _("成功")), ("FAILED", _("失败"))])
+ status = models.CharField(
+ _("结果状态"),
+ max_length=LEN_LONG,
+ choices=[("SUCCESS", _("成功")), ("FAILED", _("失败"))],
+ )
action_type = models.CharField(_("行为类型"), max_length=LEN_LONG)
action_detail = JSONField(_("行为详情"), default=EMPTY_DICT)
create_time = models.DateTimeField(_("动作发生时间"), auto_now_add=True)
diff --git a/itsm/sla_engine/monitor.py b/itsm/sla_engine/monitor.py
index 098c658ab..57dc58554 100644
--- a/itsm/sla_engine/monitor.py
+++ b/itsm/sla_engine/monitor.py
@@ -26,21 +26,29 @@
import datetime
import json
+from celery import shared_task
from celery.schedules import crontab
-from celery.task import periodic_task, task
+from blueapps.contrib.celery_tools.periodic import periodic_task
from django.db import transaction
from common.redis import Cache
from itsm.sla.models import Action
from itsm.sla_engine.actions import SlaTaskAction
-from itsm.sla_engine.constants import SLA_ACTION_TIME, RUNNING, REPLY_WARING, REPLY_TIMEOUT
+from itsm.sla_engine.constants import (
+ SLA_ACTION_TIME,
+ RUNNING,
+ REPLY_WARING,
+ REPLY_TIMEOUT,
+)
from itsm.sla_engine.models import SlaTask
from itsm.ticket.models import Ticket
-@task
+@shared_task
def action_exclude(ac_key, ac_value, ac_time):
- ticket_id, sla_task_id, action_policy_type = [int(item) for item in ac_key.split("-")]
+ ticket_id, sla_task_id, action_policy_type = [
+ int(item) for item in ac_key.split("-")
+ ]
sla_task = SlaTask.objects.get(id=sla_task_id)
if sla_task.task_status != RUNNING:
@@ -51,7 +59,9 @@ def action_exclude(ac_key, ac_value, ac_time):
with transaction.atomic():
for action in Action.objects.filter(id__in=action_ids):
- sla_task_action = SlaTaskAction(action, ticket, sla_task, action_policy_type, ac_time)
+ sla_task_action = SlaTaskAction(
+ action, ticket, sla_task, action_policy_type, ac_time
+ )
sla_task_action.alert()
@@ -78,7 +88,14 @@ def sla_task_metric():
sla_redis_inst.srem(SLA_ACTION_TIME, ac_time)
-@periodic_task(run_every=(crontab(minute="*/10", )), ignore_result=True)
+@periodic_task(
+ run_every=(
+ crontab(
+ minute="*/10",
+ )
+ ),
+ ignore_result=True,
+)
def compensate_task():
"""
补偿遗漏的提醒任务
@@ -105,7 +122,14 @@ def compensate_task():
sla_redis_inst.srem(SLA_ACTION_TIME, ac_time)
-@periodic_task(run_every=(crontab(minute="*/10", )), ignore_result=True)
+@periodic_task(
+ run_every=(
+ crontab(
+ minute="*/10",
+ )
+ ),
+ ignore_result=True,
+)
def rebuild_sla_task():
"""
根据SlaTask表中RUNNING的任务重建入库redis失败的任务
@@ -114,8 +138,10 @@ def rebuild_sla_task():
for sla_task in sla_tasks:
action_policies = sla_task.action_policies
if sla_task.is_reply_need and not sla_task.is_replied:
- action_policies = action_policies.exclude(type__in=[REPLY_WARING, REPLY_TIMEOUT])
- ac_keys = sla_task.get_ac_keys(action_policies)
+ action_policies = action_policies.exclude(
+ type__in=[REPLY_WARING, REPLY_TIMEOUT]
+ )
+ _ = sla_task.get_ac_keys(action_policies)
def update_sla_task(ac_time_dict, current_time):
diff --git a/itsm/task/models.py b/itsm/task/models.py
index f11e88c63..f7a3f2c37 100644
--- a/itsm/task/models.py
+++ b/itsm/task/models.py
@@ -34,7 +34,7 @@
from django.db.models import Q
from django.db import models, transaction
from django.utils.functional import cached_property
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mako.template import Template
from bulk_update.helper import bulk_update
from pipeline.engine import api as pipeline_api
@@ -138,7 +138,9 @@ class Task(Model):
ticket_id = models.IntegerField(_("单据ID"), default=0)
state_id = models.IntegerField(_("节点ID"), default=0)
- activity_id = models.CharField(_("Pipeline节点ID"), max_length=LEN_NORMAL, blank=True)
+ activity_id = models.CharField(
+ _("Pipeline节点ID"), max_length=LEN_NORMAL, blank=True
+ )
name = models.CharField(_("任务的名称"), max_length=LEN_LONG)
task_schema_id = models.IntegerField(_("对应的任务模板ID"), null=False)
component_type = models.CharField(
@@ -148,16 +150,27 @@ class Task(Model):
null=False,
)
processors_type = models.CharField(
- _("处理人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("处理人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
processors = models.CharField(
- _("处理人列表"), max_length=LEN_LONG, default=EMPTY_STRING, null=True, blank=True
+ _("处理人列表"),
+ max_length=LEN_LONG,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
inputs = jsonfield.JSONField(
- _("组件输入信息"), help_text=_("当前组件输入参数引用的参数变量"), default=EMPTY_DICT
+ _("组件输入信息"),
+ help_text=_("当前组件输入参数引用的参数变量"),
+ default=EMPTY_DICT,
)
outputs = jsonfield.JSONField(
- _("组件输出信息"), help_text=_("当前组件输出信息,比如sops各阶段返回"), default=EMPTY_DICT
+ _("组件输出信息"),
+ help_text=_("当前组件输出信息,比如sops各阶段返回"),
+ default=EMPTY_DICT,
)
order = models.IntegerField(_("任务的执行顺序"), default=1)
@@ -168,8 +181,12 @@ class Task(Model):
default="NEW",
)
- executor = models.CharField(_("处理人"), max_length=LEN_NORMAL, default=EMPTY_STRING)
- confirmer = models.CharField(_("确认人"), max_length=LEN_NORMAL, default=EMPTY_STRING)
+ executor = models.CharField(
+ _("处理人"), max_length=LEN_NORMAL, default=EMPTY_STRING
+ )
+ confirmer = models.CharField(
+ _("确认人"), max_length=LEN_NORMAL, default=EMPTY_STRING
+ )
start_at = models.DateTimeField(_("开始执行的时间"), null=True)
end_at = models.DateTimeField(_("结束执行的时间"), null=True)
pipeline_data = jsonfield.JSONField(_("Pipeline流程树元数据"), default=EMPTY_DICT)
@@ -795,7 +812,8 @@ def retry_sops_task(self, sops_task, retry_operator):
# 获取不到标准运维任务信息,直接设置为异常
return {
"result": False,
- "message": "重试失败,获取标准运维任务信息出错 %s" % res.get("message", ""),
+ "message": "重试失败,获取标准运维任务信息出错 %s"
+ % res.get("message", ""),
}
failed_nodes = [
@@ -805,7 +823,7 @@ def retry_sops_task(self, sops_task, retry_operator):
]
if not failed_nodes:
# 不存在失败节点的时候,说明任务成功执行了
- return {"result": True, "message": u"重试成功,标准运维任务已经在执行中"}
+ return {"result": True, "message": "重试成功,标准运维任务已经在执行中"}
for node_id in failed_nodes:
# 依次进行重试,除了并行节点,一般只会有一个错误的id
@@ -1001,11 +1019,16 @@ class SopsTask(Model):
task_id = models.CharField(_("itsm任务ID"), max_length=LEN_NORMAL)
task_name = models.CharField(_("任务的名称"), max_length=LEN_LONG)
sops_template_id = models.IntegerField(_("sops任务模板ID"))
- sops_task_id = models.IntegerField(_("sops任务ID,成功启动后填充"), null=True, blank=True)
+ sops_task_id = models.IntegerField(
+ _("sops任务ID,成功启动后填充"), null=True, blank=True
+ )
# params|detail|status
sops_task_info = jsonfield.JSONField(_("sops任务信息"), default=EMPTY_DICT)
sops_task_url = models.CharField(
- _("sops任务详情链接,成功启动后填充"), max_length=LEN_LONG, null=True, blank=True
+ _("sops任务详情链接,成功启动后填充"),
+ max_length=LEN_LONG,
+ null=True,
+ blank=True,
)
creator = models.CharField(_("创建者"), max_length=LEN_NORMAL, blank=True)
@@ -1056,12 +1079,14 @@ def get_status(self):
"task_name": self.task_name,
"sops_task_url": self.sops_task_url,
"create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
- "start_time": self.start_time.strftime("%Y-%m-%d %H:%M:%S")
- if self.start_time
- else "",
- "finish_time": self.finish_time.strftime("%Y-%m-%d %H:%M:%S")
- if self.finish_time
- else "",
+ "start_time": (
+ self.start_time.strftime("%Y-%m-%d %H:%M:%S") if self.start_time else ""
+ ),
+ "finish_time": (
+ self.finish_time.strftime("%Y-%m-%d %H:%M:%S")
+ if self.finish_time
+ else ""
+ ),
"elapsed_time": self.elapsed_time,
"state": self.state,
}
@@ -1123,12 +1148,14 @@ def get_status(self):
"task_name": self.task_name,
"sub_task_url": self.sub_task_url,
"create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
- "start_time": self.start_time.strftime("%Y-%m-%d %H:%M:%S")
- if self.start_time
- else "",
- "finish_time": self.finish_time.strftime("%Y-%m-%d %H:%M:%S")
- if self.finish_time
- else "",
+ "start_time": (
+ self.start_time.strftime("%Y-%m-%d %H:%M:%S") if self.start_time else ""
+ ),
+ "finish_time": (
+ self.finish_time.strftime("%Y-%m-%d %H:%M:%S")
+ if self.finish_time
+ else ""
+ ),
"elapsed_time": self.elapsed_time,
"state": self.state,
"sub_pipeline_id": self.sub_pipeline_id,
@@ -1195,7 +1222,9 @@ class TaskLibTasks(Model):
component_type = models.CharField(_("任务类型"), max_length=LEN_NORMAL)
processors_type = models.CharField(_("处理人类型"), max_length=LEN_NORMAL)
processors = models.CharField(_("处理人"), max_length=LEN_LONG)
- fields = jsonfield.JSONField(_("字段列表"), max_length=LEN_XX_LONG, default=EMPTY_LIST)
+ fields = jsonfield.JSONField(
+ _("字段列表"), max_length=LEN_XX_LONG, default=EMPTY_LIST
+ )
sub_template_id = models.CharField(_("子模版ID"), default="", max_length=LEN_NORMAL)
project_id = models.CharField(_("项目ID/业务ID"), default="", max_length=LEN_NORMAL)
exclude_task_nodes = jsonfield.JSONField(
diff --git a/itsm/task/permissions.py b/itsm/task/permissions.py
index c4cd9b792..918d8922a 100644
--- a/itsm/task/permissions.py
+++ b/itsm/task/permissions.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import permissions
from itsm.role.models import UserRole
@@ -69,7 +69,7 @@ def __init__(self):
def has_object_permission(self, request, view, obj):
username = request.user.username
- if view.action == 'proceed':
+ if view.action == "proceed":
return obj.can_process(username)
return True
diff --git a/itsm/task/serializers.py b/itsm/task/serializers.py
index 2df8f4293..ce9069d2b 100644
--- a/itsm/task/serializers.py
+++ b/itsm/task/serializers.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.request import Request
@@ -112,12 +112,18 @@ class TaskSerializer(serializers.ModelSerializer):
"""任务序列化"""
name = serializers.CharField(required=False, max_length=50, allow_blank=True)
- processors_type = serializers.CharField(required=False, allow_blank=True, max_length=LEN_LONG, default=EMPTY_STRING)
- processors = serializers.CharField(required=False, allow_blank=True, max_length=LEN_LONG, default=EMPTY_STRING)
+ processors_type = serializers.CharField(
+ required=False, allow_blank=True, max_length=LEN_LONG, default=EMPTY_STRING
+ )
+ processors = serializers.CharField(
+ required=False, allow_blank=True, max_length=LEN_LONG, default=EMPTY_STRING
+ )
task_schema_id = serializers.IntegerField(required=True)
state_id = serializers.IntegerField(required=True)
order = serializers.IntegerField(required=False, default=1)
- component_type = serializers.CharField(required=False, max_length=LEN_LONG, default=EMPTY_STRING)
+ component_type = serializers.CharField(
+ required=False, max_length=LEN_LONG, default=EMPTY_STRING
+ )
fields = serializers.JSONField(required=False, default={})
class Meta:
@@ -154,22 +160,28 @@ def validate(self, attrs):
return validated_data
try:
- schema_instance = TaskSchema.objects.get(id=validated_data['task_schema_id'])
+ schema_instance = TaskSchema.objects.get(
+ id=validated_data["task_schema_id"]
+ )
except TaskSchema.DoesNotExist:
raise ValidationError(detail=_("对应的任务模板配置不存在"))
- ticket = Ticket.objects.get(id=validated_data['ticket_id'])
+ ticket = Ticket.objects.get(id=validated_data["ticket_id"])
task_config = TaskConfig.objects.filter(
- workflow_id=ticket.flow_id, workflow_type=VERSION, create_task_state=validated_data['state_id']
+ workflow_id=ticket.flow_id,
+ workflow_type=VERSION,
+ create_task_state=validated_data["state_id"],
).first()
if task_config:
- validated_data['execute_state_id'] = task_config.execute_task_state
+ validated_data["execute_state_id"] = task_config.execute_task_state
else:
is_exist = TaskConfig.objects.filter(
- workflow_id=ticket.flow_id, workflow_type=VERSION, execute_task_state=validated_data['state_id']
+ workflow_id=ticket.flow_id,
+ workflow_type=VERSION,
+ execute_task_state=validated_data["state_id"],
).exists()
if is_exist:
- validated_data['execute_state_id'] = validated_data['state_id']
+ validated_data["execute_state_id"] = validated_data["state_id"]
else:
raise ValidationError(detail=_("对应的任务配置不存在"))
# 更新component_type
@@ -183,15 +195,25 @@ def to_representation(self, instance):
data = super(TaskSerializer, self).to_representation(instance)
# 首尾去掉逗号
data.update(processors=normal_name(data["processors"]))
- if isinstance(self.context.get("view"), ModelViewSet) and self.context["view"].detail:
+ if (
+ isinstance(self.context.get("view"), ModelViewSet)
+ and self.context["view"].detail
+ ):
create_fields = TaskFieldSerializer(instance.create_fields, many=True).data
- operate_fields = TaskFieldSerializer(instance.operate_fields, many=True).data
- confirm_fields = TaskFieldSerializer(instance.confirm_fields, many=True).data
+ operate_fields = TaskFieldSerializer(
+ instance.operate_fields, many=True
+ ).data
+ confirm_fields = TaskFieldSerializer(
+ instance.confirm_fields, many=True
+ ).data
if instance.component_type == SOPS_TASK:
sops_task = SopsTask.objects.get(task_id=instance.id)
try:
detail = client_backend.sops.get_task_detail(
- {"bk_biz_id": sops_task.bk_biz_id, "task_id": sops_task.sops_task_id}
+ {
+ "bk_biz_id": sops_task.bk_biz_id,
+ "task_id": sops_task.sops_task_id,
+ }
)
except Exception:
raise ComponentCallError(_("标准运维获取任务详情失败"))
@@ -200,10 +222,14 @@ def to_representation(self, instance):
if field["key"] == SOPS_TEMPLATE_KEY:
sops_constants = {}
for sops_constant in detail["constants"].values():
- sops_constants[sops_constant["key"]] = sops_constant["value"]
+ sops_constants[sops_constant["key"]] = sops_constant[
+ "value"
+ ]
for constant in field["value"]["constants"]:
if constant.get("is_quoted", False):
- current_value = Template(constant["value"]).render(**outputs)
+ current_value = Template(constant["value"]).render(
+ **outputs
+ )
changed = (
current_value != sops_constants[constant["key"]]
if constant["key"] in sops_constants
@@ -212,17 +238,23 @@ def to_representation(self, instance):
constant["changed"] = changed
else:
constant["changed"] = False
- constant["value"] = sops_constants.get(constant["key"], constant.get("value", ""))
- field["display_value"]["constants"] = field["value"]["constants"]
+ constant["value"] = sops_constants.get(
+ constant["key"], constant.get("value", "")
+ )
+ field["display_value"]["constants"] = field["value"][
+ "constants"
+ ]
data["sops_task_url"] = sops_task.sops_task_url
data["fields"] = {
- 'create_fields': create_fields,
- 'operate_fields': operate_fields,
- 'confirm_fields': confirm_fields,
+ "create_fields": create_fields,
+ "operate_fields": operate_fields,
+ "confirm_fields": confirm_fields,
}
if isinstance(self.context.get("request"), Request):
- data["can_process"] = instance.can_process(self.context["request"].user.username)
+ data["can_process"] = instance.can_process(
+ self.context["request"].user.username
+ )
return data
@@ -257,7 +289,9 @@ def __init__(self, instance=None, data=empty, **kwargs):
def get_sops_tasks(self):
tasks = [] if self.instance is None else self.instance
task_ids = [task.id for task in tasks]
- sops_task_ids = SopsTask.objects.filter(task_id__in=task_ids).values("task_id", "sops_task_url")
+ sops_task_ids = SopsTask.objects.filter(task_id__in=task_ids).values(
+ "task_id", "sops_task_url"
+ )
sops_task_map = {}
for sops_task in sops_task_ids:
sops_task_map[sops_task["task_id"]] = sops_task["sops_task_url"]
@@ -266,7 +300,9 @@ def get_sops_tasks(self):
def get_devops_tasks(self):
tasks = [] if self.instance is None else self.instance
task_ids = [task.id for task in tasks]
- sub_task_ids = SubTask.objects.filter(task_id__in=task_ids).values("task_id", "sub_task_url")
+ sub_task_ids = SubTask.objects.filter(task_id__in=task_ids).values(
+ "task_id", "sub_task_url"
+ )
sub_task_map = {}
for sub_task in sub_task_ids:
sub_task_map[sub_task["task_id"]] = sub_task["sub_task_url"]
@@ -277,9 +313,13 @@ def to_representation(self, instance):
# 首尾去掉逗号
data.update(processors=normal_name(data["processors"]))
if isinstance(self.context.get("request"), Request):
- data["can_process"] = instance.can_process(self.context["request"].user.username)
+ data["can_process"] = instance.can_process(
+ self.context["request"].user.username
+ )
if instance.component_type in [SOPS_TASK, DEVOPS_TASK]:
- data["task_url"] = self.sops_tasks.get(str(data["id"]), "") or self.devops_tasks.get(str(data["id"]), "")
+ data["task_url"] = self.sops_tasks.get(
+ str(data["id"]), ""
+ ) or self.devops_tasks.get(str(data["id"]), "")
return data
@@ -294,8 +334,12 @@ def update(self, instance, validated_data):
class TaskOrderSerializer(serializers.Serializer):
- task_orders = serializers.ListField(required=True, allow_empty=True, validators=[TaskOrdersValidator()])
- ticket_id = serializers.IntegerField(required=True, validators=[TicketValidValidator()])
+ task_orders = serializers.ListField(
+ required=True, allow_empty=True, validators=[TaskOrdersValidator()]
+ )
+ ticket_id = serializers.IntegerField(
+ required=True, validators=[TicketValidValidator()]
+ )
def create(self, validated_data):
return super().create(validated_data)
@@ -305,7 +349,9 @@ def update(self, instance, validated_data):
class TaskFieldBatchUpdateSerializer(serializers.Serializer):
- fields = serializers.ListField(required=True, allow_empty=True, validators=[TaskFieldBatchUpdateValidator()])
+ fields = serializers.ListField(
+ required=True, allow_empty=True, validators=[TaskFieldBatchUpdateValidator()]
+ )
def create(self, validated_data):
return super().create(validated_data)
@@ -333,7 +379,10 @@ def validate(self, attrs):
class TaskProceedSerializer(serializers.Serializer):
- action = serializers.ChoiceField(choices=[(ACTION_OPERATE, _("处理")), (ACTION_CONFIRM, _("总结"))], required=True)
+ action = serializers.ChoiceField(
+ choices=[(ACTION_OPERATE, _("处理")), (ACTION_CONFIRM, _("总结"))],
+ required=True,
+ )
fields = serializers.ListField(required=True, allow_empty=True)
def validate(self, attrs):
@@ -341,11 +390,13 @@ def validate(self, attrs):
# 校验fields
fields = validated_data.get("fields", {})
- action = attrs.get('action')
- task = self.context['task']
+ action = attrs.get("action")
+ task = self.context["task"]
# 检验字段合法性:必填校验(未考虑隐藏字段)
- task_fields = task.operate_fields if action == ACTION_OPERATE else task.confirm_fields
+ task_fields = (
+ task.operate_fields if action == ACTION_OPERATE else task.confirm_fields
+ )
validate_task_fields(task_fields, fields)
return validated_data
@@ -370,7 +421,7 @@ def validate(self, attrs):
fields = validated_data.get("fields", {})
# 检验字段合法性:必填校验(未考虑隐藏字段)
- task_fields = self.context['task'].operate_fields
+ task_fields = self.context["task"].operate_fields
validate_task_fields(task_fields, fields)
return validated_data
@@ -392,8 +443,12 @@ class TaskLibTasksSerializer(serializers.ModelSerializer):
processors_type = serializers.CharField(required=True, max_length=LEN_NORMAL)
processors = serializers.CharField(required=True, max_length=LEN_LONG)
fields = serializers.JSONField(required=True)
- sub_template_id = serializers.CharField(required=False, default="", max_length=LEN_NORMAL, allow_blank=True)
- project_id = serializers.CharField(required=False, default="", max_length=LEN_NORMAL, allow_blank=True)
+ sub_template_id = serializers.CharField(
+ required=False, default="", max_length=LEN_NORMAL, allow_blank=True
+ )
+ project_id = serializers.CharField(
+ required=False, default="", max_length=LEN_NORMAL, allow_blank=True
+ )
exclude_task_nodes = serializers.JSONField(required=True)
class Meta:
@@ -425,10 +480,14 @@ def validate(self, attrs):
if (
self.instance is None
and TaskLib.objects.filter(
- service_id=attrs['service_id'], name=attrs['name'], creator=attrs['creator']
+ service_id=attrs["service_id"],
+ name=attrs["name"],
+ creator=attrs["creator"],
).exists()
):
- raise serializers.ValidationError({str(_('参数校验失败')): _('您名下已经有同名任务库,请尝试换个名称')})
+ raise serializers.ValidationError(
+ {str(_("参数校验失败")): _("您名下已经有同名任务库,请尝试换个名称")}
+ )
return attrs
diff --git a/itsm/task/tasks.py b/itsm/task/tasks.py
index 2bb4dff0b..52484cc43 100644
--- a/itsm/task/tasks.py
+++ b/itsm/task/tasks.py
@@ -28,7 +28,7 @@
from collections import defaultdict
from operator import itemgetter
from celery.schedules import crontab
-from celery.task import periodic_task
+from blueapps.contrib.celery_tools.periodic import periodic_task
from itsm.component.constants.task import (
NEED_UPDATE_TASK_STATUS,
@@ -48,7 +48,7 @@
from itsm.task.models import SopsTask, Task, SubTask
from itsm.ticket.models import TicketGlobalVariable
-logger = logging.getLogger('celery')
+logger = logging.getLogger("celery")
def get_tasks_status(bk_biz_id, sops_task_ids):
@@ -58,42 +58,63 @@ def get_tasks_status(bk_biz_id, sops_task_ids):
{"__raw": True, "task_id_list": list(sops_task_ids), "bk_biz_id": bk_biz_id}
)
- if not res.get('result', False):
- logger.error('sops_task_poller failed: {}'.format(res.get('message')))
+ if not res.get("result", False):
+ logger.error("sops_task_poller failed: {}".format(res.get("message")))
return None
- return res.get('data')
+ return res.get("data")
def get_task_detail(bk_biz_id, sops_task_id):
"""查询任务详情"""
res = client_backend.sops.get_task_detail(
- {"__raw": True, "task_id": sops_task_id, "bk_biz_id": bk_biz_id, })
- if not res.get('result', False):
- logger.warning('sops_task_poller->get_task_detail({}) failed: {}'.format(sops_task_id,
- res.get(
- 'message')))
+ {
+ "__raw": True,
+ "task_id": sops_task_id,
+ "bk_biz_id": bk_biz_id,
+ }
+ )
+ if not res.get("result", False):
+ logger.warning(
+ "sops_task_poller->get_task_detail({}) failed: {}".format(
+ sops_task_id, res.get("message")
+ )
+ )
return None
- return res.get('data')
+ return res.get("data")
def get_task_status(bk_biz_id, sops_task_id):
"""查询各节点状态"""
res = client_backend.sops.get_task_status(
- {"__raw": True, "task_id": sops_task_id, "bk_biz_id": bk_biz_id, })
- if not res.get('result', False):
- logger.warning('sops_task_poller->get_task_status({}) failed: {}'.format(sops_task_id,
- res.get(
- 'message')))
+ {
+ "__raw": True,
+ "task_id": sops_task_id,
+ "bk_biz_id": bk_biz_id,
+ }
+ )
+ if not res.get("result", False):
+ logger.warning(
+ "sops_task_poller->get_task_status({}) failed: {}".format(
+ sops_task_id, res.get("message")
+ )
+ )
return None
- return res.get('data')
+ return res.get("data")
-@periodic_task(run_every=(crontab(minute="*/2", )), ignore_result=True)
+@periodic_task(
+ run_every=(
+ crontab(
+ minute="*/2",
+ )
+ ),
+ ignore_result=True,
+)
@share_lock()
def sops_task_poller(task_ids=None):
"""sops任务轮询
@@ -101,8 +122,9 @@ def sops_task_poller(task_ids=None):
"""
# 支持查询指定task的状态
if task_ids:
- sync_ids = Task.objects.filter(id__in=task_ids, status__in=NEED_SYNC_STATUS).values_list(
- 'id', flat=True)
+ sync_ids = Task.objects.filter(
+ id__in=task_ids, status__in=NEED_SYNC_STATUS
+ ).values_list("id", flat=True)
running_sops_tasks = SopsTask.objects.filter(task_id__in=sync_ids)
else:
running_sops_tasks = SopsTask.objects.filter(state__in=[RUNNING, SUSPENDED])
@@ -118,10 +140,12 @@ def sops_task_poller(task_ids=None):
continue
for status in tasks_status:
- sops_task = running_sops_tasks.get(sops_task_id=status['id'])
- sops_task_state = DELETED if status["is_deleted"] else status['status']['state']
+ sops_task = running_sops_tasks.get(sops_task_id=status["id"])
+ sops_task_state = (
+ DELETED if status["is_deleted"] else status["status"]["state"]
+ )
sops_task.state = sops_task_state
- sops_task.elapsed_time = status['status']['elapsed_time']
+ sops_task.elapsed_time = status["status"]["elapsed_time"]
if sops_task_state in SOPS_TASK_STARTED_STATUS and not sops_task.executor:
detail = get_task_detail(bk_biz_id, sops_task.sops_task_id)
sops_task.executor = detail["executor"]
@@ -133,23 +157,23 @@ def sops_task_poller(task_ids=None):
Task.objects.filter(id=sops_task.task_id).update(status=sops_task_state)
continue
- if sops_task_state != 'FINISHED':
+ if sops_task_state != "FINISHED":
continue
# 执行结束
- sops_task.finish_time = status['finish_time'].rstrip("+0800").strip()
+ sops_task.finish_time = status["finish_time"].rstrip("+0800").strip()
# 查询流程详情并补充detail信息到status中
detail = get_task_detail(bk_biz_id, sops_task.sops_task_id)
if not detail:
continue
- pipeline_tree = detail['pipeline_tree']
- activities = pipeline_tree['activities']
+ pipeline_tree = detail["pipeline_tree"]
+ activities = pipeline_tree["activities"]
activities.update(
{
- pipeline_tree['start_event']['id']: pipeline_tree['start_event'],
- pipeline_tree['end_event']['id']: pipeline_tree['end_event'],
+ pipeline_tree["start_event"]["id"]: pipeline_tree["start_event"],
+ pipeline_tree["end_event"]["id"]: pipeline_tree["end_event"],
}
)
sops_task.executor = detail["executor"]
@@ -163,22 +187,23 @@ def sops_task_poller(task_ids=None):
cleaned_status = {}
- for node_id, node_info in status['children'].items():
+ for node_id, node_info in status["children"].items():
if node_id not in activities:
continue
node_info.update(
{
- 'incoming': activities[node_id]['incoming'],
- 'outgoing': activities[node_id]['outgoing'],
- 'labels': activities[node_id]['labels'],
- 'type': activities[node_id]['type'],
- 'stage_name': activities[node_id].get('stage_name', ''),
- 'component_code': activities[node_id].get('component', {}).get('code',
- 'unknown'),
+ "incoming": activities[node_id]["incoming"],
+ "outgoing": activities[node_id]["outgoing"],
+ "labels": activities[node_id]["labels"],
+ "type": activities[node_id]["type"],
+ "stage_name": activities[node_id].get("stage_name", ""),
+ "component_code": activities[node_id]
+ .get("component", {})
+ .get("code", "unknown"),
}
)
cleaned_status[node_id] = node_info
- status['children'] = cleaned_status
+ status["children"] = cleaned_status
sops_task.sops_task_info.update(status=status)
sops_task.save()
@@ -186,23 +211,23 @@ def sops_task_poller(task_ids=None):
do_after_sops_task_finished(sops_task.id)
# 结束处理后的统一通知和触发器
- sops_task.task.do_after_finish_operate(operator='system')
+ sops_task.task.do_after_finish_operate(operator="system")
def get_step_label_type(labels, default_label=2):
"""1:发布准备, 2:操作执行, 3:DB变更, 4:DB备份, 5:现网测试"""
ops_types = {
- 'ExecuteTask': 2,
- 'PrepareTask': 1,
- 'DbChange': 3,
- 'DbBackup': 4,
- 'TestOnline': 5,
+ "ExecuteTask": 2,
+ "PrepareTask": 1,
+ "DbChange": 3,
+ "DbBackup": 4,
+ "TestOnline": 5,
}
for label in labels:
- if label['group'] == 'TimerGroup':
- return ops_types.get(label['label'], default_label)
+ if label["group"] == "TimerGroup":
+ return ops_types.get(label["label"], default_label)
return default_label
@@ -213,25 +238,25 @@ def get_step_list_data(status, executor):
steps = [
{
# 节点开始执行时间
- "start_time": step['start_time'][:-6],
+ "start_time": step["start_time"][:-6],
# 插件code
- "tag_code": step['component_code'],
+ "tag_code": step["component_code"],
# 插件name
- "tag_name": step['name'],
+ "tag_name": step["name"],
# 执行结果:success/fail
"result": "success",
# 执行人
"operator": executor,
# 1:发布准备, 2:操作执行, 3:DB变更, 4:DB备份, 5:现网测试
- "type": get_step_label_type(step['labels']),
+ "type": get_step_label_type(step["labels"]),
# 节点结束执行时间
- "end_time": step['finish_time'][:-6],
+ "end_time": step["finish_time"][:-6],
}
- for step_id, step in status['children'].items()
- if step['component_code'] != 'unknown'
+ for step_id, step in status["children"].items()
+ if step["component_code"] != "unknown"
]
- return sorted(steps, key=itemgetter('start_time'))
+ return sorted(steps, key=itemgetter("start_time"))
def get_tag_data(status):
@@ -239,15 +264,15 @@ def get_tag_data(status):
tag_data = {
# 完全成功-1|成功但有问题-2|发布失败-1m
- "isSuccess": 1 if status['state'] == 'FINISHED' else 2,
+ "isSuccess": 1 if status["state"] == "FINISHED" else 2,
# 实际开始时间m
- "actualBeginTime": status['start_time'][:-6],
+ "actualBeginTime": status["start_time"][:-6],
# 实际结束时间m
- "actualEndTime": status['finish_time'][:-6],
+ "actualEndTime": status["finish_time"][:-6],
# 任务准备时长m
"prepareTime": 0,
# 运维执行时长m
- "executeTime": status['elapsed_time'],
+ "executeTime": status["elapsed_time"],
# 现网测试时长m
"testTime": 0,
# 停机比例:0-100
@@ -267,34 +292,36 @@ def get_tag_data(status):
is_shutdown, total_time = 0, 0
stop_time, start_time = None, None
- for node_id, node_info in status['children'].items():
- labels = node_info['labels']
+ for node_id, node_info in status["children"].items():
+ labels = node_info["labels"]
for label in labels:
- if label['group'] == 'TimerGroup':
- elapsed_time = node_info['elapsed_time']
- if label['label'] == 'ExecuteTask':
- tag_data['executeTime'] += elapsed_time
- elif label['label'] == 'PrepareTask':
- tag_data['prepareTime'] += elapsed_time
- elif label['label'] == 'TestOnline':
- tag_data['testTime'] += elapsed_time
- elif label['label'] == 'DbChange':
- tag_data['reviewDbChangeTime'] += elapsed_time
- tag_data['reviewIsDbChange'] = 1
- elif label['label'] == 'DbBackup':
- tag_data['dbBackupTime'] += elapsed_time
+ if label["group"] == "TimerGroup":
+ elapsed_time = node_info["elapsed_time"]
+ if label["label"] == "ExecuteTask":
+ tag_data["executeTime"] += elapsed_time
+ elif label["label"] == "PrepareTask":
+ tag_data["prepareTime"] += elapsed_time
+ elif label["label"] == "TestOnline":
+ tag_data["testTime"] += elapsed_time
+ elif label["label"] == "DbChange":
+ tag_data["reviewDbChangeTime"] += elapsed_time
+ tag_data["reviewIsDbChange"] = 1
+ elif label["label"] == "DbBackup":
+ tag_data["dbBackupTime"] += elapsed_time
total_time += elapsed_time
- elif label['group'] == 'AreaOpsGroup':
- elapsed_time = node_info['elapsed_time']
- if label['label'] == 'StopService':
+ elif label["group"] == "AreaOpsGroup":
+ elapsed_time = node_info["elapsed_time"]
+ if label["label"] == "StopService":
stop_time = datetime.datetime.strptime(
- node_info['start_time'].rstrip("+0800").strip(), '%Y-%m-%d %H:%M:%S'
+ node_info["start_time"].rstrip("+0800").strip(),
+ "%Y-%m-%d %H:%M:%S",
)
is_shutdown = 1
- elif label['label'] == 'StartService':
+ elif label["label"] == "StartService":
start_time = datetime.datetime.strptime(
- node_info['start_time'].rstrip("+0800").strip(), '%Y-%m-%d %H:%M:%S'
+ node_info["start_time"].rstrip("+0800").strip(),
+ "%Y-%m-%d %H:%M:%S",
)
total_time += elapsed_time
@@ -303,9 +330,9 @@ def get_tag_data(status):
shutdown_percent = shutdown_time / total_time * 100
tag_data.update(
{
- 'reviewIsShutdown': is_shutdown,
- 'reviewShutdownTime': shutdown_time,
- 'reviewNumerator': int(shutdown_percent),
+ "reviewIsShutdown": is_shutdown,
+ "reviewShutdownTime": shutdown_time,
+ "reviewNumerator": int(shutdown_percent),
}
)
@@ -318,7 +345,7 @@ def do_after_sops_task_finished(sops_task_id):
sops_task = SopsTask.objects.get(pk=sops_task_id)
task = sops_task.task
- status = sops_task.sops_task_info.get('status')
+ status = sops_task.sops_task_info.get("status")
tag_data = get_tag_data(status)
logger.info("sops_task get_tag_data is {}".format(tag_data))
@@ -328,20 +355,28 @@ def do_after_sops_task_finished(sops_task_id):
# 更新sops任务信息到task.outputs
task.outputs = {
- 'tag_data': tag_data,
- 'sops_step_list': get_step_list_data(status, sops_task.executor),
+ "tag_data": tag_data,
+ "sops_step_list": get_step_list_data(status, sops_task.executor),
}
task.save()
-@periodic_task(run_every=(crontab(minute="*/5", )), ignore_result=True)
+@periodic_task(
+ run_every=(
+ crontab(
+ minute="*/5",
+ )
+ ),
+ ignore_result=True,
+)
@share_lock()
def devops_task_poller(task_ids=None):
"""蓝盾任务轮询"""
if task_ids:
- sync_ids = Task.objects.filter(id__in=task_ids, status__in=NEED_SYNC_STATUS).values_list(
- 'id', flat=True)
+ sync_ids = Task.objects.filter(
+ id__in=task_ids, status__in=NEED_SYNC_STATUS
+ ).values_list("id", flat=True)
devops_tasks = SubTask.objects.filter(task_id__in=sync_ids)
else:
devops_tasks = SubTask.objects.filter(state=RUNNING)
diff --git a/itsm/task/validators.py b/itsm/task/validators.py
index 2c577d6af..084d48d5e 100644
--- a/itsm/task/validators.py
+++ b/itsm/task/validators.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.request import Request
@@ -55,30 +55,47 @@ def set_context(self, serializer_field):
class TaskOrdersValidator(object):
def __call__(self, value):
task_ids = [i["task_id"] for i in value]
- valid_task_ids = list(Task.objects.filter(id__in=task_ids).values_list("id", flat=True))
+ valid_task_ids = list(
+ Task.objects.filter(id__in=task_ids).values_list("id", flat=True)
+ )
invalid_task_ids = set(task_ids).difference(valid_task_ids)
if invalid_task_ids:
- raise ValidationError(_("无效任务ID(%s)" % (",".join([str(i) for i in invalid_task_ids]))))
+ raise ValidationError(
+ _("无效任务ID(%s)" % (",".join([str(i) for i in invalid_task_ids])))
+ )
class TaskFieldBatchUpdateValidator(object):
def __call__(self, value):
field_ids = [v["id"] for v in value if "id" in v]
- valid_field_ids = TaskField.objects.filter(id__in=field_ids).values_list("id", flat=True)
+ valid_field_ids = TaskField.objects.filter(id__in=field_ids).values_list(
+ "id", flat=True
+ )
invalid_field_ids = set(field_ids).difference(valid_field_ids)
if invalid_field_ids:
- raise ValidationError(_("无效任务字段ID(%s)" % (",".join([str(i) for i in invalid_field_ids]))))
+ raise ValidationError(
+ _(
+ "无效任务字段ID(%s)"
+ % (",".join([str(i) for i in invalid_field_ids]))
+ )
+ )
def validate_task_fields(task_fields, fields):
- required_fields = filter(lambda f: f.validate_type == 'REQUIRE', task_fields)
+ required_fields = filter(lambda f: f.validate_type == "REQUIRE", task_fields)
required_keys = {f.key for f in required_fields}
- fields_for_key = {f['key']: f for f in fields}
+ fields_for_key = {f["key"]: f for f in fields}
field_keys = set(fields_for_key.keys())
lost_keys = required_keys - field_keys
if lost_keys:
- raise serializers.ValidationError({str(_('参数校验失败')): _('任务处理失败,缺少参数:{}'.format(list(lost_keys)))})
+ raise serializers.ValidationError(
+ {
+ str(_("参数校验失败")): _(
+ "任务处理失败,缺少参数:{}".format(list(lost_keys))
+ )
+ }
+ )
# 正则校验, 时间校验
for task_field in task_fields:
diff --git a/itsm/task/views/task.py b/itsm/task/views/task.py
index da654d4e3..78dc97c84 100644
--- a/itsm/task/views/task.py
+++ b/itsm/task/views/task.py
@@ -27,7 +27,7 @@
from collections import OrderedDict
from django.db import transaction
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
@@ -66,7 +66,7 @@
class TaskViewSet(component_viewsets.ModelViewSet):
- queryset = Task.objects.all().order_by('create_at')
+ queryset = Task.objects.all().order_by("create_at")
filter_fields = {
"ticket_id": ["exact"],
"component_type": ["exact", "in"],
@@ -79,7 +79,7 @@ class TaskViewSet(component_viewsets.ModelViewSet):
permission_classes = (TaskPermissionValidate,)
def get_serializer_class(self):
- if self.action == 'list':
+ if self.action == "list":
return TaskListSerializer
return TaskSerializer
@@ -110,7 +110,9 @@ def create_task(data, username):
instance = serializer.save(creator=username)
instance.create_sub_task(fields=fields, operator=username, data=data)
instance.do_after_create(fields)
- instance.create_task_pipeline(need_start) # 放在事务里会在事务未提交时去获取,导致获取不到
+ instance.create_task_pipeline(
+ need_start
+ ) # 放在事务里会在事务未提交时去获取,导致获取不到
return instance
@@ -121,14 +123,14 @@ def create(self, request, *args, **kwargs):
task = self.create_task(request.data, request.user.username)
- return Response({'task_id': task.id}, status=status.HTTP_201_CREATED)
+ return Response({"task_id": task.id}, status=status.HTTP_201_CREATED)
except ComponentCallError as error:
return Response(
{
- 'result': False,
- 'message': error.message,
- 'data': error.ERROR_CODE,
- 'code': ComponentCallError.ERROR_CODE_INT,
+ "result": False,
+ "message": error.message,
+ "data": error.ERROR_CODE,
+ "code": ComponentCallError.ERROR_CODE_INT,
}
)
@@ -142,11 +144,11 @@ def batch_create(self, request, *args, **kwargs):
"""
data = request.data
common_info = {
- "ticket_id": data['ticket_id'],
+ "ticket_id": data["ticket_id"],
}
tasks = []
- for task_data in data['tasks']:
+ for task_data in data["tasks"]:
task_data.update(common_info)
task = self.create_task(task_data, request.user.username)
tasks.append(task.id)
@@ -175,20 +177,22 @@ def perform_update(self, serializer):
instance = serializer.save(**update_info)
if instance.component_type == "SOPS":
try:
- instance.update_sops_task(fields=fields["sops_templates"], operator=username)
+ instance.update_sops_task(
+ fields=fields["sops_templates"], operator=username
+ )
except ComponentCallError as error:
return Response(
{
- 'result': False,
- 'message': error.message,
- 'data': error.ERROR_CODE,
- 'code': ComponentCallError.ERROR_CODE_INT,
+ "result": False,
+ "message": error.message,
+ "data": error.ERROR_CODE,
+ "code": ComponentCallError.ERROR_CODE_INT,
}
)
else:
serializer.save(**update_info)
- @action(detail=True, methods=['get'])
+ @action(detail=True, methods=["get"])
def fields(self, request, *args, **kwargs):
instance = self.get_object()
field_view = TaskFieldViewSet()
@@ -201,13 +205,17 @@ def set_order(self, request, *args, **kwargs):
"""设置单据任务列表在整个生命周期下的执行顺序
生命周期: 创建任务->处理任务->总结任务
"""
- serializer = TaskOrderSerializer(data=request.data, context=self.get_serializer_context())
+ serializer = TaskOrderSerializer(
+ data=request.data, context=self.get_serializer_context()
+ )
serializer.is_valid(raise_exception=True)
ticket_id = request.data["ticket_id"]
# 示例数据: [{"task_id": 1, "order": 1}, {"task_id": 2, "order": 2}]
task_orders = request.data["task_orders"]
task_id_order_mapping = {i["task_id"]: i["order"] for i in task_orders}
- tasks = Task.objects.filter(ticket_id=ticket_id, id__in=task_id_order_mapping.keys())
+ tasks = Task.objects.filter(
+ ticket_id=ticket_id, id__in=task_id_order_mapping.keys()
+ )
for task in tasks:
task.order = task_id_order_mapping.get(task.id, EMPTY_INT)
@@ -222,7 +230,7 @@ def proceed(self, request, *args, **kwargs):
task = self.get_object()
username = request.user.username
- serializer = TaskProceedSerializer(data=request.data, context={'task': task})
+ serializer = TaskProceedSerializer(data=request.data, context={"task": task})
serializer.is_valid(raise_exception=True)
proceed_action = serializer.validated_data["action"]
@@ -239,7 +247,7 @@ def proceed(self, request, *args, **kwargs):
except Exception as e:
raise CallTaskPipelineError(_("任务节点回调异常(%s)") % e)
if not res.result:
- return Response({'result': False, 'message': res.message})
+ return Response({"result": False, "message": res.message})
else:
task.confirmer = username
task.save(update_fields=["confirmer"])
@@ -254,24 +262,30 @@ def retry(self, request, *args, **kwargs):
task = self.get_object()
username = request.user.username
- serializer = TaskRetrySerializer(data=request.data, context={'task': task})
+ serializer = TaskRetrySerializer(data=request.data, context={"task": task})
serializer.is_valid(raise_exception=True)
# 覆盖sops_template字段
sops_templates = serializer.validated_data[SOPS_TEMPLATE_KEY]
- task.create_fields.filter(key=SOPS_TEMPLATE_KEY).update(_value=json.dumps(sops_templates))
+ task.create_fields.filter(key=SOPS_TEMPLATE_KEY).update(
+ _value=json.dumps(sops_templates)
+ )
fields = serializer.validated_data["fields"]
try:
- callback_result = task.activity_callback(ACTION_OPERATE, fields, username, False)
+ callback_result = task.activity_callback(
+ ACTION_OPERATE, fields, username, False
+ )
except Exception as e:
raise CallTaskPipelineError(_("任务节点回调异常(%s)") % e)
if not callback_result.result:
# 回调失败的时候直接抛出异常,记录回调信息
logger.error(_("任务节点回调异常(%s)"), callback_result.message)
- raise CallTaskPipelineError(_("任务节点回调异常(%s)") % callback_result.message)
+ raise CallTaskPipelineError(
+ _("任务节点回调异常(%s)") % callback_result.message
+ )
with transaction.atomic():
task.update_executor_status(username, RUNNING)
@@ -290,7 +304,7 @@ def skip(self, request, *args, **kwargs):
raise CallTaskPipelineError(_("任务节点回调异常(%s)") % e)
if not res.result:
- return Response({'result': False, 'message': res.message})
+ return Response({"result": False, "message": res.message})
with transaction.atomic():
task.update_executor_status(username, SKIPPED)
@@ -325,15 +339,21 @@ def batch_update(self, request):
serializer = TaskFieldBatchUpdateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
- fields = OrderedDict({field["id"]: field for field in serializer.validated_data["fields"]})
- ordering = "FIELD(`id`, {})".format(",".join(["'{}'".format(field_id) for field_id in fields.keys()]))
+ fields = OrderedDict(
+ {field["id"]: field for field in serializer.validated_data["fields"]}
+ )
+ ordering = "FIELD(`id`, {})".format(
+ ",".join(["'{}'".format(field_id) for field_id in fields.keys()])
+ )
task_fields = TaskField.objects.filter(id__in=fields.keys()).extra(
select={"custom_order": ordering}, order_by=["custom_order"]
)
for task_field in task_fields:
value = fields[task_field.id].get("value")
- task_field._value = json.dumps(value) if task_field.type in JSON_HANDLE_FIELDS else value
+ task_field._value = (
+ json.dumps(value) if task_field.type in JSON_HANDLE_FIELDS else value
+ )
task_field.choice = fields[task_field.id].get("choice", EMPTY_LIST)
bulk_update(task_fields, update_fields=["_value", "choice"])
@@ -345,11 +365,11 @@ class TaskLibViewSet(component_viewsets.ModelViewSet):
serializer_class = TaskLibSerializer
pagination_class = None
filter_fields = {
- 'service_id': ['exact', 'in'],
+ "service_id": ["exact", "in"],
}
def get_serializer_class(self):
- if self.action == 'list':
+ if self.action == "list":
return TaskLibListSerializer
return TaskLibSerializer
@@ -371,7 +391,7 @@ def create(self, request, *args, **kwargs):
task_id_list = request.data.pop("tasks", [])
instance.create_lib_tasks(task_id_list)
- return Response({'task_lib_id': instance.id}, status=status.HTTP_201_CREATED)
+ return Response({"task_lib_id": instance.id}, status=status.HTTP_201_CREATED)
def update(self, request, *args, **kwargs):
with transaction.atomic():
@@ -382,7 +402,7 @@ def update(self, request, *args, **kwargs):
instance.lib_tasks.all().delete()
instance.create_lib_tasks(task_id_list)
- return Response({'task_lib_id': instance.id}, status=status.HTTP_201_CREATED)
+ return Response({"task_lib_id": instance.id}, status=status.HTTP_201_CREATED)
@action(detail=True, methods=["get"])
def tasks(self, request, *args, **kwargs):
diff --git a/itsm/tests/iadmin/test_system_settings.py b/itsm/tests/iadmin/test_system_settings.py
index fd7d8f5b0..ab9b416ca 100644
--- a/itsm/tests/iadmin/test_system_settings.py
+++ b/itsm/tests/iadmin/test_system_settings.py
@@ -26,12 +26,15 @@
__author__ = "蓝鲸智云"
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
+import mock
from django.test import TestCase, override_settings
class SystemSettingsTest(TestCase):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_configrations(self):
+ @mock.patch("itsm.iadmin.permissions.SystemSettingPermit.has_permission")
+ def test_configrations(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/iadmin/system_settings/configrations/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
self.assertEqual(rsp.status_code, 200)
@@ -39,7 +42,9 @@ def test_configrations(self):
self.assertIsInstance(rsp.data, dict)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_change_settings(self):
+ @mock.patch("itsm.iadmin.permissions.SystemSettingPermit.has_permission")
+ def test_change_settings(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/iadmin/system_settings/3/"
data = {"key": "FIRST_STATE_SWITCH", "type": "FUNCTION", "value": "off"}
rsp = self.client.put(path=url, data=data, content_type="application/json")
diff --git a/itsm/tests/iam/test_utils.py b/itsm/tests/iam/test_utils.py
index 16c839df3..0184200f7 100644
--- a/itsm/tests/iam/test_utils.py
+++ b/itsm/tests/iam/test_utils.py
@@ -61,6 +61,7 @@ def test_batch_resource_multi_actions_allowed(self):
]
actions = ["project_view"]
settings.ENVIRONMENT = "dev"
+ settings.IAM_SKIP_AUTH = True
data = self.request.batch_resource_multi_actions_allowed(
actions=actions, resources=resources
)
diff --git a/itsm/tests/openapi/test_ticket.py b/itsm/tests/openapi/test_ticket.py
index c44e2c2ea..db8500b85 100644
--- a/itsm/tests/openapi/test_ticket.py
+++ b/itsm/tests/openapi/test_ticket.py
@@ -41,6 +41,7 @@
from itsm.service.models import Service, CatalogService
from itsm.workflow.models import WorkflowVersion
from itsm.role.models import UserRole
+from pipeline.engine.models import FunctionSwitch
class TicketOpenTest(TestCase):
@@ -52,6 +53,7 @@ def setUp(self):
CatalogService.objects.create(
service_id=1, is_deleted=False, catalog_id=2, creator="admin"
)
+ FunctionSwitch.objects.init_db()
def tearDown(self):
Ticket.objects.all().delete()
@@ -362,7 +364,9 @@ def test_comment(
resp = self.client.post(url, json.dumps(data), content_type="application/json")
self.assertEqual(resp.data["result"], False)
- self.assertEqual(resp.data["message"], "参数验证失败: sn=11111对应的单据不存在!")
+ self.assertEqual(
+ resp.data["message"], "参数验证失败: sn=11111对应的单据不存在!"
+ )
data["sn"] = sn
resp = self.client.post(url, json.dumps(data), content_type="application/json")
@@ -379,7 +383,9 @@ def test_comment(
resp = self.client.post(url, json.dumps(data), content_type="application/json")
self.assertEqual(resp.data["result"], False)
- self.assertEqual(resp.data["message"], "参数验证失败: 单据评价记录未存在,无法评价!")
+ self.assertEqual(
+ resp.data["message"], "参数验证失败: 单据评价记录未存在,无法评价!"
+ )
TicketComment.objects.get_or_create(ticket_id=ticket.id, creator=ticket.creator)
@@ -395,4 +401,6 @@ def test_comment(
resp = self.client.post(url, json.dumps(data), content_type="application/json")
self.assertEqual(resp.data["result"], False)
- self.assertEqual(resp.data["message"], "参数验证失败: 该单据已经被评论,请勿重复评论")
+ self.assertEqual(
+ resp.data["message"], "参数验证失败: 该单据已经被评论,请勿重复评论"
+ )
diff --git a/itsm/tests/postman/test_remote_system.py b/itsm/tests/postman/test_remote_system.py
index c4294c971..f31a35d8a 100644
--- a/itsm/tests/postman/test_remote_system.py
+++ b/itsm/tests/postman/test_remote_system.py
@@ -48,4 +48,4 @@ def test_get_systems(self):
self.assertEqual(resp.data["result"], True)
self.assertEqual(resp.data["code"], "OK")
- self.assertEqual(len(resp.data["data"]), 6)
+ self.assertIsInstance(resp.data["data"], list)
diff --git a/itsm/tests/project/test_project.py b/itsm/tests/project/test_project.py
index aeca3b2ef..9e4311068 100644
--- a/itsm/tests/project/test_project.py
+++ b/itsm/tests/project/test_project.py
@@ -75,7 +75,16 @@ def test_create_project(self, grant_instance_creator_related_actions):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.auth_iam.utils.grant_instance_creator_related_actions")
- def test_update_records(self, grant_instance_creator_related_actions) -> None:
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.has_permission")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_update_records(
+ self,
+ patch_iam_auth,
+ patch_has_permission,
+ grant_instance_creator_related_actions,
+ ) -> None:
+ patch_iam_auth.return_value = True
+ patch_has_permission.return_value = True
grant_instance_creator_related_actions.return_value = True
resp = self.client.post("/api/project/projects/", CREATE_PROJECT_DATA)
diff --git a/itsm/tests/service/test_service.py b/itsm/tests/service/test_service.py
index 64bd79701..7d066f3ca 100644
--- a/itsm/tests/service/test_service.py
+++ b/itsm/tests/service/test_service.py
@@ -97,7 +97,11 @@ def auth_result(apply_actions, resource_info):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_create_service(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.service.permissions.ServicePermit.has_permission")
+ def test_create_service(
+ self, patch_has_permission, patch_misc_get_bk_users, path_get_bk_users
+ ):
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
url = "/api/service/projects/"
@@ -120,7 +124,11 @@ def test_create_service(self, patch_misc_get_bk_users, path_get_bk_users):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_import(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.service.permissions.ServicePermit.has_permission")
+ def test_import(
+ self, patch_has_permission, patch_misc_get_bk_users, path_get_bk_users
+ ):
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
url = "/api/service/projects/"
@@ -184,7 +192,17 @@ def test_import(self, patch_misc_get_bk_users, path_get_bk_users):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_save_configs(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.service.permissions.ServicePermit.has_permission")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_save_configs(
+ self,
+ patch_iam_auth,
+ patch_has_permission,
+ patch_misc_get_bk_users,
+ path_get_bk_users,
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
url = "/api/service/projects/"
@@ -204,7 +222,17 @@ def test_save_configs(self, patch_misc_get_bk_users, path_get_bk_users):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_favorite(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.service.permissions.ServicePermit.has_permission")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_favorite(
+ self,
+ patch_iam_auth,
+ patch_has_permission,
+ patch_misc_get_bk_users,
+ path_get_bk_users,
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
url = "/api/service/projects/"
@@ -233,7 +261,11 @@ def test_favorite(self, patch_misc_get_bk_users, path_get_bk_users):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_clone(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.service.permissions.ServicePermit.has_permission")
+ def test_clone(
+ self, patch_has_permission, patch_misc_get_bk_users, path_get_bk_users
+ ):
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
@@ -252,7 +284,17 @@ def test_clone(self, patch_misc_get_bk_users, path_get_bk_users):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_export_and_import(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.service.permissions.ServicePermit.has_permission")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_export_and_import(
+ self,
+ patch_iam_auth,
+ patch_has_permission,
+ patch_misc_get_bk_users,
+ path_get_bk_users,
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
url = "/api/service/projects/"
diff --git a/itsm/tests/sla/test_view.py b/itsm/tests/sla/test_view.py
index 256e941d7..8025f6f5f 100644
--- a/itsm/tests/sla/test_view.py
+++ b/itsm/tests/sla/test_view.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import json
+import mock
from django.test import TestCase, override_settings
from itsm.sla.models import Sla
@@ -16,7 +17,11 @@ def test_protocols_list(self):
self.assertIsInstance(rsp.data["data"], dict)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_put_protocols(self):
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.has_permission")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_put_protocols(self, patch_iam_auth, patch_has_permission):
+ patch_iam_auth.return_value = True
+ patch_has_permission.return_value = True
data = {
"name": "7*24",
"is_enabled": True,
@@ -33,7 +38,9 @@ def test_put_protocols(self):
self.assertEqual(rsp.data["result"], True)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_post_protocols(self):
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.has_permission")
+ def test_post_protocols(self, patch_has_permission):
+ patch_has_permission.return_value = True
data = {
"name": "7*24",
"is_enabled": True,
@@ -47,7 +54,9 @@ def test_post_protocols(self):
rsp = self.client.post(path=url, data=data, content_type="application/json")
self.assertEqual(rsp.data["result"], False)
- self.assertEqual(rsp.data["message"], "参数验证失败: 服务协议名称:[7*24] 已存在")
+ self.assertEqual(
+ rsp.data["message"], "参数验证失败: 服务协议名称:[7*24] 已存在"
+ )
data["name"] = "5*24"
url = "/api/sla/protocols/"
@@ -65,7 +74,9 @@ def test_schedules_list(self):
self.assertIsInstance(rsp.data["data"], list)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_post_chedules(self):
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.has_permission")
+ def test_post_chedules(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/sla/schedules/"
data = {
"name": "测试服务名称",
@@ -127,7 +138,9 @@ def test_ticket_highlight(self):
self.assertEqual(rsp.data["data"], "1")
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_matrix_of_service_type(self):
+ @mock.patch("itsm.sla.permissions.SlaMatrixPermit.has_permission")
+ def test_matrix_of_service_type(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/sla/matrixs/matrix_of_service_type/"
data = {"service_type": "request"}
rsp = self.client.post(
diff --git a/itsm/tests/task/test_task.py b/itsm/tests/task/test_task.py
index 01967cb4b..1e3cb8191 100644
--- a/itsm/tests/task/test_task.py
+++ b/itsm/tests/task/test_task.py
@@ -34,15 +34,32 @@
from itsm.ticket.models import Ticket
from itsm.workflow.models import TaskSchema, TaskFieldSchema, TaskConfig, VERSION
from pipeline.engine.models import FunctionSwitch
-from .test_params import sops_create_res, task_params, create_ticket_data, create_sops_task_data
+from .test_params import (
+ sops_create_res,
+ task_params,
+ create_ticket_data,
+ create_sops_task_data,
+)
from ...service.models import CatalogService
class SopsTaskTest(TestCase):
def setUp(self):
app.conf.update(CELERY_ALWAYS_EAGER=True)
- CatalogService.objects.create(service_id=1, is_deleted=False, catalog_id=2, creator="admin")
+ CatalogService.objects.create(
+ service_id=1, is_deleted=False, catalog_id=2, creator="admin"
+ )
FunctionSwitch.objects.init_db()
+ self.patcher_has_permission = mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_permission",
+ return_value=True,
+ )
+ self.patcher_has_object_permission = mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_object_permission",
+ return_value=True,
+ )
+ self.patcher_has_permission.start()
+ self.patcher_has_object_permission.start()
def tearDown(self):
Ticket.objects.all().delete()
@@ -52,10 +69,12 @@ def tearDown(self):
SopsTask.objects.all().delete()
Task.objects.all().delete()
TaskConfig.objects.all().delete()
+ self.patcher_has_permission.stop()
+ self.patcher_has_object_permission.stop()
@mock.patch("itsm.task.models.client_backend.sops")
@mock.patch.object(Task, "call_sops_create_task")
- @override_settings(MIDDLEWARE=('itsm.tests.middlewares.OverrideMiddleware',))
+ @override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def test_create_normal_task(self, mock_create_res, client_backend):
client_backend.get_task_detail.return_value = {
"result": True,
@@ -63,22 +82,29 @@ def test_create_normal_task(self, mock_create_res, client_backend):
"constants": {},
"data": {
"task_url": "xxxx",
- }
+ },
}
mock_create_res.return_value = sops_create_res, task_params
data = copy.deepcopy(create_ticket_data)
url = "/api/ticket/receipts/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
ticket_id = rsp.data["data"]["id"]
task_schema = TaskSchema.objects.create(name="test", component_type="NORMAL")
- TaskFieldSchema.objects.create(key="task_name", name="任务名称", task_schema=task_schema)
- TaskFieldSchema.objects.create(key="processors", name="处理人", task_schema=task_schema,
- sequence=1)
+ TaskFieldSchema.objects.create(
+ key="task_name", name="任务名称", task_schema=task_schema
+ )
+ TaskFieldSchema.objects.create(
+ key="processors", name="处理人", task_schema=task_schema, sequence=1
+ )
ticket = Ticket.objects.get(id=ticket_id)
TaskConfig.objects.create(
- workflow_id=ticket.flow_id, workflow_type=VERSION,
- execute_task_state=ticket.first_state_id, task_schema_id=task_schema.id,
- create_task_state=ticket.first_state_id
+ workflow_id=ticket.flow_id,
+ workflow_type=VERSION,
+ execute_task_state=ticket.first_state_id,
+ task_schema_id=task_schema.id,
+ create_task_state=ticket.first_state_id,
)
data = {
"processors": "hoganren1",
@@ -89,13 +115,15 @@ def test_create_normal_task(self, mock_create_res, client_backend):
"task_schema_id": task_schema.id,
}
url = "/api/task/tasks/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
self.assertEqual(rsp.data["code"], "OK")
self.assertEqual(rsp.data["message"], "success")
@mock.patch("itsm.task.models.client_backend.sops")
@mock.patch.object(Task, "call_sops_create_task")
- @override_settings(MIDDLEWARE=('itsm.tests.middlewares.OverrideMiddleware',))
+ @override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def test_create_normal_task_need_start(self, mock_create_res, client_backend):
client_backend.get_task_detail.return_value = {
"result": True,
@@ -103,22 +131,29 @@ def test_create_normal_task_need_start(self, mock_create_res, client_backend):
"constants": {},
"data": {
"task_url": "xxxx",
- }
+ },
}
mock_create_res.return_value = sops_create_res, task_params
data = copy.deepcopy(create_ticket_data)
url = "/api/ticket/receipts/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
ticket_id = rsp.data["data"]["id"]
task_schema = TaskSchema.objects.create(name="test", component_type="NORMAL")
- TaskFieldSchema.objects.create(key="task_name", name="任务名称", task_schema=task_schema)
- TaskFieldSchema.objects.create(key="processors", name="处理人", task_schema=task_schema,
- sequence=1)
+ TaskFieldSchema.objects.create(
+ key="task_name", name="任务名称", task_schema=task_schema
+ )
+ TaskFieldSchema.objects.create(
+ key="processors", name="处理人", task_schema=task_schema, sequence=1
+ )
ticket = Ticket.objects.get(id=ticket_id)
TaskConfig.objects.create(
- workflow_id=ticket.flow_id, workflow_type=VERSION,
- execute_task_state=ticket.first_state_id, task_schema_id=task_schema.id,
- create_task_state=ticket.first_state_id
+ workflow_id=ticket.flow_id,
+ workflow_type=VERSION,
+ execute_task_state=ticket.first_state_id,
+ task_schema_id=task_schema.id,
+ create_task_state=ticket.first_state_id,
)
data = {
"processors": "hoganren1",
@@ -130,13 +165,15 @@ def test_create_normal_task_need_start(self, mock_create_res, client_backend):
"need_start": True,
}
url = "/api/task/tasks/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
self.assertEqual(rsp.data["code"], "OK")
self.assertEqual(rsp.data["message"], "success")
@mock.patch("itsm.task.models.client_backend.sops")
@mock.patch.object(Task, "call_sops_create_task")
- @override_settings(MIDDLEWARE=('itsm.tests.middlewares.OverrideMiddleware',))
+ @override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def test_create_sops_task_from_template(self, mock_create_res, client_backend):
client_backend.get_task_detail.return_value = {
"result": True,
@@ -144,32 +181,38 @@ def test_create_sops_task_from_template(self, mock_create_res, client_backend):
"constants": {},
"data": {
"task_url": "xxxx",
- }
+ },
}
mock_create_res.return_value = sops_create_res, task_params
data = copy.deepcopy(create_ticket_data)
url = "/api/ticket/receipts/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
ticket_id = rsp.data["data"]["id"]
task_schema = TaskSchema.objects.filter(component_type="SOPS").first()
data = copy.deepcopy(create_sops_task_data)
ticket = Ticket.objects.get(id=ticket_id)
TaskConfig.objects.create(
- workflow_id=ticket.flow_id, workflow_type=VERSION,
- execute_task_state=ticket.first_state_id, task_schema_id=task_schema.id,
- create_task_state=ticket.first_state_id
+ workflow_id=ticket.flow_id,
+ workflow_type=VERSION,
+ execute_task_state=ticket.first_state_id,
+ task_schema_id=task_schema.id,
+ create_task_state=ticket.first_state_id,
)
data["ticket_id"] = ticket_id
data["state_id"] = ticket.first_state_id
data["task_schema_id"] = task_schema.id
url = "/api/task/tasks/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
self.assertEqual(rsp.data["code"], "OK")
self.assertEqual(rsp.data["message"], "success")
@mock.patch("itsm.task.models.client_backend.sops")
@mock.patch.object(Task, "call_sops_update_task")
- @override_settings(MIDDLEWARE=('itsm.tests.middlewares.OverrideMiddleware',))
+ @override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def test_create_sops_task_from_exist(self, mock_update_res, client_backend):
mock_update_res.return_value = sops_create_res, task_params
client_backend.get_task_detail.return_value = {
@@ -178,19 +221,23 @@ def test_create_sops_task_from_exist(self, mock_update_res, client_backend):
"constants": {},
"data": {
"task_url": "xxxx",
- }
+ },
}
data = copy.deepcopy(create_ticket_data)
url = "/api/ticket/receipts/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
ticket_id = rsp.data["data"]["id"]
task_schema = TaskSchema.objects.filter(component_type="SOPS").first()
data = copy.deepcopy(create_sops_task_data)
ticket = Ticket.objects.get(id=ticket_id)
TaskConfig.objects.create(
- workflow_id=ticket.flow_id, workflow_type=VERSION,
- execute_task_state=ticket.first_state_id, task_schema_id=task_schema.id,
- create_task_state=ticket.first_state_id
+ workflow_id=ticket.flow_id,
+ workflow_type=VERSION,
+ execute_task_state=ticket.first_state_id,
+ task_schema_id=task_schema.id,
+ create_task_state=ticket.first_state_id,
)
data["ticket_id"] = ticket_id
data["task_schema_id"] = task_schema.id
@@ -198,14 +245,16 @@ def test_create_sops_task_from_exist(self, mock_update_res, client_backend):
data["state_id"] = ticket.first_state_id
data["fields"]["sops_templates"]["task_id"] = 28239
url = "/api/task/tasks/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
self.assertEqual(rsp.data["code"], "OK")
self.assertEqual(rsp.data["message"], "success")
@mock.patch("itsm.task.models.client_backend.sops")
@mock.patch.object(Task, "call_sops_create_task")
@mock.patch.object(Task, "update_sops_task")
- @override_settings(MIDDLEWARE=('itsm.tests.middlewares.OverrideMiddleware',))
+ @override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def test_update_sops_task(self, mock_create_res, mock_update_res, client_backend):
# pipeline.return_value = None
mock_create_res.return_value = sops_create_res, task_params
@@ -216,28 +265,36 @@ def test_update_sops_task(self, mock_create_res, mock_update_res, client_backend
"constants": {},
"data": {
"task_url": "xxxx",
- }
+ },
}
data = copy.deepcopy(create_ticket_data)
url = "/api/ticket/receipts/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
ticket_id = rsp.data["data"]["id"]
task_schema = TaskSchema.objects.filter(component_type="SOPS").first()
data = copy.deepcopy(create_sops_task_data)
ticket = Ticket.objects.get(id=ticket_id)
TaskConfig.objects.create(
- workflow_id=ticket.flow_id, workflow_type=VERSION,
- execute_task_state=ticket.first_state_id, task_schema_id=task_schema.id,
- create_task_state=ticket.first_state_id
+ workflow_id=ticket.flow_id,
+ workflow_type=VERSION,
+ execute_task_state=ticket.first_state_id,
+ task_schema_id=task_schema.id,
+ create_task_state=ticket.first_state_id,
)
data["ticket_id"] = ticket_id
data["state_id"] = ticket.first_state_id
data["task_schema_id"] = task_schema.id
url = "/api/task/tasks/"
- rsp = self.client.post(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
task_id = rsp.data["data"]["task_id"]
url = "/api/task/tasks/{}/".format(task_id)
- rsp = self.client.patch(path=url, data=json.dumps(data), content_type="application/json")
+ rsp = self.client.patch(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
self.assertEqual(rsp.data["code"], "OK")
self.assertEqual(rsp.data["message"], "success")
diff --git a/itsm/tests/ticket/test_event_log.py b/itsm/tests/ticket/test_event_log.py
index 34c20f64e..56679e866 100644
--- a/itsm/tests/ticket/test_event_log.py
+++ b/itsm/tests/ticket/test_event_log.py
@@ -25,6 +25,7 @@
import json
+import mock
from django.test import TestCase, override_settings
from itsm.service.models import CatalogService
@@ -33,16 +34,22 @@
class TicketEventLogTestCase(TestCase):
-
def setUp(self) -> None:
- CatalogService.objects.create(service_id=1, is_deleted=False, catalog_id=2, creator="admin")
+ CatalogService.objects.create(
+ service_id=1, is_deleted=False, catalog_id=2, creator="admin"
+ )
TicketEventLog.objects.all().delete()
- @override_settings(MIDDLEWARE=('itsm.tests.middlewares.OverrideMiddleware',))
- def test_get_index_ticket_event_log(self):
+ @override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ def test_get_index_ticket_event_log(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/ticket/receipts/"
- resp = self.client.post(path=url, data=json.dumps(CREATE_TICKET_PARAMS),
- content_type="application/json")
+ resp = self.client.post(
+ path=url,
+ data=json.dumps(CREATE_TICKET_PARAMS),
+ content_type="application/json",
+ )
sn = resp.data["data"]["sn"]
diff --git a/itsm/tests/ticket/test_ticket.py b/itsm/tests/ticket/test_ticket.py
index a02bb4689..86ab40679 100644
--- a/itsm/tests/ticket/test_ticket.py
+++ b/itsm/tests/ticket/test_ticket.py
@@ -27,15 +27,15 @@
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
import json
+
import mock
from blueapps.core.celery.celery import app
-
-from django.test import TestCase, override_settings
from django.core.cache import cache
+from django.test import TestCase, override_settings
+from itsm.component.constants import APPROVAL_STATE
from itsm.service.models import CatalogService, Service
from itsm.ticket.models import Ticket, Status, AttentionUsers
-from itsm.component.constants import APPROVAL_STATE
class TicketTest(TestCase):
@@ -53,7 +53,9 @@ def tearDown(self):
AttentionUsers.objects.all().delete()
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_create_ticket(self):
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ def test_create_ticket(self, patch_has_permission):
+ patch_has_permission.return_value = True
data = {
"catalog_id": 3,
"service_id": 1,
@@ -94,9 +96,28 @@ def test_create_ticket(self):
self.assertEqual(rsp.data["message"], "success")
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
+ @mock.patch("itsm.auth_iam.utils.IamRequest.batch_resource_multi_actions_allowed")
@mock.patch("itsm.role.models.get_user_departments")
- def test_list(self, patch_get_user_departments):
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ @mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_list(
+ self,
+ patch_iam_auth,
+ patch_has_object_permission,
+ patch_has_permission,
+ patch_get_user_departments,
+ patch_batch_resource_multi_actions_allowed,
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
patch_get_user_departments.return_value = {}
+ patch_batch_resource_multi_actions_allowed.return_value = {
+ "1": {"ticket_view": True}
+ }
data = {
"catalog_id": 3,
"service_id": 1,
@@ -142,9 +163,18 @@ def test_list(self, patch_get_user_departments):
self.assertEqual(["admin"], list_rsp.data["data"]["items"][0]["followers"])
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideTestMiddleware",))
+ @mock.patch("itsm.auth_iam.utils.IamRequest.batch_resource_multi_actions_allowed")
@mock.patch("itsm.role.models.get_user_departments")
- def test_list_follower(self, patch_get_user_departments):
+ def test_list_follower(
+ self, patch_get_user_departments, patch_batch_resource_multi_actions_allowed
+ ):
patch_get_user_departments.return_value = {}
+ patch_batch_resource_multi_actions_allowed.return_value = {
+ "1": {"ticket_view": True}
+ }
+
+ # 当前测试使用test用户为admin用户提单,需要允许代提单
+ Service.objects.filter(id=1).update(can_ticket_agency=True)
data = {
"catalog_id": 3,
"service_id": 1,
@@ -228,7 +258,22 @@ def test_list_follower(self, patch_get_user_departments):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_retrieve(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ @mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_retrieve(
+ self,
+ patch_iam_auth,
+ patch_has_object_permission,
+ patch_has_permission,
+ patch_misc_get_bk_users,
+ path_get_bk_users,
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
data = {
@@ -279,9 +324,25 @@ def test_retrieve(self, patch_misc_get_bk_users, path_get_bk_users):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_add_follower(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ @mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_object_permission"
+ )
+ def test_add_follower(
+ self,
+ patch_has_object_permission,
+ patch_has_permission,
+ patch_misc_get_bk_users,
+ path_get_bk_users,
+ ):
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
+
+ # 当前测试使用admin用户为test用户提单,需要允许代提单
+ Service.objects.filter(id=1).update(can_ticket_agency=True)
+
data = {
"catalog_id": 3,
"service_id": 1,
@@ -339,7 +400,22 @@ def test_add_follower(self, patch_misc_get_bk_users, path_get_bk_users):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_delete_follower(self, patch_misc_get_bk_users, path_get_bk_users):
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ @mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_delete_follower(
+ self,
+ patch_iam_auth,
+ patch_has_object_permission,
+ patch_has_permission,
+ patch_misc_get_bk_users,
+ path_get_bk_users,
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
data = {
@@ -397,11 +473,27 @@ def test_delete_follower(self, patch_misc_get_bk_users, path_get_bk_users):
self.assertEqual(ticket_id, list_rsp.data["data"]["id"])
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideTestMiddleware",))
+ @mock.patch("itsm.auth_iam.utils.IamRequest.resource_multi_actions_allowed")
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- def test_operate(self, patch_misc_get_bk_users, path_get_bk_users):
+ def test_operate(
+ self,
+ patch_misc_get_bk_users,
+ path_get_bk_users,
+ patch_resource_multi_actions_allowed,
+ ):
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
+ patch_resource_multi_actions_allowed.return_value = {"ticket_management": True}
+
+ # 打印调试信息
+ print(
+ "Mocked resource_multi_actions_allowed:",
+ patch_resource_multi_actions_allowed.return_value,
+ )
+
+ # 当前测试使用test用户为admin用户提单,需要允许代提单
+ Service.objects.filter(id=1).update(can_ticket_agency=True)
data = {
"catalog_id": 3,
"service_id": 1,
@@ -526,12 +618,26 @@ def test_batch_approval_add_queue_error(
@override_settings(
MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",), ENVIRONMENT="dev"
)
+ @mock.patch("itsm.auth_iam.utils.IamRequest")
@mock.patch("itsm.ticket.serializers.ticket.get_bk_users")
@mock.patch("itsm.component.utils.misc.get_bk_users")
- @mock.patch("itsm.auth_iam.utils.IamRequest")
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ @mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
def test_exception_distribute(
- self, patch_misc_get_bk_users, path_get_bk_users, patch_iam_request
+ self,
+ patch_iam_auth,
+ patch_has_object_permission,
+ patch_has_permission,
+ patch_misc_get_bk_users,
+ path_get_bk_users,
+ patch_iam_request,
):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
patch_misc_get_bk_users.return_value = {}
path_get_bk_users.return_value = {}
patch_iam_request.resource_multi_actions_allowed.return_value = {
diff --git a/itsm/tests/ticket/test_ticket_view.py b/itsm/tests/ticket/test_ticket_view.py
index c1aef75be..5523a493b 100644
--- a/itsm/tests/ticket/test_ticket_view.py
+++ b/itsm/tests/ticket/test_ticket_view.py
@@ -27,16 +27,62 @@
import mock
from django.test import TestCase, override_settings
+from blueapps.core.celery.celery import app
from itsm.service.models import CatalogService
-from itsm.ticket.models import Ticket
+from itsm.ticket.models import Ticket, AttentionUsers
+from pipeline.engine.models import FunctionSwitch
class TicketViewTest(TestCase):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def setUp(self):
+ Ticket.objects.all().delete()
+ # CatalogService.objects.all().delete()
+ self.patcher_has_permission = mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_permission",
+ return_value=True,
+ )
+ self.patcher_has_object_permission = mock.patch(
+ "itsm.ticket.permissions.TicketPermissionValidate.has_object_permission",
+ return_value=True,
+ )
+ self.patcher_batch_resource_multi_actions_allowed = mock.patch(
+ "itsm.auth_iam.utils.IamRequest.batch_resource_multi_actions_allowed",
+ return_value={"1": {"ticket_view": True}},
+ )
+ self.patcher_transform_username = mock.patch(
+ "itsm.component.utils.misc.transform_username",
+ return_value={"admin": "admin(admin)"},
+ )
+ self.patcher_transform_single_username = mock.patch(
+ "itsm.component.utils.misc.transform_single_username",
+ return_value="admin(admin)",
+ )
+ self.patcher_get_bk_users = mock.patch(
+ "itsm.component.utils.client_backend_query.get_bk_users",
+ return_value="admin(admin)",
+ )
+ self.patcher_get_user_departments = mock.patch(
+ "itsm.component.utils.client_backend_query.get_user_departments",
+ return_value=["1"],
+ )
+
+ self.patch_get_bk_users = self.patcher_get_bk_users.start()
+ self.patch_transform_single_username = (
+ self.patcher_transform_single_username.start()
+ )
+ self.patch_transform_username = self.patcher_transform_username.start()
+ self.patch_has_permission = self.patcher_has_permission.start()
+ self.patch_has_object_permission = self.patcher_has_object_permission.start()
+ self.patch_batch_resource_multi_actions_allowed = (
+ self.patcher_batch_resource_multi_actions_allowed.start()
+ )
+ self.patch_get_user_departments = self.patcher_get_user_departments.start()
+
CatalogService.objects.create(
service_id=1, is_deleted=False, catalog_id=2, creator="admin"
)
+ FunctionSwitch.objects.init_db()
data = {
"catalog_id": 3,
"service_id": 1,
@@ -77,8 +123,15 @@ def setUp(self):
self.assertEqual(rsp.data["message"], "success")
def tearDown(self):
- Ticket.objects.all().delete()
+ self.patcher_has_permission.stop()
+ self.patcher_has_object_permission.stop()
+ self.patcher_batch_resource_multi_actions_allowed.stop()
+ self.patcher_transform_username.stop()
+ self.patcher_transform_single_username.stop()
+ self.patcher_get_bk_users.stop()
+ self.patcher_get_user_departments.stop()
CatalogService.objects.all().delete()
+ Ticket.objects.all().delete()
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.role.models.get_user_departments")
@@ -213,27 +266,6 @@ def test_export_group_by_service(self, get_user_departments):
rsp = self.client.get(path=url, data=None, content_type="application/json")
self.assertEqual(rsp.status_code, 200)
- @override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- @mock.patch("itsm.role.models.get_user_departments")
- @mock.patch("itsm.ticket.serializers.ticket.transform_single_username")
- @mock.patch("itsm.component.utils.client_backend_query.get_bk_users")
- def test_print_ticket(
- self, get_user_departments, transform_single_username, get_bk_users
- ):
- get_user_departments.return_value = ["1"]
- get_bk_users.return_value = ["1"]
- transform_single_username.return_value = "admin(管理员)"
- url = "/api/ticket/receipts/"
- rsp = self.client.get(path=url, data=None, content_type="application/json")
-
- url = "/api/ticket/receipts/{}/print_ticket/".format(
- rsp.data["data"]["items"][0]["id"]
- )
- rsp = self.client.get(path=url, data=None, content_type="application/json")
- self.assertEqual(rsp.status_code, 200)
- self.assertEqual(rsp.data["message"], "success")
- self.assertIsInstance(rsp.data["data"], dict)
-
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def test_get_global_choices(self):
url = "/api/ticket/receipts/get_global_choices/"
@@ -390,14 +422,12 @@ def test_my_approval_ticket(self, get_user_departments):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.role.models.get_user_departments")
- @mock.patch("itsm.component.utils.client_backend_query.get_bk_users")
- def test_tickets_processors(self, get_user_departments, get_bk_users):
+ def test_tickets_can_operate(self, get_user_departments):
get_user_departments.return_value = ["1"]
- get_bk_users.return_value = ["1"]
url = "/api/ticket/receipts/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
- url = "/api/ticket/receipts/tickets_processors/?ids={}".format(
+ url = "/api/ticket/receipts/tickets_can_operate/?ids={}".format(
rsp.data["data"]["items"][0]["id"]
)
rsp = self.client.get(path=url, data=None, content_type="application/json")
@@ -407,12 +437,11 @@ def test_tickets_processors(self, get_user_departments, get_bk_users):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.role.models.get_user_departments")
- def test_tickets_can_operate(self, get_user_departments):
+ def test_tree_view(self, get_user_departments):
get_user_departments.return_value = ["1"]
url = "/api/ticket/receipts/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
-
- url = "/api/ticket/receipts/tickets_can_operate/?ids={}".format(
+ url = "/api/ticket/remark/tree_view/?ticket_id={}&show_type=1".format(
rsp.data["data"]["items"][0]["id"]
)
rsp = self.client.get(path=url, data=None, content_type="application/json")
@@ -422,11 +451,11 @@ def test_tickets_can_operate(self, get_user_departments):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.role.models.get_user_departments")
- def test_tree_view(self, get_user_departments):
+ def test_remark(self, get_user_departments):
get_user_departments.return_value = ["1"]
url = "/api/ticket/receipts/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
- url = "/api/ticket/remark/tree_view/?ticket_id={}&show_type=1".format(
+ url = "/api/ticket/remark/?ticket_id={}".format(
rsp.data["data"]["items"][0]["id"]
)
rsp = self.client.get(path=url, data=None, content_type="application/json")
@@ -434,14 +463,140 @@ def test_tree_view(self, get_user_departments):
self.assertEqual(rsp.data["message"], "success")
self.assertIsInstance(rsp.data["data"], dict)
+
+class TicketPrintAndProcessorsTest(TestCase):
+ def setUp(self):
+ app.conf.update(CELERY_ALWAYS_EAGER=True)
+ Ticket.objects.all().delete()
+ AttentionUsers.objects.all().delete()
+
+ CatalogService.objects.create(
+ service_id=1, is_deleted=False, catalog_id=2, creator="admin"
+ )
+
+ def tearDown(self):
+ Ticket.objects.all().delete()
+ AttentionUsers.objects.all().delete()
+
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@mock.patch("itsm.role.models.get_user_departments")
- def test_remark(self, get_user_departments):
+ @mock.patch("itsm.ticket.serializers.ticket.transform_single_username")
+ @mock.patch("itsm.component.utils.client_backend_query.get_bk_users")
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ def test_print_ticket(
+ self,
+ patch_has_permission,
+ get_user_departments,
+ transform_single_username,
+ get_bk_users,
+ ):
+ patch_has_permission.return_value = True
get_user_departments.return_value = ["1"]
+ get_bk_users.return_value = ["1"]
+ transform_single_username.return_value = "admin(管理员)"
+
+ data = {
+ "catalog_id": 3,
+ "service_id": 1,
+ "service_type": "request",
+ "fields": [
+ {
+ "type": "STRING",
+ "id": 1,
+ "key": "title",
+ "value": "test_ticket",
+ "choice": [],
+ },
+ {
+ "type": "STRING",
+ "id": 5,
+ "key": "apply_content",
+ "value": "测试内容",
+ },
+ {
+ "type": "STRING",
+ "key": "ZHIDINGSHENPIREN",
+ "value": "test",
+ },
+ {
+ "type": "STRING",
+ "key": "apply_reason",
+ "value": "test",
+ },
+ ],
+ "creator": "admin",
+ "attention": True,
+ }
url = "/api/ticket/receipts/"
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
+
+ url = "/api/ticket/receipts/{}/print_ticket/".format(rsp.data["data"]["id"])
rsp = self.client.get(path=url, data=None, content_type="application/json")
- url = "/api/ticket/remark/?ticket_id={}".format(
- rsp.data["data"]["items"][0]["id"]
+ self.assertEqual(rsp.status_code, 200)
+ self.assertEqual(rsp.data["message"], "success")
+ self.assertIsInstance(rsp.data["data"], dict)
+
+ @override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
+ @mock.patch("itsm.component.utils.misc.transform_username")
+ @mock.patch("itsm.component.utils.misc.transform_single_username")
+ @mock.patch("itsm.component.utils.client_backend_query.get_bk_users")
+ @mock.patch("itsm.role.models.get_user_departments")
+ @mock.patch("itsm.ticket.permissions.TicketPermissionValidate.has_permission")
+ def test_tickets_processors(
+ self,
+ patch_has_permission,
+ get_user_departments,
+ patch_get_bk_users,
+ patch_transform_single_username,
+ patch_transform_username,
+ ):
+ patch_has_permission.return_value = True
+ get_user_departments.return_value = ["1"]
+ patch_get_bk_users.return_value = {"admin": "admin(admin)"}
+ patch_transform_single_username.return_value = "admin(admin)"
+ patch_transform_username.return_value = "admin(admin)"
+
+ data = {
+ "catalog_id": 3,
+ "service_id": 1,
+ "service_type": "request",
+ "fields": [
+ {
+ "type": "STRING",
+ "id": 1,
+ "key": "title",
+ "value": "test_ticket",
+ "choice": [],
+ },
+ {
+ "type": "STRING",
+ "id": 5,
+ "key": "apply_content",
+ "value": "测试内容",
+ },
+ {
+ "type": "STRING",
+ "key": "ZHIDINGSHENPIREN",
+ "value": "test",
+ },
+ {
+ "type": "STRING",
+ "key": "apply_reason",
+ "value": "test",
+ },
+ ],
+ "creator": "admin",
+ "attention": True,
+ }
+ url = "/api/ticket/receipts/"
+ rsp = self.client.post(
+ path=url, data=json.dumps(data), content_type="application/json"
+ )
+
+ url = "/api/ticket/receipts/tickets_processors/?ids={}".format(
+ rsp.data["data"]["id"]
)
rsp = self.client.get(path=url, data=None, content_type="application/json")
self.assertEqual(rsp.status_code, 200)
@@ -455,6 +610,11 @@ class OperationalDataViewTest(TestCase):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def setUp(self):
+ self.patcher = mock.patch(
+ "itsm.component.drf.permissions.IamAuthPermit.iam_auth"
+ )
+ self.mock_iam_auth = self.patcher.start()
+ self.mock_iam_auth.return_value = True
CatalogService.objects.create(
service_id=1, is_deleted=False, catalog_id=2, creator="admin"
)
@@ -500,6 +660,7 @@ def setUp(self):
def tearDown(self):
Ticket.objects.all().delete()
CatalogService.objects.all().delete()
+ self.patcher.stop()
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def test_overview_count(self):
diff --git a/itsm/tests/ticket_status/test_views.py b/itsm/tests/ticket_status/test_views.py
index a79843c61..d05181585 100644
--- a/itsm/tests/ticket_status/test_views.py
+++ b/itsm/tests/ticket_status/test_views.py
@@ -26,6 +26,7 @@
__author__ = "蓝鲸智云"
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
+import mock
from django.test import TestCase, override_settings
from itsm.ticket_status.models import StatusTransit, TicketStatusConfig, TicketStatus
@@ -33,7 +34,9 @@
class TicketStatusTest(TestCase):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_variable_list(self):
+ @mock.patch("itsm.ticket_status.permissions.TicketStatusPermit.has_permission")
+ def test_variable_list(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/ticket_status/status/get_configs/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
self.assertEqual(len(rsp.data), 4)
@@ -42,7 +45,9 @@ def test_variable_list(self):
self.assertIsInstance(rsp.data, dict)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_save_status_of_service_type(self):
+ @mock.patch("itsm.ticket_status.permissions.TicketStatusPermit.has_permission")
+ def test_save_status_of_service_type(self, patch_has_permission):
+ patch_has_permission.return_value = True
data = {
"service_type": "change",
"ticket_status_ids": [1, 2, 3, 4, 5, 6, 7, 8],
@@ -74,7 +79,9 @@ def test_save_status_of_service_type(self):
}
rsp = self.client.post(path=url, data=data, content_type="application/json")
self.assertEqual(rsp.status_code, 200)
- self.assertEqual(rsp.data["message"], "0:设置为起始状态的工单状态不存在,请联系管理员")
+ self.assertEqual(
+ rsp.data["message"], "0:设置为起始状态的工单状态不存在,请联系管理员"
+ )
self.assertEqual(rsp.data["result"], False)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
@@ -95,7 +102,9 @@ def test_ticket_status_list(self):
self.assertEqual(rsp.data["result"], True)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_patch(self):
+ @mock.patch("itsm.ticket_status.permissions.TicketStatusPermit.has_permission")
+ def test_patch(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/ticket_status/status/3/"
data = {"name": "已解决", "desc": "", "color_hex": "#3A84FF"}
rsp = self.client.patch(path=url, data=data, content_type="application/json")
@@ -120,7 +129,9 @@ def test_ticket_status_config_model(self):
config = TicketStatusConfig.objects.get(id=1)
config.init_ticket_status_config()
self.assertEqual(config.service_type_name, "变更管理")
- self.assertEqual(config.ticket_status, "新/处理中/已解决/待确认/挂起/已完成/已终止/已撤销")
+ self.assertEqual(
+ config.ticket_status, "新/处理中/已解决/待确认/挂起/已完成/已终止/已撤销"
+ )
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
def test_ticket_status_model(self):
@@ -140,7 +151,9 @@ def test_list(self):
self.assertEqual(len(rsp.data["data"]), 39)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_save_transit_of_service_type(self):
+ @mock.patch("itsm.ticket_status.permissions.TicketStatusPermit.has_permission")
+ def test_save_transit_of_service_type(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/ticket_status/transit/save_transit_of_service_type/"
data = {
"service_type": "change",
@@ -193,7 +206,9 @@ def test_save_transit_of_service_type(self):
self.assertEqual(rsp.data["result"], False)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_set_transit_rule(self):
+ @mock.patch("itsm.ticket_status.permissions.TicketStatusPermit.has_permission")
+ def test_set_transit_rule(self, patch_has_permission):
+ patch_has_permission.return_value = True
url = "/api/ticket_status/status/1/set_transit_rule/"
data = {"to_status": 6, "threshold": "1", "threshold_unit": "m"}
rsp = self.client.post(path=url, data=data, content_type="application/json")
@@ -204,7 +219,9 @@ def test_set_transit_rule(self):
data.pop("to_status")
rsp = self.client.post(path=url, data=data, content_type="application/json")
self.assertEqual(rsp.status_code, 200)
- self.assertEqual(rsp.data["message"], "0:流转目标的单据状态不存在,请联系管理员")
+ self.assertEqual(
+ rsp.data["message"], "0:流转目标的单据状态不存在,请联系管理员"
+ )
self.assertEqual(rsp.data["result"], False)
data = {"to_status": 6, "threshold": "1", "threshold_unit": "m"}
diff --git a/itsm/tests/trigger/test_trigger.py b/itsm/tests/trigger/test_trigger.py
index e72b5aed4..a8c005da8 100644
--- a/itsm/tests/trigger/test_trigger.py
+++ b/itsm/tests/trigger/test_trigger.py
@@ -24,6 +24,7 @@
"""
import json
+import mock
from django.test import TestCase, override_settings
@@ -61,7 +62,13 @@ def test_clone(self):
self.assertEqual(rsp.data["result"], False)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_create_or_update_rules(self):
+ @mock.patch("itsm.trigger.permissions.WorkflowTriggerPermit.has_permission")
+ @mock.patch("itsm.trigger.permissions.WorkflowTriggerPermit.has_object_permission")
+ def test_create_or_update_rules(
+ self, patch_has_object_permission, patch_has_permission
+ ):
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
url = "/api/trigger/triggers/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
print(json.loads(rsp.content.decode("utf-8")))
@@ -80,7 +87,14 @@ def test_create_or_update_rules(self):
self.assertEqual(rsp.data["message"], "success")
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_create_or_update_action_schemas(self):
+ @mock.patch("itsm.trigger.permissions.WorkflowTriggerPermit.has_permission")
+ @mock.patch("itsm.trigger.permissions.WorkflowTriggerPermit.has_object_permission")
+ def test_create_or_update_action_schemas(
+ self, patch_has_object_permission, patch_has_permission
+ ):
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
+
url = "/api/trigger/triggers/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
print(json.loads(rsp.content.decode("utf-8")))
diff --git a/itsm/tests/workflow/test_workflow_serializer.py b/itsm/tests/workflow/test_workflow_serializer.py
index fbdcae481..829c5ed47 100644
--- a/itsm/tests/workflow/test_workflow_serializer.py
+++ b/itsm/tests/workflow/test_workflow_serializer.py
@@ -31,9 +31,30 @@
class WorkflowSerializerTest(TestCase):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
+ @mock.patch("itsm.workflow.permissions.WorkflowIamAuth.has_object_permission")
+ @mock.patch(
+ "itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_object_permission"
+ )
+ @mock.patch("itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_permission")
@mock.patch("itsm.workflow.serializers.workflow.transform_single_username")
- def test_serializer(self, transform_single_username):
+ @mock.patch("itsm.trigger.permissions.WorkflowTriggerPermit.has_permission")
+ @mock.patch("itsm.trigger.permissions.WorkflowTriggerPermit.has_object_permission")
+ def test_serializer(
+ self,
+ patch_workflow_trigger_permit_has_object_permission,
+ patch_workflow_trigger_permit_has_permission,
+ transform_single_username,
+ patch_has_permission,
+ patch_base_workflow_element_iam_auth_has_object_permission,
+ patch_workflow_iam_auth_has_object_permission,
+ ):
+ patch_workflow_trigger_permit_has_object_permission.return_value = True
+ patch_workflow_trigger_permit_has_permission.return_value = True
transform_single_username.return_value = "admin(管理员)"
+ patch_has_permission.return_value = True
+ patch_base_workflow_element_iam_auth_has_object_permission.return_value = True
+ patch_workflow_iam_auth_has_object_permission.return_value = True
+
workflow_name = "test_now_{}".format(datetime.now().strftime("%Y%m%d%H%M%S"))
create_data = {
"name": workflow_name,
@@ -196,9 +217,15 @@ def test_serializer(self, transform_single_username):
],
}
]
+
+ list_triggers_url = "/api/trigger/triggers/"
+ list_triggers_rsp = self.client.get(
+ path=list_triggers_url, data=None, content_type="application/json"
+ )
+ print(json.loads(list_triggers_rsp.content.decode("utf-8")))
schemas_url = (
"/api/trigger/triggers/{}/create_or_update_action_schemas/".format(
- workflow_id
+ list_triggers_rsp.data["data"][0]["id"]
)
)
schemas_rsp = self.client.post(
@@ -219,8 +246,8 @@ def test_serializer(self, transform_single_username):
"action_schemas": schemas_rsp.data["data"],
}
]
- rule_url = "/api/trigger/triggers/{}/create_or_update_action_schemas/".format(
- workflow_id
+ rule_url = "/api/trigger/triggers/{}/create_or_update_rules/".format(
+ list_triggers_rsp.data["data"][0]["id"]
)
rule_rsp = self.client.post(
path=rule_url, data=rule_data, content_type="application/json"
diff --git a/itsm/tests/workflow/test_workflow_views.py b/itsm/tests/workflow/test_workflow_views.py
index 3fc6b4cb4..6bb0929bc 100644
--- a/itsm/tests/workflow/test_workflow_views.py
+++ b/itsm/tests/workflow/test_workflow_views.py
@@ -24,6 +24,7 @@
"""
import copy
+import mock
from django.test import TestCase, override_settings
from itsm.tests.data.datas import DATA
@@ -48,7 +49,15 @@ def test_get_regex_choice(self):
self.assertEqual(rsp.data["data"]["regex_choice"], [("EMPTY", "")])
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_variables(self):
+ @mock.patch("itsm.component.utils.misc.transform_single_username")
+ @mock.patch("itsm.component.utils.client_backend_query.get_bk_users")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_variables(
+ self, patch_iam_auth, patch_get_bk_users, patch_transform_single_username
+ ):
+ patch_iam_auth.return_value = True
+ patch_get_bk_users.return_value = {"admin": "admin(admin)"}
+ patch_transform_single_username.return_value = "admin(admin)"
url = "/api/workflow/templates/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/templates/{}/variables/".format(rsp.data["data"][0]["id"])
@@ -58,7 +67,15 @@ def test_variables(self):
self.assertIsInstance(rsp.data["data"], list)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_create_accept_transitions(self):
+ @mock.patch("itsm.component.utils.misc.transform_single_username")
+ @mock.patch("itsm.component.utils.client_backend_query.get_bk_users")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_create_accept_transitions(
+ self, patch_iam_auth, patch_get_bk_users, patch_transform_single_username
+ ):
+ patch_iam_auth.return_value = True
+ patch_get_bk_users.return_value = {"admin": "admin(admin)"}
+ patch_transform_single_username.return_value = "admin(admin)"
url = "/api/workflow/templates/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/templates/{}/create_accept_transitions/".format(
@@ -70,7 +87,15 @@ def test_create_accept_transitions(self):
self.assertIsInstance(rsp.data["data"], list)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_deploy(self):
+ @mock.patch("itsm.component.utils.misc.transform_single_username")
+ @mock.patch("itsm.component.utils.client_backend_query.get_bk_users")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_deploy(
+ self, patch_iam_auth, patch_get_bk_users, patch_transform_single_username
+ ):
+ patch_iam_auth.return_value = True
+ patch_get_bk_users.return_value = {"admin": "admin(admin)"}
+ patch_transform_single_username.return_value = "admin(admin)"
url = "/api/workflow/templates/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/templates/{}/deploy/".format(rsp.data["data"][0]["id"])
@@ -91,7 +116,15 @@ def test_exports(self):
self.assertEqual(rsp.status_code, 200)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_table(self):
+ @mock.patch("itsm.component.utils.misc.transform_single_username")
+ @mock.patch("itsm.component.utils.client_backend_query.get_bk_users")
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_table(
+ self, patch_iam_auth, patch_get_bk_users, patch_transform_single_username
+ ):
+ patch_iam_auth.return_value = True
+ patch_get_bk_users.return_value = {"admin": "admin(admin)"}
+ patch_transform_single_username.return_value = "admin(admin)"
url = "/api/workflow/templates/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/templates/{}/table/".format(rsp.data["data"][0]["id"])
@@ -103,7 +136,17 @@ def test_table(self):
class StateViewTest(TestCase):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_exports(self):
+ @mock.patch("itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_permission")
+ @mock.patch(
+ "itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_exports(
+ self, patch_iam_auth, patch_has_object_permission, patch_has_permission
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
url = "/api/workflow/states/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/states/{}/variables/".format(rsp.data["data"][0]["id"])
@@ -114,7 +157,21 @@ def test_exports(self):
self.assertIsInstance(rsp.data["data"], list)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_group_variables(self):
+ @mock.patch("itsm.workflow.permissions.WorkflowIamAuth.has_object_permission")
+ @mock.patch(
+ "itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_object_permission"
+ )
+ @mock.patch("itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_permission")
+ def test_group_variables(
+ self,
+ patch_has_permission,
+ patch_base_workflow_element_iam_auth_has_object_permission,
+ patch_workflow_iam_auth_has_object_permission,
+ ):
+ patch_has_permission.return_value = True
+ patch_base_workflow_element_iam_auth_has_object_permission.return_value = True
+ patch_workflow_iam_auth_has_object_permission.return_value = True
+
workflow_data = copy.deepcopy(DATA)
workflow, _, _ = Workflow.objects.restore(workflow_data)
version = workflow.create_version()
@@ -141,7 +198,17 @@ def test_group_variables(self):
self.assertIsInstance(rsp.data["data"], list)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_sign_variables(self):
+ @mock.patch("itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_permission")
+ @mock.patch(
+ "itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_sign_variables(
+ self, patch_iam_auth, patch_has_object_permission, patch_has_permission
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
url = "/api/workflow/states/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/states/{}/sign_variables/".format(
@@ -154,7 +221,17 @@ def test_sign_variables(self):
self.assertIsInstance(rsp.data["data"], list)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_pre_states(self):
+ @mock.patch("itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_permission")
+ @mock.patch(
+ "itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_pre_states(
+ self, patch_iam_auth, patch_has_object_permission, patch_has_permission
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
url = "/api/workflow/states/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/states/{}/pre_states/".format(rsp.data["data"][0]["id"])
@@ -165,7 +242,17 @@ def test_pre_states(self):
self.assertIsInstance(rsp.data["data"], list)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_post_states(self):
+ @mock.patch("itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_permission")
+ @mock.patch(
+ "itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_post_states(
+ self, patch_iam_auth, patch_has_object_permission, patch_has_permission
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
url = "/api/workflow/states/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/states/{}/post_states/".format(rsp.data["data"][0]["id"])
@@ -176,7 +263,17 @@ def test_post_states(self):
self.assertIsInstance(rsp.data["data"], list)
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_add_fields_from_table(self):
+ @mock.patch("itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_permission")
+ @mock.patch(
+ "itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_add_fields_from_table(
+ self, patch_iam_auth, patch_has_object_permission, patch_has_permission
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
url = "/api/workflow/states/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
url = "/api/workflow/states/{}/add_fields_from_table/".format(
@@ -192,7 +289,17 @@ def test_add_fields_from_table(self):
self.assertEqual(rsp.data["message"], "success")
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_clone(self):
+ @mock.patch("itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_permission")
+ @mock.patch(
+ "itsm.workflow.permissions.BaseWorkflowElementIamAuth.has_object_permission"
+ )
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_clone(
+ self, patch_iam_auth, patch_has_object_permission, patch_has_permission
+ ):
+ patch_iam_auth.return_value = True
+ patch_has_object_permission.return_value = True
+ patch_has_permission.return_value = True
url1 = "/api/workflow/states/"
rsp1 = self.client.get(path=url1, data=None, content_type="application/json")
url = "/api/workflow/states/{}/clone/".format(rsp1.data["data"][0]["id"])
@@ -270,7 +377,9 @@ def test_post_state(self):
class TaskSchemaViewTest(TestCase):
@override_settings(MIDDLEWARE=("itsm.tests.middlewares.OverrideMiddleware",))
- def test_variables(self):
+ @mock.patch("itsm.component.drf.permissions.IamAuthPermit.iam_auth")
+ def test_variables(self, patch_iam_auth):
+ patch_iam_auth.return_value = True
url = "/api/workflow/task_schemas/"
rsp = self.client.get(path=url, data=None, content_type="application/json")
diff --git a/itsm/ticket/managers.py b/itsm/ticket/managers.py
index ce0db4c4c..a5ead6c4a 100644
--- a/itsm/ticket/managers.py
+++ b/itsm/ticket/managers.py
@@ -36,7 +36,7 @@
from django.db import models, connections, NotSupportedError
from django.db.models import F, Q, QuerySet, AutoField
from django.forms import model_to_dict
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.constants import (
@@ -837,9 +837,9 @@ def create_log(
from_state_id=state_id,
type=operate_type,
operator=log_operator,
- message="%s..." % message[0:500]
- if len(message) > 500
- else message, # 防止消息太长
+ message=(
+ "%s..." % message[0:500] if len(message) > 500 else message
+ ), # 防止消息太长
workflow_id=ticket.flow.id,
processors_type=getattr(status, "processors_type", ""),
processors=getattr(status, "processors", ""),
@@ -855,6 +855,7 @@ def create_log(
)
from itsm.ticket.tasks import ticket_set_history_operators
+
ticket_set_history_operators.delay(ticket.id, log_operator)
return log
@@ -1002,6 +1003,7 @@ def _batched_insert(self, objs, fields, batch_size, ignore_conflicts=False):
max_batch_size = max(ops.bulk_batch_size(fields, objs), 1)
batch_size = min(batch_size, max_batch_size) if batch_size else max_batch_size
inserted_rows = []
+ on_conflict = "DO NOTHING" if ignore_conflicts else None
bulk_return = connections[self.db].features.can_return_rows_from_bulk_insert
for item in [objs[i : i + batch_size] for i in range(0, len(objs), batch_size)]:
if bulk_return and not ignore_conflicts:
@@ -1011,7 +1013,7 @@ def _batched_insert(self, objs, fields, batch_size, ignore_conflicts=False):
fields=fields,
using=self.db,
returning_fields=self.model._meta.db_returning_fields,
- ignore_conflicts=ignore_conflicts,
+ on_conflict=on_conflict,
)
)
else:
@@ -1019,7 +1021,7 @@ def _batched_insert(self, objs, fields, batch_size, ignore_conflicts=False):
item,
fields=fields,
using=self.db,
- ignore_conflicts=ignore_conflicts,
+ on_conflict=on_conflict,
)
return inserted_rows
diff --git a/itsm/ticket/migrations/0037_auto_20200212_1554.py b/itsm/ticket/migrations/0037_auto_20200212_1554.py
index 7e18086cc..66819e96f 100644
--- a/itsm/ticket/migrations/0037_auto_20200212_1554.py
+++ b/itsm/ticket/migrations/0037_auto_20200212_1554.py
@@ -34,94 +34,150 @@
class Migration(migrations.Migration):
dependencies = [
- ('ticket', '0036_auto_20200114_1855'),
+ ("ticket", "0036_auto_20200114_1855"),
]
operations = [
migrations.CreateModel(
- name='SignTask',
+ name="SignTask",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('creator', models.CharField(blank=True, max_length=64, null=True, verbose_name='创建人')),
- ('create_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
- ('update_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
- ('updated_by', models.CharField(blank=True, max_length=64, null=True, verbose_name='修改人')),
- ('end_at', models.DateTimeField(blank=True, null=True, verbose_name='结束时间')),
- ('is_deleted', models.BooleanField(db_index=True, default=False, verbose_name='是否软删除')),
- ('status_id', models.IntegerField(verbose_name='状态ID')),
- ('order', models.IntegerField(default=-1, verbose_name='顺序')),
(
- 'status',
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "creator",
+ models.CharField(
+ blank=True, max_length=64, null=True, verbose_name="创建人"
+ ),
+ ),
+ (
+ "create_at",
+ models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
+ ),
+ (
+ "update_at",
+ models.DateTimeField(auto_now=True, verbose_name="更新时间"),
+ ),
+ (
+ "updated_by",
+ models.CharField(
+ blank=True, max_length=64, null=True, verbose_name="修改人"
+ ),
+ ),
+ (
+ "end_at",
+ models.DateTimeField(
+ blank=True, null=True, verbose_name="结束时间"
+ ),
+ ),
+ (
+ "is_deleted",
+ models.BooleanField(
+ db_index=True, default=False, verbose_name="是否软删除"
+ ),
+ ),
+ ("status_id", models.IntegerField(verbose_name="状态ID")),
+ ("order", models.IntegerField(default=-1, verbose_name="顺序")),
+ (
+ "status",
models.CharField(
- choices=[('WAIT', '未激活'), ('RUNNING', '执行中'), ('FINISHED', '已完成')],
- default='WAIT',
+ choices=[
+ ("WAIT", "未激活"),
+ ("RUNNING", "执行中"),
+ ("FINISHED", "已完成"),
+ ],
+ default="WAIT",
max_length=32,
- verbose_name='任务状态',
+ verbose_name="任务状态",
),
),
- ('processor', models.CharField(max_length=255, verbose_name='处理人')),
- ('is_active', models.BooleanField(default=False, verbose_name='是否激活')),
- ('is_passed', models.NullBooleanField(verbose_name='是否审批通过')),
+ ("processor", models.CharField(max_length=255, verbose_name="处理人")),
+ (
+ "is_active",
+ models.BooleanField(default=False, verbose_name="是否激活"),
+ ),
+ (
+ "is_passed",
+ models.BooleanField(verbose_name="是否审批通过", null=True),
+ ),
+ ],
+ options={
+ "verbose_name": "会签任务",
+ "verbose_name_plural": "会签任务",
+ "ordering": ("-id",),
+ },
+ managers=[
+ ("_objects", django.db.models.manager.Manager()),
],
- options={'verbose_name': '会签任务', 'verbose_name_plural': '会签任务', 'ordering': ('-id',),},
- managers=[('_objects', django.db.models.manager.Manager()),],
),
migrations.CreateModel(
- name='TaskField',
+ name="TaskField",
fields=[
(
- 'ticketfield_ptr',
+ "ticketfield_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
- to='ticket.TicketField',
+ to="ticket.TicketField",
),
),
],
- options={'abstract': False,},
- bases=('ticket.ticketfield',),
- managers=[('_objects', django.db.models.manager.Manager()),],
+ options={
+ "abstract": False,
+ },
+ bases=("ticket.ticketfield",),
+ managers=[
+ ("_objects", django.db.models.manager.Manager()),
+ ],
),
migrations.AddField(
- model_name='status', name='is_sequential', field=models.BooleanField(default=False, verbose_name='是否是串行任务'),
+ model_name="status",
+ name="is_sequential",
+ field=models.BooleanField(default=False, verbose_name="是否是串行任务"),
),
migrations.AddField(
- model_name='status',
- name='type',
+ model_name="status",
+ name="type",
field=models.CharField(
choices=[
- ('START', '开始节点(圆形)'),
- ('NORMAL', '普通节点'),
- ('SIGN', '会签节点'),
- ('TASK', '自动节点'),
- ('TASK-SOPS', '标准运维节点'),
- ('ROUTER', '分支网关节点(菱形)'),
- ('ROUTER-P', '并行网关节点'),
- ('COVERAGE', '汇聚网关节点'),
- ('END', '结束节点(圆形)'),
+ ("START", "开始节点(圆形)"),
+ ("NORMAL", "普通节点"),
+ ("SIGN", "会签节点"),
+ ("TASK", "自动节点"),
+ ("TASK-SOPS", "标准运维节点"),
+ ("ROUTER", "分支网关节点(菱形)"),
+ ("ROUTER-P", "并行网关节点"),
+ ("COVERAGE", "汇聚网关节点"),
+ ("END", "结束节点(圆形)"),
],
- default='NORMAL',
+ default="NORMAL",
max_length=32,
- verbose_name='节点类型',
+ verbose_name="节点类型",
),
),
migrations.AlterField(
- model_name='status',
- name='action_type',
+ model_name="status",
+ name="action_type",
field=models.CharField(
choices=[
- ('TRANSITION', '提交'),
- ('DISTRIBUTE', '分派'),
- ('CLAIM', '认领'),
- ('SIGN', '会签'),
- ('AUTOMATIC', '自动执行'),
+ ("TRANSITION", "提交"),
+ ("DISTRIBUTE", "分派"),
+ ("CLAIM", "认领"),
+ ("SIGN", "会签"),
+ ("AUTOMATIC", "自动执行"),
],
- default='TRANSITION',
+ default="TRANSITION",
max_length=32,
- verbose_name='节点内部操作类型',
+ verbose_name="节点内部操作类型",
),
),
]
diff --git a/itsm/ticket/models/basic.py b/itsm/ticket/models/basic.py
index 649c82471..6e1af76be 100644
--- a/itsm/ticket/models/basic.py
+++ b/itsm/ticket/models/basic.py
@@ -24,7 +24,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import LEN_NORMAL
@@ -32,12 +32,16 @@
class Model(models.Model):
"""基础字段"""
- FIELDS = ('creator', 'create_at', 'updated_by', 'update_at', 'end_at')
+ FIELDS = ("creator", "create_at", "updated_by", "update_at", "end_at")
- creator = models.CharField(_("创建人"), max_length=LEN_NORMAL, null=True, blank=True)
+ creator = models.CharField(
+ _("创建人"), max_length=LEN_NORMAL, null=True, blank=True
+ )
create_at = models.DateTimeField(_("创建时间"), auto_now_add=True)
update_at = models.DateTimeField(_("更新时间"), auto_now=True)
- updated_by = models.CharField(_("修改人"), max_length=LEN_NORMAL, null=True, blank=True)
+ updated_by = models.CharField(
+ _("修改人"), max_length=LEN_NORMAL, null=True, blank=True
+ )
end_at = models.DateTimeField(_("结束时间"), null=True, blank=True)
is_deleted = models.BooleanField(_("是否软删除"), default=False, db_index=True)
diff --git a/itsm/ticket/models/event.py b/itsm/ticket/models/event.py
index d5de91f1e..b83a428e2 100644
--- a/itsm/ticket/models/event.py
+++ b/itsm/ticket/models/event.py
@@ -24,7 +24,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
EMPTY_INT,
@@ -44,33 +44,53 @@
class TicketEventLog(Event):
"""单据操作日志表"""
- SOURCE_TYPE = [(WEB, _('页面操作')), (MOBILE, _('移动端操作')), (SYS, _('系统操作')), (SYSTEM_OPERATE, _('自动执行'))]
+ SOURCE_TYPE = [
+ (WEB, _("页面操作")),
+ (MOBILE, _("移动端操作")),
+ (SYS, _("系统操作")),
+ (SYSTEM_OPERATE, _("自动执行")),
+ ]
# id = models.BigAutoField(u'日志大于2^31-1,默认id无法继续创建')
- ticket = models.ForeignKey('ticket.Ticket', help_text=_('关联工单'), related_name='logs', on_delete=models.CASCADE)
- is_valid = models.BooleanField(_('是否有效流程节点'), default=True)
- deal_time = models.IntegerField(_('处理时间'), default=0)
+ ticket = models.ForeignKey(
+ "ticket.Ticket",
+ help_text=_("关联工单"),
+ related_name="logs",
+ on_delete=models.CASCADE,
+ )
+ is_valid = models.BooleanField(_("是否有效流程节点"), default=True)
+ deal_time = models.IntegerField(_("处理时间"), default=0)
# 日志固化角色人员, 若角色新增人员,无法添加到快照中,需要手动添加
- processors_snap = models.CharField(_('处理人快照'), max_length=LEN_XX_LONG, default=EMPTY_STRING, null=True, blank=True)
- source = models.CharField(_('日志来源'), max_length=LEN_SHORT, choices=SOURCE_TYPE, default=WEB)
- status = models.IntegerField(_('节点处理状态'), default=EMPTY_INT)
+ processors_snap = models.CharField(
+ _("处理人快照"),
+ max_length=LEN_XX_LONG,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
+ )
+ source = models.CharField(
+ _("日志来源"), max_length=LEN_SHORT, choices=SOURCE_TYPE, default=WEB
+ )
+ status = models.IntegerField(_("节点处理状态"), default=EMPTY_INT)
objects = managers.TicketLogManager()
class Meta:
- app_label = 'ticket'
- verbose_name = _('单据流转日志')
- verbose_name_plural = _('单据流转日志')
- ordering = ('id',)
+ app_label = "ticket"
+ verbose_name = _("单据流转日志")
+ verbose_name_plural = _("单据流转日志")
+ ordering = ("id",)
index_together = (("operate_at", "operator", "is_deleted"),)
def __unicode__(self):
- return '{}({})'.format(self.ticket, self.from_state_id)
+ return "{}({})".format(self.ticket, self.from_state_id)
def update_deal_time(self):
log_ids = list(
- TicketEventLog.objects.filter(ticket_id=self.ticket_id).order_by('id').values_list('id', flat=True)
+ TicketEventLog.objects.filter(ticket_id=self.ticket_id)
+ .order_by("id")
+ .values_list("id", flat=True)
)
last_log_index = log_ids.index(self.id) - 1
@@ -99,9 +119,13 @@ def translated_message(self):
@classmethod
def fix_deal_time(cls, *args, **kwargs):
"""为之前的单据添加处理事件"""
- print('\nfix history ticket deal_time')
+ print("\nfix history ticket deal_time")
try:
- for log in TicketEventLog.objects.all().exclude(message__in=['流程开始', '单据流程结束']).exclude(type='UNSUSPEND'):
+ for log in (
+ TicketEventLog.objects.all()
+ .exclude(message__in=["流程开始", "单据流程结束"])
+ .exclude(type="UNSUSPEND")
+ ):
log.update_deal_time()
except Exception as e:
- print('\nfix history ticket deal_time exception: %s' % e)
+ print("\nfix history ticket deal_time exception: %s" % e)
diff --git a/itsm/ticket/models/field.py b/itsm/ticket/models/field.py
index a55e74d16..2bcab7796 100644
--- a/itsm/ticket/models/field.py
+++ b/itsm/ticket/models/field.py
@@ -24,11 +24,16 @@
"""
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import EMPTY_STRING, LEN_SHORT, SHOW_BY_CONDITION
from itsm.component.utils.conversion import format_exp_value, show_conditions_validate
-from itsm.component.utils.misc import get_choice_route, get_field_display_value, get_field_value, set_field_value
+from itsm.component.utils.misc import (
+ get_choice_route,
+ get_field_display_value,
+ get_field_value,
+ set_field_value,
+)
from itsm.workflow.models import BaseField
from . import managers
@@ -37,13 +42,29 @@
class TicketField(BaseField):
"""表单字段值表"""
- SOURCE = [('CUSTOM', '自定义添加'), ('TABLE', '基础模型添加')]
+ SOURCE = [("CUSTOM", "自定义添加"), ("TABLE", "基础模型添加")]
- ticket = models.ForeignKey('ticket.Ticket', help_text=_("关联工单"), related_name="fields", on_delete=models.CASCADE)
- state_id = models.CharField("对应的状态id", max_length=LEN_SHORT, default=EMPTY_STRING, null=True, blank=True)
+ ticket = models.ForeignKey(
+ "ticket.Ticket",
+ help_text=_("关联工单"),
+ related_name="fields",
+ on_delete=models.CASCADE,
+ )
+ state_id = models.CharField(
+ "对应的状态id",
+ max_length=LEN_SHORT,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
+ )
_value = models.TextField(_("表单值"), null=True, blank=True)
- source = models.CharField(_('添加方式'), max_length=LEN_SHORT, choices=SOURCE, default='CUSTOM')
- workflow_field_id = models.IntegerField(_('流程版本字段ID'), default=-1,)
+ source = models.CharField(
+ _("添加方式"), max_length=LEN_SHORT, choices=SOURCE, default="CUSTOM"
+ )
+ workflow_field_id = models.IntegerField(
+ _("流程版本字段ID"),
+ default=-1,
+ )
objects = managers.TicketFieldManager()
@@ -79,23 +100,25 @@ def value(self, v):
def _display_value(self):
"""用于获取日志接口的数据展示"""
if not self._value:
- return ''
+ return ""
- if self.type in ['SELECT', 'RADIO']:
- return {str(choice['key']): choice['name'] for choice in self.choice}.get(self._value, self._value)
+ if self.type in ["SELECT", "RADIO"]:
+ return {str(choice["key"]): choice["name"] for choice in self.choice}.get(
+ self._value, self._value
+ )
- if self.type in ['MULTISELECT', 'CHECKBOX', 'MEMBERS']:
- choice = {str(choice['key']): choice['name'] for choice in self.choice}
- return ','.join([choice.get(key, key) for key in self._value.split(',')])
+ if self.type in ["MULTISELECT", "CHECKBOX", "MEMBERS"]:
+ choice = {str(choice["key"]): choice["name"] for choice in self.choice}
+ return ",".join([choice.get(key, key) for key in self._value.split(",")])
- if self.type == 'TREESELECT':
+ if self.type == "TREESELECT":
route = get_choice_route(self.choice, self._value)
- return '->'.join([item['name'] for item in route]) or self._value
+ return "->".join([item["name"] for item in route]) or self._value
- if self.type == 'TABLE':
- return {'header': self.choice, 'value': self.value}
- if self.type == 'CUSTOMTABLE':
- return {'header': self.meta, 'value': self.value}
+ if self.type == "TABLE":
+ return {"header": self.choice, "value": self.value}
+ if self.type == "CUSTOMTABLE":
+ return {"header": self.meta, "value": self.value}
return self._value
@@ -112,8 +135,9 @@ def show_result(self, show_all_fields):
if self.show_type == SHOW_BY_CONDITION:
key_value = {
- 'params_%s' % item['key']: format_exp_value(item['type'], item['_value'])
- for item in self.ticket.fields.values('key', '_value', 'type')
+ "params_%s"
+ % item["key"]: format_exp_value(item["type"], item["_value"])
+ for item in self.ticket.fields.values("key", "_value", "type")
}
if show_conditions_validate(self.show_conditions, key_value):
return False
@@ -126,10 +150,21 @@ class TaskField(BaseField):
SOURCE = [("CUSTOM", "自定义添加"), ("TABLE", "基础模型添加")]
- state_id = models.CharField("对应的状态id", max_length=LEN_SHORT, default=EMPTY_STRING, null=True, blank=True)
+ state_id = models.CharField(
+ "对应的状态id",
+ max_length=LEN_SHORT,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
+ )
_value = models.TextField(_("表单值"), null=True, blank=True)
- source = models.CharField(_("添加方式"), max_length=LEN_SHORT, choices=SOURCE, default="CUSTOM")
- workflow_field_id = models.IntegerField(_("流程版本字段ID"), default=-1,)
+ source = models.CharField(
+ _("添加方式"), max_length=LEN_SHORT, choices=SOURCE, default="CUSTOM"
+ )
+ workflow_field_id = models.IntegerField(
+ _("流程版本字段ID"),
+ default=-1,
+ )
task_id = models.IntegerField(_("任务ID"), default=-1)
class Meta:
diff --git a/itsm/ticket/models/misc.py b/itsm/ticket/models/misc.py
index 4ad2fcdee..c678fedcd 100644
--- a/itsm/ticket/models/misc.py
+++ b/itsm/ticket/models/misc.py
@@ -30,7 +30,7 @@
from django.conf import settings
from django.db import models, transaction
from django.utils.crypto import get_random_string
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mptt.fields import TreeForeignKey
from itsm.component.constants import (
@@ -53,7 +53,9 @@ class TicketTemplate(models.Model):
name = models.CharField(_("模板名称"), max_length=LEN_NORMAL)
creator = models.CharField(_("创建人"), max_length=LEN_NORMAL)
- service = models.CharField(_("对应服务主键"), default=EMPTY_STRING, max_length=LEN_NORMAL)
+ service = models.CharField(
+ _("对应服务主键"), default=EMPTY_STRING, max_length=LEN_NORMAL
+ )
template = jsonfield.JSONField(
_("单据模板字段"), default=EMPTY_LIST, null=True, blank=True
)
@@ -103,11 +105,15 @@ class TicketComment(models.Model):
on_delete=models.CASCADE,
)
stars = models.IntegerField("评价等级1~5,5星为最好", default=0)
- comments = models.CharField(_("评价信息"), max_length=LEN_LONG, null=True, blank=True)
+ comments = models.CharField(
+ _("评价信息"), max_length=LEN_LONG, null=True, blank=True
+ )
source = models.CharField(
_("评价来源"), choices=SOURCE_CHOICE, default="SYS", max_length=LEN_NORMAL
)
- creator = models.CharField(_("创建人"), max_length=LEN_NORMAL, null=True, blank=True)
+ creator = models.CharField(
+ _("创建人"), max_length=LEN_NORMAL, null=True, blank=True
+ )
create_at = models.DateTimeField(_("创建时间"), auto_now_add=True)
update_at = models.DateTimeField(_("更新时间"), auto_now=True)
is_deleted = models.BooleanField(_("是否软删除"), default=False)
@@ -222,13 +228,19 @@ class NotifyLogModel(models.Model):
state_id = models.IntegerField(_("发送节点ID"), default=EMPTY_INT)
state_name = models.CharField(
- _("节点名称"), max_length=LEN_NORMAL, default=EMPTY_STRING, null=True, blank=True
+ _("节点名称"),
+ max_length=LEN_NORMAL,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
creator = models.CharField(
_("创建人"), max_length=LEN_NORMAL, default=EMPTY_STRING, null=True, blank=True
)
create_at = models.DateTimeField(_("创建时间"), auto_now_add=True)
- message = models.TextField(_("通知信息"), default=EMPTY_STRING, null=True, blank=True)
+ message = models.TextField(
+ _("通知信息"), default=EMPTY_STRING, null=True, blank=True
+ )
notify_type = models.CharField(_("通知方式"), max_length=LEN_SHORT, default="EMAIL")
is_deleted = models.BooleanField(_("是否软删除"), default=False, db_index=True)
@@ -316,7 +328,11 @@ class TicketSuperviseNotifyLog(NotifyLogModel):
on_delete=models.CASCADE,
)
supervised = models.CharField(
- _("被督办的人"), max_length=LEN_LONG, default=EMPTY_STRING, null=True, blank=True
+ _("被督办的人"),
+ max_length=LEN_LONG,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
class Meta:
@@ -380,9 +396,13 @@ class TicketRemark(BaseMpttModel):
]
key = models.CharField(_("目录关键字"), max_length=LEN_LONG, unique=True)
- content = models.TextField(_("评论内容"), max_length=LEN_LONG, null=True, blank=True)
+ content = models.TextField(
+ _("评论内容"), max_length=LEN_LONG, null=True, blank=True
+ )
order = models.IntegerField(_("节点顺序"), default=FIRST_ORDER)
- remark_type = models.CharField(_("评论类型"), max_length=LEN_SHORT, choices=REMARK_TYPE)
+ remark_type = models.CharField(
+ _("评论类型"), max_length=LEN_SHORT, choices=REMARK_TYPE
+ )
parent = TreeForeignKey(
"self",
on_delete=models.CASCADE,
@@ -391,11 +411,9 @@ class TicketRemark(BaseMpttModel):
blank=True,
related_name="children",
)
- ticket_id = models.IntegerField(
- _("单据id"), max_length=LEN_SHORT, null=False, default=0
- )
- users = models.JSONField(_("用户@的用户列表"), default=[])
- update_log = models.JSONField(_("用户评论的更新记录"), default=[])
+ ticket_id = models.IntegerField(_("单据id"), null=False, default=0)
+ users = models.JSONField(_("用户@的用户列表"), default=list)
+ update_log = models.JSONField(_("用户评论的更新记录"), default=list)
class Meta:
app_label = "ticket"
diff --git a/itsm/ticket/models/ticket.py b/itsm/ticket/models/ticket.py
index 8704307b9..3535d7b17 100644
--- a/itsm/ticket/models/ticket.py
+++ b/itsm/ticket/models/ticket.py
@@ -144,7 +144,8 @@
BK_PLUGIN_STATE,
SUSPENDED,
SHOW_BY_CONDITION,
- VARIABLE_LEADER, FIELD_IGNORE_ESCAPE,
+ VARIABLE_LEADER,
+ FIELD_IGNORE_ESCAPE,
)
from itsm.component.constants.trigger import (
CREATE_TICKET,
@@ -248,7 +249,7 @@ class SignTask(Model):
_("任务状态"), max_length=LEN_SHORT, choices=TASK_STATUS_CHOICES, default="WAIT"
)
processor = models.CharField(_("处理人"), max_length=LEN_LONG)
- is_passed = models.NullBooleanField(_("是否审批通过"), null=True)
+ is_passed = models.BooleanField(_("是否审批通过"), null=True)
objects = managers.SignTaskManager()
@@ -309,22 +310,37 @@ class Status(Model):
)
# 当前环节处理人
processors_type = models.CharField(
- _("处理人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("处理人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
processors = models.CharField(
- _("处理人列表"), max_length=LEN_XX_LONG, default=EMPTY_STRING, null=True, blank=True
+ _("处理人列表"),
+ max_length=LEN_XX_LONG,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
# 被转单人
delivers_type = models.CharField(
- _("转单人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("转单人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
+ )
+ delivers = models.TextField(
+ _("转单人列表"), default=EMPTY_STRING, null=True, blank=True
)
- delivers = models.TextField(_("转单人列表"), default=EMPTY_STRING, null=True, blank=True)
can_deliver = models.BooleanField(_("能否转单"), default=False)
# 被分派人
# TODO assignors_type/assignors是被分派人
assignors_type = models.CharField(
- _("派单人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("派单人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
assignors = models.TextField(
_("派单人列表"), default=EMPTY_STRING, null=True, blank=True
@@ -655,8 +671,8 @@ def log_detail(self, processors_type, processors):
[
_(role.name)
for role in UserRole.objects.filter(
- id__in=processors.split(",")
- )
+ id__in=processors.split(",")
+ )
]
),
)
@@ -1326,7 +1342,10 @@ def __init__(self, *args, **kwargs):
is_supervise_needed = models.BooleanField(_("是否需要督办"), default=False)
supervise_type = models.CharField(
- _("督办人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("督办人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
supervisor = models.CharField(
_("督办列表"), max_length=LEN_LONG, default=EMPTY_STRING, null=True, blank=True
@@ -1341,7 +1360,9 @@ def __init__(self, *args, **kwargs):
# Deprecated Fields
# 针对节点的字段需要迁移到新的表中
- current_state_id = models.CharField(_("当前状态ID"), null=True, max_length=LEN_NORMAL)
+ current_state_id = models.CharField(
+ _("当前状态ID"), null=True, max_length=LEN_NORMAL
+ )
current_assignor = models.CharField(
_("分派人列表"), max_length=LEN_LONG, default=EMPTY_STRING
)
@@ -1349,17 +1370,31 @@ def __init__(self, *args, **kwargs):
_("处理者列表"), max_length=LEN_LONG, default=EMPTY_STRING
)
current_assignor_type = models.CharField(
- _("分派人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("分派人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
current_processors_type = models.CharField(
- _("处理者类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("处理者类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
- updated_by = models.CharField(_("修改人"), default=EMPTY_STRING, max_length=LEN_LONG)
+ updated_by = models.CharField(
+ _("修改人"), default=EMPTY_STRING, max_length=LEN_LONG
+ )
- service = models.CharField(_("对应服务主键"), default="custom", max_length=LEN_NORMAL)
+ service = models.CharField(
+ _("对应服务主键"), default="custom", max_length=LEN_NORMAL
+ )
service_property = jsonfield.JSONCharField(
- _("业务特性json字段"), max_length=LEN_LONG, default=EMPTY_DICT, null=True, blank=True
+ _("业务特性json字段"),
+ max_length=LEN_LONG,
+ default=EMPTY_DICT,
+ null=True,
+ blank=True,
)
workflow_snap_id = models.IntegerField(_("对应的快照信息"), default=0)
"""
@@ -1916,8 +1951,8 @@ def is_running(self):
return (
self.current_status
in TicketStatus.objects.filter(
- service_type=self.service_type, is_over=False
- ).values_list("key", flat=True)
+ service_type=self.service_type, is_over=False
+ ).values_list("key", flat=True)
and self.current_status != SUSPEND
)
@@ -2257,8 +2292,8 @@ def has_perm(self, username):
[
status.can_operate(username)
for status in self.node_status.filter(
- status__in=Status.CAN_OPERATE_STATUS
- )
+ status__in=Status.CAN_OPERATE_STATUS
+ )
]
)
@@ -2274,8 +2309,8 @@ def can_view(self, username):
or username in self.task_operators
or self.can_operate(username)
or AttentionUsers.objects.filter(
- ticket_id=self.id, follower=username
- ).exists()
+ ticket_id=self.id, follower=username
+ ).exists()
):
# 与单据操作相关的人,都是可以查看的
return True
@@ -2337,10 +2372,10 @@ def can_close(self, username):
if (
self.is_over
or not StatusTransit.objects.filter(
- service_type=self.service_type,
- from_status__key=self.current_status,
- to_status__is_over=True,
- ).exists()
+ service_type=self.service_type,
+ from_status__key=self.current_status,
+ to_status__is_over=True,
+ ).exists()
):
# 当前状态无法到达关闭的时候,不可以进行关闭操作按钮
return False
@@ -2381,7 +2416,9 @@ def update_priority(self, urgency=None, impact=None):
impact = self.fields.get(key=FIELD_PY_IMPACT, source=BASE_MODEL).value
except TicketField.DoesNotExist as error:
- logger.warning("当前单据不包含影响范围的字段, error is {}".format(error))
+ logger.warning(
+ "当前单据不包含影响范围的字段, error is {}".format(error)
+ )
return {}
if not urgency:
@@ -2411,7 +2448,9 @@ def update_priority(self, urgency=None, impact=None):
sla_instance = Sla.objects.get(id=sla_id)
except Sla.DoesNotExist as error:
logger.warning(
- "Failed to get sla_instance from Sla, error is {}".format(error)
+ "Failed to get sla_instance from Sla, error is {}".format(
+ error
+ )
)
return {}
default_priority = sla_instance.get_default_policy()
@@ -3086,7 +3125,10 @@ def fill_state_fields(self, fields):
filter_field_query_set = self.fields.filter(key__in=fields_map.keys())
for ticket_field in filter_field_query_set:
ticket_field.value = fields_map[ticket_field.key]["value"]
- if isinstance(ticket_field.value, str) and ticket_field.type not in FIELD_IGNORE_ESCAPE:
+ if (
+ isinstance(ticket_field.value, str)
+ and ticket_field.type not in FIELD_IGNORE_ESCAPE
+ ):
need_escape = True
try:
json.loads(ticket_field.value)
@@ -3182,7 +3224,7 @@ def _formatted(pros_type, pros, ticket):
for user in f_value.split(","):
# 历史数据中多选人员选择字段存入了中文名: miya(miya),暂时兼容
- username = user[0: user.find("(")] if "(" in user else user
+ username = user[0 : user.find("(")] if "(" in user else user
var_pros = "{},{}".format(var_pros, username)
# 取到第一个处理人则停止解析
@@ -3260,13 +3302,13 @@ def _formatted(pros_type, pros, ticket):
action_type = (
SYSTEM_OPERATE
if state.type
- in [
- TASK_STATE,
- TASK_SOPS_STATE,
- TASK_DEVOPS_STATE,
- WEBHOOK_STATE,
- BK_PLUGIN_STATE,
- ]
+ in [
+ TASK_STATE,
+ TASK_SOPS_STATE,
+ TASK_DEVOPS_STATE,
+ WEBHOOK_STATE,
+ BK_PLUGIN_STATE,
+ ]
else TRANSITION_OPERATE
)
@@ -3907,6 +3949,7 @@ def do_in_sign_state(self, node_status, fields, operator, source):
# Update ticket priority, processors, history operators
self.update_priority()
from itsm.ticket.tasks import ticket_set_history_operators
+
ticket_set_history_operators.delay(self.id, operator)
# Update sla task
@@ -4017,7 +4060,10 @@ def get_list_view(self):
reason = self.get_field_value("reason", None)
if reason is None:
list_view.append(
- {"key": "提单时间", "value": self.create_at.strftime("%Y-%m-%d %H:%M:%S")}
+ {
+ "key": "提单时间",
+ "value": self.create_at.strftime("%Y-%m-%d %H:%M:%S"),
+ }
)
else:
list_view.append({"key": "申请理由", "value": reason})
@@ -4146,14 +4192,20 @@ def send_trigger_signal(self, signal, sender=None, context=None):
rule_source_type=SOURCE_TICKET,
)
logger.info(
- "[ticket->send_trigger_signal] 触发器发送发生成功, ticket_id={}".format(self.id)
+ "[ticket->send_trigger_signal] 触发器发送发生成功, ticket_id={}".format(
+ self.id
+ )
)
except BaseException:
logger.info(
- "[ticket->send_trigger_signal] 触发器事件发送失败, ticket_id={}".format(self.id)
+ "[ticket->send_trigger_signal] 触发器事件发送失败, ticket_id={}".format(
+ self.id
+ )
)
logger.exception(
- _("触发器事件发送失败, ticket_sn {} signal :{}").format(self.sn, signal)
+ _("触发器事件发送失败, ticket_sn {} signal :{}").format(
+ self.sn, signal
+ )
)
def create_ticket_relation(self, from_ticket_id):
@@ -4368,7 +4420,9 @@ def update_status(self, state_id):
def terminate(self, state_id, operator="", terminate_message="--"):
"""终止单据"""
node_status = self.status(state_id)
- message = _("{operator}处理节点【{name}】(流程被终止,【终止原因】:{detail_message}).")
+ message = _(
+ "{operator}处理节点【{name}】(流程被终止,【终止原因】:{detail_message})."
+ )
# 创建流转日志
with transaction.atomic():
# 撤销流程
@@ -4424,7 +4478,11 @@ def terminate(self, state_id, operator="", terminate_message="--"):
)
self.stop_all_sla()
- return {"result": True, "message": _("流程终止成功:%s") % res.message, "code": 0}
+ return {
+ "result": True,
+ "message": _("流程终止成功:%s") % res.message,
+ "code": 0,
+ }
def suspend(self, suspend_message, operator="system"):
"""挂起"""
diff --git a/itsm/ticket/permissions.py b/itsm/ticket/permissions.py
index 4adf1eb8b..859a319f8 100644
--- a/itsm/ticket/permissions.py
+++ b/itsm/ticket/permissions.py
@@ -25,7 +25,7 @@
import operator
from functools import reduce
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django.conf import settings
from rest_framework import permissions
from rest_framework.serializers import ValidationError
@@ -62,16 +62,16 @@ class TicketPermissionValidate(permissions.BasePermission):
def __init__(self):
self.message = _("抱歉,您无权查看该单据")
-
+
def has_permission(self, request, view):
"""工单创建权限"""
if view.action != "create":
return True
-
+
service_id = request.data.get("service_id")
if not service_id:
return False
-
+
queryset = Service.objects.filter(pk=service_id, is_valid=True)
conditions = Service.permission_filter(request.user.username)
queryset = queryset.filter(reduce(operator.or_, conditions))
@@ -110,7 +110,9 @@ def has_object_permission(self, request, view, obj):
if view.action == "exception_distribute":
if not iam_ticket_manage_auth:
- self.message = _("抱歉,您无权执行此操作,因为您该服务没有工单管理的权限")
+ self.message = _(
+ "抱歉,您无权执行此操作,因为您该服务没有工单管理的权限"
+ )
return False
else:
return True
@@ -204,9 +206,9 @@ def iam_ticket_view_auth(self, request, obj):
str(resource["resource_id"]),
{
"iam_resource_owner": resource.get("creator", ""),
- "_bk_iam_path_": bk_iam_path
- if resource["resource_type"] != "project"
- else "",
+ "_bk_iam_path_": (
+ bk_iam_path if resource["resource_type"] != "project" else ""
+ ),
"name": resource.get("resource_name", ""),
},
)
@@ -337,7 +339,7 @@ def has_object_permission(self, request, view, obj):
if UserRole.is_itsm_superuser(username):
return True
-
+
if username == obj.creator:
return True
diff --git a/itsm/ticket/serializers/event.py b/itsm/ticket/serializers/event.py
index 61e070dde..e02b52e55 100644
--- a/itsm/ticket/serializers/event.py
+++ b/itsm/ticket/serializers/event.py
@@ -24,7 +24,7 @@
"""
from django.contrib.auth import get_user_model
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import empty
@@ -43,21 +43,21 @@ class EventSerializer(serializers.ModelSerializer):
class Meta:
model = TicketEventLog
fields = (
- 'id',
- 'ticket',
- 'type',
- 'operator',
- 'operate_at',
- 'deal_time',
- 'processors_type',
- 'processors',
- 'message',
- 'detail_message',
- 'action',
- 'from_state_name',
- 'ticket_id',
- 'form_data',
- 'from_state_id',
+ "id",
+ "ticket",
+ "type",
+ "operator",
+ "operate_at",
+ "deal_time",
+ "processors_type",
+ "processors",
+ "message",
+ "detail_message",
+ "action",
+ "from_state_name",
+ "ticket_id",
+ "form_data",
+ "from_state_id",
)
def __init__(self, instance=None, data=empty, **kwargs):
@@ -72,27 +72,42 @@ def get_related_users(self):
"""
logs = (
- [self.instance] if isinstance(self.instance,
- TicketEventLog) else [] if self.instance is None else self.instance
+ [self.instance]
+ if isinstance(self.instance, TicketEventLog)
+ else [] if self.instance is None else self.instance
)
all_related_users = [inst.operator for inst in logs if inst.operator]
- return get_bk_users(format='dict', users=list(set(all_related_users)))
+ return get_bk_users(format="dict", users=list(set(all_related_users)))
def to_representation(self, instance):
data = super(EventSerializer, self).to_representation(instance)
- data['message'] = translate(instance.message, data, related_operators=self.related_users)
- data['operator'] = self.related_users.get(instance.operator)
+ data["message"] = translate(
+ instance.message, data, related_operators=self.related_users
+ )
+ data["operator"] = self.related_users.get(instance.operator)
form_data = []
- origin_form_data = data['form_data'].values() if isinstance(data['form_data'], dict) else data['form_data']
+ origin_form_data = (
+ data["form_data"].values()
+ if isinstance(data["form_data"], dict)
+ else data["form_data"]
+ )
for item in origin_form_data:
- if not item.get('show_result'):
+ if not item.get("show_result"):
continue
value_status = item.get("value_status")
if value_status:
- item.update({"name": _("{}(修改前)" if value_status == 'before' else "{}(修改后)").format(item['name'])})
+ item.update(
+ {
+ "name": _(
+ "{}(修改前)" if value_status == "before" else "{}(修改后)"
+ ).format(item["name"])
+ }
+ )
form_data.append(item)
- data['form_data'] = form_data
- node_status = Status.objects.filter(ticket_id=instance.ticket_id, state_id=instance.from_state_id).first()
- data['from_state_type'] = getattr(node_status, "type", "")
+ data["form_data"] = form_data
+ node_status = Status.objects.filter(
+ ticket_id=instance.ticket_id, state_id=instance.from_state_id
+ ).first()
+ data["from_state_type"] = getattr(node_status, "type", "")
return data
diff --git a/itsm/ticket/serializers/misc.py b/itsm/ticket/serializers/misc.py
index 3b802e868..60bcf89d4 100644
--- a/itsm/ticket/serializers/misc.py
+++ b/itsm/ticket/serializers/misc.py
@@ -22,7 +22,7 @@
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from common.utils import texteditor_escape
@@ -62,7 +62,11 @@ def validate(self, attrs):
raise serializers.ValidationError(_("该模板名字已存在"))
if self.context["view"].action == "update":
if (
- TicketTemplate.objects.filter(name=attrs.get("name"), creator=creator, service=attrs.get("service"))
+ TicketTemplate.objects.filter(
+ name=attrs.get("name"),
+ creator=creator,
+ service=attrs.get("service"),
+ )
.exclude(id=pk)
.exists()
):
@@ -86,13 +90,17 @@ def validate(self, attrs):
creator = self.context["request"].user.username
if self.context["view"].action == "create":
if TicketStateDraft.objects.filter(
- ticket_id=attrs.get("ticket_id"), creator=creator, state_id=attrs.get("state_id")
+ ticket_id=attrs.get("ticket_id"),
+ creator=creator,
+ state_id=attrs.get("state_id"),
).exists():
raise serializers.ValidationError(_("该模板名字已存在"))
if self.context["view"].action == "update":
if (
TicketStateDraft.objects.filter(
- ticket_id=attrs.get("ticket_id"), creator=creator, state_id=attrs.get("state_id")
+ ticket_id=attrs.get("ticket_id"),
+ creator=creator,
+ state_id=attrs.get("state_id"),
)
.exclude(id=self.context["view"].kwargs.get("pk"))
.exists()
@@ -106,7 +114,9 @@ class CommentSerializer(serializers.ModelSerializer):
"""工单评价序列化"""
stars = serializers.IntegerField(required=True, max_value=6, min_value=1)
- comments = serializers.CharField(required=False, max_length=LEN_LONG, allow_null=True, allow_blank=True)
+ comments = serializers.CharField(
+ required=False, max_length=LEN_LONG, allow_null=True, allow_blank=True
+ )
class Meta:
model = TicketComment
@@ -125,7 +135,7 @@ class Meta:
def update(self, instance, validated_data):
if instance.stars != 0:
raise serializers.ValidationError(_("该单据已经被评论,请勿重复评论"))
-
+
validated_data["comments"] = texteditor_escape(
validated_data["comments"], is_support_img=False
)
@@ -133,8 +143,12 @@ def update(self, instance, validated_data):
def to_representation(self, instance):
data = super(CommentSerializer, self).to_representation(instance)
- data["has_invited"] = ",".join(instance.invite.all().values_list("receiver", flat=True))
- data["creator"] = get_bk_users(format="dict", users=[data["creator"]]).get(data["creator"], data["creator"])
+ data["has_invited"] = ",".join(
+ instance.invite.all().values_list("receiver", flat=True)
+ )
+ data["creator"] = get_bk_users(format="dict", users=[data["creator"]]).get(
+ data["creator"], data["creator"]
+ )
return data
@@ -165,7 +179,9 @@ class Meta:
def to_representation(self, instance):
data = super(FollowerNotifyLogSerializer, self).to_representation(instance)
- data["creator_zh"] = get_bk_users(format="dict", users=[instance.creator]).get(instance.creator, "")
+ data["creator_zh"] = get_bk_users(format="dict", users=[instance.creator]).get(
+ instance.creator, ""
+ )
data["create_at"] = "{}".format(get_time(instance.create_at))
data["group"] = transform_username(
UserRole.get_users_by_type(
diff --git a/itsm/ticket/serializers/ticket.py b/itsm/ticket/serializers/ticket.py
index a8f0f60f0..195de5e54 100644
--- a/itsm/ticket/serializers/ticket.py
+++ b/itsm/ticket/serializers/ticket.py
@@ -28,7 +28,7 @@
from datetime import datetime
from django.contrib.auth import get_user_model
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import JSONField, empty
@@ -114,7 +114,11 @@
TaskFieldSerializer,
)
from itsm.ticket.tasks import remark_notify
-from itsm.ticket.utils import compute_list_difference, get_user_profile, filter_sensitive_info
+from itsm.ticket.utils import (
+ compute_list_difference,
+ get_user_profile,
+ filter_sensitive_info,
+)
from itsm.ticket.validators import CreateTicketValidator, StateOperateValidator
from itsm.ticket_status.models import TicketStatus
from itsm.workflow.models import WorkflowVersion
@@ -374,9 +378,11 @@ def to_representation(self, inst):
sla_timeout=sla_task_info["sla_timeout"],
sla_status=sla_task_info["sla_status"],
sla_task_status=sla_task_info["task_status"],
- sla_deadline=sla_task_info["deadline"].strftime("%Y-%m-%d %H:%M:%S")
- if sla_task_info["deadline"]
- else "--",
+ sla_deadline=(
+ sla_task_info["deadline"].strftime("%Y-%m-%d %H:%M:%S")
+ if sla_task_info["deadline"]
+ else "--"
+ ),
)
return data
@@ -691,9 +697,11 @@ def to_client_representation(self):
"name", "--"
),
current_steps=steps.get(real_ticket["id"], []),
- priority_name=inst["meta"]["priority"]["name"]
- if "priority" in inst["meta"]
- else "--",
+ priority_name=(
+ inst["meta"]["priority"]["name"]
+ if "priority" in inst["meta"]
+ else "--"
+ ),
create_at=inst["create_at"].strftime("%Y-%m-%d %H:%M:%S"),
current_processors=inst.get("current_processors", ""),
can_comment=self.can_comment(inst, comments, is_email_invite_token),
@@ -797,9 +805,7 @@ def get_related_users(self):
tickets = (
[self.instance]
if isinstance(self.instance, Ticket)
- else []
- if self.instance is None
- else self.instance
+ else [] if self.instance is None else self.instance
)
for inst in tickets:
@@ -811,9 +817,7 @@ def get_attention_users(self):
tickets = (
[self.instance]
if isinstance(self.instance, Ticket)
- else []
- if self.instance is None
- else self.instance
+ else [] if self.instance is None else self.instance
)
ticket_ids = [ticket.id for ticket in tickets]
followers = AttentionUsers.objects.filter(ticket_id__in=ticket_ids).values(
@@ -874,7 +878,7 @@ def to_representation(self, inst):
# 当前步骤、单据状态、优先级来源母单
master_ticket = inst.get_master_ticket()
master_or_self_ticket = master_ticket if master_ticket else inst
-
+
meta = master_or_self_ticket.get_meta()
data.update(
@@ -882,7 +886,7 @@ def to_representation(self, inst):
current_status_display=master_or_self_ticket.current_status_display,
current_steps=master_or_self_ticket.brief_current_steps,
priority_name=master_or_self_ticket.priority_name,
- meta=meta
+ meta=meta,
)
can_comment = inst.can_comment(username) or is_email_invite_token
@@ -956,7 +960,9 @@ def run_validation(self, data):
service_type=service.key, is_start=True
).key
except TicketStatus.DoesNotExist:
- raise serializers.ValidationError({_("工单状态"): _("工单状态不存在,请检查")})
+ raise serializers.ValidationError(
+ {_("工单状态"): _("工单状态不存在,请检查")}
+ )
state_processors = data.get("meta", {}).get("state_processors", {})
for state_id, state_processor in state_processors.items():
@@ -1185,7 +1191,9 @@ def run_validation(self, data=empty):
)
if ticket_to_ticket.related_status == "RUNNING":
- raise serializers.ValidationError({_("母子单"): _("正在解绑中... 请勿重复执行")})
+ raise serializers.ValidationError(
+ {_("母子单"): _("正在解绑中... 请勿重复执行")}
+ )
return value
@@ -1469,11 +1477,11 @@ def to_representation(self, instance):
"current_status": instance.get_current_status_display(),
"current_steps": instance.get_current_state_name(),
"current_processors_type": instance.get_current_role_display(),
- "current_processors": transform_username(
- instance.get_current_processors()
- )
- if instance.get_current_processors()
- else "--",
+ "current_processors": (
+ transform_username(instance.get_current_processors())
+ if instance.get_current_processors()
+ else "--"
+ ),
}
)
diff --git a/itsm/ticket/tasks.py b/itsm/ticket/tasks.py
index 7693fb3fa..a2442761b 100644
--- a/itsm/ticket/tasks.py
+++ b/itsm/ticket/tasks.py
@@ -31,12 +31,12 @@
from django.conf import settings
from django.contrib.auth import get_user_model
-from celery import Task
+from celery import Task, shared_task
from celery.schedules import crontab
-from celery.task import periodic_task, task
+from blueapps.contrib.celery_tools.periodic import periodic_task
from django.db.models import Q
from django.db import connection, transaction
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from common.mymako import render_mako_tostring
@@ -78,7 +78,9 @@ def auto_comment():
logger.info(_("添加默认评价"))
TicketComment.objects.filter(
stars=0, create_at__lte=now - timedelta(days=AUTO_COMMENT_DAYS)
- ).update(**{"stars": 4, "comments": "系统默认评价:满意!", "creator": "系统评价"})
+ ).update(
+ **{"stars": 4, "comments": "系统默认评价:满意!", "creator": "系统评价"}
+ )
@periodic_task(run_every=crontab(hour=0, minute=0, day_of_week=1))
@@ -185,14 +187,16 @@ def weekly_statical():
message = render_mako_tostring("weekly_statical_report.html", locals())
- notifier = EmailNotifier(title="流程服务统计周报", receivers=receivers, message=message)
+ notifier = EmailNotifier(
+ title="流程服务统计周报", receivers=receivers, message=message
+ )
try:
notifier.send()
except ComponentCallError as error:
logger.info("统计日报发送失败, 组件错误: %s" % str(error))
-@task
+@shared_task
def start_pipeline(ticket, **kwargs):
ticket.start(**kwargs)
@@ -225,7 +229,7 @@ def on_failure(self, exc, task_id, args, kwargs, einfo):
).update(related_status="UNBIND_FAILED")
-@task(base=ClonePipelineCallback)
+@shared_task(base=ClonePipelineCallback)
def clone_pipeline(ticket, parent_ticket):
ticket.clone_pipeline(parent_ticket)
@@ -248,7 +252,7 @@ def dispatch_retry_notify_event(ticket, state_id, receivers):
retry_notify.apply_async(args=[ticket, state_id, receivers], countdown=countdown)
-@task
+@shared_task
def retry_notify(ticket, state_id, receivers):
# 每次发通知前查看一下当前单据的最新状态,并更新之后的单据对象
ticket.refresh_from_db()
@@ -257,7 +261,9 @@ def retry_notify(ticket, state_id, receivers):
# 需要停止发送消息的状态列表
status = ["REVOKED", "TERMINATED", "FINISHED"]
if ticket.current_status in status or not ticket.is_current_step(state_id):
- logger.info(_("当前任务已过期, ticket_id={}, state_id={}".format(ticket.id, state_id)))
+ logger.info(
+ _("当前任务已过期, ticket_id={}, state_id={}".format(ticket.id, state_id))
+ )
else:
# 当前状态存在,才发送通知
logger.info(
@@ -266,10 +272,10 @@ def retry_notify(ticket, state_id, receivers):
ticket.notify(state_id, receivers)
-@task
+@shared_task
def notify_task(ticket, receivers, message, action, **kwargs):
"""发送通知"""
- task_id = kwargs.get("task_id")
+ task_id = kwargs.pop("task_id", None)
# 关闭通知服务
if CLOSE_NOTIFY == "close":
@@ -297,7 +303,7 @@ def notify_task(ticket, receivers, message, action, **kwargs):
logger.exception("send email exception: %s" % str(e))
-@task
+@shared_task
def notify_fast_approval_task(ticket, state_id, receivers):
"""发送快速审批通知"""
@@ -417,7 +423,7 @@ def build_auto_transit_rules(ticket, auto_transits):
return rules
-@task
+@shared_task
def remark_notify(ticket_id, creator, message, receivers):
ticket = Ticket.objects.get(id=ticket_id)
title = "{0}在单据{1}({2})下评论@了您".format(creator, ticket.title, ticket.sn)
@@ -516,12 +522,12 @@ def consume_notify():
queryset = Ticket.objects.filter(current_status="RUNNING", is_deleted=False)
- for _ in range(1, end):
+ for i in range(1, end):
user = email_notify.lpop("notify_queue")
send_message(user, queryset)
-@task
+@shared_task
def ticket_set_history_operators(ticket_id, current_operator):
"""设置历史处理人"""
with transaction.atomic():
diff --git a/itsm/ticket/utils.py b/itsm/ticket/utils.py
index 60c461a9a..61b73db14 100644
--- a/itsm/ticket/utils.py
+++ b/itsm/ticket/utils.py
@@ -30,7 +30,7 @@
import requests
from django.conf import settings
from django.contrib.auth import get_user_model
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mako.template import Template
from common.log import logger
@@ -102,6 +102,9 @@ def build_message(_notify, task_id, ticket, message, action, **kwargs):
else:
custom_notify = get_custom_notify(ticket, action, _notify.type)
+ if task_id is not None:
+ kwargs["task_id"] = task_id
+
# 获取单据上下文
context = ticket.get_notify_context()
context.update(
diff --git a/itsm/ticket/validators/field.py b/itsm/ticket/validators/field.py
index ce7c9bd39..bdd40b9a4 100644
--- a/itsm/ticket/validators/field.py
+++ b/itsm/ticket/validators/field.py
@@ -27,7 +27,7 @@
import re
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from pipeline.utils.boolrule import BoolRule
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
@@ -62,7 +62,9 @@ def field_validate(field, state_fields, key_value, **kwargs):
field_obj = state_fields.get(field["key"], None)
if field_obj is None:
- raise serializers.ValidationError(_("【{}】字段不存在,请联系管理员").format(field["key"]))
+ raise serializers.ValidationError(
+ _("【{}】字段不存在,请联系管理员").format(field["key"])
+ )
field_obj = bunchify(field_obj)
@@ -104,7 +106,9 @@ def required_validate(field, field_obj, key_value, skip_readonly=False):
if field_obj.type in ["CUSTOMTABLE", "TABLE"]:
if not field["value"]:
- raise serializers.ValidationError(_("【{}】为必填项").format(field_obj.name))
+ raise serializers.ValidationError(
+ _("【{}】为必填项").format(field_obj.name)
+ )
# 表格类型字段的必填校验
field_column_schema = (
@@ -124,7 +128,9 @@ def required_validate(field, field_obj, key_value, skip_readonly=False):
for column, value in row.items():
if column in required_columns and not value:
raise serializers.ValidationError(
- _("表格字段【{field_name}】的第【{index}】行【{column_name}】为必填项").format(
+ _(
+ "表格字段【{field_name}】的第【{index}】行【{column_name}】为必填项"
+ ).format(
field_name=field_obj.name,
index=index + 1,
column_name=required_columns[column],
@@ -154,14 +160,18 @@ def choice_validate(field, field_obj, key_value, **kwargs):
choice = get_choice(field_obj, key_value, **kwargs)
if not choice:
- raise serializers.ValidationError(_("【%s】选项不存在,请联系管理员") % field_obj.key)
+ raise serializers.ValidationError(
+ _("【%s】选项不存在,请联系管理员") % field_obj.key
+ )
# 更新choice
field["choice"] = choice
if field_obj.type == "TREESELECT":
if not choice:
- raise serializers.ValidationError(_("数据字典不存在,请检查字典编码: %s") % field_obj.key)
+ raise serializers.ValidationError(
+ _("数据字典不存在,请检查字典编码: %s") % field_obj.key
+ )
key_choice = [str(item["id"]) for _choice in choice for item in walk(_choice)]
else:
key_choice = [str(item["key"]) for item in choice]
@@ -229,7 +239,9 @@ def custom_regex_validate(field, field_obj):
if not re.match(r"{}".format(custom_regex), str(field["value"])):
raise serializers.ValidationError(_("用户输入的值不符合自定义正则规则"))
except Exception as e:
- raise serializers.ValidationError(_("自定义正则出现异常, error = {}".format(e)))
+ raise serializers.ValidationError(
+ _("自定义正则出现异常, error = {}".format(e))
+ )
def validate_expression(field, expression, ticket, key_value=None):
@@ -327,7 +339,9 @@ def regex_validate(field, field_obj, ticket=None, key_value=None):
if rule.expressions:
results = []
for expression in rule.expressions:
- results.append(validate_expression(field, expression, ticket, key_value))
+ results.append(
+ validate_expression(field, expression, ticket, key_value)
+ )
expression_type = {"and": all, "or": any}
if not expression_type.get(rule.type, any)(results):
@@ -390,7 +404,9 @@ def date_validate(self, value):
value = datetime.datetime.strptime(value, "%Y-%m-%d")
except ValueError:
raise serializers.ValidationError(
- _("【{}】{} 不匹配日期格式{}").format(self.field_name, value, "%Y-%m-%d")
+ _("【{}】{} 不匹配日期格式{}").format(
+ self.field_name, value, "%Y-%m-%d"
+ )
)
if self.validate_type == "after_date" and value < datetime.datetime.now():
diff --git a/itsm/ticket/validators/misc.py b/itsm/ticket/validators/misc.py
index 9cbcd9a60..f58d0d673 100644
--- a/itsm/ticket/validators/misc.py
+++ b/itsm/ticket/validators/misc.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
from django.conf import settings
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from common.log import logger
@@ -38,7 +38,7 @@ def days_validate(days):
try:
days = int(days)
except ValueError:
- raise serializers.ValidationError(_('天数参数类型错误!'))
+ raise serializers.ValidationError(_("天数参数类型错误!"))
return days
@@ -46,54 +46,62 @@ def notify_log_validate(data, operator):
"""发送通知的校验"""
# 发送关注信息的校验
- message = data.get('message', '')
- ticket_id = data.get('ticket_id')
+ message = data.get("message", "")
+ ticket_id = data.get("ticket_id")
if not message:
- raise serializers.ValidationError(_('请填写关注信息!'))
+ raise serializers.ValidationError(_("请填写关注信息!"))
if len(message) > 200:
- raise serializers.ValidationError(_('关注信息不能超过200个字符!'))
+ raise serializers.ValidationError(_("关注信息不能超过200个字符!"))
try:
ticket = Ticket.objects.get(id=ticket_id)
except Ticket.DoesNotExist:
- logger.error('发送关注通知校验失败:单据不存在 ticket_id={}'.format(ticket_id))
- raise serializers.ValidationError(_('发送关注通知校验失败:单据不存在,请联系管理员'))
+ logger.error("发送关注通知校验失败:单据不存在 ticket_id={}".format(ticket_id))
+ raise serializers.ValidationError(
+ _("发送关注通知校验失败:单据不存在,请联系管理员")
+ )
bk_biz_id = ticket.bk_biz_id
if not ticket.can_invite_followers(operator):
- raise serializers.ValidationError(_('发送关注通知校验失败:单据已结束或权限不足'))
+ raise serializers.ValidationError(
+ _("发送关注通知校验失败:单据已结束或权限不足")
+ )
- followers = data.get('followers')
- followers_type = data.get('followers_type')
- receivers = UserRole.get_users_by_type(bk_biz_id=bk_biz_id, user_type=followers_type, users=followers)
+ followers = data.get("followers")
+ followers_type = data.get("followers_type")
+ receivers = UserRole.get_users_by_type(
+ bk_biz_id=bk_biz_id, user_type=followers_type, users=followers
+ )
if not receivers:
logger.error(
- '发送关注通知校验失败:接收人不存在:receivers={}, bk_biz_id={}, followers={}, followers_type={}'.format(
+ "发送关注通知校验失败:接收人不存在:receivers={}, bk_biz_id={}, followers={}, followers_type={}".format(
receivers, bk_biz_id, followers, followers_type
)
)
- raise serializers.ValidationError(_('发送关注通知校验失败:通知人不存在或通知角色没有人员,请联系管理员'))
+ raise serializers.ValidationError(
+ _("发送关注通知校验失败:通知人不存在或通知角色没有人员,请联系管理员")
+ )
- return ticket, ','.join(receivers)
+ return ticket, ",".join(receivers)
def sms_comment_validate(queryset, data):
"""接收短信评价校验"""
try:
- comment = queryset.get(invite__code=data.get('code'))
+ comment = queryset.get(invite__code=data.get("code"))
except TicketComment.DoesNotExist:
- raise serializers.ValidationError(_('单据评论信息不存在,请联系管理员!'))
+ raise serializers.ValidationError(_("单据评论信息不存在,请联系管理员!"))
try:
- stars = int(data.get('stars'))
+ stars = int(data.get("stars"))
except ValueError:
- raise serializers.ValidationError(_('评价信息不正确,请联系管理员!'))
- if comment.ticket.sn != data.get('sn'):
- raise serializers.ValidationError(_('单据评论信息不匹配'))
+ raise serializers.ValidationError(_("评价信息不正确,请联系管理员!"))
+ if comment.ticket.sn != data.get("sn"):
+ raise serializers.ValidationError(_("单据评论信息不匹配"))
if comment.stars:
- raise serializers.ValidationError(_('该单据已经被评论,请勿重复评论!'))
+ raise serializers.ValidationError(_("该单据已经被评论,请勿重复评论!"))
if stars not in list(range(1, 6)):
- raise serializers.ValidationError(_('请从(1~5星)选择评价星级!'))
+ raise serializers.ValidationError(_("请从(1~5星)选择评价星级!"))
return comment, stars
@@ -101,28 +109,30 @@ def sms_invite_validate(ticket, numbers, invitor):
"""发送号码前评论校验"""
if not ticket.can_comment(invitor):
- raise serializers.ValidationError(_('抱歉,您无权发送评价邀请'))
-
+ raise serializers.ValidationError(_("抱歉,您无权发送评价邀请"))
+
if settings.TICKET_INVITE_SMS_COUNT:
if len(numbers) > settings.TICKET_INVITE_SMS_COUNT:
raise serializers.ValidationError(_("SMS 发送评价邀请超过限额"))
- invite_count = TicketCommentInvite.objects.filter(comment__ticket__id=ticket.id).count()
+ invite_count = TicketCommentInvite.objects.filter(
+ comment__ticket__id=ticket.id
+ ).count()
if invite_count > settings.TICKET_INVITE_SMS_COUNT:
raise serializers.ValidationError(_("SMS 发送评价邀请超过限额"))
for number in numbers:
try:
- Regex(validate_type='phone_num').validate(number)
+ Regex(validate_type="phone_num").validate(number)
except Exception as error:
- raise serializers.ValidationError('【{}】{}'.format(number, str(error)))
+ raise serializers.ValidationError("【{}】{}".format(number, str(error)))
def email_invite_validate(ticket, invitor, receiver):
"""邮件邀请评价校验"""
if not ticket.can_comment(invitor):
- raise serializers.ValidationError(_('抱歉,您无权发送评价邀请'))
+ raise serializers.ValidationError(_("抱歉,您无权发送评价邀请"))
if receiver not in get_bk_users(users=[receiver]):
- raise serializers.ValidationError(_('【{}】用户不存在').format(receiver))
+ raise serializers.ValidationError(_("【{}】用户不存在").format(receiver))
diff --git a/itsm/ticket/validators/ticket.py b/itsm/ticket/validators/ticket.py
index 1d414a0a6..9c45deeea 100644
--- a/itsm/ticket/validators/ticket.py
+++ b/itsm/ticket/validators/ticket.py
@@ -27,7 +27,7 @@
from django.conf import settings
from django.db.models import Q
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
@@ -100,7 +100,9 @@ def create_validate(self, value, fields, username, **kwargs):
lost_keys = required_keys - field_keys
if lost_keys:
- raise CreateTicketError(_("单据创建失败,缺少参数:{}".format(list(lost_keys))))
+ raise CreateTicketError(
+ _("单据创建失败,缺少参数:{}".format(list(lost_keys)))
+ )
first_state_permission(fields, state, username)
# create_permission_validate(service, username)
@@ -173,7 +175,9 @@ def deliver_validate(self, value):
"""转单校验"""
if not self.current_node.can_deliver:
- raise ParamError(_("指定流程节点【{}】不支持转单操作.").format(self.current_node.name))
+ raise ParamError(
+ _("指定流程节点【{}】不支持转单操作.").format(self.current_node.name)
+ )
if not self.current_node.status == RUNNING:
raise ParamError(_("当前任务状态下无法转单."))
@@ -239,7 +243,9 @@ def processor_validate(self, value, reference_processor_type, reference_processo
if not set(processors).issubset(set(valid_person)):
raise ParamError(
- _("当前分配的部分用户可能不符合条件,请确保用户在{}中").format(",".join(set(valid_person)))
+ _("当前分配的部分用户可能不符合条件,请确保用户在{}中").format(
+ ",".join(set(valid_person))
+ )
)
@@ -252,7 +258,9 @@ def first_state_permission(fields, first_state, username):
user_department_ids = get_user_department_ids(username)
state_department_id = first_state["processors"]
if int(state_department_id) not in user_department_ids:
- raise CreateTicketError(_("【{}】没有任务【提单】的【提交】操作权限.").format(username))
+ raise CreateTicketError(
+ _("【{}】没有任务【提单】的【提交】操作权限.").format(username)
+ )
else:
return
@@ -266,7 +274,9 @@ def first_state_permission(fields, first_state, username):
):
return
- raise CreateTicketError(_("【{}】没有任务【提单】的【提交】操作权限.").format(username))
+ raise CreateTicketError(
+ _("【{}】没有任务【提单】的【提交】操作权限.").format(username)
+ )
def create_permission_validate(service, username):
@@ -278,7 +288,9 @@ def create_permission_validate(service, username):
):
return
- raise CreateTicketError(_("【{}】没有任务【提单】的【提交】操作权限.").format(username))
+ raise CreateTicketError(
+ _("【{}】没有任务【提单】的【提交】操作权限.").format(username)
+ )
def first_state_field_validate(state_fields, fields, **kwargs):
@@ -310,10 +322,14 @@ def derive_validate(username, ticket_id):
raise serializers.ValidationError({_("单据"): _("单据不存在,请联系管理员!")})
if ticket.is_over:
- raise serializers.ValidationError({_("单据"): _("单据已结束,无法新建关联单!")})
+ raise serializers.ValidationError(
+ {_("单据"): _("单据已结束,无法新建关联单!")}
+ )
if not ticket.has_perm(username):
- raise serializers.ValidationError({_("单据"): _("抱歉,您没有单据操作权限,请联系管理员!")})
+ raise serializers.ValidationError(
+ {_("单据"): _("抱歉,您没有单据操作权限,请联系管理员!")}
+ )
def bind_derive_tickets_validate(from_ticket_id, to_ticket_ids):
@@ -324,7 +340,7 @@ def bind_derive_tickets_validate(from_ticket_id, to_ticket_ids):
:return:
"""
# id有效性校验
- if type(to_ticket_ids) != list:
+ if not isinstance(to_ticket_ids, list):
raise serializers.ValidationError(_("参数类型错误:[to_tickets]"))
if from_ticket_id in to_ticket_ids:
@@ -348,7 +364,9 @@ def bind_derive_tickets_validate(from_ticket_id, to_ticket_ids):
)
).exists():
raise serializers.ValidationError(
- _("单据【{}】和【{}】已存在关联关系").format(from_ticket.sn, to_ticket.sn)
+ _("单据【{}】和【{}】已存在关联关系").format(
+ from_ticket.sn, to_ticket.sn
+ )
)
@@ -398,7 +416,9 @@ def merge_validate(from_ticket_ids, to_ticket_id, operator):
for ticket in tickets:
if ticket["flow_id"] != to_ticket["flow_id"]:
- raise serializers.ValidationError(_("母子单必须属于同一个流程版本,无法关联"))
+ raise serializers.ValidationError(
+ _("母子单必须属于同一个流程版本,无法关联")
+ )
from_ticket_ids.pop(from_ticket_ids.index(ticket["id"]))
@@ -413,7 +433,9 @@ def ticket_can_be_master(ticket_id):
"""
ticket = Ticket.objects.get(id=ticket_id)
if ticket.is_slave:
- raise serializers.ValidationError(_("单据 [{}] 已经是子单,无法成为母单".format(ticket.sn)))
+ raise serializers.ValidationError(
+ _("单据 [{}] 已经是子单,无法成为母单".format(ticket.sn))
+ )
def tickets_can_be_slave(ticket_ids):
@@ -446,11 +468,17 @@ def withdraw_validate(operator, ticket, ignore_user=False):
if not ticket.flow.is_revocable:
# 不可撤销或者已经结束的单,直接返回
- raise serializers.ValidationError(_("抱歉,当前流程配置无法撤单,请联系服务负责人"))
+ raise serializers.ValidationError(
+ _("抱歉,当前流程配置无法撤单,请联系服务负责人")
+ )
if operator != ticket.creator and operator != settings.SYSTEM_USE_API_ACCOUNT:
raise serializers.ValidationError(
- _("抱歉,你无权撤销单据,撤销单据的非当前单据的提单人, {}!={}".format(operator, ticket.creator))
+ _(
+ "抱歉,你无权撤销单据,撤销单据的非当前单据的提单人, {}!={}".format(
+ operator, ticket.creator
+ )
+ )
)
if ticket.is_over:
@@ -475,7 +503,9 @@ def terminate_validate(username, ticket, state_id, terminate_message):
status = ticket.status(state_id)
if not status:
- raise serializers.ValidationError(_("流程节点(%s)不存在,请联系管理员.") % state_id)
+ raise serializers.ValidationError(
+ _("流程节点(%s)不存在,请联系管理员.") % state_id
+ )
if ticket.is_slave:
raise serializers.ValidationError(_("抱歉,子单为只读状态,无法操作"))
@@ -492,7 +522,9 @@ def terminate_validate(username, ticket, state_id, terminate_message):
)
if not status.can_terminate:
- raise serializers.ValidationError(_("指定流程节点【{}】不支持终止操作.").format(status.name))
+ raise serializers.ValidationError(
+ _("指定流程节点【{}】不支持终止操作.").format(status.name)
+ )
if not status.can_operate(username):
raise serializers.ValidationError(
@@ -578,7 +610,9 @@ def ticket_operate_validate(fields, state_id, ticket, username):
bk_biz_id = get_bk_biz_id(fields)
if not status.can_first_state_operate(username, bk_biz_id):
raise serializers.ValidationError(
- _("【{}】没有任务【{}】的【提交】操作权限.").format(username, status.name)
+ _("【{}】没有任务【{}】的【提交】操作权限.").format(
+ username, status.name
+ )
)
elif not status.can_operate(username, TRANSITION_OPERATE):
@@ -592,7 +626,9 @@ def ticket_status_validate(ticket, state_id):
raise serializers.ValidationError(_("抱歉,子单为只读状态,无法操作"))
if not ticket.status(state_id):
- raise serializers.ValidationError(_("流程节点(%s)不存在,请联系管理员.") % state_id)
+ raise serializers.ValidationError(
+ _("流程节点(%s)不存在,请联系管理员.") % state_id
+ )
if ticket.current_status in TICKET_END_STATUS:
raise serializers.ValidationError(_("单据状态为结束状态,无法继续转换"))
diff --git a/itsm/ticket/views/field.py b/itsm/ticket/views/field.py
index badddc524..832f309b0 100644
--- a/itsm/ticket/views/field.py
+++ b/itsm/ticket/views/field.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework.decorators import action
from rest_framework.response import Response
diff --git a/itsm/ticket/views/misc.py b/itsm/ticket/views/misc.py
index d8ff2e23a..6c20268ed 100644
--- a/itsm/ticket/views/misc.py
+++ b/itsm/ticket/views/misc.py
@@ -25,7 +25,7 @@
from django.conf import settings
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mako.template import Template
from rest_framework import serializers
from rest_framework.decorators import action
@@ -108,7 +108,7 @@ class CommentViewSet(component_viewsets.NormalModelViewSet):
"stars": ["exact"],
}
ordering_fields = "__all__"
-
+
def list(self, request, *args, **kwargs):
return Response()
diff --git a/itsm/ticket/views/operational.py b/itsm/ticket/views/operational.py
index e34723b62..22160b49d 100644
--- a/itsm/ticket/views/operational.py
+++ b/itsm/ticket/views/operational.py
@@ -28,7 +28,7 @@
from django.db import connection
from django.db.models import Count, Q, Case, When, Max
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.generics import get_object_or_404
@@ -107,13 +107,15 @@ def overview_count(self, request, *args, **kwargs):
return Response(
{
"count": project_analysis.get_ticket_count(),
- "service_count": 1
- if service_id
- else project_analysis.get_service_count(),
+ "service_count": (
+ 1 if service_id else project_analysis.get_service_count()
+ ),
"biz_count": project_analysis.get_biz_count(),
- "user_count": project_analysis.get_ticket_user_count()
- if service_id
- else user_count(project_key=project_key),
+ "user_count": (
+ project_analysis.get_ticket_user_count()
+ if service_id
+ else user_count(project_key=project_key)
+ ),
}
)
@@ -482,7 +484,7 @@ def distribute_statistics(self, request):
filter_serializer = TicketOrganizationSerializer(data=request.query_params)
filter_serializer.is_valid(raise_exception=True)
kwargs = self.combine_date(filter_serializer.validated_data)
-
+
level_dict = {
1: ("first_level_id", "first_level_name"),
2: ("second_level_id", "second_level_name"),
@@ -531,9 +533,9 @@ def get_time_params(request, params_key="create_at"):
return time_params
except KeyError:
raise ValidationError(
- _("日期范围输入有误,请重新输入,例如:{}__gte=2019-01-01, {}__lte=2019-01-02").format(
- params_key, params_key
- )
+ _(
+ "日期范围输入有误,请重新输入,例如:{}__gte=2019-01-01, {}__lte=2019-01-02"
+ ).format(params_key, params_key)
)
@staticmethod
@@ -562,7 +564,9 @@ def get_month_params(queryset, request):
except ValueError:
raise ValidationError(
- _("日期范围输入有误,请重新输入,例如:create_at__gte=2019-01, create_at__lte=2019-02")
+ _(
+ "日期范围输入有误,请重新输入,例如:create_at__gte=2019-01, create_at__lte=2019-02"
+ )
)
# 左开右闭
@@ -1162,9 +1166,11 @@ def new_tickets(self, request, *args, **kwargs):
filter_result.sort(key=lambda x: x["day"])
return Response(filter_result)
-
+
@staticmethod
def combine_date(kwargs):
if kwargs.get("create_at__lte"):
- kwargs["create_at__lte"] = datetime.combine(kwargs["create_at__lte"], time(23, 59, 59))
+ kwargs["create_at__lte"] = datetime.combine(
+ kwargs["create_at__lte"], time(23, 59, 59)
+ )
return kwargs
diff --git a/itsm/ticket/views/ticket.py b/itsm/ticket/views/ticket.py
index 9eb37e208..b49010f9b 100644
--- a/itsm/ticket/views/ticket.py
+++ b/itsm/ticket/views/ticket.py
@@ -37,7 +37,7 @@
from django.db import connection, transaction
from django.db.models import Count, Q
from django.http import HttpResponse
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mako.template import Template
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
@@ -81,7 +81,8 @@
OPEN,
GENERAL,
ORGANIZATION,
- SUSPENDED, LEN_X_LONG,
+ SUSPENDED,
+ LEN_X_LONG,
)
from itsm.component.constants.flow import EXPORT_SUPPORTED_TYPE
from itsm.component.dlls.component import ComponentLibrary
@@ -380,16 +381,22 @@ def create(self, request, *args, **kwargs):
# creator(实际提单人)和updated_by在serializer.to_internal_value(data)中获取
instance = serializer.save(meta=meta)
- logger.info(f"[TICKET] create ticket do_after_create begin ticket_id=>{instance.id}")
-
+ logger.info(
+ f"[TICKET] create ticket do_after_create begin ticket_id=>{instance.id}"
+ )
+
instance.do_after_create(
request.data["fields"], request.data.get("from_ticket_id", None)
)
- logger.info(f"[TICKET] create ticket do_after_create end ticket_id=>{instance.id}")
-
+ logger.info(
+ f"[TICKET] create ticket do_after_create end ticket_id=>{instance.id}"
+ )
+
start_pipeline.apply_async([instance])
- logger.info(f"[TICKET] create ticket start_pipeline end ticket_id=>{instance.id}")
-
+ logger.info(
+ f"[TICKET] create ticket start_pipeline end ticket_id=>{instance.id}"
+ )
+
return Response({"sn": instance.sn, "id": instance.id}, status=201)
@action(detail=True, methods=["get"])
@@ -573,11 +580,13 @@ def send_sms(self, request, *args, **kwargs):
return Response(
{
"result": len(fail_numbers) == 0,
- "message": _("【{}】发送短信失败,请检查电话号码是否正确或联系管理员!").format(
- ",".join(fail_numbers)
- )
- if fail_numbers
- else "success",
+ "message": (
+ _(
+ "【{}】发送短信失败,请检查电话号码是否正确或联系管理员!"
+ ).format(",".join(fail_numbers))
+ if fail_numbers
+ else "success"
+ ),
"data": links,
"code": "OK" if len(fail_numbers) == 0 else "SEND_SMS_FAILED",
}
@@ -639,7 +648,9 @@ def send_email(self, request, *args, **kwargs):
return Response(
{
"result": False,
- "message": _("【{}】发送邮件失败,请检查用户邮件配置是否正确或联系管理员!").format(receiver),
+ "message": _(
+ "【{}】发送邮件失败,请检查用户邮件配置是否正确或联系管理员!"
+ ).format(receiver),
"data": ticket_url,
"code": "SEND_EMAIL_FAILED",
}
@@ -670,9 +681,11 @@ def export_excel(self, request, *args, **kwargs):
ticket_releate_fields = group_by(ticket_fields, ["ticket_id"], dict_result=True)
field_filter_conditions = set(
[
- "{}({})".format(field_obj["name"], field_obj["state_name"])
- if field_obj["state_name"]
- else field_obj["name"]
+ (
+ "{}({})".format(field_obj["name"], field_obj["state_name"])
+ if field_obj["state_name"]
+ else field_obj["name"]
+ )
for field_obj in ticket_fields
]
)
@@ -719,7 +732,11 @@ def export_group_by_service(self, request, *args, **kwargs):
json.loads(base64.b64decode(service_fields)) if service_fields else {}
)
except BaseException:
- raise ValidationError(_("解析导出的提单字段异常:请检查请求参数内容,提单字段需要通过base64编码。"))
+ raise ValidationError(
+ _(
+ "解析导出的提单字段异常:请检查请求参数内容,提单字段需要通过base64编码。"
+ )
+ )
# 获取字段的展示title,先将所有的字段混合起来
all_service_field_keys = []
@@ -779,7 +796,6 @@ def get_service_ticket_fields():
@staticmethod
def generate_xls(head_fields, ticket_values_list, service_type):
-
"""生成文档"""
service_type_name = SERVICE_DICT.get(service_type) or "ALL"
sheet_name = (
@@ -926,9 +942,9 @@ def proceed(self, request, *args, **kwargs):
return Response(
{
- "code": ResponseCodeStatus.OK
- if res.result
- else ResponseCodeStatus.FAILED,
+ "code": (
+ ResponseCodeStatus.OK if res.result else ResponseCodeStatus.FAILED
+ ),
"message": res.message,
"result": res.result,
}
@@ -953,9 +969,9 @@ def retry(self, request, *args, **kwargs):
return Response(
{
- "code": ResponseCodeStatus.OK
- if res.result
- else ResponseCodeStatus.FAILED,
+ "code": (
+ ResponseCodeStatus.OK if res.result else ResponseCodeStatus.FAILED
+ ),
"message": res.message,
"result": res.result,
}
@@ -984,9 +1000,9 @@ def ignore(self, request, *args, **kwargs):
)
return Response(
{
- "code": ResponseCodeStatus.OK
- if res.result
- else ResponseCodeStatus.FAILED,
+ "code": (
+ ResponseCodeStatus.OK if res.result else ResponseCodeStatus.FAILED
+ ),
"message": res.message,
"result": res.result,
}
@@ -1247,10 +1263,12 @@ def close(self, request, *args, **kwargs):
close_status = request.data.get("current_status")
if close_status not in ticket.status_instance.to_over_status_keys:
raise ValidationError(_("设置的关闭状态不在正确状态范围之内"))
-
+
desc = request.data.get("desc") or ""
if len(desc) > LEN_X_LONG:
- raise ValidationError(_("关单失败,原因描述超过 {len} 字符").format(len=LEN_X_LONG))
+ raise ValidationError(
+ _("关单失败,原因描述超过 {len} 字符").format(len=LEN_X_LONG)
+ )
ticket.close(
close_status=close_status,
@@ -1447,8 +1465,7 @@ def states_response(self, ticket, request, detail=False):
if many and detail:
status = status.filter(
- ~Q(status__in=["TERMINATED"])
- | Q(state_id=ticket.first_state_id)
+ ~Q(status__in=["TERMINATED"]) | Q(state_id=ticket.first_state_id)
)
show_all_fields = many or status.status != "FINISHED"
ticket_status = StatusSerializer(
diff --git a/itsm/ticket/views/ticket_remark.py b/itsm/ticket/views/ticket_remark.py
index 938e7adf2..c38871e57 100644
--- a/itsm/ticket/views/ticket_remark.py
+++ b/itsm/ticket/views/ticket_remark.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from django.http import Http404
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework.decorators import action
from rest_framework.response import Response
@@ -48,14 +48,14 @@ def list(self, request, *args, **kwargs):
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
-
+
def retrieve(self, request, *args, **kwargs):
try:
return super().retrieve(request, *args, **kwargs)
except Http404:
"""兼容父级评论删除情况"""
return Response([])
-
+
@action(detail=False, methods=["get"])
def tree_view(self, request):
"""评论视图"""
diff --git a/itsm/ticket_status/models.py b/itsm/ticket_status/models.py
index a480a23c2..8a85cb8da 100644
--- a/itsm/ticket_status/models.py
+++ b/itsm/ticket_status/models.py
@@ -27,7 +27,7 @@
from datetime import datetime
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
BUILTIN_TICKET_STATUS,
diff --git a/itsm/ticket_status/permissions.py b/itsm/ticket_status/permissions.py
index 38eea93f1..2c534b17d 100644
--- a/itsm/ticket_status/permissions.py
+++ b/itsm/ticket_status/permissions.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import permissions
from itsm.component.drf.permissions import IamAuthPermit
@@ -62,7 +62,7 @@ def has_permission(self, request, view):
# 关联实例的请求,需要针对对象进行鉴权
if view.action in getattr(view, "permission_free_actions", []):
return True
-
+
if view.action in ["get_configs"]:
apply_actions = ["ticket_state_view", "platform_manage_access"]
elif view.action in ["overall_ticket_statuses", "list", "next_over_status"]:
diff --git a/itsm/ticket_status/serializers.py b/itsm/ticket_status/serializers.py
index 2f530307d..e2eb8ca59 100644
--- a/itsm/ticket_status/serializers.py
+++ b/itsm/ticket_status/serializers.py
@@ -23,11 +23,17 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import empty
-from itsm.component.constants import FIRST_ORDER, LEN_LONG, LEN_NORMAL, LEN_SHORT, PROCESS_RUNNING
+from itsm.component.constants import (
+ FIRST_ORDER,
+ LEN_LONG,
+ LEN_NORMAL,
+ LEN_SHORT,
+ PROCESS_RUNNING,
+)
from itsm.service.validators import service_type_validator
from itsm.ticket_status.models import StatusTransit, TicketStatus, TicketStatusConfig
from itsm.ticket_status.validators import TicketStatusValidator
@@ -36,7 +42,15 @@
class TicketStatusConfigSerializer(serializers.ModelSerializer):
class Meta:
model = TicketStatusConfig
- fields = ("id", "service_type", "service_type_name", "ticket_status", "updated_by", "update_at", "configured")
+ fields = (
+ "id",
+ "service_type",
+ "service_type_name",
+ "ticket_status",
+ "updated_by",
+ "update_at",
+ "configured",
+ )
class TicketStatusOptionSerializer(serializers.Serializer):
@@ -49,9 +63,18 @@ class TicketStatusSerializer(serializers.ModelSerializer):
"""单据状态序列化"""
name = serializers.CharField(
- required=True, max_length=LEN_LONG, error_messages={"blank": _("请输入状态名称"), "max_length": _("状态名称长度不能大于255个字符")}
+ required=True,
+ max_length=LEN_LONG,
+ error_messages={
+ "blank": _("请输入状态名称"),
+ "max_length": _("状态名称长度不能大于255个字符"),
+ },
+ )
+ color_hex = serializers.CharField(
+ required=True,
+ max_length=LEN_SHORT,
+ error_messages={"blank": _("二进制颜色不能为空")},
)
- color_hex = serializers.CharField(required=True, max_length=LEN_SHORT, error_messages={"blank": _("二进制颜色不能为空")})
service_type = serializers.CharField(
required=True,
max_length=LEN_NORMAL,
@@ -85,9 +108,13 @@ def create(self, validated_data):
# 获取同一服务类型下的最大order数值
ticket_status = (
- TicketStatus.objects.filter(service_type=validated_data["service_type"]).order_by("order").last()
+ TicketStatus.objects.filter(service_type=validated_data["service_type"])
+ .order_by("order")
+ .last()
+ )
+ created_order = (
+ ticket_status.order + FIRST_ORDER if ticket_status else FIRST_ORDER
)
- created_order = ticket_status.order + FIRST_ORDER if ticket_status else FIRST_ORDER
validated_data.update(
order=created_order,
key=TicketStatus.get_unique_key(validated_data["name"]),
@@ -103,7 +130,13 @@ def create(self, validated_data):
def update(self, instance, validated_data):
"""更新单据状态"""
# 内置的单据状态 只有以下属性可以被编辑
- can_edited = ("is_start", "is_over", "name", "desc", "color_hex") + TicketStatus.DISPLAY_FIELDS
+ can_edited = (
+ "is_start",
+ "is_over",
+ "name",
+ "desc",
+ "color_hex",
+ ) + TicketStatus.DISPLAY_FIELDS
if instance.is_builtin and set(validated_data.keys()).difference(can_edited):
raise serializers.ValidationError(_("抱歉,内置单据状态无法被编辑"))
diff --git a/itsm/ticket_status/validators.py b/itsm/ticket_status/validators.py
index adae9b0a0..4f538356a 100644
--- a/itsm/ticket_status/validators.py
+++ b/itsm/ticket_status/validators.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from common.log import logger
@@ -31,22 +31,37 @@
from itsm.ticket_status.models import TicketStatus
-def save_ticket_status_validate(service_type, ticket_status_ids, start_status_id, over_status_ids):
+def save_ticket_status_validate(
+ service_type, ticket_status_ids, start_status_id, over_status_ids
+):
"""保存工单状态的校验"""
service_type_validator(service_type)
# 确保传入完整的工单状态ID用于排序
- status_ids = list(TicketStatus.objects.status_of_service_type(service_type).values_list("id", flat=True))
+ status_ids = list(
+ TicketStatus.objects.status_of_service_type(service_type).values_list(
+ "id", flat=True
+ )
+ )
if set(status_ids) != set(ticket_status_ids):
- raise serializers.ValidationError(_("用于排序的工单状态参数不合法,请联系管理员"))
+ raise serializers.ValidationError(
+ _("用于排序的工单状态参数不合法,请联系管理员")
+ )
if start_status_id not in status_ids:
- raise serializers.ValidationError(_("设置为起始状态的工单状态不存在,请联系管理员"))
+ raise serializers.ValidationError(
+ _("设置为起始状态的工单状态不存在,请联系管理员")
+ )
if set(over_status_ids).difference(status_ids):
- logger.error("设置为结束状态的工作状态(id=%s)不存在" % set(over_status_ids).difference(status_ids))
- raise serializers.ValidationError(_("设置为结束状态的工作状态不存在,请联系管理员"))
+ logger.error(
+ "设置为结束状态的工作状态(id=%s)不存在"
+ % set(over_status_ids).difference(status_ids)
+ )
+ raise serializers.ValidationError(
+ _("设置为结束状态的工作状态不存在,请联系管理员")
+ )
def save_transit_validate(service_type, transits):
@@ -65,19 +80,24 @@ def save_transit_validate(service_type, transits):
not_over_status_ids.append(s.id)
for transit in transits:
- if transit.get("from_status") not in all_status_ids or transit.get("to_status") not in all_status_ids:
+ if (
+ transit.get("from_status") not in all_status_ids
+ or transit.get("to_status") not in all_status_ids
+ ):
logger.error("单据状态ID(%s)不存在" % transit.get("from_status"))
serializers.ValidationError(_("单据状态不存在,请联系管理员"))
if transit.get("from_status") not in not_over_status_ids:
- logger.error("单据状态ID(%s)为结束状态,无法继续转换" % transit.get("from_status"))
+ logger.error(
+ "单据状态ID(%s)为结束状态,无法继续转换" % transit.get("from_status")
+ )
serializers.ValidationError(_("单据状态为结束状态,无法继续转换"))
def set_transit_rule_validate(from_status, to_status_id, threshold, threshold_unit):
"""设置状态转换规则"""
- if threshold_unit not in ['m', 'h', 'd']:
+ if threshold_unit not in ["m", "h", "d"]:
raise serializers.ValidationError(_("阈值单位错误,请重新输入"))
if not str(threshold).isdigit():
@@ -94,7 +114,9 @@ def from_status_id_validator(from_status_id):
if from_status_id is None:
raise serializers.ValidationError(_("from_status_id不能为空"))
if not TicketStatus.objects.filter(id=from_status_id).exists():
- raise serializers.ValidationError(_("id为【{}】的工单状态不存在,请联系管理员".format(from_status_id)))
+ raise serializers.ValidationError(
+ _("id为【{}】的工单状态不存在,请联系管理员".format(from_status_id))
+ )
class TicketStatusValidator(object):
@@ -109,9 +131,11 @@ def __call__(self, value):
def name_unique_validate(self, value):
"""名称唯一性校验"""
if self.instance:
- obj = TicketStatus.objects.filter(service_type=self.instance.service_type).exclude(id=self.instance.id)
+ obj = TicketStatus.objects.filter(
+ service_type=self.instance.service_type
+ ).exclude(id=self.instance.id)
else:
- service_type = value.get('service_type')
+ service_type = value.get("service_type")
obj = TicketStatus.objects.filter(service_type=service_type)
- if obj.filter(name=value.get('name')).exists():
+ if obj.filter(name=value.get("name")).exists():
raise serializers.ValidationError(_("状态名称已存在,请重新输入"))
diff --git a/itsm/ticket_status/views.py b/itsm/ticket_status/views.py
index ae1bdf5f6..ba7af4392 100644
--- a/itsm/ticket_status/views.py
+++ b/itsm/ticket_status/views.py
@@ -27,7 +27,7 @@
from django.db import transaction
from django.db.models import Q
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django_bulk_update.helper import bulk_update
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
diff --git a/itsm/trigger/action/components/api.py b/itsm/trigger/action/components/api.py
index 7ede25db4..056ebf090 100644
--- a/itsm/trigger/action/components/api.py
+++ b/itsm/trigger/action/components/api.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.esb.backend_component import bk
from itsm.postman.models import RemoteApi
@@ -42,7 +42,9 @@ class ApiForms(BaseForm):
api_source = ApiSourceField(name=_("API接口ID"))
req_params = ApiInfoField(name=_("API请求参数配置"))
response = JSONField(
- name=_("API系统返回参数"), display=False, default={"ref_type": "reference", "value": "api_response_message"}
+ name=_("API系统返回参数"),
+ display=False,
+ default={"ref_type": "reference", "value": "api_response_message"},
)
@@ -53,10 +55,16 @@ class APIComponent(BaseComponent):
form_class = ApiForms
def get_api_source_config(self):
- remote_api = RemoteApi._objects.get(id=self.data.get_one_of_inputs("api_source"))
- api_config = remote_api.get_api_config(self.data.get_one_of_inputs("req_params"))
+ remote_api = RemoteApi._objects.get(
+ id=self.data.get_one_of_inputs("api_source")
+ )
+ api_config = remote_api.get_api_config(
+ self.data.get_one_of_inputs("req_params")
+ )
# 默认API处理人的请求用户为触发事件的操作人
- api_config['query_params']['__remote_user__'] = self.context.get("operator", 'admin')
+ api_config["query_params"]["__remote_user__"] = self.context.get(
+ "operator", "admin"
+ )
return api_config
def _execute(self):
@@ -65,9 +73,9 @@ def _execute(self):
"""
api_config = self.get_api_source_config()
rsp = bk.http(config=api_config)
- self.data.set_outputs("api_response_message", rsp.get('message'))
- self.data.set_outputs("api_response", rsp.get('message'))
- if rsp['result']:
+ self.data.set_outputs("api_response_message", rsp.get("message"))
+ self.data.set_outputs("api_response", rsp.get("message"))
+ if rsp["result"]:
return True
- self.data.set_outputs("message", rsp['message'])
+ self.data.set_outputs("message", rsp["message"])
return False
diff --git a/itsm/trigger/action/components/automatic_announcement.py b/itsm/trigger/action/components/automatic_announcement.py
index 3a2925ac3..cd4a621a8 100644
--- a/itsm/trigger/action/components/automatic_announcement.py
+++ b/itsm/trigger/action/components/automatic_announcement.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.utils import robot
from itsm.trigger.action.core.component import BaseComponent
@@ -42,7 +42,9 @@ class RobotForms(BaseForm):
chat_id = StringField(name="Chat ID", required=False, tips="群ID, 多个用,区分")
content = StringField(name=_("文本内容"), field_type="TEXT", required=True)
mentioned_list = StringField(
- name="需要提及的人的列表", tips="英文半角逗号分割的英文名列表,不填则默认@所有成员,None不@任何人", required=False
+ name="需要提及的人的列表",
+ tips="英文半角逗号分割的英文名列表,不填则默认@所有成员,None不@任何人",
+ required=False,
)
diff --git a/itsm/trigger/action/components/modify_field.py b/itsm/trigger/action/components/modify_field.py
index 213e98e8b..e864029f5 100644
--- a/itsm/trigger/action/components/modify_field.py
+++ b/itsm/trigger/action/components/modify_field.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.trigger.action.core.component import BaseComponent
from itsm.trigger.action.core import SelectField, CascadeField, BaseForm
@@ -38,8 +38,15 @@ class FieldForms(BaseForm):
发送通知的输入数据格式
"""
- field_key = SelectField(name=_("修改的字段"), source_type="RPC", source_uri="table_fields", use_variable=False)
- field_value = CascadeField(name=_("设置的字段值"), source_type="FIELD", source_uri="field_key")
+ field_key = SelectField(
+ name=_("修改的字段"),
+ source_type="RPC",
+ source_uri="table_fields",
+ use_variable=False,
+ )
+ field_value = CascadeField(
+ name=_("设置的字段值"), source_type="FIELD", source_uri="field_key"
+ )
class ModifyPublicFieldComponent(BaseComponent):
@@ -53,7 +60,9 @@ def _execute(self):
try:
dst_ticket = Ticket.objects.get(sn=self.context.get("ticket_sn"))
except Ticket.DoesNotExist:
- self.data.set_outputs("message", _("对应的单据【%s】不存在") % self.context.get("ticket_sn"))
+ self.data.set_outputs(
+ "message", _("对应的单据【%s】不存在") % self.context.get("ticket_sn")
+ )
return False
dst_ticket.refresh_from_db()
dst_field_key = self.data.get_one_of_inputs("field_key")
@@ -66,7 +75,7 @@ def _execute(self):
self.data.set_outputs("field_key__display", first_field.name)
self.data.set_outputs("field_value__display", first_field.display_value)
- if dst_field_key in ['bk_biz_id', "current_status", "title"]:
+ if dst_field_key in ["bk_biz_id", "current_status", "title"]:
setattr(dst_ticket, dst_field_key, dst_field_value)
dst_ticket.save(update_fields=[dst_field_key])
return True
diff --git a/itsm/trigger/action/components/modify_first_state_field.py b/itsm/trigger/action/components/modify_first_state_field.py
index 536fd255d..ed32d3210 100644
--- a/itsm/trigger/action/components/modify_first_state_field.py
+++ b/itsm/trigger/action/components/modify_first_state_field.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.trigger.action.core.component import BaseComponent
from itsm.trigger.action.core import SelectField, CascadeField, BaseForm
diff --git a/itsm/trigger/action/components/modify_processor.py b/itsm/trigger/action/components/modify_processor.py
index 942726829..855af8397 100644
--- a/itsm/trigger/action/components/modify_processor.py
+++ b/itsm/trigger/action/components/modify_processor.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import FLOW_SIGNAL, TRANSITION_SIGNAL, TASK_SIGNAL
from itsm.trigger.action.core.component import BaseComponent
@@ -63,8 +63,8 @@ def _execute(self):
self.data.set_outputs("message", "设置处理人为空")
return False
- dst_state.processors_type = processors['member_type']
- dst_state.processors = processors['members']
+ dst_state.processors_type = processors["member_type"]
+ dst_state.processors = processors["members"]
dst_state.save(update_fields=["processors_type", "processors"])
dst_state.ticket.set_current_processors()
@@ -78,6 +78,6 @@ def update_context(self):
dst_state = Status.objects.get(id=self.context.get("dst_state"))
except Status.DoesNotExist:
return self.context
- self.context.update(dst_state.ticket.get_output_fields(return_format='dict'))
+ self.context.update(dst_state.ticket.get_output_fields(return_format="dict"))
self.validate_inputs()
return self.context
diff --git a/itsm/trigger/action/components/modify_specified_state_processor.py b/itsm/trigger/action/components/modify_specified_state_processor.py
index 8e468fb05..24b1eaebd 100644
--- a/itsm/trigger/action/components/modify_specified_state_processor.py
+++ b/itsm/trigger/action/components/modify_specified_state_processor.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import FLOW_STATES, TASK_SIGNAL
from itsm.trigger.action.core.component import BaseComponent
@@ -69,15 +69,21 @@ def _execute(self):
states = self.data.get_one_of_inputs("states", [])
- all_status = Status.objects.filter(ticket=ticket, status__in=Status.CAN_OPERATE_STATUS, state_id__in=states)
+ all_status = Status.objects.filter(
+ ticket=ticket, status__in=Status.CAN_OPERATE_STATUS, state_id__in=states
+ )
processors = self.data.get_one_of_inputs("processors")
if not processors:
self.data.set_outputs("message", "设置处理人为空")
return False
- all_status.update(processors_type=processors['member_type'], processors=processors["members"])
- self.data.set_outputs("states__display", ",".join(set(all_status.values_list("name", flat=True))))
+ all_status.update(
+ processors_type=processors["member_type"], processors=processors["members"]
+ )
+ self.data.set_outputs(
+ "states__display", ",".join(set(all_status.values_list("name", flat=True)))
+ )
ticket.set_current_processors()
return True
@@ -90,6 +96,6 @@ def update_context(self):
dst_state = Status.objects.get(id=self.context.get("dst_state"))
except Status.DoesNotExist:
return self.context
- self.context.update(dst_state.ticket.get_output_fields(return_format='dict'))
+ self.context.update(dst_state.ticket.get_output_fields(return_format="dict"))
self.validate_inputs()
return self.context
diff --git a/itsm/trigger/action/components/modify_ticket_status.py b/itsm/trigger/action/components/modify_ticket_status.py
index 7f443979b..460232d7b 100644
--- a/itsm/trigger/action/components/modify_ticket_status.py
+++ b/itsm/trigger/action/components/modify_ticket_status.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import FIELD_STATUS
from itsm.trigger.action.core.component import BaseComponent
@@ -37,7 +37,9 @@
def get_ticket_status_names():
"""Get all ticket status display name"""
- status_names = TicketStatus.objects.get_overall_status_names(exclude_keys=["SUSPENDED"])
+ status_names = TicketStatus.objects.get_overall_status_names(
+ exclude_keys=["SUSPENDED"]
+ )
return [{"key": key, "name": name} for key, name in status_names.items()]
@@ -68,20 +70,27 @@ def _execute(self):
dst_status = self.data.get_one_of_inputs("dst_status")
print("dst_status is {}".format(dst_status))
# Whether follow status transit rule
- from_status = TicketStatus.objects.get(service_type=dst_ticket.service_type, key=dst_ticket.current_status)
- all_status_info = from_status.from_transits.values("to_status__key", "to_status__name")
+ from_status = TicketStatus.objects.get(
+ service_type=dst_ticket.service_type, key=dst_ticket.current_status
+ )
+ all_status_info = from_status.from_transits.values(
+ "to_status__key", "to_status__name"
+ )
for to_status_info in all_status_info:
- if to_status_info['to_status__key'] == dst_status:
+ if to_status_info["to_status__key"] == dst_status:
# Update status from ticket
dst_ticket.current_status = dst_status
- dst_ticket.save(update_fields=['current_status'])
+ dst_ticket.save(update_fields=["current_status"])
# Update status from field
dst_ticket.fields.filter(key=FIELD_STATUS).update(_value=dst_status)
return True
# TODO 更新错误信息
- self.data.set_outputs('message', _('工单状态无法更新为%s, 不满足状态流转规则, 请联系管理员! ' % dst_status))
+ self.data.set_outputs(
+ "message",
+ _("工单状态无法更新为%s, 不满足状态流转规则, 请联系管理员! " % dst_status),
+ )
return False
diff --git a/itsm/trigger/action/components/send_message.py b/itsm/trigger/action/components/send_message.py
index fdd34a3ff..3607722e5 100644
--- a/itsm/trigger/action/components/send_message.py
+++ b/itsm/trigger/action/components/send_message.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import WEIXIN, EMAIL, SMS
from itsm.component.notify import (
diff --git a/itsm/trigger/action/components/unbind_parent_child_tickets.py b/itsm/trigger/action/components/unbind_parent_child_tickets.py
index 96d41573b..c8dad72ca 100644
--- a/itsm/trigger/action/components/unbind_parent_child_tickets.py
+++ b/itsm/trigger/action/components/unbind_parent_child_tickets.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import MASTER_SLAVE
from itsm.trigger.action.core.component import BaseComponent
@@ -59,18 +59,25 @@ def _execute(self):
try:
master_ticket = Ticket.objects.get(sn=self.context.get("ticket_sn"))
except Ticket.DoesNotExist:
- self.data.set_outputs("message", _("对应的单据【%s】不存在") % self.context.get("ticket_sn"))
+ self.data.set_outputs(
+ "message", _("对应的单据【%s】不存在") % self.context.get("ticket_sn")
+ )
return False
slave_tickets = [
item.from_ticket
- for item in TicketToTicket.objects.filter(related_type=MASTER_SLAVE, to_ticket=master_ticket)
+ for item in TicketToTicket.objects.filter(
+ related_type=MASTER_SLAVE, to_ticket=master_ticket
+ )
]
if not slave_tickets:
- self.data.set_outputs("message", _("当前的单据【%s】不存在母子关联单") % self.context.get("ticket_sn"))
+ self.data.set_outputs(
+ "message",
+ _("当前的单据【%s】不存在母子关联单") % self.context.get("ticket_sn"),
+ )
return False
- TicketToTicket.objects.filter(related_type=MASTER_SLAVE, to_ticket=master_ticket).update(
- related_status="RUNNING"
- )
+ TicketToTicket.objects.filter(
+ related_type=MASTER_SLAVE, to_ticket=master_ticket
+ ).update(related_status="RUNNING")
slave_tickets_sn = []
for slave_ticket in slave_tickets:
clone_pipeline.apply_async(args=(slave_ticket, master_ticket))
diff --git a/itsm/trigger/action/components/update_ticket_status.py b/itsm/trigger/action/components/update_ticket_status.py
index 7ae6b4734..f6f9401a1 100644
--- a/itsm/trigger/action/components/update_ticket_status.py
+++ b/itsm/trigger/action/components/update_ticket_status.py
@@ -24,7 +24,7 @@
"""
from django import forms
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import FIELD_STATUS
from itsm.component.dlls.component import BaseComponentForm
@@ -38,7 +38,9 @@
def get_ticket_status_names():
"""Get all ticket status display name"""
- status_names = TicketStatus.objects.get_overall_status_names(exclude_keys=["SUSPENDED"])
+ status_names = TicketStatus.objects.get_overall_status_names(
+ exclude_keys=["SUSPENDED"]
+ )
return [(key, name) for key, name in status_names.items()]
@@ -49,7 +51,9 @@ class UpdateTicketStatus(BaseComponent):
is_sub_class = True
class Form(BaseComponentForm):
- ticket_status_name = forms.ChoiceField(label=_("单据状态"), required=True, choices=get_ticket_status_names)
+ ticket_status_name = forms.ChoiceField(
+ label=_("单据状态"), required=True, choices=get_ticket_status_names
+ )
def clean(self):
"""Form data clean"""
@@ -62,19 +66,29 @@ def _execute(self):
ticket = Ticket.objects.get(id=ticket_id)
# Whether follow status transit rule
- from_status = TicketStatus.objects.get(service_type=ticket.service_type, key=ticket.current_status)
- to_status_infos = from_status.from_transits.values("to_status__key", "to_status__name")
+ from_status = TicketStatus.objects.get(
+ service_type=ticket.service_type, key=ticket.current_status
+ )
+ to_status_infos = from_status.from_transits.values(
+ "to_status__key", "to_status__name"
+ )
for to_status_info in to_status_infos:
- if to_status_info['to_status__key'] == ticket_status_key:
+ if to_status_info["to_status__key"] == ticket_status_key:
# Update status from ticket
ticket.current_status = ticket_status_key
- ticket.save(update_fields=['current_status'])
+ ticket.save(update_fields=["current_status"])
# Update status from field
ticket.fields.filter(key=FIELD_STATUS).update(_value=ticket_status_key)
return True
- self.data.set_outputs('message', _('工单状态无法更新为%s, 不满足状态流转规则, 请联系管理员! ' % ticket_status_key))
+ self.data.set_outputs(
+ "message",
+ _(
+ "工单状态无法更新为%s, 不满足状态流转规则, 请联系管理员! "
+ % ticket_status_key
+ ),
+ )
return False
diff --git a/itsm/trigger/action/core/component.py b/itsm/trigger/action/core/component.py
index 75409fa82..6a65c730e 100644
--- a/itsm/trigger/action/core/component.py
+++ b/itsm/trigger/action/core/component.py
@@ -24,7 +24,7 @@
"""
import traceback
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django.forms.fields import CallableChoiceIterator
from django.forms.forms import DeclarativeFieldsMetaclass
@@ -39,9 +39,9 @@ class BaseComponent(metaclass=BaseComponentMeta): # noqa
Base class for component
"""
- name = 'UNKNOWN' # display name
- code = 'unknown' # 在trigger组件里唯一
- type = 'trigger'
+ name = "UNKNOWN" # display name
+ code = "unknown" # 在trigger组件里唯一
+ type = "trigger"
Form = None
is_async = True
need_refresh = True
@@ -53,7 +53,11 @@ def __init__(self, context, params_schema, action_id=None, countdown=0):
self.params_schema = params_schema
self.action_id = action_id
self.countdown = countdown
- self.form = self.form_class(self.params_schema, self.context) if self.form_class else None
+ self.form = (
+ self.form_class(self.params_schema, self.context)
+ if self.form_class
+ else None
+ )
self.validate_inputs()
def invoke(self, inputs):
@@ -115,16 +119,18 @@ def get_inputs(cls):
# Whether define form data
if isinstance(cls.Form, DeclarativeFieldsMetaclass):
for field_name, field in cls.Form.declared_fields.items():
- choices = getattr(field, "choices") if hasattr(field, "choices") else None
+ choices = (
+ getattr(field, "choices") if hasattr(field, "choices") else None
+ )
if isinstance(choices, CallableChoiceIterator):
choices = choices.choices_func()
input_data = {
- 'name': _(field_name),
- 'label': _(field.label),
- 'initial': field.initial,
- 'required': field.required,
- 'choices': choices,
+ "name": _(field_name),
+ "label": _(field.label),
+ "initial": field.initial,
+ "required": field.required,
+ "choices": choices,
}
inputs.append(input_data)
diff --git a/itsm/trigger/action/core/field.py b/itsm/trigger/action/core/field.py
index 60bd8868d..fb90bf559 100644
--- a/itsm/trigger/action/core/field.py
+++ b/itsm/trigger/action/core/field.py
@@ -28,14 +28,19 @@
import copy
import datetime
import re
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django.core.exceptions import ValidationError
from mako.template import Template
from common.log import logger
from itsm.role.models import UserRole
from itsm.postman.models import RemoteApi
-from itsm.component.constants import DEFAULT_BK_BIZ_ID, EMPTY_DICT, PROCESSOR_CHOICES, PERSON
+from itsm.component.constants import (
+ DEFAULT_BK_BIZ_ID,
+ EMPTY_DICT,
+ PROCESSOR_CHOICES,
+ PERSON,
+)
from itsm.component.utils.basic import list_by_separator, dotted_name, normal_name
VAR_STR_MATCH = re.compile(r"\$\{\s*[\w\|]+\s*\}")
@@ -47,7 +52,7 @@ class BaseField:
"""
enabled_field_types = []
- default_field_type = 'STRING'
+ default_field_type = "STRING"
def __init__(
self,
@@ -64,7 +69,7 @@ def __init__(
is_tips=False,
tips="",
):
- """
+ """
{
"type": "STRING|TEXT|SELECT|MULTISELECT|MEMBERS|",
"choice": [],
@@ -78,7 +83,7 @@ def __init__(
# 代码规范兼容
if choice is None:
choice = []
-
+
self.type = self.default_field_type
self.choice = choice
self.name = name
@@ -112,7 +117,11 @@ def to_representation_data(self, value, context=None, **kwargs):
return clean_data
def set_field_type(self, field_type):
- self.type = field_type if field_type in self.enabled_field_types else self.default_field_type
+ self.type = (
+ field_type
+ if field_type in self.enabled_field_types
+ else self.default_field_type
+ )
def get_field_schema(self, key):
return {
@@ -135,7 +144,7 @@ def get_field_schema(self, key):
class ApiSourceField(BaseField):
enabled_field_types = ["API"]
- default_field_type = 'API'
+ default_field_type = "API"
def validate(self, value):
"""根据字段内容进行校验"""
@@ -148,8 +157,8 @@ def validate(self, value):
def to_internal_data(self, value, context=None, **kwargs):
"""
:param value: 输入值
- :param context:
- :return:
+ :param context:
+ :return:
"""
parse_tool = ParamParseTool(context)
return self.validate(parse_tool(param=value, **kwargs))
@@ -157,7 +166,7 @@ def to_internal_data(self, value, context=None, **kwargs):
class ApiInfoField(BaseField):
enabled_field_types = ["API_INFO"]
- default_field_type = 'API_INFO'
+ default_field_type = "API_INFO"
def validate(self, value):
"""根据字段内容进行校验"""
@@ -167,9 +176,9 @@ def validate(self, value):
def to_internal_data(self, value, context=None, **kwargs):
"""
根据输入格式来对数据做渲染
- :param value:
- :param context:
- :return:
+ :param value:
+ :param context:
+ :return:
"""
parse_tool = ParamParseTool(context)
@@ -212,11 +221,11 @@ def _list_clean(list_value):
return clean_data
def _datetime_clean(_value):
- return _value.strftime('%Y-%m-%d %H:%M:%S')
+ return _value.strftime("%Y-%m-%d %H:%M:%S")
def _date_clean(_value):
- return _value.strftime('%Y-%m-%d')
-
+ return _value.strftime("%Y-%m-%d")
+
def _leaf_clean(_value):
result = self.validate(parse_tool(param=_value, **kwargs))
if isinstance(result, datetime.datetime):
@@ -242,7 +251,7 @@ class MemberField(BaseField):
"""
enabled_field_types = ["MEMBERS", "MULTI_MEMBERS"]
- default_field_type = 'MEMBERS'
+ default_field_type = "MEMBERS"
def __init__(self, name, **kwargs):
self.convert_to_users = kwargs.pop("convert_to_users", True)
@@ -255,9 +264,9 @@ def validate(self):
def to_internal_data(self, value, context=None, **kwargs):
if self.convert_to_users:
- return self._convert_to_users(value['value'], context, **kwargs)
+ return self._convert_to_users(value["value"], context, **kwargs)
else:
- members = self._direct_convert(value['value'], context, **kwargs)
+ members = self._direct_convert(value["value"], context, **kwargs)
if kwargs.get("display"):
return self.display_convert(members)
@@ -271,10 +280,14 @@ def _convert_to_users(member_value, context, **kwargs):
bk_biz_id = context.get("bk_biz_id", DEFAULT_BK_BIZ_ID)
for member in copy.deepcopy(member_value):
member["value"] = UserRole.get_users_by_type(
- bk_biz_id, member['value']['member_type'], member['value']['members']
+ bk_biz_id, member["value"]["member_type"], member["value"]["members"]
)
parse_people = parse_tool(param=member)
- members.extend(parse_people if isinstance(parse_people, list) else list_by_separator(parse_people))
+ members.extend(
+ parse_people
+ if isinstance(parse_people, list)
+ else list_by_separator(parse_people)
+ )
return members
def _direct_convert(self, member_value, context, **kwargs):
@@ -282,14 +295,17 @@ def _direct_convert(self, member_value, context, **kwargs):
members = []
parse_tool = ParamParseTool(context)
for member in member_value:
- parse_value = {"ref_type": member['ref_type'], "value": member['value']['members']}
- member['value']['members'] = dotted_name(parse_tool(param=parse_value))
- if member['value']['member_type'] in ['VARIABLE', "EMPTY"]:
- member['value']['member_type'] = 'PERSON'
- if self.type == 'MEMBERS':
- members = member['value']
+ parse_value = {
+ "ref_type": member["ref_type"],
+ "value": member["value"]["members"],
+ }
+ member["value"]["members"] = dotted_name(parse_tool(param=parse_value))
+ if member["value"]["member_type"] in ["VARIABLE", "EMPTY"]:
+ member["value"]["member_type"] = "PERSON"
+ if self.type == "MEMBERS":
+ members = member["value"]
break
- members.append(member['value'])
+ members.append(member["value"])
return members
def display_convert(self, members):
@@ -304,13 +320,15 @@ def display_convert(self, members):
def get_members_display(member):
members_type = dict(PROCESSOR_CHOICES)
- if member['member_type'] == PERSON:
- display_value = normal_name(member['members'])
+ if member["member_type"] == PERSON:
+ display_value = normal_name(member["members"])
else:
display_value = ",".join(
- UserRole.objects.filter(id__in=list_by_separator(member['members'])).values_list("name", flat=True)
+ UserRole.objects.filter(
+ id__in=list_by_separator(member["members"])
+ ).values_list("name", flat=True)
)
- return ("{}:{}").format(members_type.get(member['member_type']), display_value)
+ return ("{}:{}").format(members_type.get(member["member_type"]), display_value)
class SelectField(BaseField):
@@ -319,7 +337,7 @@ class SelectField(BaseField):
"""
enabled_field_types = ["SELECT", "RADIO"]
- default_field_type = 'SELECT'
+ default_field_type = "SELECT"
def validate(self):
return True
@@ -336,7 +354,7 @@ class MultiSelectField(BaseField):
"""多项选择字段"""
enabled_field_types = ["MULTISELECT", "CHECKBOX"]
- default_field_type = 'MULTISELECT'
+ default_field_type = "MULTISELECT"
def validate(self):
return True
@@ -355,14 +373,14 @@ class StringField(BaseField):
"""
enabled_field_types = ["STRING", "TEXT"]
- default_field_type = 'STRING'
+ default_field_type = "STRING"
def validate(self):
return True
def to_internal_data(self, value, context=None, **kwargs):
"""
- {
+ {
"key": "content",
"value": "工单编号:${sn} 标题:${title}",
"ref_type": "import"
@@ -421,8 +439,8 @@ def to_internal_data(self, value, context=None, **kwargs):
class SubComponentField(BaseField):
"""
- 时间格式
- """
+ 时间格式
+ """
enabled_field_types = ["SUBCOMPONENT"]
default_field_type = "SUBCOMPONENT"
@@ -449,22 +467,28 @@ def to_representation_data(self, value, context=None, sub_actions=None, flat=Fal
clean_data = sub_action.to_representation_data()
if flat:
sub_component_data.extend(clean_data)
- sub_component_data.append({"code": sub_action.code, "name": sub_action.name, "fields": clean_data})
+ sub_component_data.append(
+ {"code": sub_action.code, "name": sub_action.name, "fields": clean_data}
+ )
return sub_component_data
def get_field_schema(self, key):
"""
- 子响应函数组的格式返回
- :param key:
- :return:
- """
+ 子响应函数组的格式返回
+ :param key:
+ :return:
+ """
schema = super(SubComponentField, self).get_field_schema(key)
- schema['sub_components'] = []
+ schema["sub_components"] = []
for _component in self.sub_components:
sub_component_field_schema = _component.get_inputs()
- schema['sub_components'].append(
- dict(key=_component.code, name=_component.name, field_schema=sub_component_field_schema)
+ schema["sub_components"].append(
+ dict(
+ key=_component.code,
+ name=_component.name,
+ field_schema=sub_component_field_schema,
+ )
)
return schema
@@ -496,11 +520,13 @@ def __init__(self, context):
def __call__(self, *args, **kwargs):
param = kwargs.get("param", EMPTY_DICT)
- parse_method = getattr(self, "{}_parse".format(param.get('ref_type', 'direct')), self.direct_parse)
+ parse_method = getattr(
+ self, "{}_parse".format(param.get("ref_type", "direct")), self.direct_parse
+ )
if parse_method is None:
- return param['value']
+ return param["value"]
kwargs.update(param_key=param.get("key"))
- return parse_method(param['value'], **kwargs)
+ return parse_method(param["value"], **kwargs)
def import_parse(self, value, **kwargs):
try:
@@ -516,7 +542,11 @@ def reference_parse(self, value, **kwargs):
display = kwargs.get("display", False)
if len(reference_keys) == 1:
# 只引用了一个参数的使用
- key = "{}__display".format(reference_keys[0]) if display else reference_keys[0]
+ key = (
+ "{}__display".format(reference_keys[0])
+ if display
+ else reference_keys[0]
+ )
return self.context.get(key) or self.context.get(reference_keys[0])
if display:
return ",".join(
@@ -526,7 +556,9 @@ def reference_parse(self, value, **kwargs):
if key in self.context
]
)
- return ",".join([self.context.get(key) for key in reference_keys if key in self.context])
+ return ",".join(
+ [self.context.get(key) for key in reference_keys if key in self.context]
+ )
def direct_parse(self, value, **kwargs):
if isinstance(value, str) and VAR_STR_MATCH.findall(value):
@@ -534,5 +566,7 @@ def direct_parse(self, value, **kwargs):
display = kwargs.get("display", False)
if display:
- return self.context.get("{}__display".format(kwargs.get("param_key"))) or value
+ return (
+ self.context.get("{}__display".format(kwargs.get("param_key"))) or value
+ )
return value
diff --git a/itsm/trigger/models/action.py b/itsm/trigger/models/action.py
index b3dbe47ce..696b74071 100644
--- a/itsm/trigger/models/action.py
+++ b/itsm/trigger/models/action.py
@@ -29,7 +29,7 @@
import jsonfield
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from mako.template import Template
from itsm.component.utils.client_backend_query import get_bk_users
@@ -110,10 +110,14 @@ class Action(TriggerBaseModel):
end_time = models.DateTimeField(_("任务结束事件"), null=True)
operator = models.CharField(_("执行人"), max_length=LEN_NORMAL, default=SYS)
ex_data = jsonfield.JSONField(
- _("执行错误信息"), help_text=_("状态为失败的时候记录的错误日志"), default=EMPTY_DICT
+ _("执行错误信息"),
+ help_text=_("状态为失败的时候记录的错误日志"),
+ default=EMPTY_DICT,
)
- params = jsonfield.JSONField(_("执行的参数"), help_text=_("手动触发器实际执行的参数信息"), default={})
+ params = jsonfield.JSONField(
+ _("执行的参数"), help_text=_("手动触发器实际执行的参数信息"), default={}
+ )
objects = ActionManagers()
diff --git a/itsm/trigger/models/base.py b/itsm/trigger/models/base.py
index 87aa75301..51d82a0a2 100644
--- a/itsm/trigger/models/base.py
+++ b/itsm/trigger/models/base.py
@@ -24,7 +24,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.db import managers
from itsm.component.constants import LEN_NORMAL
diff --git a/itsm/trigger/models/trigger.py b/itsm/trigger/models/trigger.py
index 0bcc37585..fb3bd4d23 100644
--- a/itsm/trigger/models/trigger.py
+++ b/itsm/trigger/models/trigger.py
@@ -25,7 +25,7 @@
import datetime
from itertools import chain
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
import jsonfield
from itsm.component.dlls.component import ComponentLibrary
@@ -77,8 +77,12 @@ class ActionSchema(TriggerBaseModel):
)
can_repeat = models.BooleanField(_("是否可以重复执行"), default=False)
- params = jsonfield.JSONField(_("配置参数"), help_text=_("当前响应事件配置的参数模版"), default={})
- inputs = jsonfield.JSONField(_("输入参数"), help_text=_("输入参数引用的参数变量"), default={})
+ params = jsonfield.JSONField(
+ _("配置参数"), help_text=_("当前响应事件配置的参数模版"), default={}
+ )
+ inputs = jsonfield.JSONField(
+ _("输入参数"), help_text=_("输入参数引用的参数变量"), default={}
+ )
class Meta:
verbose_name = _("响应动作参数配置表")
@@ -135,7 +139,9 @@ class Trigger(TriggerBaseModel):
signal = models.CharField(_("触发事件信号"), null=False, max_length=LEN_MIDDLE)
sender = models.CharField(
- _("触发对象"), help_text=_("一般为触发该信号的实际对象模型id"), max_length=LEN_NORMAL
+ _("触发对象"),
+ help_text=_("一般为触发该信号的实际对象模型id"),
+ max_length=LEN_NORMAL,
)
# inputs 好像暂时没需要
@@ -157,7 +163,10 @@ class Trigger(TriggerBaseModel):
is_draft = models.BooleanField(_("是否为草稿"), default=True)
is_enabled = models.BooleanField(_("是否可启用"), default=False)
icon = models.CharField(
- _("对应的icon"), default=EMPTY_STRING, choices=TRIGGER_ICON_CHOICE, max_length=64
+ _("对应的icon"),
+ default=EMPTY_STRING,
+ choices=TRIGGER_ICON_CHOICE,
+ max_length=64,
)
project_key = models.CharField(
_("项目key"), max_length=LEN_SHORT, null=False, default=0
@@ -179,17 +188,19 @@ def __unicode__(self):
def trigger_rules(self, source_type, source_id):
return [
{
- "conditions": item.condition
- if item.by_condition
- else {
- "all": [
- {
- "name": "constant_bool_true",
- "operator": "is_true",
- "value": True,
- }
- ]
- },
+ "conditions": (
+ item.condition
+ if item.by_condition
+ else {
+ "all": [
+ {
+ "name": "constant_bool_true",
+ "operator": "is_true",
+ "value": True,
+ }
+ ]
+ }
+ ),
"actions": [
{
"name": "trigger_handle",
diff --git a/itsm/trigger/signal/signals.py b/itsm/trigger/signal/signals.py
index c88cd4d3f..020fe6c0e 100644
--- a/itsm/trigger/signal/signals.py
+++ b/itsm/trigger/signal/signals.py
@@ -31,5 +31,5 @@
# 统一用一个信号来接收,然后统一分配具体的事项
trigger_signal = TriggerSignal()
-action_finish = Signal(providing_args=("action_id", "result", "error_message"))
-post_action_finish = Signal(providing_args=("instance",))
+action_finish = Signal() # providing_args=("action_id", "result", "error_message")
+post_action_finish = Signal() # providing_args=("instance",)
diff --git a/itsm/trigger/tasks.py b/itsm/trigger/tasks.py
index 43534ffc3..3096568f4 100644
--- a/itsm/trigger/tasks.py
+++ b/itsm/trigger/tasks.py
@@ -27,9 +27,9 @@
__copyright__ = "Copyright © 2012-2020 Tencent BlueKing. All Rights Reserved."
-from celery import task
+from celery import shared_task
-@task
+@shared_task
def async_execute_action(action):
action.execute()
diff --git a/itsm/trigger/urls.py b/itsm/trigger/urls.py
index 69aace4e5..6b9d3342d 100644
--- a/itsm/trigger/urls.py
+++ b/itsm/trigger/urls.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.conf.urls import url
+from django.urls import re_path
from rest_framework.routers import DefaultRouter
from itsm.trigger.views import (
@@ -41,4 +41,4 @@
routers.register(r"actions", ActionViewSet, basename="actions")
-urlpatterns = routers.urls + [url(r'^components/$', ComponentApiViewSet.as_view())]
+urlpatterns = routers.urls + [re_path(r"^components/$", ComponentApiViewSet.as_view())]
diff --git a/itsm/trigger/validators.py b/itsm/trigger/validators.py
index 1cce0d16d..a4e20805f 100644
--- a/itsm/trigger/validators.py
+++ b/itsm/trigger/validators.py
@@ -24,7 +24,7 @@
"""
from common.log import logger
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.drf.exception import ValidationError
from itsm.component.exceptions import ComponentNotExist
from itsm.trigger.models import Trigger, ActionSchema
@@ -40,8 +40,8 @@ def __init__(self, instance=None):
def __call__(self, value):
if not self.instance:
- self.source_id = value['source_id']
- self.source_type = value['source_type']
+ self.source_id = value["source_id"]
+ self.source_type = value["source_type"]
self.name_validate(value)
def name_validate(self, value):
@@ -50,7 +50,7 @@ def name_validate(self, value):
统一类型统一来源对象的name必须唯一
"""
trigger_query_set = Trigger.objects.filter(
- name=value['name'], source_type=self.source_type, source_id=self.source_id
+ name=value["name"], source_type=self.source_type, source_id=self.source_id
)
if self.instance and trigger_query_set.exclude(id=self.instance.id).exists():
raise ValidationError(_("存在其他相同名称的触发器,请修改后再提交"))
@@ -70,16 +70,20 @@ def name_validate(rules):
名称的唯一性校验
同一个触发器下面如果名称不为空,则不允许相同
"""
- all_name = [rule['name'] for rule in rules if rule.get("name")]
+ all_name = [rule["name"] for rule in rules if rule.get("name")]
if len(set(all_name)) < len(all_name):
raise ValidationError(_("同一个触发器下的规则名称不能重复"))
@staticmethod
def action_schemas_exist_validate(rules):
for index, rule in enumerate(rules):
- action_schemas = rule['action_schemas']
- if ActionSchema.objects.filter(id__in=action_schemas).count() < len(action_schemas):
- raise ValidationError(_("第{}个规则下的响应事件部分不存在").format(index + 1))
+ action_schemas = rule["action_schemas"]
+ if ActionSchema.objects.filter(id__in=action_schemas).count() < len(
+ action_schemas
+ ):
+ raise ValidationError(
+ _("第{}个规则下的响应事件部分不存在").format(index + 1)
+ )
class ActionSchemaValidator:
@@ -95,11 +99,17 @@ def __call__(self, value):
def component_validate(self, value):
try:
- component_class = ComponentLibrary.get_component_class("trigger", component_code=value["component_type"])
+ component_class = ComponentLibrary.get_component_class(
+ "trigger", component_code=value["component_type"]
+ )
except ComponentNotExist:
raise ValidationError("非法的组件类型,请确认组件是否选择正确")
except BaseException as error:
- logger.exception("校验错误,instance id {}".format(self.instance.id if self.instance else "None"))
+ logger.exception(
+ "校验错误,instance id {}".format(
+ self.instance.id if self.instance else "None"
+ )
+ )
raise ValidationError("组件异常错误:{}".format(str(error)))
- component_class(value['params']).validate_params()
+ component_class(value["params"]).validate_params()
diff --git a/itsm/trigger/views.py b/itsm/trigger/views.py
index ffd1cf7e2..b454f857c 100644
--- a/itsm/trigger/views.py
+++ b/itsm/trigger/views.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from django.db import transaction
from rest_framework import status
from rest_framework.response import Response
@@ -239,7 +239,7 @@ def _single_update(action_data, instance):
if not rule.action_schemas:
continue
actions_schemas.extend(rule.action_schemas)
-
+
schemas = []
with transaction.atomic():
for _data in request.data:
diff --git a/itsm/workflow/backend.py b/itsm/workflow/backend.py
index eeafbb1a6..8c17d7b0b 100644
--- a/itsm/workflow/backend.py
+++ b/itsm/workflow/backend.py
@@ -34,7 +34,7 @@
import time
import six
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
COVERAGE_STATE,
@@ -102,7 +102,9 @@ def build_tree_exception_handler(self, state):
except Exception as error:
# 记录代码底层报错
logger.exception(error)
- raise WorkFlowInvalidError([state["id"]], _("当前节点画布连线不合理, 请重新确认"))
+ raise WorkFlowInvalidError(
+ [state["id"]], _("当前节点画布连线不合理, 请重新确认")
+ )
def _unpack_state(self, states, state_id):
"""状态提取"""
@@ -387,7 +389,9 @@ def build_conditions_for_gateway(self, outgoings):
evaluation = "1==1"
else:
if exp.type not in SUPPORTED_TYPE:
- raise NotImplementedError(_("不支持的数据类型 %s") % exp.type)
+ raise NotImplementedError(
+ _("不支持的数据类型 %s") % exp.type
+ )
template = get_exp_template(exp.type)
value = format_exp_value(exp.type, exp.value)
evaluation = template.format(
diff --git a/itsm/workflow/migrations/0025_auto_20200403_1739.py b/itsm/workflow/migrations/0025_auto_20200403_1739.py
index 5eb11773d..6a3be2ef8 100644
--- a/itsm/workflow/migrations/0025_auto_20200403_1739.py
+++ b/itsm/workflow/migrations/0025_auto_20200403_1739.py
@@ -32,138 +32,157 @@
class Migration(migrations.Migration):
dependencies = [
- ('workflow', '0024_auto_20200310_1709'),
+ ("workflow", "0024_auto_20200310_1709"),
]
operations = [
migrations.AlterField(
- model_name='defaultfield',
- name='source_type',
+ model_name="defaultfield",
+ name="source_type",
field=models.CharField(
- choices=[('CUSTOM', '自定义数据'), ('API', '接口数据'), ('DATADICT', '数据字典'), ('RPC', '系统数据')],
- default='CUSTOM',
+ choices=[
+ ("CUSTOM", "自定义数据"),
+ ("API", "接口数据"),
+ ("DATADICT", "数据字典"),
+ ("RPC", "系统数据"),
+ ],
+ default="CUSTOM",
max_length=32,
- verbose_name='数据来源类型',
+ verbose_name="数据来源类型",
),
),
migrations.AlterField(
- model_name='defaultfield',
- name='type',
+ model_name="defaultfield",
+ name="type",
field=models.CharField(
choices=[
- ('STRING', '单行文本'),
- ('TEXT', '多行文本'),
- ('INT', '数字'),
- ('DATE', '日期'),
- ('DATETIME', '时间'),
- ('DATETIMERANGE', '时间间隔'),
- ('TABLE', '表格'),
- ('SELECT', '单选下拉框'),
- ('MULTISELECT', '多选下拉框'),
- ('CHECKBOX', '复选框'),
- ('RADIO', '单选框'),
- ('MEMBER', '单选人员选择'),
- ('MEMBERS', '多选人员选择'),
- ('RICHTEXT', '富文本'),
- ('FILE', '附件上传'),
- ('CUSTOMTABLE', '自定义表格'),
- ('TREESELECT', '树形选择'),
- ('LINK', '链接'),
- ('CASCADE', '级联'),
+ ("STRING", "单行文本"),
+ ("TEXT", "多行文本"),
+ ("INT", "数字"),
+ ("DATE", "日期"),
+ ("DATETIME", "时间"),
+ ("DATETIMERANGE", "时间间隔"),
+ ("TABLE", "表格"),
+ ("SELECT", "单选下拉框"),
+ ("MULTISELECT", "多选下拉框"),
+ ("CHECKBOX", "复选框"),
+ ("RADIO", "单选框"),
+ ("MEMBER", "单选人员选择"),
+ ("MEMBERS", "多选人员选择"),
+ ("RICHTEXT", "富文本"),
+ ("FILE", "附件上传"),
+ ("CUSTOMTABLE", "自定义表格"),
+ ("TREESELECT", "树形选择"),
+ ("LINK", "链接"),
+ ("CASCADE", "级联"),
],
- default='STRING',
+ default="STRING",
max_length=32,
- verbose_name='字段类型',
+ verbose_name="字段类型",
),
),
migrations.AlterField(
- model_name='field',
- name='source_type',
+ model_name="field",
+ name="source_type",
field=models.CharField(
- choices=[('CUSTOM', '自定义数据'), ('API', '接口数据'), ('DATADICT', '数据字典'), ('RPC', '系统数据')],
- default='CUSTOM',
+ choices=[
+ ("CUSTOM", "自定义数据"),
+ ("API", "接口数据"),
+ ("DATADICT", "数据字典"),
+ ("RPC", "系统数据"),
+ ],
+ default="CUSTOM",
max_length=32,
- verbose_name='数据来源类型',
+ verbose_name="数据来源类型",
),
),
migrations.AlterField(
- model_name='field',
- name='type',
+ model_name="field",
+ name="type",
field=models.CharField(
choices=[
- ('STRING', '单行文本'),
- ('TEXT', '多行文本'),
- ('INT', '数字'),
- ('DATE', '日期'),
- ('DATETIME', '时间'),
- ('DATETIMERANGE', '时间间隔'),
- ('TABLE', '表格'),
- ('SELECT', '单选下拉框'),
- ('MULTISELECT', '多选下拉框'),
- ('CHECKBOX', '复选框'),
- ('RADIO', '单选框'),
- ('MEMBER', '单选人员选择'),
- ('MEMBERS', '多选人员选择'),
- ('RICHTEXT', '富文本'),
- ('FILE', '附件上传'),
- ('CUSTOMTABLE', '自定义表格'),
- ('TREESELECT', '树形选择'),
- ('LINK', '链接'),
- ('CASCADE', '级联'),
+ ("STRING", "单行文本"),
+ ("TEXT", "多行文本"),
+ ("INT", "数字"),
+ ("DATE", "日期"),
+ ("DATETIME", "时间"),
+ ("DATETIMERANGE", "时间间隔"),
+ ("TABLE", "表格"),
+ ("SELECT", "单选下拉框"),
+ ("MULTISELECT", "多选下拉框"),
+ ("CHECKBOX", "复选框"),
+ ("RADIO", "单选框"),
+ ("MEMBER", "单选人员选择"),
+ ("MEMBERS", "多选人员选择"),
+ ("RICHTEXT", "富文本"),
+ ("FILE", "附件上传"),
+ ("CUSTOMTABLE", "自定义表格"),
+ ("TREESELECT", "树形选择"),
+ ("LINK", "链接"),
+ ("CASCADE", "级联"),
],
- default='STRING',
+ default="STRING",
max_length=32,
- verbose_name='字段类型',
+ verbose_name="字段类型",
),
),
migrations.AlterField(
- model_name='templatefield',
- name='source_type',
+ model_name="templatefield",
+ name="source_type",
field=models.CharField(
- choices=[('CUSTOM', '自定义数据'), ('API', '接口数据'), ('DATADICT', '数据字典'), ('RPC', '系统数据')],
- default='CUSTOM',
+ choices=[
+ ("CUSTOM", "自定义数据"),
+ ("API", "接口数据"),
+ ("DATADICT", "数据字典"),
+ ("RPC", "系统数据"),
+ ],
+ default="CUSTOM",
max_length=32,
- verbose_name='数据来源类型',
+ verbose_name="数据来源类型",
),
),
migrations.AlterField(
- model_name='templatefield',
- name='type',
+ model_name="templatefield",
+ name="type",
field=models.CharField(
choices=[
- ('STRING', '单行文本'),
- ('TEXT', '多行文本'),
- ('INT', '数字'),
- ('DATE', '日期'),
- ('DATETIME', '时间'),
- ('DATETIMERANGE', '时间间隔'),
- ('TABLE', '表格'),
- ('SELECT', '单选下拉框'),
- ('MULTISELECT', '多选下拉框'),
- ('CHECKBOX', '复选框'),
- ('RADIO', '单选框'),
- ('MEMBER', '单选人员选择'),
- ('MEMBERS', '多选人员选择'),
- ('RICHTEXT', '富文本'),
- ('FILE', '附件上传'),
- ('CUSTOMTABLE', '自定义表格'),
- ('TREESELECT', '树形选择'),
- ('LINK', '链接'),
- ('CASCADE', '级联'),
+ ("STRING", "单行文本"),
+ ("TEXT", "多行文本"),
+ ("INT", "数字"),
+ ("DATE", "日期"),
+ ("DATETIME", "时间"),
+ ("DATETIMERANGE", "时间间隔"),
+ ("TABLE", "表格"),
+ ("SELECT", "单选下拉框"),
+ ("MULTISELECT", "多选下拉框"),
+ ("CHECKBOX", "复选框"),
+ ("RADIO", "单选框"),
+ ("MEMBER", "单选人员选择"),
+ ("MEMBERS", "多选人员选择"),
+ ("RICHTEXT", "富文本"),
+ ("FILE", "附件上传"),
+ ("CUSTOMTABLE", "自定义表格"),
+ ("TREESELECT", "树形选择"),
+ ("LINK", "链接"),
+ ("CASCADE", "级联"),
],
- default='STRING',
+ default="STRING",
max_length=32,
- verbose_name='字段类型',
+ verbose_name="字段类型",
),
),
migrations.AlterField(
- model_name='workflow',
- name='is_task_needed',
- field=models.NullBooleanField(default=False, verbose_name='是否需要关联子任务'),
+ model_name="workflow",
+ name="is_task_needed",
+ field=models.BooleanField(
+ default=False, verbose_name="是否需要关联子任务", null=True
+ ),
),
migrations.AlterField(
- model_name='workflowversion',
- name='is_task_needed',
- field=models.NullBooleanField(default=False, verbose_name='是否需要关联子任务'),
+ model_name="workflowversion",
+ name="is_task_needed",
+ field=models.BooleanField(
+ default=False, verbose_name="是否需要关联子任务", null=True
+ ),
),
]
diff --git a/itsm/workflow/models/base.py b/itsm/workflow/models/base.py
index 39e18ab13..b5a081d41 100644
--- a/itsm/workflow/models/base.py
+++ b/itsm/workflow/models/base.py
@@ -24,7 +24,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import LEN_NORMAL
from itsm.workflow import managers
@@ -33,12 +33,16 @@
class Model(models.Model):
"""基础字段"""
- FIELDS = ('creator', 'create_at', 'updated_by', 'update_at', 'end_at')
+ FIELDS = ("creator", "create_at", "updated_by", "update_at", "end_at")
- creator = models.CharField(_("创建人"), max_length=LEN_NORMAL, null=True, blank=True)
+ creator = models.CharField(
+ _("创建人"), max_length=LEN_NORMAL, null=True, blank=True
+ )
create_at = models.DateTimeField(_("创建时间"), auto_now_add=True)
update_at = models.DateTimeField(_("更新时间"), auto_now=True)
- updated_by = models.CharField(_("修改人"), max_length=LEN_NORMAL, null=True, blank=True)
+ updated_by = models.CharField(
+ _("修改人"), max_length=LEN_NORMAL, null=True, blank=True
+ )
end_at = models.DateTimeField(_("结束时间"), null=True, blank=True)
is_deleted = models.BooleanField(_("是否软删除"), default=False, db_index=True)
@@ -49,7 +53,7 @@ class Model(models.Model):
resource_operations = ["system_settings_manage"]
class Meta:
- app_label = 'workflow'
+ app_label = "workflow"
abstract = True
def delete(self, using=None):
diff --git a/itsm/workflow/models/common.py b/itsm/workflow/models/common.py
index e68a82c6a..44795bedf 100644
--- a/itsm/workflow/models/common.py
+++ b/itsm/workflow/models/common.py
@@ -26,7 +26,7 @@
import jsonfield
from django.conf import settings
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
EMPTY_DICT,
@@ -49,7 +49,10 @@ class Notify(models.Model):
is_builtin = models.BooleanField(_("是否为系统内置"), default=False)
type = models.CharField(_("通知渠道"), max_length=LEN_SHORT, default="EMAIL")
template = models.TextField(
- _("通知模板:可使用变量如下:xxx(TODO)"), default=EMPTY_STRING, null=True, blank=True
+ _("通知模板:可使用变量如下:xxx(TODO)"),
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
class Meta:
diff --git a/itsm/workflow/models/deprecated.py b/itsm/workflow/models/deprecated.py
index eb3d6847e..6df81a8ed 100644
--- a/itsm/workflow/models/deprecated.py
+++ b/itsm/workflow/models/deprecated.py
@@ -25,7 +25,7 @@
import jsonfield
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
DEFAULT_STRING,
@@ -43,28 +43,40 @@
class WorkflowSnap(models.Model):
"""实例化工作流
- 废弃:迁移至WorkflowVersion
+ 废弃:迁移至WorkflowVersion
"""
snapshot_time = models.DateTimeField(_("快照创建时间"), auto_now_add=True)
workflow_id = models.IntegerField(_("工作流ID"))
- fields = jsonfield.JSONField(_("字段快照字典"), default=EMPTY_DICT, null=True, blank=True)
- states = jsonfield.JSONField(_("状态快照字典"), default=EMPTY_DICT, null=True, blank=True)
- transitions = jsonfield.JSONField(_("流转快照字典"), default=EMPTY_DICT, null=True, blank=True)
+ fields = jsonfield.JSONField(
+ _("字段快照字典"), default=EMPTY_DICT, null=True, blank=True
+ )
+ states = jsonfield.JSONField(
+ _("状态快照字典"), default=EMPTY_DICT, null=True, blank=True
+ )
+ transitions = jsonfield.JSONField(
+ _("流转快照字典"), default=EMPTY_DICT, null=True, blank=True
+ )
# 记录主分支数据
- master = jsonfield.JSONField(_("主分支列表"), default=EMPTY_LIST, null=True, blank=True)
-
- notify = models.ManyToManyField('workflow.Notify', help_text=_("可关联多种通知方式"))
- notify_rule = models.CharField(_("通知规则"), max_length=LEN_SHORT, choices=NOTIFY_RULE_CHOICES, default="NONE")
+ master = jsonfield.JSONField(
+ _("主分支列表"), default=EMPTY_LIST, null=True, blank=True
+ )
+
+ notify = models.ManyToManyField(
+ "workflow.Notify", help_text=_("可关联多种通知方式")
+ )
+ notify_rule = models.CharField(
+ _("通知规则"), max_length=LEN_SHORT, choices=NOTIFY_RULE_CHOICES, default="NONE"
+ )
notify_freq = models.IntegerField(_("重试间隔(s)"), default=EMPTY_INT)
objects = managers.WorkflowSnapManager()
class Meta:
- app_label = 'workflow'
+ app_label = "workflow"
verbose_name = _("工作流快照")
verbose_name_plural = _("工作流快照")
@@ -75,8 +87,12 @@ def __unicode__(self):
class DefaultField(BaseField):
"""初始环节内置字段表: deprecated"""
- flow_type = models.CharField(_("流程分类"), max_length=LEN_NORMAL, default=DEFAULT_STRING)
- category = models.CharField(_("字段归类,面向业务逻辑,比如服务类型(change|event)"), max_length=LEN_MIDDLE)
+ flow_type = models.CharField(
+ _("流程分类"), max_length=LEN_NORMAL, default=DEFAULT_STRING
+ )
+ category = models.CharField(
+ _("字段归类,面向业务逻辑,比如服务类型(change|event)"), max_length=LEN_MIDDLE
+ )
objects = models.Manager()
diff --git a/itsm/workflow/models/event.py b/itsm/workflow/models/event.py
index dbbea039c..5c2112b98 100644
--- a/itsm/workflow/models/event.py
+++ b/itsm/workflow/models/event.py
@@ -25,7 +25,7 @@
import jsonfield
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
EMPTY_DICT,
@@ -47,19 +47,40 @@ class Event(models.Model):
from_state_id = models.IntegerField(_("当前状态ID"))
transition_id = models.IntegerField(_("流转ID"), null=True, blank=True)
to_state_id = models.IntegerField(_("下一个状态ID"), null=True, blank=True)
- type = models.CharField(_("流转事件类型"), max_length=LEN_SHORT, choices=OPERATE_CHOICES, default=TRANSITION_OPERATE)
- processors_type = models.CharField(_("处理人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="OPEN")
- processors = models.CharField(_("处理人列表"), max_length=LEN_LONG, default=EMPTY_STRING, null=True, blank=True)
- form_data = jsonfield.JSONField(_("表单快照字典"), default=EMPTY_DICT, null=True, blank=True)
+ type = models.CharField(
+ _("流转事件类型"),
+ max_length=LEN_SHORT,
+ choices=OPERATE_CHOICES,
+ default=TRANSITION_OPERATE,
+ )
+ processors_type = models.CharField(
+ _("处理人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="OPEN"
+ )
+ processors = models.CharField(
+ _("处理人列表"),
+ max_length=LEN_LONG,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
+ )
+ form_data = jsonfield.JSONField(
+ _("表单快照字典"), default=EMPTY_DICT, null=True, blank=True
+ )
operate_at = models.DateTimeField(_("操作时间"), auto_now_add=True)
- operator = models.CharField(_("操作人"), max_length=LEN_NORMAL, null=True, blank=True)
- message = models.CharField(_("日志概述"), max_length=LEN_X_LONG, null=True, blank=True)
+ operator = models.CharField(
+ _("操作人"), max_length=LEN_NORMAL, null=True, blank=True
+ )
+ message = models.CharField(
+ _("日志概述"), max_length=LEN_X_LONG, null=True, blank=True
+ )
is_deleted = models.BooleanField(_("是否软删除"), default=False, db_index=True)
# 新增
action = models.CharField(_("动作"), max_length=LEN_NORMAL, blank=True)
- detail_message = models.CharField(_("详细信息"), max_length=LEN_X_LONG, null=True, blank=True)
+ detail_message = models.CharField(
+ _("详细信息"), max_length=LEN_X_LONG, null=True, blank=True
+ )
from_state_name = models.CharField(_("任务name"), max_length=LEN_NORMAL, blank=True)
_objects = models.Manager()
diff --git a/itsm/workflow/models/field.py b/itsm/workflow/models/field.py
index b3cf6702d..ca2aba7cf 100644
--- a/itsm/workflow/models/field.py
+++ b/itsm/workflow/models/field.py
@@ -28,7 +28,7 @@
import jsonfield
from django.db import models
from django.forms import model_to_dict
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
BASE_MODEL,
@@ -73,33 +73,51 @@ class BaseField(Model):
is_valid = models.BooleanField(_("是否生效"), default=True)
display = models.BooleanField(_("是否显示在单据列表中"), default=False)
- source_type = models.CharField(_("数据来源类型"), max_length=LEN_SHORT, choices=SOURCE_CHOICES,
- default="CUSTOM")
- source_uri = models.CharField(_("接口uri"), max_length=LEN_LONG, default=EMPTY_STRING, null=True,
- blank=True)
- api_instance_id = models.IntegerField(_('api实例主键'), default=0, null=True, blank=True)
- kv_relation = jsonfield.JSONCharField(_("源数据的kv关系配置"), default=EMPTY_DICT,
- max_length=LEN_NORMAL)
- type = models.CharField(_("字段类型"), max_length=LEN_SHORT, choices=TYPE_CHOICES, default="STRING")
+ source_type = models.CharField(
+ _("数据来源类型"),
+ max_length=LEN_SHORT,
+ choices=SOURCE_CHOICES,
+ default="CUSTOM",
+ )
+ source_uri = models.CharField(
+ _("接口uri"), max_length=LEN_LONG, default=EMPTY_STRING, null=True, blank=True
+ )
+ api_instance_id = models.IntegerField(
+ _("api实例主键"), default=0, null=True, blank=True
+ )
+ kv_relation = jsonfield.JSONCharField(
+ _("源数据的kv关系配置"), default=EMPTY_DICT, max_length=LEN_NORMAL
+ )
+ type = models.CharField(
+ _("字段类型"), max_length=LEN_SHORT, choices=TYPE_CHOICES, default="STRING"
+ )
key = models.CharField(_("字段标识"), max_length=LEN_LONG)
name = models.CharField(_("字段名"), max_length=LEN_NORMAL)
- layout = models.CharField(_("布局"), max_length=LEN_SHORT, choices=LAYOUT_CHOICES,
- default="COL_6")
+ layout = models.CharField(
+ _("布局"), max_length=LEN_SHORT, choices=LAYOUT_CHOICES, default="COL_6"
+ )
- validate_type = models.CharField(_("校验规则"), max_length=LEN_SHORT, choices=VALIDATE_CHOICES,
- default="REQUIRE")
+ validate_type = models.CharField(
+ _("校验规则"), max_length=LEN_SHORT, choices=VALIDATE_CHOICES, default="REQUIRE"
+ )
show_type = models.IntegerField(
- _('显示条件类型'), choices=[(SHOW_DIRECTLY, '直接显示'), (SHOW_BY_CONDITION, '根据条件判断')],
- default=SHOW_DIRECTLY
+ _("显示条件类型"),
+ choices=[(SHOW_DIRECTLY, "直接显示"), (SHOW_BY_CONDITION, "根据条件判断")],
+ default=SHOW_DIRECTLY,
)
show_conditions = jsonfield.JSONField(_("字段的显示条件"), default=EMPTY_DICT)
- regex = models.CharField(_("正则校验规则关键字"), max_length=LEN_NORMAL, default=EMPTY_STRING, null=True,
- blank=True)
- '''
+ regex = models.CharField(
+ _("正则校验规则关键字"),
+ max_length=LEN_NORMAL,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
+ )
+ """
regex_config.rule: dict 校验规则
regex_config.rule.expressions: []dict 表达式
regex_config.rule.type: string 表达式关系,and/or
@@ -109,19 +127,39 @@ class BaseField(Model):
"type":"and"
}
}
- '''
- regex_config = jsonfield.JSONCharField(_('正则校验规则配置'), max_length=LEN_LONG, default=EMPTY_DICT)
- custom_regex = models.CharField(_("自定义正则规则"), max_length=LEN_MIDDLE, default=EMPTY_STRING,
- null=True, blank=True)
- desc = models.CharField(_("字段填写说明"), max_length=LEN_MIDDLE, default=EMPTY_STRING, null=True,
- blank=True)
- tips = models.CharField(_("字段展示说明"), max_length=LEN_MIDDLE, default=EMPTY_STRING, null=True,
- blank=True)
- is_tips = models.BooleanField(_('额外提示'), default=False)
- default = models.CharField(_("默认值"), max_length=LEN_XX_LONG, default=EMPTY_STRING, null=True,
- blank=True)
+ """
+ regex_config = jsonfield.JSONCharField(
+ _("正则校验规则配置"), max_length=LEN_LONG, default=EMPTY_DICT
+ )
+ custom_regex = models.CharField(
+ _("自定义正则规则"),
+ max_length=LEN_MIDDLE,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
+ )
+ desc = models.CharField(
+ _("字段填写说明"),
+ max_length=LEN_MIDDLE,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
+ )
+ tips = models.CharField(
+ _("字段展示说明"),
+ max_length=LEN_MIDDLE,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
+ )
+ is_tips = models.BooleanField(_("额外提示"), default=False)
+ default = models.CharField(
+ _("默认值"), max_length=LEN_XX_LONG, default=EMPTY_STRING, null=True, blank=True
+ )
choice = jsonfield.JSONField(_("选项"), default=EMPTY_LIST)
- related_fields = jsonfield.JSONField(_("级联字段"), default=EMPTY_DICT, null=True, blank=True)
+ related_fields = jsonfield.JSONField(
+ _("级联字段"), default=EMPTY_DICT, null=True, blank=True
+ )
meta = jsonfield.JSONField(_("复杂描述信息"), default=EMPTY_DICT)
class Meta:
@@ -145,7 +183,7 @@ def tag_data(self, fields=None, exclude=None):
_exclude.extend(exclude)
for f in chain(opts.concrete_fields, opts.private_fields):
- if not getattr(f, 'editable', False):
+ if not getattr(f, "editable", False):
continue
if fields and f.name not in fields:
continue
@@ -153,18 +191,20 @@ def tag_data(self, fields=None, exclude=None):
continue
if isinstance(f, models.ForeignKey):
- data['{}_id'.format(f.name)] = getattr(getattr(self, f.name), 'id', '')
+ data["{}_id".format(f.name)] = getattr(getattr(self, f.name), "id", "")
else:
- data[f.name] = getattr(self, f.name, '')
+ data[f.name] = getattr(self, f.name, "")
- if self.source_type == 'API':
+ if self.source_type == "API":
api_instance_info = RemoteApiInstance.objects.get(
- id=self.api_instance_id).tag_data()
+ id=self.api_instance_id
+ ).tag_data()
remote_api_info = RemoteApi.objects.get(
- id=api_instance_info['remote_api']).tag_data()
- data['api_info'] = {
- 'api_instance_info': api_instance_info,
- 'remote_api_info': remote_api_info,
+ id=api_instance_info["remote_api"]
+ ).tag_data()
+ data["api_info"] = {
+ "api_instance_info": api_instance_info,
+ "remote_api_info": remote_api_info,
}
return data
@@ -173,19 +213,31 @@ def tag_data(self, fields=None, exclude=None):
class Field(BaseField):
"""字段表"""
- SOURCE = [('CUSTOM', '自定义添加'), ('TABLE', '基础模型添加')]
+ SOURCE = [("CUSTOM", "自定义添加"), ("TABLE", "基础模型添加")]
- workflow = models.ForeignKey('workflow.Workflow', help_text=_("关联流程"), related_name="fields",
- on_delete=models.CASCADE)
- state = models.ForeignKey('workflow.State', help_text=_("关联流程"), related_name="state_fields",
- null=True, blank=True, on_delete=models.CASCADE)
- source = models.CharField(_('添加方式'), max_length=LEN_SHORT, choices=SOURCE, default='CUSTOM')
+ workflow = models.ForeignKey(
+ "workflow.Workflow",
+ help_text=_("关联流程"),
+ related_name="fields",
+ on_delete=models.CASCADE,
+ )
+ state = models.ForeignKey(
+ "workflow.State",
+ help_text=_("关联流程"),
+ related_name="state_fields",
+ null=True,
+ blank=True,
+ on_delete=models.CASCADE,
+ )
+ source = models.CharField(
+ _("添加方式"), max_length=LEN_SHORT, choices=SOURCE, default="CUSTOM"
+ )
objects = managers.FieldManager()
_objects = models.Manager()
class Meta:
- app_label = 'workflow'
+ app_label = "workflow"
verbose_name = _("字段表")
verbose_name_plural = _("字段表")
@@ -211,8 +263,12 @@ def clone(self, key=None):
class TemplateField(BaseField):
"""字段库"""
- flow_type = models.CharField(_("流程分类"), max_length=LEN_NORMAL, default=DEFAULT_STRING)
- project_key = models.CharField(_("项目key"), max_length=LEN_SHORT, null=False, default=0)
+ flow_type = models.CharField(
+ _("流程分类"), max_length=LEN_NORMAL, default=DEFAULT_STRING
+ )
+ project_key = models.CharField(
+ _("项目key"), max_length=LEN_SHORT, null=False, default=0
+ )
objects = managers.TemplateFieldManager()
@@ -248,29 +304,34 @@ class Table(Model):
基础模型
"""
- fields = models.ManyToManyField(TemplateField, help_text=_('关联的公共字段'), related_name="tables")
- name = models.CharField(_('模型名称'), max_length=LEN_LONG)
- desc = models.CharField(_('基础模型描述'), max_length=LEN_LONG, null=True, blank=True)
- fields_order = jsonfield.JSONField(_('字段排序'), default=[])
+ fields = models.ManyToManyField(
+ TemplateField, help_text=_("关联的公共字段"), related_name="tables"
+ )
+ name = models.CharField(_("模型名称"), max_length=LEN_LONG)
+ desc = models.CharField(
+ _("基础模型描述"), max_length=LEN_LONG, null=True, blank=True
+ )
+ fields_order = jsonfield.JSONField(_("字段排序"), default=[])
- is_builtin = models.BooleanField(_('是否内置字段'), default=False)
+ is_builtin = models.BooleanField(_("是否内置字段"), default=False)
- version = models.CharField(_("Table版本:空"), max_length=LEN_NORMAL, null=True, blank=True,
- default=EMPTY)
+ version = models.CharField(
+ _("Table版本:空"), max_length=LEN_NORMAL, null=True, blank=True, default=EMPTY
+ )
objects = managers.TableManager()
class Meta:
- app_label = 'workflow'
- verbose_name = _('基础模型')
- verbose_name_plural = _('基础模型')
+ app_label = "workflow"
+ verbose_name = _("基础模型")
+ verbose_name_plural = _("基础模型")
def __unicode__(self):
return self.name
def add_fields(self, fields):
"""增加字段"""
- table_fields = list(self.fields.values_list('id', flat=True))
+ table_fields = list(self.fields.values_list("id", flat=True))
for field in fields:
if field not in table_fields:
table_fields.append(field)
@@ -279,7 +340,7 @@ def add_fields(self, fields):
def remove_fields(self, fields):
"""删除字段"""
- table_fields = list(self.fields.values_list('id', flat=True))
+ table_fields = list(self.fields.values_list("id", flat=True))
for field in fields:
try:
table_fields.remove(field)
@@ -291,7 +352,7 @@ def remove_fields(self, fields):
def tag_data(self, exclude=None):
"""获取table快照数据"""
- _exclude = list(self.FIELDS) + ['is_builtin', 'fields', 'fields_order']
+ _exclude = list(self.FIELDS) + ["is_builtin", "fields", "fields_order"]
if isinstance(exclude, (list, tuple)):
_exclude.extend(exclude)
@@ -304,22 +365,29 @@ def tag_data(self, exclude=None):
id_to_key[tf.id] = tf.key
fields.append(tf.tag_data())
- field_key_order = [id_to_key[field_id] for field_id in self.fields_order if
- field_id in id_to_key]
+ field_key_order = [
+ id_to_key[field_id]
+ for field_id in self.fields_order
+ if field_id in id_to_key
+ ]
data = model_to_dict(self, exclude=_exclude)
- data.update(fields=fields, fields_order=self.fields_order, field_key_order=field_key_order)
+ data.update(
+ fields=fields,
+ fields_order=self.fields_order,
+ field_key_order=field_key_order,
+ )
return data
def clone(self):
"""返回克隆对象"""
- fields = self.fields.values_list('id', flat=True)
+ fields = self.fields.values_list("id", flat=True)
self.id = None
self.save()
version_name = create_version_number()
- self.name = '{}_{}'.format(self.name, version_name)
+ self.name = "{}_{}".format(self.name, version_name)
self.fields = fields
self.version = version_name
self.save()
diff --git a/itsm/workflow/models/state.py b/itsm/workflow/models/state.py
index 348bef0d3..23072bdcd 100644
--- a/itsm/workflow/models/state.py
+++ b/itsm/workflow/models/state.py
@@ -29,7 +29,7 @@
import jsonfield
from django.db import models, transaction
from django.db.models import Q
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
DEFAULT_STRING,
@@ -93,7 +93,11 @@ class State(Model):
)
name = models.CharField(_("状态名"), max_length=LEN_NORMAL)
desc = models.CharField(
- _("状态描述"), max_length=LEN_NORMAL, default=EMPTY_STRING, null=True, blank=True
+ _("状态描述"),
+ max_length=LEN_NORMAL,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
type = models.CharField(
_("状态类型"),
@@ -111,16 +115,24 @@ class State(Model):
_("处理人列表"), default=EMPTY_STRING, null=True, blank=True
)
assignors_type = models.CharField(
- _("派单人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("派单人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
assignors = models.TextField(
_("派单人列表"), default=EMPTY_STRING, null=True, blank=True
)
can_deliver = models.BooleanField(_("能否转单"), default=False)
delivers_type = models.CharField(
- _("转单人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("转单人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
+ )
+ delivers = models.TextField(
+ _("转单人列表"), default=EMPTY_STRING, null=True, blank=True
)
- delivers = models.TextField(_("转单人列表"), default=EMPTY_STRING, null=True, blank=True)
distribute_type = models.CharField(
_("分配方式"),
@@ -129,7 +141,9 @@ class State(Model):
default="PROCESS",
)
- notify = models.ManyToManyField("workflow.Notify", help_text=_("可关联多种通知方式"))
+ notify = models.ManyToManyField(
+ "workflow.Notify", help_text=_("可关联多种通知方式")
+ )
notify_rule = models.CharField(
_("通知规则"), max_length=LEN_SHORT, choices=NOTIFY_RULE_CHOICES, default="NONE"
)
@@ -148,27 +162,42 @@ class State(Model):
is_builtin = models.BooleanField(_("是否为系统内置"), default=False)
# 是否允许在单据处理人为空时跳过
- is_allow_skip = models.BooleanField(_("是否允许在单据处理人为空时跳过"), default=False)
+ is_allow_skip = models.BooleanField(
+ _("是否允许在单据处理人为空时跳过"), default=False
+ )
# 会签及任务控制
is_sequential = models.BooleanField(_("是否是串行任务"), default=False)
finish_condition = jsonfield.JSONField(_("可向下调度的条件"), default=EMPTY_DICT)
variables = jsonfield.JSONField(_("变量"), default=EMPTY_VARIABLE, null=True)
- axis = jsonfield.JSONCharField(_("节点的坐标轴"), max_length=128, default=EMPTY_DICT)
+ axis = jsonfield.JSONCharField(
+ _("节点的坐标轴"), max_length=128, default=EMPTY_DICT
+ )
api_instance_id = models.IntegerField(
_("api实例主键"), default=0, null=True, blank=True
)
extras = jsonfield.JSONCharField(
- _("额外信息"), max_length=LEN_XXX_LONG, default=EMPTY_DICT, null=True, blank=True
+ _("额外信息"),
+ max_length=LEN_XXX_LONG,
+ default=EMPTY_DICT,
+ null=True,
+ blank=True,
)
# deprecated fields
followers_type = models.CharField(
- _("关注人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("关注人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
followers = models.CharField(
- _("关注人列表"), max_length=LEN_LONG, default=EMPTY_STRING, null=True, blank=True
+ _("关注人列表"),
+ max_length=LEN_LONG,
+ default=EMPTY_STRING,
+ null=True,
+ blank=True,
)
label = models.CharField(_("标签记录"), max_length=LEN_LONG, default="EMPTY")
diff --git a/itsm/workflow/models/task.py b/itsm/workflow/models/task.py
index f3f1d0fc1..06002f422 100644
--- a/itsm/workflow/models/task.py
+++ b/itsm/workflow/models/task.py
@@ -28,7 +28,7 @@
import jsonfield
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
EMPTY_DICT,
@@ -44,7 +44,8 @@
LEN_XX_LONG,
SOURCE_TASK,
LEN_SHORT,
- TASK_TYPE, PUBLIC_PROJECT_PROJECT_KEY,
+ TASK_TYPE,
+ PUBLIC_PROJECT_PROJECT_KEY,
)
from itsm.workflow.models import Model, BaseField
from itsm.workflow.managers import TaskSchemaManager, TaskSchemaFieldManager
@@ -57,20 +58,38 @@ class TaskSchema(Model):
"""
fields = (
- "id", "name", "is_builtin", "component_type", "desc", "is_draft", "can_edit", "owners")
+ "id",
+ "name",
+ "is_builtin",
+ "component_type",
+ "desc",
+ "is_draft",
+ "can_edit",
+ "owners",
+ )
name = models.CharField(_("任务模版的名称"), max_length=LEN_MIDDLE, null=False)
is_builtin = models.BooleanField(_("是否内置"), default=False)
- component_type = models.CharField(_("任务组件类型"), choices=TASK_COMPONENT_CHOICE,
- max_length=LEN_NORMAL)
- desc = models.CharField(_("任务模版的名称"), max_length=LEN_X_LONG, default=EMPTY_STRING, blank=True)
+ component_type = models.CharField(
+ _("任务组件类型"), choices=TASK_COMPONENT_CHOICE, max_length=LEN_NORMAL
+ )
+ desc = models.CharField(
+ _("任务模版的名称"), max_length=LEN_X_LONG, default=EMPTY_STRING, blank=True
+ )
is_draft = models.BooleanField(_("是否为草稿"), default=True)
is_enabled = models.BooleanField(_("是否为开启状态"), default=False)
owners = models.CharField(_("负责人"), max_length=LEN_XX_LONG, default=EMPTY_STRING)
- can_edit = models.BooleanField(_("是否可编辑状态"), help_text=_("当为流程version引用的时候,不可编辑和查看"),
- default=True)
- inputs = jsonfield.JSONField(_("组件输入信息"), help_text=_("当前组件输入参数引用的参数变量"), default=EMPTY_DICT)
+ can_edit = models.BooleanField(
+ _("是否可编辑状态"),
+ help_text=_("当为流程version引用的时候,不可编辑和查看"),
+ default=True,
+ )
+ inputs = jsonfield.JSONField(
+ _("组件输入信息"),
+ help_text=_("当前组件输入参数引用的参数变量"),
+ default=EMPTY_DICT,
+ )
objects = TaskSchemaManager()
@@ -84,7 +103,7 @@ class Meta:
verbose_name = _("任务模型")
verbose_name_plural = _("任务模型")
ordering = ("-id",)
-
+
@property
def project_key(self):
return PUBLIC_PROJECT_PROJECT_KEY
@@ -104,8 +123,13 @@ def get_variables(self, stage=None):
break
variables = [
- {"key": _field.key, "type": _field.type, "source": "field", "name": _field.name,
- "choice": _field.choice}
+ {
+ "key": _field.key,
+ "type": _field.type,
+ "source": "field",
+ "name": _field.name,
+ "choice": _field.choice,
+ }
for _field in self.all_fields.all()
]
@@ -113,8 +137,13 @@ def get_variables(self, stage=None):
variables.extend(TICKET_GLOBAL_VARIABLES)
if self.component_type == "SOPS":
variables.append(
- {"key": "sops_relate_id", "type": "string", "source": "ticket", "name": "REL单号",
- "choice": []}
+ {
+ "key": "sops_relate_id",
+ "type": "string",
+ "source": "ticket",
+ "name": "REL单号",
+ "choice": [],
+ }
)
# 工单内的信息更新
variables.extend(self.inputs.get("ticket_variables", []))
@@ -122,14 +151,21 @@ def get_variables(self, stage=None):
def tag_data(self):
triggers = []
- for trigger in Trigger.objects.filter(source_type=SOURCE_TASK, source_id=self.id):
+ for trigger in Trigger.objects.filter(
+ source_type=SOURCE_TASK, source_id=self.id
+ ):
triggers.append(trigger.tag_data())
fields = []
for field in self.all_fields.all():
fields.append(field.tag_data())
- return dict(id=self.id, name=self.name, component_type=self.component_type,
- triggers=triggers, fields=fields)
+ return dict(
+ id=self.id,
+ name=self.name,
+ component_type=self.component_type,
+ triggers=triggers,
+ fields=fields,
+ )
def restore_fields(self, fields):
TaskFieldSchema.objects.restore(fields, self)
@@ -138,10 +174,18 @@ def restore_fields(self, fields):
class TaskFieldSchema(BaseField):
"""任务对应的表单字段"""
- task_schema = models.ForeignKey(TaskSchema, related_name="all_fields", help_text=_("对应的任务模型"),
- on_delete=models.CASCADE)
- stage = models.CharField(_("所处阶段"), choices=TASK_STAGE_CHOICE, default="CREATE",
- max_length=LEN_NORMAL)
+ task_schema = models.ForeignKey(
+ TaskSchema,
+ related_name="all_fields",
+ help_text=_("对应的任务模型"),
+ on_delete=models.CASCADE,
+ )
+ stage = models.CharField(
+ _("所处阶段"),
+ choices=TASK_STAGE_CHOICE,
+ default="CREATE",
+ max_length=LEN_NORMAL,
+ )
sequence = models.IntegerField(_("序号"), default=0)
objects = TaskSchemaFieldManager()
@@ -166,12 +210,16 @@ class TaskConfig(Model):
"""
workflow_id = models.IntegerField(_("流程id"), db_index=True)
- workflow_type = models.CharField(_("流程类型"), choices=TASK_TYPE, max_length=LEN_SHORT)
+ workflow_type = models.CharField(
+ _("流程类型"), choices=TASK_TYPE, max_length=LEN_SHORT
+ )
task_schema_id = models.IntegerField(_("任务模版id"))
create_task_state = models.IntegerField(_("任务创建节点"))
execute_task_state = models.IntegerField(_("任务执行节点"))
execute_can_create = models.BooleanField(_("执行节点是否可创建"), default=False)
- need_task_finished = models.BooleanField(_("流转是否需要任务全部完成"), default=False)
+ need_task_finished = models.BooleanField(
+ _("流转是否需要任务全部完成"), default=False
+ )
class Meta:
app_label = "workflow"
diff --git a/itsm/workflow/models/transition.py b/itsm/workflow/models/transition.py
index 58aa9db95..804c9fd46 100644
--- a/itsm/workflow/models/transition.py
+++ b/itsm/workflow/models/transition.py
@@ -25,7 +25,7 @@
import jsonfield
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from itsm.component.constants import (
DEFAULT_FLOW_CONDITION,
@@ -42,15 +42,19 @@
class Condition(Model):
"""流转线条件配置"""
- workflow = models.ForeignKey('workflow.Workflow', related_name="flows", help_text=_("关联工作流"),
- on_delete=models.CASCADE)
+ workflow = models.ForeignKey(
+ "workflow.Workflow",
+ related_name="flows",
+ help_text=_("关联工作流"),
+ on_delete=models.CASCADE,
+ )
name = models.CharField(_("流转操作"), max_length=LEN_NORMAL)
data = jsonfield.JSONField(_("流转条件表达式"), default=DEFAULT_FLOW_CONDITION)
objects = managers.ConditionManager()
class Meta:
- app_label = 'workflow'
+ app_label = "workflow"
verbose_name = _("流转条件")
verbose_name_plural = _("流转条件")
@@ -66,30 +70,51 @@ class Transition(Model):
"""状态流转"""
DIRECTION_CHOICES = [
- ("BACK", '向后'),
- ("FORWARD", '向前'),
+ ("BACK", "向后"),
+ ("FORWARD", "向前"),
]
- workflow = models.ForeignKey('workflow.Workflow', related_name="transitions",
- help_text=_("关联流程"), on_delete=models.CASCADE)
+ workflow = models.ForeignKey(
+ "workflow.Workflow",
+ related_name="transitions",
+ help_text=_("关联流程"),
+ on_delete=models.CASCADE,
+ )
name = models.CharField(_("流转操作"), max_length=LEN_NORMAL)
condition = jsonfield.JSONField(_("流转条件表达式"), default=DEFAULT_FLOW_CONDITION)
condition_type = models.CharField(
- _("流转类型"), max_length=LEN_SHORT, choices=FLOW_CONDITION_TYPE_CHOICES, default="default"
+ _("流转类型"),
+ max_length=LEN_SHORT,
+ choices=FLOW_CONDITION_TYPE_CHOICES,
+ default="default",
)
# 线条的方向坐标 {"start":"left|right|top|bottom", "end": "left|right|top|bottom"}
- axis = jsonfield.JSONCharField(_("线条的坐标位置的坐标轴"), max_length=LEN_NORMAL, default=EMPTY_DICT)
+ axis = jsonfield.JSONCharField(
+ _("线条的坐标位置的坐标轴"), max_length=LEN_NORMAL, default=EMPTY_DICT
+ )
- from_state = models.ForeignKey('workflow.State', related_name="transitions_from",
- help_text=_("源状态ID"), on_delete=models.CASCADE)
- to_state = models.ForeignKey('workflow.State', related_name="transitions_to",
- help_text=_("目标状态ID"), on_delete=models.CASCADE)
+ from_state = models.ForeignKey(
+ "workflow.State",
+ related_name="transitions_from",
+ help_text=_("源状态ID"),
+ on_delete=models.CASCADE,
+ )
+ to_state = models.ForeignKey(
+ "workflow.State",
+ related_name="transitions_to",
+ help_text=_("目标状态ID"),
+ on_delete=models.CASCADE,
+ )
# deprecated fields: check_needed/opt_needed
- direction = models.CharField(_("流转方向"), max_length=LEN_SHORT, choices=DIRECTION_CHOICES,
- default="FORWARD")
+ direction = models.CharField(
+ _("流转方向"),
+ max_length=LEN_SHORT,
+ choices=DIRECTION_CHOICES,
+ default="FORWARD",
+ )
check_needed = models.BooleanField(_("是否需要校验表单完整性"), default=True)
opt_needed = models.BooleanField(_("是否需要执行操作"), default=True)
@@ -99,12 +124,14 @@ class Transition(Model):
_objects = models.Manager()
class Meta:
- app_label = 'workflow'
+ app_label = "workflow"
verbose_name = _("状态流转")
verbose_name_plural = _("状态流转")
def __unicode__(self):
- return "{} -{} {}".format(self.from_state, '>' if self.opt_needed else '-', self.to_state)
+ return "{} -{} {}".format(
+ self.from_state, ">" if self.opt_needed else "-", self.to_state
+ )
@property
def serialized_data(self):
@@ -116,4 +143,6 @@ def delete(self, using=None):
from itsm.workflow.models import State
super(Transition, self).delete(using)
- State.objects.update_state_label(self.from_state, self.to_state, operate_type='delete')
+ State.objects.update_state_label(
+ self.from_state, self.to_state, operate_type="delete"
+ )
diff --git a/itsm/workflow/models/trigger.py b/itsm/workflow/models/trigger.py
index 52fb9e8e1..07799bc59 100644
--- a/itsm/workflow/models/trigger.py
+++ b/itsm/workflow/models/trigger.py
@@ -25,9 +25,15 @@
import jsonfield
from django.db import models
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
-from itsm.component.constants import EMPTY_DICT, LEN_LONG, LEN_NORMAL, LEN_SHORT, TRIGGER_TYPE
+from itsm.component.constants import (
+ EMPTY_DICT,
+ LEN_LONG,
+ LEN_NORMAL,
+ LEN_SHORT,
+ TRIGGER_TYPE,
+)
from itsm.workflow.managers import TriggerManager
from itsm.workflow.models import Model
@@ -36,8 +42,12 @@ class Trigger(Model):
name = models.CharField(_("名称"), max_length=LEN_NORMAL)
component_key = models.CharField(_("原子key"), max_length=LEN_NORMAL)
type = models.CharField(_("类型"), max_length=LEN_SHORT, choices=TRIGGER_TYPE)
- condition = jsonfield.JSONCharField(_("触发条件"), max_length=LEN_LONG, default=EMPTY_DICT)
- inputs = jsonfield.JSONCharField(_("传入参数"), max_length=LEN_LONG, default=EMPTY_DICT)
+ condition = jsonfield.JSONCharField(
+ _("触发条件"), max_length=LEN_LONG, default=EMPTY_DICT
+ )
+ inputs = jsonfield.JSONCharField(
+ _("传入参数"), max_length=LEN_LONG, default=EMPTY_DICT
+ )
"""
key: one input key
value: mapping value
diff --git a/itsm/workflow/models/workflow.py b/itsm/workflow/models/workflow.py
index 450eee870..e16d1e70d 100644
--- a/itsm/workflow/models/workflow.py
+++ b/itsm/workflow/models/workflow.py
@@ -30,7 +30,7 @@
from django.db import models, transaction
from django.db.models import Q
from django.forms import model_to_dict
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework.exceptions import ValidationError
from itsm.component.constants import (
@@ -96,10 +96,14 @@ class WorkflowBase(ObjectManagerMixin, Model):
)
is_draft = models.BooleanField(_("是否为草稿"), default=True)
is_builtin = models.BooleanField(_("是否为系统内置"), default=False)
- is_task_needed = models.NullBooleanField(_("是否需要关联子任务"), default=False, null=True)
+ is_task_needed = models.BooleanField(
+ _("是否需要关联子任务"), default=False, null=True
+ )
owners = models.CharField(_("负责人"), max_length=LEN_XX_LONG, default=EMPTY_STRING)
- notify = models.ManyToManyField("workflow.Notify", help_text=_("可关联多种通知方式"))
+ notify = models.ManyToManyField(
+ "workflow.Notify", help_text=_("可关联多种通知方式")
+ )
notify_rule = models.CharField(
_("通知规则"), max_length=LEN_SHORT, choices=NOTIFY_RULE_CHOICES, default="NONE"
)
@@ -112,7 +116,10 @@ class WorkflowBase(ObjectManagerMixin, Model):
is_iam_used = models.BooleanField(_("是否使用IAM角色"), default=False)
is_supervise_needed = models.BooleanField(_("是否需要督办"), default=False)
supervise_type = models.CharField(
- _("督办人类型"), max_length=LEN_SHORT, choices=PROCESSOR_CHOICES, default="EMPTY"
+ _("督办人类型"),
+ max_length=LEN_SHORT,
+ choices=PROCESSOR_CHOICES,
+ default="EMPTY",
)
supervisor = models.CharField(
_("督办列表"), max_length=LEN_LONG, default=EMPTY_STRING, null=True, blank=True
@@ -125,7 +132,9 @@ class WorkflowBase(ObjectManagerMixin, Model):
)
# deprecated fields
- master = jsonfield.JSONField(_("主分支列表"), default=EMPTY_LIST, null=True, blank=True)
+ master = jsonfield.JSONField(
+ _("主分支列表"), default=EMPTY_LIST, null=True, blank=True
+ )
extras = jsonfield.JSONField(
_("其他配置信息"),
default={
@@ -183,7 +192,7 @@ class Meta:
def __unicode__(self):
return "{}({})".format(self.name, self.pk)
-
+
def get_iam_resource(self):
"""获取 workflow 关联的服务对象"""
workflow_version = WorkflowVersion.objects.filter(workflow_id=self.id).last()
@@ -506,9 +515,9 @@ def can_bind_sla(self):
if missing_field:
field_desc = [REQUIRED_FIELD[field] for field in missing_field]
raise ValidationError(
- _("检测到您的流程已经发生改变,现流程版本中缺少【{}】信息,请补充完整后重新配置sla").format(
- ",".join(field_desc)
- )
+ _(
+ "检测到您的流程已经发生改变,现流程版本中缺少【{}】信息,请补充完整后重新配置sla"
+ ).format(",".join(field_desc))
)
def get_notifiy_objs(self, notify_list):
@@ -546,8 +555,12 @@ class WorkflowVersion(WorkflowBase):
workflow_id = models.IntegerField(_("流程模板ID"))
- fields = jsonfield.JSONField(_("字段快照字典"), default=EMPTY_DICT, null=True, blank=True)
- states = jsonfield.JSONField(_("状态快照字典"), default=EMPTY_DICT, null=True, blank=True)
+ fields = jsonfield.JSONField(
+ _("字段快照字典"), default=EMPTY_DICT, null=True, blank=True
+ )
+ states = jsonfield.JSONField(
+ _("状态快照字典"), default=EMPTY_DICT, null=True, blank=True
+ )
transitions = jsonfield.JSONField(
_("流转快照字典"), default=EMPTY_DICT, null=True, blank=True
)
@@ -887,5 +900,7 @@ def can_bind_sla(self):
if missing_field:
field_desc = [REQUIRED_FIELD[field] for field in missing_field]
raise ValidationError(
- _("流程版本中缺少【{}】信息,请补充完整后再进行服务协议的关联").format(",".join(field_desc))
+ _("流程版本中缺少【{}】信息,请补充完整后再进行服务协议的关联").format(
+ ",".join(field_desc)
+ )
)
diff --git a/itsm/workflow/permissions.py b/itsm/workflow/permissions.py
index 7714ea2f6..68c5120f0 100644
--- a/itsm/workflow/permissions.py
+++ b/itsm/workflow/permissions.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import permissions
from itsm.component.constants import PUBLIC_PROJECT_PROJECT_KEY
@@ -92,7 +92,7 @@ def has_permission(self, request, view):
apply_actions = ["service_manage"]
# 节点和字段的查看,首先必须有当前流程的管理权限
- if not view.action in ["list", "batch_update"]:
+ if view.action not in ["list", "batch_update"]:
# 非列表请求,通过object鉴权
return True
@@ -102,7 +102,7 @@ def has_permission(self, request, view):
workflow_id = view.get_iam_resource_id()
if not workflow_id:
return False
-
+
try:
flow = Workflow.objects.get(id=workflow_id)
except Workflow.DoesNotExist:
@@ -182,15 +182,15 @@ def has_permission(self, request, view):
# 免鉴权需要明确声明
if view.action in getattr(view, "permission_free_actions", []):
return True
-
+
if view.action in getattr(view, "permission_create_action", ["create"]):
project_key = request.data.get("project_key", PUBLIC_PROJECT_PROJECT_KEY)
-
+
# 平台管理
if project_key == PUBLIC_PROJECT_PROJECT_KEY:
apply_actions = [view.permission_action_platform]
return self.iam_auth(request, apply_actions)
-
+
# 项目管理
apply_actions = self.get_view_iam_actions(view)
return self.iam_create_auth(request, apply_actions)
@@ -200,7 +200,7 @@ def has_object_permission(self, request, view, obj, **kwargs):
# 关联实例的请求,需要针对对象进行鉴权
if view.action in getattr(view, "permission_free_actions", []):
return True
-
+
# 平台管理
if obj.project_key == PUBLIC_PROJECT_PROJECT_KEY:
apply_actions = [view.permission_action_platform]
diff --git a/itsm/workflow/serializers/field.py b/itsm/workflow/serializers/field.py
index 1e880bef2..7fab18b39 100644
--- a/itsm/workflow/serializers/field.py
+++ b/itsm/workflow/serializers/field.py
@@ -26,7 +26,7 @@
import json
from django.db import transaction
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import JSONField, empty
from rest_framework.validators import UniqueValidator
@@ -173,7 +173,11 @@ class Meta:
"project_key",
) + model.FIELDS
read_only_fields = ("is_builtin", "key") + model.FIELDS
- create_only_fields = ("is_builtin", "key", "project_key", )
+ create_only_fields = (
+ "is_builtin",
+ "key",
+ "project_key",
+ )
def __init__(self, *args, **kwargs):
validator_class = kwargs.pop("validator_class", TemplateFieldValidator)
@@ -296,8 +300,8 @@ def update_public_field_auth_actions(self, instance, data):
[permission_action_platform], []
)
auth_actions = [
- action_id
- for action_id in self.Meta.model.public_field_resource_operations
+ action_id
+ for action_id in self.Meta.model.public_field_resource_operations
if auth_result.get(permission_action_platform)
]
data["auth_actions"] = auth_actions
@@ -438,7 +442,10 @@ class TableSerializer(AuthModelSerializer):
required=True,
max_length=LEN_MIDDLE,
validators=[
- UniqueValidator(queryset=Table.objects.all(), message=_("基础模型名称已经存在,请重新输入"))
+ UniqueValidator(
+ queryset=Table.objects.all(),
+ message=_("基础模型名称已经存在,请重新输入"),
+ )
],
)
desc = serializers.CharField(
diff --git a/itsm/workflow/serializers/global_variable.py b/itsm/workflow/serializers/global_variable.py
index 1d2370443..fae262ccb 100644
--- a/itsm/workflow/serializers/global_variable.py
+++ b/itsm/workflow/serializers/global_variable.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import JSONField
diff --git a/itsm/workflow/serializers/state.py b/itsm/workflow/serializers/state.py
index 940899ff8..fa029f596 100644
--- a/itsm/workflow/serializers/state.py
+++ b/itsm/workflow/serializers/state.py
@@ -24,7 +24,7 @@
"""
from django.db import transaction
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import JSONField, empty
diff --git a/itsm/workflow/serializers/transition.py b/itsm/workflow/serializers/transition.py
index c45a79c6b..19076523f 100644
--- a/itsm/workflow/serializers/transition.py
+++ b/itsm/workflow/serializers/transition.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.fields import JSONField
@@ -39,15 +39,18 @@ class TransitionTemplateSerializer(serializers.ModelSerializer):
required=True,
max_length=LEN_NORMAL,
allow_null=False,
- error_messages={'blank': _('请输入模板名称!'), 'max_length': _('模板名称长度不能大于64个字符')},
+ error_messages={
+ "blank": _("请输入模板名称!"),
+ "max_length": _("模板名称长度不能大于64个字符"),
+ },
)
data = JSONField(required=False, initial=DEFAULT_FLOW_CONDITION)
class Meta:
model = Condition
- fields = ('id', 'name', 'data', 'workflow')
+ fields = ("id", "name", "data", "workflow")
- read_only_fields = ('creator', 'create_at', 'update_at', 'end_at')
+ read_only_fields = ("creator", "create_at", "update_at", "end_at")
def validate(self, attrs):
"""校验参数,name不能相同等"""
@@ -55,7 +58,11 @@ def validate(self, attrs):
if Condition.objects.filter(is_deleted=False, name=attrs["name"]).exists():
raise serializers.ValidationError(_("同流程下线条模板名称已存在"))
if self.context["view"].action == "update":
- if Condition.objects.filter(is_deleted=False, name=attrs["name"]).exclude(id=self.instance.id).exists():
+ if (
+ Condition.objects.filter(is_deleted=False, name=attrs["name"])
+ .exclude(id=self.instance.id)
+ .exists()
+ ):
raise serializers.ValidationError(_("同流程下线条模板名称已存在"))
return attrs
@@ -65,35 +72,42 @@ class TransitionSerializer(serializers.ModelSerializer):
"""流转序列化"""
axis = JSONField(required=False, initial={})
- name = serializers.CharField(required=True, max_length=LEN_SHORT, allow_blank=False, allow_null=False)
+ name = serializers.CharField(
+ required=True, max_length=LEN_SHORT, allow_blank=False, allow_null=False
+ )
condition = JSONField(required=False, initial=DEFAULT_FLOW_CONDITION)
class Meta:
model = Transition
fields = (
- 'workflow',
- 'id',
- 'from_state',
- 'to_state',
- 'name',
- 'axis',
- 'condition',
- 'condition_type',
+ "workflow",
+ "id",
+ "from_state",
+ "to_state",
+ "name",
+ "axis",
+ "condition",
+ "condition_type",
) + model.FIELDS
- read_only_fields = ('key',) + model.FIELDS
+ read_only_fields = ("key",) + model.FIELDS
def __init__(self, *args, **kwargs):
super(TransitionSerializer, self).__init__(*args, **kwargs)
- self.view = self.context.get('view')
- if self.view and self.view.action == 'create':
+ self.view = self.context.get("view")
+ if self.view and self.view.action == "create":
self.validators = [TransitionValidator()]
def update(self, instance, validated_data):
instance = super(TransitionSerializer, self).update(instance, validated_data)
# 不是全局更新的情况下,需要更新条件
- if self.context['view'].action != 'partial_update' and instance.condition_type != 'default':
- State.objects.update_outputs_variables(instance.condition, instance.workflow.id)
+ if (
+ self.context["view"].action != "partial_update"
+ and instance.condition_type != "default"
+ ):
+ State.objects.update_outputs_variables(
+ instance.condition, instance.workflow.id
+ )
return instance
@@ -103,19 +117,22 @@ def create(self, validated_data):
return instance
def to_internal_value(self, data):
- if data.get('condition_type') == 'default':
- data['condition'] = DEFAULT_FLOW_CONDITION
+ if data.get("condition_type") == "default":
+ data["condition"] = DEFAULT_FLOW_CONDITION
return super(TransitionSerializer, self).to_internal_value(data)
def validate(self, attrs):
"""线条配置校验"""
- if attrs.get('condition_type', '') == 'by_field':
- for expression in attrs['condition']['expressions']:
- for condition in expression['expressions']:
- if condition['type'] == 'INT' and condition['value'] == 0:
- if not (condition['condition'] and condition['key']):
+ if attrs.get("condition_type", "") == "by_field":
+ for expression in attrs["condition"]["expressions"]:
+ for condition in expression["expressions"]:
+ if condition["type"] == "INT" and condition["value"] == 0:
+ if not (condition["condition"] and condition["key"]):
raise serializers.ValidationError(_("条件配置错误"))
- if condition['type'] == 'BOOLEAN' and condition['value'] not in [True, False]:
+ if condition["type"] == "BOOLEAN" and condition["value"] not in [
+ True,
+ False,
+ ]:
raise serializers.ValidationError(_("布尔类型的取值范围不正确"))
# elif not (condition['condition'] and condition['key'] and condition['value']):
# raise serializers.ValidationError(u"条件配置错误")
diff --git a/itsm/workflow/serializers/workflow.py b/itsm/workflow/serializers/workflow.py
index 6f886ab20..10e72f172 100644
--- a/itsm/workflow/serializers/workflow.py
+++ b/itsm/workflow/serializers/workflow.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers
from itsm.component.constants import (
@@ -53,17 +53,26 @@ class WorkflowSerializer(DynamicFieldsModelSerializer):
name = serializers.CharField(
required=True,
max_length=LEN_MIDDLE,
- error_messages={'blank': _('请输入流程名称!'), 'max_length': _('流程名称长度不能大于120个字符')},
+ error_messages={
+ "blank": _("请输入流程名称!"),
+ "max_length": _("流程名称长度不能大于120个字符"),
+ },
)
flow_type = serializers.CharField(required=True, max_length=LEN_NORMAL)
- desc = serializers.CharField(required=False, max_length=LEN_LONG, min_length=1,
- allow_blank=True)
- owners = serializers.CharField(required=False, max_length=LEN_XX_LONG, allow_blank=True)
+ desc = serializers.CharField(
+ required=False, max_length=LEN_LONG, min_length=1, allow_blank=True
+ )
+ owners = serializers.CharField(
+ required=False, max_length=LEN_XX_LONG, allow_blank=True
+ )
deploy = serializers.BooleanField(required=False)
- deploy_name = serializers.CharField(required=False, max_length=LEN_LONG, min_length=1,
- allow_blank=True)
+ deploy_name = serializers.CharField(
+ required=False, max_length=LEN_LONG, min_length=1, allow_blank=True
+ )
# 基础模型
- table = serializers.PrimaryKeyRelatedField(required=True, queryset=Table.objects.all())
+ table = serializers.PrimaryKeyRelatedField(
+ required=True, queryset=Table.objects.all()
+ )
# 业务属性字段
is_biz_needed = serializers.BooleanField(required=False)
# 是否自动过单
@@ -71,48 +80,51 @@ class WorkflowSerializer(DynamicFieldsModelSerializer):
is_iam_used = serializers.BooleanField(required=False)
is_supervise_needed = serializers.BooleanField(required=False)
supervise_type = serializers.ChoiceField(required=False, choices=PROCESSOR_CHOICES)
- supervisor = serializers.CharField(required=False, max_length=LEN_LONG, allow_blank=True)
+ supervisor = serializers.CharField(
+ required=False, max_length=LEN_LONG, allow_blank=True
+ )
is_enabled = serializers.BooleanField(required=False)
is_draft = serializers.BooleanField(required=False)
is_revocable = serializers.BooleanField(required=False)
revoke_config = serializers.JSONField(required=False)
notify = NotifySerializer(required=False, allow_null=True, many=True)
- notify_rule = serializers.ChoiceField(required=False, allow_blank=True,
- choices=NOTIFY_RULE_CHOICES)
+ notify_rule = serializers.ChoiceField(
+ required=False, allow_blank=True, choices=NOTIFY_RULE_CHOICES
+ )
notify_freq = serializers.IntegerField(default=EMPTY_INT)
extras = serializers.JSONField(required=False)
class Meta:
model = Workflow
fields = (
- 'id',
- 'name',
- 'desc',
- 'flow_type',
- 'version_number',
- 'deploy',
- 'deploy_name',
- 'notify',
- 'notify_rule',
- 'notify_freq',
- 'is_biz_needed',
- 'is_iam_used',
- 'is_enabled',
- 'is_draft',
- 'is_revocable',
- 'is_builtin',
- 'is_supervise_needed',
- 'supervisor',
- 'supervise_type',
- 'table',
- 'owners',
- 'extras',
- 'revoke_config',
- 'is_auto_approve',
- ) + model.FIELDS
+ "id",
+ "name",
+ "desc",
+ "flow_type",
+ "version_number",
+ "deploy",
+ "deploy_name",
+ "notify",
+ "notify_rule",
+ "notify_freq",
+ "is_biz_needed",
+ "is_iam_used",
+ "is_enabled",
+ "is_draft",
+ "is_revocable",
+ "is_builtin",
+ "is_supervise_needed",
+ "supervisor",
+ "supervise_type",
+ "table",
+ "owners",
+ "extras",
+ "revoke_config",
+ "is_auto_approve",
+ ) + model.FIELDS
# 只读字段在创建和更新时均被忽略
- read_only_fields = ('creator', 'create_at', 'update_at', 'end_at')
+ read_only_fields = ("creator", "create_at", "update_at", "end_at")
def save(self, **kwargs):
instance = super(WorkflowSerializer, self).save(**kwargs)
@@ -120,15 +132,15 @@ def save(self, **kwargs):
return instance
def update(self, instance, validated_data):
- deploy = validated_data.pop('deploy', None)
- deploy_name = validated_data.pop('deploy_name', None)
+ deploy = validated_data.pop("deploy", None)
+ deploy_name = validated_data.pop("deploy_name", None)
- is_biz_needed = validated_data.get('is_biz_needed', None)
+ is_biz_needed = validated_data.get("is_biz_needed", None)
if is_biz_needed is False and instance.is_biz_needed is True:
related_validate(instance.fields.get(key=FIELD_BIZ))
flow = super(WorkflowSerializer, self).update(instance, validated_data)
- if 'task_settings' in validated_data.get('extras', {}):
- flow.create_task(validated_data['extras']['task_settings'])
+ if "task_settings" in validated_data.get("extras", {}):
+ flow.create_task(validated_data["extras"]["task_settings"])
# 是否立即部署
if deploy:
@@ -139,33 +151,39 @@ def update(self, instance, validated_data):
def to_internal_value(self, data):
validated_data = super(WorkflowSerializer, self).to_internal_value(data)
- if validated_data.get('supervise_type') == 'PERSON':
- validated_data['supervisor'] = dotted_name(validated_data.get('supervisor', ''))
+ if validated_data.get("supervise_type") == "PERSON":
+ validated_data["supervisor"] = dotted_name(
+ validated_data.get("supervisor", "")
+ )
notify_list = validated_data.pop("notify", None)
if notify_list:
- validated_data['notify'] = Notify.objects.filter(
+ validated_data["notify"] = Notify.objects.filter(
type__in=(notify["type"] for notify in notify_list)
- ).values_list('pk', flat=True)
+ ).values_list("pk", flat=True)
if "owners" in validated_data:
validated_data["owners"] = dotted_name(validated_data["owners"])
- if "is_enabled" in validated_data and "extras" not in validated_data and self.instance:
+ if (
+ "is_enabled" in validated_data
+ and "extras" not in validated_data
+ and self.instance
+ ):
# 如果extras不存在,直接置空
extras = self.instance.extras
extras["task_settings"] = []
- validated_data['extras'] = extras
+ validated_data["extras"] = extras
return validated_data
def to_representation(self, instance):
data = super(WorkflowSerializer, self).to_representation(instance)
data["owners"] = normal_name(data.get("owners"))
- data['updated_by'] = transform_single_username(data['updated_by'])
+ data["updated_by"] = transform_single_username(data["updated_by"])
- if "supervise_type" in data and data['supervise_type'] == 'PERSON':
- data['supervisor'] = dotted_property(data, 'supervisor')
+ if "supervise_type" in data and data["supervise_type"] == "PERSON":
+ data["supervisor"] = dotted_property(data, "supervisor")
task_settings = data["extras"].get("task_settings")
if task_settings and isinstance(task_settings, dict):
data["extras"]["task_settings"] = []
@@ -174,7 +192,9 @@ def to_representation(self, instance):
{
"task_schema_id": task_settings["task_schema_ids"][0],
"create_task_state": task_settings["create_task_state"],
- "execute_can_create": task_settings.get("execute_can_create", False),
+ "execute_can_create": task_settings.get(
+ "execute_can_create", False
+ ),
"execute_task_state": task_settings["execute_task_state"],
"need_task_finished": task_settings["need_task_finished"],
}
@@ -186,29 +206,47 @@ def to_representation(self, instance):
def validate_notify_freq(self, value):
"""通知频率校验"""
if value not in NOTIFY_FREQ_CHOICES:
- raise serializers.ValidationError({str(_('参数校验失败')): _('通知频率参数不正确')})
+ raise serializers.ValidationError(
+ {str(_("参数校验失败")): _("通知频率参数不正确")}
+ )
return value
def validate(self, attrs):
- name = attrs.get('name')
+ name = attrs.get("name")
if self.instance:
if Workflow.objects.filter(name=name).exclude(id=self.instance.id).exists():
- raise serializers.ValidationError({str(_('参数校验失败')): _('系统中已存在同名流程,请尝试换个流程名称')})
+ raise serializers.ValidationError(
+ {
+ str(_("参数校验失败")): _(
+ "系统中已存在同名流程,请尝试换个流程名称"
+ )
+ }
+ )
else:
if Workflow.objects.filter(name=name).exists():
- raise serializers.ValidationError({str(_('参数校验失败')): _('系统中已存在同名流程,请尝试换个流程名称')})
+ raise serializers.ValidationError(
+ {
+ str(_("参数校验失败")): _(
+ "系统中已存在同名流程,请尝试换个流程名称"
+ )
+ }
+ )
- notify_rule = attrs.get('notify_rule', 'NONE')
- notify = attrs.get('notify', '')
- if notify_rule != 'NONE' and not notify:
- raise serializers.ValidationError({str(_('参数校验失败')): _('至少选择一种通知类型')})
+ notify_rule = attrs.get("notify_rule", "NONE")
+ notify = attrs.get("notify", "")
+ if notify_rule != "NONE" and not notify:
+ raise serializers.ValidationError(
+ {str(_("参数校验失败")): _("至少选择一种通知类型")}
+ )
- deploy = attrs.get('deploy')
- deploy_name = attrs.get('deploy_name')
+ deploy = attrs.get("deploy")
+ deploy_name = attrs.get("deploy_name")
if deploy:
WorkflowPipelineValidator(self.instance)(if_deploy=True)
if deploy and not deploy_name:
- raise serializers.ValidationError({str(_('参数校验失败')): _('请指定部署流程名')})
+ raise serializers.ValidationError(
+ {str(_("参数校验失败")): _("请指定部署流程名")}
+ )
return attrs
@@ -219,31 +257,31 @@ class WorkflowVersionSerializer(DynamicFieldsModelSerializer):
class Meta:
model = WorkflowVersion
main_fields = (
- 'id',
- 'name',
- 'desc',
- 'workflow_id',
- 'flow_type',
- 'version_number',
- 'is_builtin',
- 'is_enabled',
- 'is_draft',
- 'is_revocable',
- 'updated_by',
- 'update_at',
- 'creator',
+ "id",
+ "name",
+ "desc",
+ "workflow_id",
+ "flow_type",
+ "version_number",
+ "is_builtin",
+ "is_enabled",
+ "is_draft",
+ "is_revocable",
+ "updated_by",
+ "update_at",
+ "creator",
)
- property_fields = ('service_cnt',)
+ property_fields = ("service_cnt",)
fields = property_fields + main_fields
read_only_fields = (
- 'service_cnt',
- 'version_number',
- 'is_builtin',
- 'is_draft',
- 'workflow_id',
- 'updated_by',
- 'update_at',
+ "service_cnt",
+ "version_number",
+ "is_builtin",
+ "is_draft",
+ "workflow_id",
+ "updated_by",
+ "update_at",
)
def to_representation(self, instance):
@@ -260,10 +298,14 @@ def to_representation(self, instance):
attribute = field.get_attribute(instance)
except SkipField:
if field.field_name in self.Meta.property_fields:
- attribute = getattr(self.Meta.model.objects, field.field_name)(instance)
+ attribute = getattr(self.Meta.model.objects, field.field_name)(
+ instance
+ )
else:
continue
- check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
+ check_for_none = (
+ attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
+ )
if check_for_none is None:
ret[field.field_name] = None
else:
@@ -275,12 +317,17 @@ class OperationalDataWorkflowSerializer(WorkflowSerializer):
"""运营数据流程序列化"""
def to_representation(self, instance):
- data = super(OperationalDataWorkflowSerializer, self).to_representation(instance)
+ data = super(OperationalDataWorkflowSerializer, self).to_representation(
+ instance
+ )
query_type = self.context.get("query_type", "list")
if query_type == "detail":
states_serializer = StateSerializer(instance.states, many=True)
fields_serializer = FieldSerializer(instance.fields, many=True)
data.update(
- {"states": states_serializer.data, "fields": fields_serializer.data, }
+ {
+ "states": states_serializer.data,
+ "fields": fields_serializer.data,
+ }
)
return data
diff --git a/itsm/workflow/signals/__init__.py b/itsm/workflow/signals/__init__.py
index a13f844c5..a2f858ce4 100644
--- a/itsm/workflow/signals/__init__.py
+++ b/itsm/workflow/signals/__init__.py
@@ -25,5 +25,5 @@
from django.dispatch import Signal
-state_created = Signal(providing_args=["flow_id", "state_id", "state_type"])
-state_deleted = Signal(providing_args=["flow_id", "state_id"])
+state_created = Signal() # providing_args=["flow_id", "state_id", "state_type"]
+state_deleted = Signal() # providing_args=["flow_id", "state_id"]
diff --git a/itsm/workflow/tasks.py b/itsm/workflow/tasks.py
index d87e93aad..bdd47f799 100644
--- a/itsm/workflow/tasks.py
+++ b/itsm/workflow/tasks.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from celery.schedules import crontab
-from celery.task import periodic_task
+from blueapps.contrib.celery_tools.periodic import periodic_task
from common.log import logger
from itsm.component.constants import PUBLIC_PROJECT_PROJECT_KEY
diff --git a/itsm/workflow/utils.py b/itsm/workflow/utils.py
index d2233283c..933fd775c 100644
--- a/itsm/workflow/utils.py
+++ b/itsm/workflow/utils.py
@@ -24,7 +24,7 @@
"""
from django.conf import settings
from django.db.models import Q
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.constants import (
diff --git a/itsm/workflow/validators.py b/itsm/workflow/validators.py
index 9c39bcb68..9022c31a2 100644
--- a/itsm/workflow/validators.py
+++ b/itsm/workflow/validators.py
@@ -27,7 +27,7 @@
import six
from django.db.models import Q
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from common.log import logger
from itsm.component.constants import (
@@ -155,7 +155,9 @@ def related_states_validate(field_key, workflow_states):
filter_key in task_api_instance.req_body
or filter_key in task_api_instance.req_params
):
- raise ParamError(_("该字段正在被【{}】节点引用,请先取消引用").format(task_state.name))
+ raise ParamError(
+ _("该字段正在被【{}】节点引用,请先取消引用").format(task_state.name)
+ )
sops_states = StateExtrasSerializer(
workflow_states.filter(type=TASK_SOPS_STATE, is_draft=False), many=True
@@ -172,7 +174,9 @@ def related_states_validate(field_key, workflow_states):
]
)
if field_key in keys:
- raise ParamError(_("该字段正在被【{}】节点引用,请先取消引用").format(state["name"]))
+ raise ParamError(
+ _("该字段正在被【{}】节点引用,请先取消引用").format(state["name"])
+ )
def related_transitions_validate(field_key, workflow_transitions):
@@ -195,7 +199,11 @@ def related_transitions_validate(field_key, workflow_transitions):
for inside_exp in outside_exp["expressions"]:
keys.append(inside_exp["key"])
if field_key in keys:
- raise ParamError(_("该字段正作为【{}】线条的判断条件,请先取消引用").format(transition["name"]))
+ raise ParamError(
+ _("该字段正作为【{}】线条的判断条件,请先取消引用").format(
+ transition["name"]
+ )
+ )
def show_conditions_validate(field_key, workflow_fields):
@@ -211,7 +219,11 @@ def show_conditions_validate(field_key, workflow_fields):
if field_key in [
exp["key"] for exp in condition["show_conditions"]["expressions"]
]:
- raise ParamError(_("该字段正作为【{}】字段的显示条件,请先取消引用").format(condition["name"]))
+ raise ParamError(
+ _("该字段正作为【{}】字段的显示条件,请先取消引用").format(
+ condition["name"]
+ )
+ )
def related_field_validate(field_key, workflow_fields):
@@ -223,7 +235,9 @@ def related_field_validate(field_key, workflow_fields):
related_fields = RelatedFieldSerializer(workflow_fields, many=True).data
for field in related_fields:
if field_key in field["related_fields"].get("rely_on", []):
- raise ParamError(_("该字段正在被【{}】字段引用,请先取消引用").format(field["name"]))
+ raise ParamError(
+ _("该字段正在被【{}】字段引用,请先取消引用").format(field["name"])
+ )
def template_fields_exists_validate(fields):
@@ -236,7 +250,9 @@ def template_fields_exists_validate(fields):
)
if set(fields).difference(template_fields):
raise ParamError(
- _("{}公共字段不存在,请联系管理员").format(list(set(fields).difference(template_fields)))
+ _("{}公共字段不存在,请联系管理员").format(
+ list(set(fields).difference(template_fields))
+ )
)
@@ -248,7 +264,9 @@ def table_remove_fiels_validate(fields, table):
table_fields = list(table.fields.values_list("id", flat=True))
if not set(fields).issubset(table_fields):
raise ParamError(
- _("{}公共字段不存在,请联系管理员").format(list(set(table_fields).difference(fields)))
+ _("{}公共字段不存在,请联系管理员").format(
+ list(set(table_fields).difference(fields))
+ )
)
@@ -313,7 +331,9 @@ def trigger_validate(source_instance, source_type=SOURCE_WORKFLOW):
if draft_triggers and source_type == SOURCE_WORKFLOW:
raise WorkFlowInvalidError(
[],
- _("{source_type}【{source_name}】内的触发器【{trigger_name}】为草稿状态,无法部署").format(
+ _(
+ "{source_type}【{source_name}】内的触发器【{trigger_name}】为草稿状态,无法部署"
+ ).format(
source_type=source_type_dict.get(source_type),
source_name=source_instance.name,
trigger_name=",".join(draft_triggers),
@@ -336,7 +356,9 @@ def task_validate(self):
).values_list("name", flat=True)
if draft_tasks:
raise WorkFlowInvalidError(
- [], _("流程内引用的任务模版【%s】为草稿状态,无法部署") % ",".join(draft_tasks)
+ [],
+ _("流程内引用的任务模版【%s】为草稿状态,无法部署")
+ % ",".join(draft_tasks),
)
for task in TaskSchema.objects.filter(id__in=task_schema_ids):
@@ -399,7 +421,11 @@ def flat_condition(expressions):
]
conditions.extend(
[
- {"condition": item.show_conditions, "name": item.name, "obj_type": "字段"}
+ {
+ "condition": item.show_conditions,
+ "name": item.name,
+ "obj_type": "字段",
+ }
for item in self.instance.fields.all()
if item.show_conditions
]
@@ -441,13 +467,17 @@ def pipeline_validate(self):
invalid_state_ids = [
int(self.states_map[k]) for arg in error.args for k, v in arg.items()
]
- raise WorkFlowInvalidError(invalid_state_ids, _("当前流程画布连线不合理,请重新确认. "))
+ raise WorkFlowInvalidError(
+ invalid_state_ids, _("当前流程画布连线不合理,请重新确认. ")
+ )
except ConvergeMatchError as error:
invalid_state_ids = [int(self.states_map[error.gateway_id])]
raise WorkFlowInvalidError(invalid_state_ids, str(error))
except StreamValidateError as error:
invalid_state_ids = [int(self.states_map[error.node_id])]
- raise WorkFlowInvalidError(invalid_state_ids, _("当前节点画布连线不合理,请重新确认. "))
+ raise WorkFlowInvalidError(
+ invalid_state_ids, _("当前节点画布连线不合理,请重新确认. ")
+ )
except Exception as error:
logger.exception(str(error))
raise WorkFlowInvalidError([], str(error))
@@ -568,7 +598,9 @@ def key_validate(self, value):
flow_id=value.get("workflow").id, key=value.get("key")
).exists()
):
- raise ParamError(_("当前流程已存在唯一标识【{}】,请重新输入").format(value.get("key")))
+ raise ParamError(
+ _("当前流程已存在唯一标识【{}】,请重新输入").format(value.get("key"))
+ )
@staticmethod
def custom_table_validate(value):
@@ -611,7 +643,9 @@ def select_type_choice_validate(self, value):
choice = value.get("choice", [])
if not choice:
raise ParamError(
- _("【{0}】请输入自定义数据,换行分隔。").format(SELECT_TYPE_CHOICES[value.get("type")])
+ _("【{0}】请输入自定义数据,换行分隔。").format(
+ SELECT_TYPE_CHOICES[value.get("type")]
+ )
)
for field in choice:
name = field.get("name")
@@ -632,7 +666,9 @@ def field_common_validate(name):
if not name:
raise ParamError(_("请输入选项值"))
if len(name) > LEN_MIDDLE:
- raise ParamError(_("自定义数据【{}】长度不能大于{}个字符").format(name, LEN_MIDDLE))
+ raise ParamError(
+ _("自定义数据【{}】长度不能大于{}个字符").format(name, LEN_MIDDLE)
+ )
def field_key_validate(self, key):
"""
@@ -641,7 +677,9 @@ def field_key_validate(self, key):
self.field_common_validate(key)
if not re.match("^[_a-zA-Z0-9]*$", key) or len(key) > LEN_MIDDLE:
raise ParamError(
- _("自定义数据key值【{}】仅支持【英文、数字和下划线】,长度小于128字符,请重新输入").format(key)
+ _(
+ "自定义数据key值【{}】仅支持【英文、数字和下划线】,长度小于128字符,请重新输入"
+ ).format(key)
)
@staticmethod
@@ -690,7 +728,11 @@ def key_validate(self, value):
).exists():
if value.get("key") in [FIELD_TITLE, FIELD_BIZ]:
raise ParamError(_("title, bk_biz_id 为内置唯一标识,请重新输入"))
- raise ParamError(_("当前项目字段库已存在唯一标识【{}】,请重新输入").format(value.get("key")))
+ raise ParamError(
+ _("当前项目字段库已存在唯一标识【{}】,请重新输入").format(
+ value.get("key")
+ )
+ )
def name_validate(self, value):
fields = TemplateField.objects.filter(
@@ -701,7 +743,9 @@ def name_validate(self, value):
if self.instance:
fields = fields.exclude(id=self.instance.id)
if fields.exists():
- raise ParamError(_("字段库已存在名称【{}】,请重新输入").format(value.get("name")))
+ raise ParamError(
+ _("字段库已存在名称【{}】,请重新输入").format(value.get("name"))
+ )
def template_field_can_destroy(instance):
@@ -710,7 +754,9 @@ def template_field_can_destroy(instance):
if tables:
table_names = ",".join([table.name for table in tables])
- raise ValidationError(_("字段已被基础模型[{}]引用,无法删除".format(table_names)))
+ raise ValidationError(
+ _("字段已被基础模型[{}]引用,无法删除".format(table_names))
+ )
class StatePollValidator(object):
@@ -838,13 +884,17 @@ def state_validate(self):
and self.to_state.label == EMPTY
):
if self.from_state.label.rfind(NORMAL_STATE_LABEL_PREFIX) == -1:
- raise TransitionError(_("待连接的聚合网关与当前节点不在同一个工作区域内"))
+ raise TransitionError(
+ _("待连接的聚合网关与当前节点不在同一个工作区域内")
+ )
if (
self.from_state.type == COVERAGE_STATE == self.to_state.type
and find_sub_string(self.from_state.label, ROUTER_STATE_LABEL_PREFIX)
== GLOBAL_LABEL
):
- raise TransitionError(_("待连接的聚合网关与当前节点不在同一个工作区域内"))
+ raise TransitionError(
+ _("待连接的聚合网关与当前节点不在同一个工作区域内")
+ )
return
# 以下label不相等且可能多入多出的情况
diff --git a/itsm/workflow/views.py b/itsm/workflow/views.py
index e0d709c69..f9e85a747 100644
--- a/itsm/workflow/views.py
+++ b/itsm/workflow/views.py
@@ -34,7 +34,7 @@
from django.db.models import Q
from django.http import StreamingHttpResponse, FileResponse, Http404
from django.utils.encoding import escape_uri_path
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from rest_framework import serializers, status, permissions
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
@@ -47,7 +47,8 @@
DateTimeType,
TimeType,
BooleanType,
- SelectMultipleType, SelectType,
+ SelectMultipleType,
+ SelectType,
)
from common.log import logger
from itsm.component.constants import (
@@ -192,10 +193,6 @@ class WorkflowViewSet(
permission_free_actions = ["get_global_choices", "list"]
permission_classes = (WorkflowIamAuth,)
- def get_queryset(self):
- self.queryset = self.queryset.exclude(flow_type="internal")
- return self.queryset
-
@action(detail=False, methods=["get"])
def get_global_choices(self, request):
"""查询全局选项列表信息"""
@@ -301,11 +298,11 @@ def exports(self, request, pk=None):
response = FileResponse(json.dumps([data], cls=JsonEncoder, indent=2))
response["Content-Type"] = "application/octet-stream"
# 中文文件名乱码问题
- response[
- "Content-Disposition"
- ] = "attachment; filename*=UTF-8''bk_itsm_{}_{}.json".format(
- escape_uri_path(workflow.name),
- create_version_number(),
+ response["Content-Disposition"] = (
+ "attachment; filename*=UTF-8''bk_itsm_{}_{}.json".format(
+ escape_uri_path(workflow.name),
+ create_version_number(),
+ )
)
return response
@@ -540,7 +537,7 @@ def get_iam_resource_id(self):
if self.action == "batch_update":
return self.request.data.get("workflow_id")
return None
-
+
def perform_destroy(self, instance):
# 从开始节点出来的连线只能由一条, 且不能被删除
if instance.from_state.type == START_STATE:
@@ -586,7 +583,9 @@ def download_file(self, request, *args, **kwargs):
field_object = self.get_object()
if field_object.type != "FILE":
- raise serializers.ValidationError(_("当前字段非附件字段,无法下载附件文件!"))
+ raise serializers.ValidationError(
+ _("当前字段非附件字段,无法下载附件文件!")
+ )
try:
files = (
field_object.choice
@@ -595,7 +594,9 @@ def download_file(self, request, *args, **kwargs):
)
except Exception:
logger.exception("json解析错误")
- raise serializers.ValidationError(_("当前字段解析信息出错,请确认是否已进行数据升级!"))
+ raise serializers.ValidationError(
+ _("当前字段解析信息出错,请确认是否已进行数据升级!")
+ )
file_info = files.get(unique_key)
if not file_info:
@@ -606,7 +607,9 @@ def download_file(self, request, *args, **kwargs):
if not store.exists(file_path):
raise serializers.ValidationError(
- _("要下载的文件【{}】不存在, 可能已经被删除,请与管理员确认!").format(file_info["name"])
+ _("要下载的文件【{}】不存在, 可能已经被删除,请与管理员确认!").format(
+ file_info["name"]
+ )
)
response = StreamingHttpResponse(FileWrapper(store.open(file_path, "rb"), 512))
@@ -750,7 +753,7 @@ class TemplateFieldViewSet(component_viewsets.ModelViewSet):
"destroy": "field_delete",
"update": "field_edit",
}
-
+
filter_fields = {
"id": ["in"],
"key": ["exact", "in", "contains", "startswith"],
@@ -1102,7 +1105,7 @@ class TaskSchemaViewSet(DynamicListModelMixin, component_viewsets.ModelViewSet):
permission_action_platform = "public_task_template_manage"
permission_create_action = ["create", "clone"]
permission_resource_is_project = True
-
+
pagination_class = None
def update(self, request, *args, **kwargs):
@@ -1116,7 +1119,9 @@ def update(self, request, *args, **kwargs):
isinstance(task_fields, dict)
and isinstance(task_fields["task_field_ids"], list)
):
- raise serializers.ValidationError(_("任务字段排序参数不合法,请联系管理员"))
+ raise serializers.ValidationError(
+ _("任务字段排序参数不合法,请联系管理员")
+ )
ordering = "FIELD(`id`, {})".format(
",".join(
diff --git a/pipeline/component_framework/models.py b/pipeline/component_framework/models.py
index 39fc13e77..988b5783c 100644
--- a/pipeline/component_framework/models.py
+++ b/pipeline/component_framework/models.py
@@ -12,7 +12,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.component_framework.constants import LEGACY_PLUGINS_VERSION
from pipeline.component_framework.library import ComponentLibrary
@@ -40,7 +40,9 @@ class ComponentModel(models.Model):
"""
code = models.CharField(_("组件编码"), max_length=255)
- version = models.CharField(_("组件版本"), max_length=64, default=LEGACY_PLUGINS_VERSION)
+ version = models.CharField(
+ _("组件版本"), max_length=64, default=LEGACY_PLUGINS_VERSION
+ )
name = models.CharField(_("组件名称"), max_length=255)
status = models.BooleanField(_("组件是否可用"), default=True)
diff --git a/pipeline/contrib/external_plugins/models/base.py b/pipeline/contrib/external_plugins/models/base.py
index 986eb72a9..f1544a072 100644
--- a/pipeline/contrib/external_plugins/models/base.py
+++ b/pipeline/contrib/external_plugins/models/base.py
@@ -17,7 +17,7 @@
from copy import deepcopy
from django.db import IntegrityError, models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.component_framework.library import ComponentLibrary
from pipeline.contrib.external_plugins import exceptions
@@ -38,14 +38,18 @@ def package_source(cls):
class SourceManager(models.Manager):
def create_source(self, name, packages, from_config, **kwargs):
create_kwargs = deepcopy(kwargs)
- create_kwargs.update({"name": name, "packages": packages, "from_config": from_config})
+ create_kwargs.update(
+ {"name": name, "packages": packages, "from_config": from_config}
+ )
return self.create(**create_kwargs)
def remove_source(self, source_id):
source = self.get(id=source_id)
if source.from_config:
- raise exceptions.InvalidOperationException("Can not remove source create from config")
+ raise exceptions.InvalidOperationException(
+ "Can not remove source create from config"
+ )
source.delete()
@@ -66,11 +70,15 @@ def update_source_from_config(self, configs):
defaults["packages"] = config["packages"]
try:
- self.update_or_create(name=config["name"], from_config=True, defaults=defaults)
+ self.update_or_create(
+ name=config["name"], from_config=True, defaults=defaults
+ )
except IntegrityError:
raise exceptions.InvalidOperationException(
'There is a external source named "{source_name}" but not create from config, '
- "can not do source update operation".format(source_name=config["name"])
+ "can not do source update operation".format(
+ source_name=config["name"]
+ )
)
@@ -103,11 +111,20 @@ def imported_plugins(self):
try:
importer = self.importer()
except ValueError as e:
- logger.exception("ExternalPackageSource[name={}] call importer error: {}".format(self.name, e))
+ logger.exception(
+ "ExternalPackageSource[name={}] call importer error: {}".format(
+ self.name, e
+ )
+ )
return plugins
for component in ComponentLibrary.component_list():
- component_importer = getattr(sys.modules[component.__module__], "__loader__", None)
- if isinstance(component_importer, type(importer)) and component_importer.name == self.name:
+ component_importer = getattr(
+ sys.modules[component.__module__], "__loader__", None
+ )
+ if (
+ isinstance(component_importer, type(importer))
+ and component_importer.name == self.name
+ ):
plugins.append(
{
"code": component.code,
@@ -130,7 +147,9 @@ def modules(self):
@staticmethod
def update_package_source_from_config(source_configs):
- classified_config = {source_type: [] for source_type in list(source_cls_factory.keys())}
+ classified_config = {
+ source_type: [] for source_type in list(source_cls_factory.keys())
+ }
for config in deepcopy(source_configs):
classified_config.setdefault(config.pop("type"), []).append(config)
diff --git a/pipeline/contrib/external_plugins/models/source.py b/pipeline/contrib/external_plugins/models/source.py
index 8aae5d918..d876fa354 100644
--- a/pipeline/contrib/external_plugins/models/source.py
+++ b/pipeline/contrib/external_plugins/models/source.py
@@ -12,11 +12,21 @@
"""
from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.conf import settings
-from pipeline.contrib.external_plugins.models.base import FILE_SYSTEM, GIT, S3, ExternalPackageSource, package_source
-from pipeline.contrib.external_plugins.utils.importer import FSModuleImporter, GitRepoModuleImporter, S3ModuleImporter
+from pipeline.contrib.external_plugins.models.base import (
+ FILE_SYSTEM,
+ GIT,
+ S3,
+ ExternalPackageSource,
+ package_source,
+)
+from pipeline.contrib.external_plugins.utils.importer import (
+ FSModuleImporter,
+ GitRepoModuleImporter,
+ S3ModuleImporter,
+)
@package_source
@@ -82,7 +92,9 @@ def type():
return FILE_SYSTEM
def importer(self):
- return FSModuleImporter(name=self.name, modules=list(self.packages.keys()), path=self.path)
+ return FSModuleImporter(
+ name=self.name, modules=list(self.packages.keys()), path=self.path
+ )
def details(self):
return {"path": self.path}
diff --git a/pipeline/contrib/periodic_task/models.py b/pipeline/contrib/periodic_task/models.py
index 929a28e47..282768b51 100644
--- a/pipeline/contrib/periodic_task/models.py
+++ b/pipeline/contrib/periodic_task/models.py
@@ -19,7 +19,7 @@
from django.core.exceptions import MultipleObjectsReturned, ValidationError
from django.db import models
from django.db.models import signals
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.constants import PIPELINE_DEFAULT_PRIORITY
from pipeline.contrib.periodic_task.djcelery import managers
@@ -28,7 +28,12 @@
from pipeline.contrib.periodic_task.djcelery.utils import now
from pipeline.contrib.periodic_task.signals import periodic_task_start_failed
from pipeline.exceptions import InvalidOperationException
-from pipeline.models import CompressJSONField, PipelineInstance, PipelineTemplate, Snapshot
+from pipeline.models import (
+ CompressJSONField,
+ PipelineInstance,
+ PipelineTemplate,
+ Snapshot,
+)
from pipeline.utils.uniqid import uniqid
PERIOD_CHOICES = (
@@ -43,7 +48,11 @@
@python_2_unicode_compatible
class IntervalSchedule(models.Model):
every = models.IntegerField(_("every"), null=False)
- period = models.CharField(_("period"), max_length=24, choices=PERIOD_CHOICES,)
+ period = models.CharField(
+ _("period"),
+ max_length=24,
+ choices=PERIOD_CHOICES,
+ )
class Meta:
verbose_name = _("interval")
@@ -83,9 +92,21 @@ def cronexp(field):
class CrontabSchedule(models.Model):
minute = models.CharField(_("minute"), max_length=64, default="*")
hour = models.CharField(_("hour"), max_length=64, default="*")
- day_of_week = models.CharField(_("day of week"), max_length=64, default="*",)
- day_of_month = models.CharField(_("day of month"), max_length=64, default="*",)
- month_of_year = models.CharField(_("month of year"), max_length=64, default="*",)
+ day_of_week = models.CharField(
+ _("day of week"),
+ max_length=64,
+ default="*",
+ )
+ day_of_month = models.CharField(
+ _("day of month"),
+ max_length=64,
+ default="*",
+ )
+ month_of_year = models.CharField(
+ _("month of year"),
+ max_length=64,
+ default="*",
+ )
timezone = timezone_field.TimeZoneField(default="UTC")
class Meta:
@@ -153,10 +174,19 @@ def last_change(cls):
@python_2_unicode_compatible
class DjCeleryPeriodicTask(models.Model):
- name = models.CharField(_("name"), max_length=200, unique=True, help_text=_("Useful description"),)
+ name = models.CharField(
+ _("name"),
+ max_length=200,
+ unique=True,
+ help_text=_("Useful description"),
+ )
task = models.CharField(_("task name"), max_length=200)
interval = models.ForeignKey(
- IntervalSchedule, null=True, blank=True, verbose_name=_("interval"), on_delete=models.CASCADE,
+ IntervalSchedule,
+ null=True,
+ blank=True,
+ verbose_name=_("interval"),
+ on_delete=models.CASCADE,
)
crontab = models.ForeignKey(
CrontabSchedule,
@@ -166,19 +196,60 @@ class DjCeleryPeriodicTask(models.Model):
on_delete=models.CASCADE,
help_text=_("Use one of interval/crontab"),
)
- args = models.TextField(_("Arguments"), blank=True, default="[]", help_text=_("JSON encoded positional arguments"),)
+ args = models.TextField(
+ _("Arguments"),
+ blank=True,
+ default="[]",
+ help_text=_("JSON encoded positional arguments"),
+ )
kwargs = models.TextField(
- _("Keyword arguments"), blank=True, default="{}", help_text=_("JSON encoded keyword arguments"),
+ _("Keyword arguments"),
+ blank=True,
+ default="{}",
+ help_text=_("JSON encoded keyword arguments"),
)
queue = models.CharField(
- _("queue"), max_length=200, blank=True, null=True, default=None, help_text=_("Queue defined in CELERY_QUEUES"),
+ _("queue"),
+ max_length=200,
+ blank=True,
+ null=True,
+ default=None,
+ help_text=_("Queue defined in CELERY_QUEUES"),
+ )
+ exchange = models.CharField(
+ _("exchange"),
+ max_length=200,
+ blank=True,
+ null=True,
+ default=None,
+ )
+ routing_key = models.CharField(
+ _("routing key"),
+ max_length=200,
+ blank=True,
+ null=True,
+ default=None,
+ )
+ expires = models.DateTimeField(
+ _("expires"),
+ blank=True,
+ null=True,
+ )
+ enabled = models.BooleanField(
+ _("enabled"),
+ default=True,
+ )
+ last_run_at = models.DateTimeField(
+ auto_now=False,
+ auto_now_add=False,
+ editable=False,
+ blank=True,
+ null=True,
+ )
+ total_run_count = models.PositiveIntegerField(
+ default=0,
+ editable=False,
)
- exchange = models.CharField(_("exchange"), max_length=200, blank=True, null=True, default=None,)
- routing_key = models.CharField(_("routing key"), max_length=200, blank=True, null=True, default=None,)
- expires = models.DateTimeField(_("expires"), blank=True, null=True,)
- enabled = models.BooleanField(_("enabled"), default=True,)
- last_run_at = models.DateTimeField(auto_now=False, auto_now_add=False, editable=False, blank=True, null=True,)
- total_run_count = models.PositiveIntegerField(default=0, editable=False,)
date_changed = models.DateTimeField(auto_now=True)
description = models.TextField(_("description"), blank=True)
@@ -192,9 +263,13 @@ class Meta:
def validate_unique(self, *args, **kwargs):
super(DjCeleryPeriodicTask, self).validate_unique(*args, **kwargs)
if not self.interval and not self.crontab:
- raise ValidationError({"interval": ["One of interval or crontab must be set."]})
+ raise ValidationError(
+ {"interval": ["One of interval or crontab must be set."]}
+ )
if self.interval and self.crontab:
- raise ValidationError({"crontab": ["Only one of interval or crontab must be set"]})
+ raise ValidationError(
+ {"crontab": ["Only one of interval or crontab must be set"]}
+ )
def save(self, *args, **kwargs):
self.exchange = self.exchange or None
@@ -286,10 +361,16 @@ class PeriodicTask(models.Model):
)
cron = models.CharField(_("调度策略"), max_length=128)
celery_task = models.ForeignKey(
- DjCeleryPeriodicTask, verbose_name=_("celery 周期任务实例"), null=True, on_delete=models.SET_NULL
+ DjCeleryPeriodicTask,
+ verbose_name=_("celery 周期任务实例"),
+ null=True,
+ on_delete=models.SET_NULL,
)
snapshot = models.ForeignKey(
- Snapshot, related_name="periodic_tasks", verbose_name=_("用于创建流程实例的结构数据"), on_delete=models.DO_NOTHING
+ Snapshot,
+ related_name="periodic_tasks",
+ verbose_name=_("用于创建流程实例的结构数据"),
+ on_delete=models.DO_NOTHING,
)
total_run_count = models.PositiveIntegerField(_("执行次数"), default=0)
last_run_at = models.DateTimeField(_("上次运行时间"), null=True)
@@ -350,7 +431,9 @@ def modify_cron(self, cron, timezone=None):
def modify_constants(self, constants):
if self.enabled:
- raise InvalidOperationException("can not modify constants when task is enabled")
+ raise InvalidOperationException(
+ "can not modify constants when task is enabled"
+ )
exec_data = self.execution_data
for key, value in list(constants.items()):
if key in exec_data["constants"]:
@@ -361,7 +444,9 @@ def modify_constants(self, constants):
class PeriodicTaskHistoryManager(models.Manager):
- def record_schedule(self, periodic_task, pipeline_instance, ex_data, start_success=True):
+ def record_schedule(
+ self, periodic_task, pipeline_instance, ex_data, start_success=True
+ ):
history = self.create(
periodic_task=periodic_task,
pipeline_instance=pipeline_instance,
@@ -372,14 +457,20 @@ def record_schedule(self, periodic_task, pipeline_instance, ex_data, start_succe
)
if not start_success:
- periodic_task_start_failed.send(sender=PeriodicTask, periodic_task=periodic_task, history=history)
+ periodic_task_start_failed.send(
+ sender=PeriodicTask, periodic_task=periodic_task, history=history
+ )
return history
class PeriodicTaskHistory(models.Model):
periodic_task = models.ForeignKey(
- PeriodicTask, related_name="instance_rel", verbose_name=_("周期任务"), null=True, on_delete=models.DO_NOTHING
+ PeriodicTask,
+ related_name="instance_rel",
+ verbose_name=_("周期任务"),
+ null=True,
+ on_delete=models.DO_NOTHING,
)
pipeline_instance = models.ForeignKey(
PipelineInstance,
diff --git a/pipeline/contrib/periodic_task/patch/djcelery/djcelery_patch.py b/pipeline/contrib/periodic_task/patch/djcelery/djcelery_patch.py
index 7a4220fd6..123d187d1 100644
--- a/pipeline/contrib/periodic_task/patch/djcelery/djcelery_patch.py
+++ b/pipeline/contrib/periodic_task/patch/djcelery/djcelery_patch.py
@@ -14,7 +14,7 @@
import datetime
import logging
-from anyjson import dumps, loads
+import json
from celery import current_app, schedules
from celery.utils.log import get_logger
@@ -54,7 +54,9 @@ def djcelry_upgrade():
return
# insert djcelery migration record
- cursor.execute("select * from `django_migrations` where app='djcelery' and name='0001_initial';")
+ cursor.execute(
+ "select * from `django_migrations` where app='djcelery' and name='0001_initial';"
+ )
row = cursor.fetchall()
if not row:
cursor.execute(
@@ -86,10 +88,12 @@ def __init__(self, model):
logger.warning("Disabling %s", self.name)
self._disable(model)
try:
- self.args = loads(model.args or "[]")
- self.kwargs = loads(model.kwargs or "{}")
+ self.args = json.loads(model.args or "[]")
+ self.kwargs = json.loads(model.kwargs or "{}")
except ValueError:
- logging.error("Failed to serialize arguments for %s.", self.name, exc_info=1)
+ logging.error(
+ "Failed to serialize arguments for %s.", self.name, exc_info=1
+ )
logging.warning("Disabling %s", self.name)
self._disable(model)
@@ -123,12 +127,15 @@ def from_entry(cls, name, skip_fields=("relative", "options"), **entry):
fields[t[2]] = None
fields[model_field] = model_schedule
- fields["args"] = dumps(fields.get("args") or [])
- fields["kwargs"] = dumps(fields.get("kwargs") or {})
+ fields["args"] = json.dumps(fields.get("args") or [])
+ fields["kwargs"] = json.dumps(fields.get("kwargs") or {})
fields["queue"] = options.get("queue")
fields["exchange"] = options.get("exchange")
fields["routing_key"] = options.get("routing_key")
- obj, _ = DjCeleryPeriodicTask._default_manager.update_or_create(name=name, defaults=fields,)
+ obj, _ = DjCeleryPeriodicTask._default_manager.update_or_create(
+ name=name,
+ defaults=fields,
+ )
return cls(obj)
@@ -136,7 +143,9 @@ def from_entry(cls, name, skip_fields=("relative", "options"), **entry):
def create_or_update_task(cls, name, **schedule_dict):
if "schedule" not in schedule_dict:
try:
- schedule_dict["schedule"] = DjCeleryPeriodicTask._default_manager.get(name=name).schedule
+ schedule_dict["schedule"] = DjCeleryPeriodicTask._default_manager.get(
+ name=name
+ ).schedule
except DjCeleryPeriodicTask.DoesNotExist:
pass
cls.Entry.from_entry(name, **schedule_dict)
diff --git a/pipeline/contrib/periodic_task/signals/__init__.py b/pipeline/contrib/periodic_task/signals/__init__.py
index bebb9add9..658418deb 100644
--- a/pipeline/contrib/periodic_task/signals/__init__.py
+++ b/pipeline/contrib/periodic_task/signals/__init__.py
@@ -13,6 +13,10 @@
from django.dispatch import Signal
-pre_periodic_task_start = Signal(providing_args=["periodic_task", "pipeline_instance"])
-post_periodic_task_start = Signal(providing_args=["periodic_task", "pipeline_instance"])
-periodic_task_start_failed = Signal(providing_args=["periodic_task", "history"])
+pre_periodic_task_start = (
+ Signal()
+) # providing_args=["periodic_task", "pipeline_instance"]
+post_periodic_task_start = (
+ Signal()
+) # providing_args=["periodic_task", "pipeline_instance"]
+periodic_task_start_failed = Signal() # providing_args=["periodic_task", "history"]
diff --git a/pipeline/contrib/periodic_task/tasks.py b/pipeline/contrib/periodic_task/tasks.py
index a9d93fc4b..11a88b19a 100644
--- a/pipeline/contrib/periodic_task/tasks.py
+++ b/pipeline/contrib/periodic_task/tasks.py
@@ -16,7 +16,7 @@
import traceback
import pytz
-from celery import task
+from celery import shared_task
from django.utils import timezone
from pipeline.contrib.periodic_task import signals
@@ -27,7 +27,7 @@
logger = logging.getLogger("celery")
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def periodic_task_start(*args, **kwargs):
try:
periodic_task = PeriodicTask.objects.get(id=kwargs["period_task_id"])
@@ -61,25 +61,38 @@ def periodic_task_start(*args, **kwargs):
)
result = instance.start(
- periodic_task.creator, check_workers=False, priority=periodic_task.priority, queue=periodic_task.queue
+ periodic_task.creator,
+ check_workers=False,
+ priority=periodic_task.priority,
+ queue=periodic_task.queue,
)
except Exception:
et = traceback.format_exc()
logger.error(et)
PeriodicTaskHistory.objects.record_schedule(
- periodic_task=periodic_task, pipeline_instance=None, ex_data=et, start_success=False
+ periodic_task=periodic_task,
+ pipeline_instance=None,
+ ex_data=et,
+ start_success=False,
)
return
if not result.result:
PeriodicTaskHistory.objects.record_schedule(
- periodic_task=periodic_task, pipeline_instance=None, ex_data=result.message, start_success=False
+ periodic_task=periodic_task,
+ pipeline_instance=None,
+ ex_data=result.message,
+ start_success=False,
)
return
periodic_task.total_run_count += 1
periodic_task.last_run_at = timezone.now()
periodic_task.save()
- signals.post_periodic_task_start.send(sender=PeriodicTask, periodic_task=periodic_task, pipeline_instance=instance)
+ signals.post_periodic_task_start.send(
+ sender=PeriodicTask, periodic_task=periodic_task, pipeline_instance=instance
+ )
- PeriodicTaskHistory.objects.record_schedule(periodic_task=periodic_task, pipeline_instance=instance, ex_data="")
+ PeriodicTaskHistory.objects.record_schedule(
+ periodic_task=periodic_task, pipeline_instance=instance, ex_data=""
+ )
diff --git a/pipeline/contrib/statistics/models.py b/pipeline/contrib/statistics/models.py
index 2a8c985f3..0e1415bd1 100644
--- a/pipeline/contrib/statistics/models.py
+++ b/pipeline/contrib/statistics/models.py
@@ -12,7 +12,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
class ComponentInTemplate(models.Model):
@@ -20,7 +20,9 @@ class ComponentInTemplate(models.Model):
template_id = models.CharField(_("模板ID"), max_length=32)
node_id = models.CharField(_("节点ID"), max_length=32)
is_sub = models.BooleanField(_("是否子流程引用"), default=False)
- subprocess_stack = models.TextField(_("子流程堆栈"), default="[]", help_text=_("JSON 格式的列表"))
+ subprocess_stack = models.TextField(
+ _("子流程堆栈"), default="[]", help_text=_("JSON 格式的列表")
+ )
version = models.CharField(_("插件版本"), max_length=255, default="legacy")
class Meta:
@@ -36,9 +38,13 @@ class ComponentExecuteData(models.Model):
instance_id = models.CharField(_("实例ID"), max_length=32)
node_id = models.CharField(_("节点ID"), max_length=32)
is_sub = models.BooleanField(_("是否子流程引用"), default=False)
- subprocess_stack = models.TextField(_("子流程堆栈"), default="[]", help_text=_("JSON 格式的列表"))
+ subprocess_stack = models.TextField(
+ _("子流程堆栈"), default="[]", help_text=_("JSON 格式的列表")
+ )
started_time = models.DateTimeField(_("标准插件执行开始时间"))
- archived_time = models.DateTimeField(_("标准插件执行结束时间"), null=True, blank=True)
+ archived_time = models.DateTimeField(
+ _("标准插件执行结束时间"), null=True, blank=True
+ )
elapsed_time = models.IntegerField(_("标准插件执行耗时(s)"), null=True, blank=True)
status = models.BooleanField(_("是否执行成功"), default=False)
is_skip = models.BooleanField(_("是否跳过"), default=False)
@@ -65,7 +71,12 @@ class Meta:
verbose_name_plural = _("Pipeline模板引用数据")
def __unicode__(self):
- return "{}_{}_{}_{}".format(self.template_id, self.atom_total, self.subprocess_total, self.gateways_total)
+ return "{}_{}_{}_{}".format(
+ self.template_id,
+ self.atom_total,
+ self.subprocess_total,
+ self.gateways_total,
+ )
class InstanceInPipeline(models.Model):
@@ -79,4 +90,9 @@ class Meta:
verbose_name_plural = _("Pipeline实例引用数据")
def __unicode__(self):
- return "{}_{}_{}_{}".format(self.instance_id, self.atom_total, self.subprocess_total, self.gateways_total)
+ return "{}_{}_{}_{}".format(
+ self.instance_id,
+ self.atom_total,
+ self.subprocess_total,
+ self.gateways_total,
+ )
diff --git a/pipeline/core/flow/activity/service_activity.py b/pipeline/core/flow/activity/service_activity.py
index e3264c9ee..abdbdfa37 100644
--- a/pipeline/core/flow/activity/service_activity.py
+++ b/pipeline/core/flow/activity/service_activity.py
@@ -14,11 +14,16 @@
from abc import ABCMeta, abstractmethod
from copy import deepcopy
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.builder import empty_data
from pipeline.core.flow.activity.base import Activity
-from pipeline.core.flow.io import BooleanItemSchema, InputItem, IntItemSchema, OutputItem
+from pipeline.core.flow.io import (
+ BooleanItemSchema,
+ InputItem,
+ IntItemSchema,
+ OutputItem,
+)
from pipeline.utils.utils import convert_bytes_to_str
@@ -30,8 +35,18 @@ class Service(object, metaclass=ABCMeta):
OutputItem = OutputItem
interval = None
default_outputs = [
- OutputItem(name=_("执行结果"), key="_result", type="bool", schema=BooleanItemSchema(description=_("是否执行成功"))),
- OutputItem(name=_("循环次数"), key="_loop", type="int", schema=IntItemSchema(description=_("循环执行次数"))),
+ OutputItem(
+ name=_("执行结果"),
+ key="_result",
+ type="bool",
+ schema=BooleanItemSchema(description=_("是否执行成功")),
+ ),
+ OutputItem(
+ name=_("循环次数"),
+ key="_loop",
+ type="int",
+ schema=IntItemSchema(description=_("循环执行次数")),
+ ),
]
def __init__(self, name=None):
@@ -234,13 +249,13 @@ def next(self):
class DefaultIntervalGenerator(AbstractIntervalGenerator):
def next(self):
super(DefaultIntervalGenerator, self).next()
- return self.count ** 2
+ return self.count**2
class SquareIntervalGenerator(AbstractIntervalGenerator):
def next(self):
super(SquareIntervalGenerator, self).next()
- return self.count ** 2
+ return self.count**2
class NullIntervalGenerator(AbstractIntervalGenerator):
diff --git a/pipeline/core/flow/io.py b/pipeline/core/flow/io.py
index 05661e2d5..329f59585 100644
--- a/pipeline/core/flow/io.py
+++ b/pipeline/core/flow/io.py
@@ -12,7 +12,7 @@
"""
import abc
-from collections import Mapping
+from collections.abc import Mapping
class DataItem(object, metaclass=abc.ABCMeta):
@@ -91,7 +91,9 @@ def _type(cls):
class ArrayItemSchema(ItemSchema):
def __init__(self, item_schema, *args, **kwargs):
if not isinstance(item_schema, ItemSchema):
- raise TypeError("item_schema of ArrayItemSchema must be subclass of ItemSchema")
+ raise TypeError(
+ "item_schema of ArrayItemSchema must be subclass of ItemSchema"
+ )
self.item_schema = item_schema
super(ArrayItemSchema, self).__init__(*args, **kwargs)
@@ -110,15 +112,22 @@ def __init__(self, property_schemas, *args, **kwargs):
if not isinstance(property_schemas, Mapping):
raise TypeError("property_schemas of ObjectItemSchema must be Mapping type")
- if not all([isinstance(value, ItemSchema) for value in list(property_schemas.values())]):
- raise TypeError("value in property_schemas of ObjectItemSchema must be subclass of ItemSchema")
+ if not all(
+ [isinstance(value, ItemSchema) for value in list(property_schemas.values())]
+ ):
+ raise TypeError(
+ "value in property_schemas of ObjectItemSchema must be subclass of ItemSchema"
+ )
self.property_schemas = property_schemas
super(ObjectItemSchema, self).__init__(*args, **kwargs)
def as_dict(self):
base = super(ObjectItemSchema, self).as_dict()
- properties = {prop: schema.as_dict() for prop, schema in list(self.property_schemas.items())}
+ properties = {
+ prop: schema.as_dict()
+ for prop, schema in list(self.property_schemas.items())
+ }
base["properties"] = properties
return base
diff --git a/pipeline/core/flow/signals.py b/pipeline/core/flow/signals.py
index 62dd35660..f63af1e0c 100644
--- a/pipeline/core/flow/signals.py
+++ b/pipeline/core/flow/signals.py
@@ -13,4 +13,4 @@
from django.dispatch import Signal
-post_new_end_event_register = Signal(providing_args=["node_type", "node_cls"])
+post_new_end_event_register = Signal() # providing_args=["node_type", "node_cls"]
diff --git a/pipeline/core/signals/__init__.py b/pipeline/core/signals/__init__.py
index 291cc6621..7caa3b5bd 100644
--- a/pipeline/core/signals/__init__.py
+++ b/pipeline/core/signals/__init__.py
@@ -13,4 +13,4 @@
from django.dispatch import Signal
-pre_variable_register = Signal(providing_args=["variable_code"])
+pre_variable_register = Signal() # providing_args=["variable_code"]
diff --git a/pipeline/django_signal_valve/models.py b/pipeline/django_signal_valve/models.py
index 359559873..2e96eb512 100644
--- a/pipeline/django_signal_valve/models.py
+++ b/pipeline/django_signal_valve/models.py
@@ -15,7 +15,7 @@
import pickle
from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
class IOField(models.BinaryField):
diff --git a/pipeline/engine/admin.py b/pipeline/engine/admin.py
index 21318da13..71ca14878 100644
--- a/pipeline/engine/admin.py
+++ b/pipeline/engine/admin.py
@@ -12,7 +12,7 @@
"""
from django.contrib import admin
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.engine import models
from pipeline.engine.conf.function_switch import FREEZE_ENGINE
diff --git a/pipeline/engine/conf/function_switch.py b/pipeline/engine/conf/function_switch.py
index a678f47aa..c88e3da0e 100644
--- a/pipeline/engine/conf/function_switch.py
+++ b/pipeline/engine/conf/function_switch.py
@@ -11,10 +11,16 @@
specific language governing permissions and limitations under the License.
"""
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
FREEZE_ENGINE = "FREEZE_ENGINE"
switch_list = [
- {"name": FREEZE_ENGINE, "description": _("用于冻结引擎, 冻结期间会屏蔽所有内部信号及暂停所有进程,同时拒绝所有流程控制请求"), "is_active": False}
+ {
+ "name": FREEZE_ENGINE,
+ "description": _(
+ "用于冻结引擎, 冻结期间会屏蔽所有内部信号及暂停所有进程,同时拒绝所有流程控制请求"
+ ),
+ "is_active": False,
+ }
]
diff --git a/pipeline/engine/models/core.py b/pipeline/engine/models/core.py
index 51d4ad67a..b065f0ddc 100644
--- a/pipeline/engine/models/core.py
+++ b/pipeline/engine/models/core.py
@@ -17,10 +17,10 @@
import traceback
from celery import current_app
-from celery.task.control import revoke
+
from django.db import models, transaction
from django.utils import timezone
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.conf import settings as pipeline_settings
from pipeline.constants import PIPELINE_DEFAULT_PRIORITY
@@ -34,6 +34,7 @@
from pipeline.log.models import LogEntry
from pipeline.utils.uniqid import node_uniqid, uniqid
+revoke = current_app.control.revoke
logger = logging.getLogger("celery")
RERUN_MAX_LIMIT = pipeline_settings.PIPELINE_RERUN_MAX_TIMES
@@ -230,13 +231,21 @@ class PipelineProcess(models.Model):
current_node_id = models.CharField(
_("当前推进到的节点的 ID"), max_length=32, default="", db_index=True
)
- destination_id = models.CharField(_("遇到该 ID 的节点就停止推进"), max_length=32, default="")
+ destination_id = models.CharField(
+ _("遇到该 ID 的节点就停止推进"), max_length=32, default=""
+ )
parent_id = models.CharField(_("父 process 的 ID"), max_length=32, default="")
ack_num = models.IntegerField(_("收到子节点 ACK 的数量"), default=0)
need_ack = models.IntegerField(_("需要收到的子节点 ACK 的数量"), default=-1)
- is_alive = models.BooleanField(_("该 process 是否还有效"), default=True, db_index=True)
- is_sleep = models.BooleanField(_("该 process 是否正在休眠"), default=False, db_index=True)
- is_frozen = models.BooleanField(_("该 process 是否被冻结"), default=False, db_index=True)
+ is_alive = models.BooleanField(
+ _("该 process 是否还有效"), default=True, db_index=True
+ )
+ is_sleep = models.BooleanField(
+ _("该 process 是否正在休眠"), default=False, db_index=True
+ )
+ is_frozen = models.BooleanField(
+ _("该 process 是否被冻结"), default=False, db_index=True
+ )
snapshot = models.ForeignKey(ProcessSnapshot, null=True, on_delete=models.SET_NULL)
objects = ProcessManager()
@@ -689,7 +698,9 @@ class NodeRelationship(models.Model):
def __unicode__(self):
return str(
"#{} -({})-> #{}".format(
- self.ancestor_id, self.distance, self.descendant_id,
+ self.ancestor_id,
+ self.distance,
+ self.descendant_id,
)
)
@@ -1165,7 +1176,10 @@ class ScheduleService(models.Model):
SCHEDULE_ID_SPLIT_DIVISION = 32
id = models.CharField(
- _("ID 节点ID+version"), max_length=NAME_MAX_LENGTH, unique=True, primary_key=True
+ _("ID 节点ID+version"),
+ max_length=NAME_MAX_LENGTH,
+ unique=True,
+ primary_key=True,
)
activity_id = models.CharField(_("节点 ID"), max_length=32, db_index=True)
process_id = models.CharField(_("Pipeline 进程 ID"), max_length=32)
@@ -1176,7 +1190,9 @@ class ScheduleService(models.Model):
service_act = IOField(verbose_name=_("待调度服务"))
is_finished = models.BooleanField(_("是否已完成"), default=False)
version = models.CharField(_("Activity 的版本"), max_length=32, db_index=True)
- is_scheduling = models.BooleanField(_("是否正在被调度"), default=False, db_index=True)
+ is_scheduling = models.BooleanField(
+ _("是否正在被调度"), default=False, db_index=True
+ )
objects = ScheduleServiceManager()
diff --git a/pipeline/engine/models/data.py b/pipeline/engine/models/data.py
index c26fcd2fb..d15dfc25e 100644
--- a/pipeline/engine/models/data.py
+++ b/pipeline/engine/models/data.py
@@ -12,7 +12,7 @@
"""
from django.db import models, transaction
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.engine.models.fields import IOField
diff --git a/pipeline/engine/models/function.py b/pipeline/engine/models/function.py
index 003a828e5..d6429e4eb 100644
--- a/pipeline/engine/models/function.py
+++ b/pipeline/engine/models/function.py
@@ -15,7 +15,7 @@
import traceback
from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.engine.conf import function_switch
@@ -31,11 +31,15 @@ def init_db(self):
if switch["name"] not in name_set:
s_to_be_created.append(
FunctionSwitch(
- name=switch["name"], description=switch["description"], is_active=switch["is_active"]
+ name=switch["name"],
+ description=switch["description"],
+ is_active=switch["is_active"],
)
)
else:
- self.filter(name=switch["name"]).update(description=switch["description"])
+ self.filter(name=switch["name"]).update(
+ description=switch["description"]
+ )
self.bulk_create(s_to_be_created)
except Exception:
logger.error("function switch init failed: %s" % traceback.format_exc())
diff --git a/pipeline/engine/signals/__init__.py b/pipeline/engine/signals/__init__.py
index 9bb510f31..e2fa6e7d3 100644
--- a/pipeline/engine/signals/__init__.py
+++ b/pipeline/engine/signals/__init__.py
@@ -13,23 +13,37 @@
from django.dispatch import Signal
-pipeline_ready = Signal(providing_args=["process_id"])
-pipeline_end = Signal(providing_args=["root_pipeline_id"])
-pipeline_revoke = Signal(providing_args=["root_pipeline_id"])
-child_process_ready = Signal(providing_args=["child_id"])
-process_ready = Signal(providing_args=["parent_id", "current_node_id", "call_from_child"])
-batch_process_ready = Signal(providing_args=["process_id_list", "pipeline_id"])
-wake_from_schedule = Signal(providing_args=["process_id, activity_id"])
-schedule_ready = Signal(providing_args=["schedule_id", "countdown", "process_id", "data_id"])
-process_unfreeze = Signal(providing_args=["process_id"])
+pipeline_ready = Signal() # providing_args=("process_id",)
+pipeline_end = Signal() # providing_args=("root_pipeline_id",)
+pipeline_revoke = Signal() # providing_args=("root_pipeline_id",)
+child_process_ready = Signal() # providing_args=("child_id",)
+process_ready = (
+ Signal()
+) # providing_args=("parent_id", "current_node_id", "call_from_child",)
+batch_process_ready = Signal() # providing_args=("process_id_list", "pipeline_id",)
+wake_from_schedule = Signal() # providing_args=("process_id", "activity_id",)
+schedule_ready = (
+ Signal()
+) # providing_args=("schedule_id", "countdown", "process_id", "data_id",)
+process_unfreeze = Signal() # providing_args=("process_id",)
# activity failed signal
-activity_failed = Signal(providing_args=["pipeline_id", "pipeline_activity_id", "subprocess_id_stack"])
+activity_failed = (
+ Signal()
+) # providing_args=("pipeline_id", "pipeline_activity_id", "subprocess_id_stack",)
# signal for developer (do not use valve to pass them!)
-service_schedule_fail = Signal(providing_args=["activity_shell", "schedule_service", "ex_data"])
-service_schedule_success = Signal(providing_args=["activity_shell", "schedule_service"])
-node_skip_call = Signal(providing_args=["process", "node"])
-node_retry_ready = Signal(providing_args=["process", "node"])
+service_schedule_fail = (
+ Signal()
+) # providing_args=("activity_shell", "schedule_service", "ex_data",)
+service_schedule_success = (
+ Signal()
+) # providing_args=("activity_shell", "schedule_service",)
+node_skip_call = Signal() # providing_args=("process", "node",)
+node_retry_ready = Signal() # providing_args=("process", "node",)
-service_activity_timeout_monitor_start = Signal(providing_args=["node_id", "version", "root_pipeline_id", "countdown"])
-service_activity_timeout_monitor_end = Signal(providing_args=["node_id", "version"])
+service_activity_timeout_monitor_start = (
+ Signal()
+) # providing_args=("node_id", "version", "root_pipeline_id", "countdown",)
+service_activity_timeout_monitor_end = (
+ Signal()
+) # providing_args=("node_id", "version",)
diff --git a/pipeline/engine/tasks.py b/pipeline/engine/tasks.py
index fc1719951..108ba5401 100644
--- a/pipeline/engine/tasks.py
+++ b/pipeline/engine/tasks.py
@@ -13,8 +13,8 @@
import logging
-from celery import task
-from celery.decorators import periodic_task
+from celery import shared_task
+from blueapps.contrib.celery_tools.periodic import periodic_task
from celery.schedules import crontab
from pipeline.conf import default_settings
@@ -22,12 +22,18 @@
from pipeline.engine import api, signals, states
from pipeline.engine.core import runtime, schedule
from pipeline.engine.health import zombie
-from pipeline.engine.models import NodeCeleryTask, NodeRelationship, PipelineProcess, ProcessCeleryTask, Status
+from pipeline.engine.models import (
+ NodeCeleryTask,
+ NodeRelationship,
+ PipelineProcess,
+ ProcessCeleryTask,
+ Status,
+)
logger = logging.getLogger("celery")
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def process_unfreeze(process_id):
process = PipelineProcess.objects.get(id=process_id)
if not process.is_alive:
@@ -37,7 +43,7 @@ def process_unfreeze(process_id):
runtime.run_loop(process)
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def start(process_id):
process = PipelineProcess.objects.get(id=process_id)
if not process.is_alive:
@@ -46,9 +52,15 @@ def start(process_id):
pipeline_id = process.root_pipeline.id
# try to run
- action_result = Status.objects.transit(pipeline_id, states.RUNNING, is_pipeline=True, start=True)
+ action_result = Status.objects.transit(
+ pipeline_id, states.RUNNING, is_pipeline=True, start=True
+ )
if not action_result.result:
- logger.warning("can not start pipeline({}), message: {}".format(pipeline_id, action_result.message))
+ logger.warning(
+ "can not start pipeline({}), message: {}".format(
+ pipeline_id, action_result.message
+ )
+ )
return
NodeRelationship.objects.build_relationship(pipeline_id, pipeline_id)
@@ -56,7 +68,7 @@ def start(process_id):
runtime.run_loop(process)
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def dispatch(child_id):
process = PipelineProcess.objects.get(id=child_id)
if not process.is_alive:
@@ -66,7 +78,7 @@ def dispatch(child_id):
runtime.run_loop(process)
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def process_wake_up(process_id, current_node_id=None, call_from_child=False):
process = PipelineProcess.objects.get(id=process_id)
if not process.is_alive:
@@ -82,7 +94,11 @@ def process_wake_up(process_id, current_node_id=None, call_from_child=False):
if not action_result.result:
# BLOCKED is a tolerant running state
if action_result.extra.state != states.BLOCKED:
- logger.warning("can not start pipeline({}), message: {}".format(pipeline_id, action_result.message))
+ logger.warning(
+ "can not start pipeline({}), message: {}".format(
+ pipeline_id, action_result.message
+ )
+ )
return
process.wake_up()
@@ -92,7 +108,7 @@ def process_wake_up(process_id, current_node_id=None, call_from_child=False):
runtime.run_loop(process)
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def wake_up(process_id):
process = PipelineProcess.objects.get(id=process_id)
if not process.is_alive:
@@ -103,18 +119,24 @@ def wake_up(process_id):
runtime.run_loop(process)
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def batch_wake_up(process_id_list, pipeline_id):
- action_result = Status.objects.transit(pipeline_id, to_state=states.RUNNING, is_pipeline=True)
+ action_result = Status.objects.transit(
+ pipeline_id, to_state=states.RUNNING, is_pipeline=True
+ )
if not action_result.result:
- logger.warning("can not start pipeline({}), message: {}".format(pipeline_id, action_result.message))
+ logger.warning(
+ "can not start pipeline({}), message: {}".format(
+ pipeline_id, action_result.message
+ )
+ )
return
for process_id in process_id_list:
task_id = wake_up.apply_async(args=[process_id]).id
ProcessCeleryTask.objects.bind(process_id, task_id)
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def wake_from_schedule(process_id, service_act_id):
process = PipelineProcess.objects.get(id=process_id)
process.wake_up()
@@ -124,27 +146,38 @@ def wake_from_schedule(process_id, service_act_id):
runtime.run_loop(process)
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def service_schedule(process_id, schedule_id, data_id=None):
schedule.schedule(process_id, schedule_id, data_id)
-@task(ignore_result=True)
+@shared_task(ignore_result=True)
def node_timeout_check(node_id, version, root_pipeline_id):
NodeCeleryTask.objects.destroy(node_id)
state = Status.objects.state_for(node_id, version=version, may_not_exist=True)
if not state or state != states.RUNNING:
- logger.warning("node {} {} timeout kill failed, node not exist or not in running".format(node_id, version))
+ logger.warning(
+ "node {} {} timeout kill failed, node not exist or not in running".format(
+ node_id, version
+ )
+ )
return
- action_result = api.forced_fail(node_id, kill=True, ex_data="node execution timeout")
+ action_result = api.forced_fail(
+ node_id, kill=True, ex_data="node execution timeout"
+ )
if action_result.result:
- signals.activity_failed.send(sender=Pipeline, pipeline_id=root_pipeline_id, pipeline_activity_id=node_id)
+ signals.activity_failed.send(
+ sender=Pipeline, pipeline_id=root_pipeline_id, pipeline_activity_id=node_id
+ )
else:
logger.warning("node {} - {} timeout kill failed".format(node_id, version))
-@periodic_task(run_every=(crontab(**default_settings.ENGINE_ZOMBIE_PROCESS_HEAL_CRON)), ignore_result=True)
+@periodic_task(
+ run_every=(crontab(**default_settings.ENGINE_ZOMBIE_PROCESS_HEAL_CRON)),
+ ignore_result=True,
+)
def heal_zombie_process():
logger.info("Zombie process heal start")
diff --git a/pipeline/log/models.py b/pipeline/log/models.py
index d2440f243..dc0e3bb6b 100644
--- a/pipeline/log/models.py
+++ b/pipeline/log/models.py
@@ -13,7 +13,7 @@
from django.db import models
from django.utils import timezone
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
class LogEntryManager(models.Manager):
@@ -26,7 +26,12 @@ def plain_log_for_node(self, node_id, history_id):
for entry in entries:
plain_entries.append(
"[%s %s] %s, exception: %s"
- % (entry.logged_at.strftime("%Y-%m-%d %H:%M:%S"), entry.level_name, entry.message, entry.exception)
+ % (
+ entry.logged_at.strftime("%Y-%m-%d %H:%M:%S"),
+ entry.level_name,
+ entry.message,
+ entry.exception,
+ )
)
return "\n".join(plain_entries)
diff --git a/pipeline/log/tasks.py b/pipeline/log/tasks.py
index e39826fca..e34f50542 100644
--- a/pipeline/log/tasks.py
+++ b/pipeline/log/tasks.py
@@ -13,7 +13,7 @@
import logging
-from celery.decorators import periodic_task
+from blueapps.contrib.celery_tools.periodic import periodic_task
from celery.schedules import crontab
from django.conf import settings
@@ -28,7 +28,9 @@ def clean_expired_log():
if expired_interval is None:
expired_interval = 30
- logger.warning("LOG_PERSISTENT_DAYS are not found in settings, use default value: 30")
+ logger.warning(
+ "LOG_PERSISTENT_DAYS are not found in settings, use default value: 30"
+ )
del_num = LogEntry.objects.delete_expired_log(expired_interval)
logger.info("%s log entry are deleted" % del_num)
diff --git a/pipeline/models.py b/pipeline/models.py
index 575e0f9be..0cfce0b95 100644
--- a/pipeline/models.py
+++ b/pipeline/models.py
@@ -21,7 +21,7 @@
from django.db import models, transaction
from django.utils import timezone
from django.utils.module_loading import import_string
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.conf import settings
from pipeline.constants import PIPELINE_DEFAULT_PRIORITY
@@ -116,7 +116,9 @@ def get_subprocess_act_list(pipeline_data):
@return: 子流程节点
"""
activities = pipeline_data[PE.activities]
- act_ids = [act_id for act_id in activities if activities[act_id][PE.type] == PE.SubProcess]
+ act_ids = [
+ act_id for act_id in activities if activities[act_id][PE.type] == PE.SubProcess
+ ]
return [activities[act_id] for act_id in act_ids]
@@ -126,7 +128,11 @@ def _act_id_in_graph(act):
@param act: 子流程节点
@return: 模板 ID:版本 或 模板ID
"""
- return "{}:{}".format(act["template_id"], act["version"]) if act.get("version") else act["template_id"]
+ return (
+ "{}:{}".format(act["template_id"], act["version"])
+ if act.get("version")
+ else act["template_id"]
+ )
class TemplateManager(models.Manager):
@@ -139,7 +145,9 @@ def subprocess_ref_validate(self, data, root_id=None, root_name=None):
@return: 引用是否合法,相关信息
"""
try:
- sub_refs, name_map = self.construct_subprocess_ref_graph(data, root_id=root_id, root_name=root_name)
+ sub_refs, name_map = self.construct_subprocess_ref_graph(
+ data, root_id=root_id, root_name=root_name
+ )
except PipelineTemplate.DoesNotExist as e:
return False, str(e)
@@ -193,7 +201,9 @@ def delete_model(self, template_ids):
template.name = uniqid()
template.save()
- def construct_subprocess_ref_graph(self, pipeline_data, root_id=None, root_name=None):
+ def construct_subprocess_ref_graph(
+ self, pipeline_data, root_id=None, root_name=None
+ ):
"""
构造子流程引用图
@param pipeline_data: pipeline 结构数据
@@ -219,7 +229,9 @@ def construct_subprocess_ref_graph(self, pipeline_data, root_id=None, root_name=
tid = tid_queue.get()
template = self.get(template_id=tid.split(":")[0])
name_map[tid] = template.name
- subprocess_act = get_subprocess_act_list(template.data_for_version(version[tid]))
+ subprocess_act = get_subprocess_act_list(
+ template.data_for_version(version[tid])
+ )
for act in subprocess_act:
ref_tid = _act_id_in_graph(act)
@@ -242,7 +254,9 @@ def unfold_subprocess(self, pipeline_data):
activities = pipeline_data[PE.activities]
for act_id, act in list(activities.items()):
if act[PE.type] == PE.SubProcess:
- subproc_data = self.get(template_id=act[PE.template_id]).data_for_version(act.get(PE.version))
+ subproc_data = self.get(
+ template_id=act[PE.template_id]
+ ).data_for_version(act.get(PE.version))
sub_id_maps = self.unfold_subprocess(subproc_data)
# act_id is new id
@@ -278,17 +292,24 @@ class PipelineTemplate(models.Model):
"""
template_id = models.CharField(_("模板ID"), max_length=32, unique=True)
- name = models.CharField(_("模板名称"), max_length=MAX_LEN_OF_NAME, default="default_template")
+ name = models.CharField(
+ _("模板名称"), max_length=MAX_LEN_OF_NAME, default="default_template"
+ )
create_time = models.DateTimeField(_("创建时间"), auto_now_add=True)
creator = models.CharField(_("创建者"), max_length=32)
description = models.TextField(_("描述"), null=True, blank=True)
editor = models.CharField(_("修改者"), max_length=32, null=True, blank=True)
edit_time = models.DateTimeField(_("修改时间"), auto_now=True)
snapshot = models.ForeignKey(
- Snapshot, verbose_name=_("模板结构数据"), related_name="snapshot_templates", on_delete=models.DO_NOTHING
+ Snapshot,
+ verbose_name=_("模板结构数据"),
+ related_name="snapshot_templates",
+ on_delete=models.DO_NOTHING,
)
has_subprocess = models.BooleanField(_("是否含有子流程"), default=False)
- is_deleted = models.BooleanField(_("是否删除"), default=False, help_text=_("表示当前模板是否删除"))
+ is_deleted = models.BooleanField(
+ _("是否删除"), default=False, help_text=_("表示当前模板是否删除")
+ )
objects = TemplateManager()
@@ -312,9 +333,9 @@ def version(self):
@property
def subprocess_version_info(self):
# 1. get all subprocess
- subprocess_info = TemplateRelationship.objects.get_subprocess_info(self.template_id).values(
- "descendant_template_id", "subprocess_node_id", "version"
- )
+ subprocess_info = TemplateRelationship.objects.get_subprocess_info(
+ self.template_id
+ ).values("descendant_template_id", "subprocess_node_id", "version")
info = {"subproc_has_update": False, "details": []}
if not subprocess_info:
return info
@@ -323,7 +344,9 @@ def subprocess_version_info(self):
temp_current_versions = {
item.template_id: item
for item in TemplateCurrentVersion.objects.filter(
- template_id__in=[item["descendant_template_id"] for item in subprocess_info]
+ template_id__in=[
+ item["descendant_template_id"] for item in subprocess_info
+ ]
)
}
@@ -332,7 +355,12 @@ def subprocess_version_info(self):
item["expired"] = (
False
if item["version"] is None
- else (item["version"] != temp_current_versions[item["descendant_template_id"]].current_version)
+ else (
+ item["version"]
+ != temp_current_versions[
+ item["descendant_template_id"]
+ ].current_version
+ )
)
info["details"].append(item)
expireds.append(item["expired"])
@@ -362,9 +390,9 @@ def referencer(self):
@return: 引用了该模板的其他模板 ID 列表
"""
referencer = TemplateRelationship.objects.referencer(self.template_id)
- template_id = self.__class__.objects.filter(template_id__in=referencer, is_deleted=False).values_list(
- "template_id", flat=True
- )
+ template_id = self.__class__.objects.filter(
+ template_id__in=referencer, is_deleted=False
+ ).values_list("template_id", flat=True)
return list(template_id)
def clone_data(self):
@@ -383,7 +411,9 @@ def update_template(self, structure_data, **kwargs):
@param kwargs: 其他参数
@return:
"""
- result, msg = PipelineTemplate.objects.subprocess_ref_validate(structure_data, self.template_id, self.name)
+ result, msg = PipelineTemplate.objects.subprocess_ref_validate(
+ structure_data, self.template_id, self.name
+ )
if not result:
raise SubprocessRefError(msg)
@@ -429,7 +459,13 @@ def referencer(self, template_id):
@param template_id: 被引用的模板
@return: 引用了该模板的其他模板 ID 列表
"""
- return list(set(self.filter(descendant_template_id=template_id).values_list("ancestor_template_id", flat=True)))
+ return list(
+ set(
+ self.filter(descendant_template_id=template_id).values_list(
+ "ancestor_template_id", flat=True
+ )
+ )
+ )
class TemplateRelationship(models.Model):
@@ -438,7 +474,9 @@ class TemplateRelationship(models.Model):
"""
ancestor_template_id = models.CharField(_("根模板ID"), max_length=32, db_index=True)
- descendant_template_id = models.CharField(_("子流程模板ID"), max_length=32, null=False)
+ descendant_template_id = models.CharField(
+ _("子流程模板ID"), max_length=32, null=False
+ )
subprocess_node_id = models.CharField(_("子流程节点 ID"), max_length=32, null=False)
version = models.CharField(_("快照字符串的md5"), max_length=32, null=False)
@@ -453,7 +491,8 @@ def update_current_version(self, template):
@return: 记录模板当前版本的对象
"""
obj, __ = self.update_or_create(
- template_id=template.template_id, defaults={"current_version": template.version}
+ template_id=template.template_id,
+ defaults={"current_version": template.version},
)
return obj
@@ -484,7 +523,9 @@ def track(self, template):
if versions and versions[0].md5 == template.snapshot.md5sum:
return versions[0]
- return self.create(template=template, snapshot=template.snapshot, md5=template.snapshot.md5sum)
+ return self.create(
+ template=template, snapshot=template.snapshot, md5=template.snapshot.md5sum
+ )
class TemplateVersion(models.Model):
@@ -492,8 +533,15 @@ class TemplateVersion(models.Model):
模板版本号记录节点
"""
- template = models.ForeignKey(PipelineTemplate, verbose_name=_("模板 ID"), null=False, on_delete=models.CASCADE)
- snapshot = models.ForeignKey(Snapshot, verbose_name=_("模板数据 ID"), null=False, on_delete=models.CASCADE)
+ template = models.ForeignKey(
+ PipelineTemplate,
+ verbose_name=_("模板 ID"),
+ null=False,
+ on_delete=models.CASCADE,
+ )
+ snapshot = models.ForeignKey(
+ Snapshot, verbose_name=_("模板数据 ID"), null=False, on_delete=models.CASCADE
+ )
md5 = models.CharField(_("快照字符串的md5"), max_length=32, db_index=True)
date = models.DateTimeField(_("添加日期"), auto_now_add=True)
@@ -506,9 +554,15 @@ class TemplateScheme(models.Model):
"""
template = models.ForeignKey(
- PipelineTemplate, verbose_name=_("对应模板 ID"), null=False, blank=False, on_delete=models.CASCADE
+ PipelineTemplate,
+ verbose_name=_("对应模板 ID"),
+ null=False,
+ blank=False,
+ on_delete=models.CASCADE,
+ )
+ unique_id = models.CharField(
+ _("方案唯一ID"), max_length=97, unique=True, null=False, blank=True
)
- unique_id = models.CharField(_("方案唯一ID"), max_length=97, unique=True, null=False, blank=True)
name = models.CharField(_("方案名称"), max_length=64, null=False, blank=False)
edit_time = models.DateTimeField(_("修改时间"), auto_now=True)
data = CompressJSONField(verbose_name=_("方案数据"))
@@ -618,9 +672,15 @@ class PipelineInstance(models.Model):
instance_id = models.CharField(_("实例ID"), max_length=32, unique=True)
template = models.ForeignKey(
- PipelineTemplate, verbose_name=_("Pipeline模板"), null=True, blank=True, on_delete=models.SET_NULL
+ PipelineTemplate,
+ verbose_name=_("Pipeline模板"),
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ )
+ name = models.CharField(
+ _("实例名称"), max_length=MAX_LEN_OF_NAME, default="default_instance"
)
- name = models.CharField(_("实例名称"), max_length=MAX_LEN_OF_NAME, default="default_instance")
creator = models.CharField(_("创建者"), max_length=32, blank=True)
create_time = models.DateTimeField(_("创建时间"), auto_now_add=True)
executor = models.CharField(_("执行者"), max_length=32, blank=True)
@@ -630,7 +690,9 @@ class PipelineInstance(models.Model):
is_started = models.BooleanField(_("是否已经启动"), default=False)
is_finished = models.BooleanField(_("是否已经完成"), default=False)
is_revoked = models.BooleanField(_("是否已经撤销"), default=False)
- is_deleted = models.BooleanField(_("是否已经删除"), default=False, help_text=_("表示当前实例是否删除"))
+ is_deleted = models.BooleanField(
+ _("是否已经删除"), default=False, help_text=_("表示当前实例是否删除")
+ )
snapshot = models.ForeignKey(
Snapshot,
blank=True,
@@ -714,7 +776,9 @@ def clone(self, creator, **kwargs):
@param kwargs: 其他参数
@return: 当前实例对象的克隆
"""
- name = kwargs.get("name") or timezone.localtime(timezone.now()).strftime("clone%Y%m%d%H%m%S")
+ name = kwargs.get("name") or timezone.localtime(timezone.now()).strftime(
+ "clone%Y%m%d%H%m%S"
+ )
instance_id = node_uniqid()
exec_data = self.execution_data
@@ -733,7 +797,9 @@ def clone(self, creator, **kwargs):
execution_snapshot=new_snapshot,
)
- def start(self, executor, check_workers=True, priority=PIPELINE_DEFAULT_PRIORITY, queue=""):
+ def start(
+ self, executor, check_workers=True, priority=PIPELINE_DEFAULT_PRIORITY, queue=""
+ ):
"""
启动当前流程
@param executor: 执行者
@@ -744,14 +810,19 @@ def start(self, executor, check_workers=True, priority=PIPELINE_DEFAULT_PRIORITY
with transaction.atomic():
instance = self.__class__.objects.select_for_update().get(id=self.id)
if instance.is_started:
- return ActionResult(result=False, message="pipeline instance already started.")
+ return ActionResult(
+ result=False, message="pipeline instance already started."
+ )
pipeline_data = instance.execution_data
try:
parser_cls = import_string(settings.PIPELINE_PARSER_CLASS)
except ImportError:
- return ActionResult(result=False, message="invalid parser class: %s" % settings.PIPELINE_PARSER_CLASS)
+ return ActionResult(
+ result=False,
+ message="invalid parser class: %s" % settings.PIPELINE_PARSER_CLASS,
+ )
instance.start_time = timezone.now()
instance.is_started = True
@@ -763,7 +834,10 @@ def start(self, executor, check_workers=True, priority=PIPELINE_DEFAULT_PRIORITY
instance, obj_type="instance", data_type="data", username=executor
),
root_pipeline_context=get_pipeline_context(
- instance, obj_type="instance", data_type="context", username=executor
+ instance,
+ obj_type="instance",
+ data_type="context",
+ username=executor,
),
)
@@ -772,7 +846,9 @@ def start(self, executor, check_workers=True, priority=PIPELINE_DEFAULT_PRIORITY
instance.save()
- act_result = task_service.run_pipeline(pipeline, check_workers=check_workers, priority=priority, queue=queue)
+ act_result = task_service.run_pipeline(
+ pipeline, check_workers=check_workers, priority=priority, queue=queue
+ )
if not act_result.result:
with transaction.atomic():
diff --git a/pipeline/utils/boolrule/boolrule.py b/pipeline/utils/boolrule/boolrule.py
index 10592ff5c..8a670b4a2 100644
--- a/pipeline/utils/boolrule/boolrule.py
+++ b/pipeline/utils/boolrule/boolrule.py
@@ -54,7 +54,9 @@ def get_val(self, context):
val = getattr(val, part) if hasattr(val, part) else val[part]
except KeyError:
- raise MissingVariableException("no value supplied for {}".format(self._path))
+ raise MissingVariableException(
+ "no value supplied for {}".format(self._path)
+ )
return val
@@ -65,7 +67,9 @@ def __repr__(self):
# Grammar definition
pathDelimiter = "."
# match gcloud's variable
-identifier = Combine(Optional("${") + Optional("_") + Word(alphas, alphanums + "_") + Optional("}"))
+identifier = Combine(
+ Optional("${") + Optional("_") + Word(alphas, alphanums + "_") + Optional("}")
+)
# identifier = Word(alphas, alphanums + "_")
propertyPath = delimitedList(identifier, pathDelimiter, combine=True)
@@ -76,7 +80,9 @@ def __repr__(self):
lparen = Suppress("(")
rparen = Suppress(")")
-binaryOp = oneOf("== != < > >= <= in notin issuperset notissuperset", caseless=True)("operator")
+binaryOp = oneOf("== != < > >= <= in notin issuperset notissuperset", caseless=True)(
+ "operator"
+)
E = CaselessLiteral("E")
numberSign = Word("+-", exact=1)
@@ -86,7 +92,9 @@ def __repr__(self):
+ Optional(E + Optional(numberSign) + Word(nums))
)
-integer = Combine(Optional(numberSign) + Word(nums) + Optional(E + Optional("+") + Word(nums)))
+integer = Combine(
+ Optional(numberSign) + Word(nums) + Optional(E + Optional("+") + Word(nums))
+)
# str_ = quotedString.addParseAction(removeQuotes)
str_ = QuotedString('"') | QuotedString("'")
@@ -96,7 +104,7 @@ def __repr__(self):
realNumber.setParseAction(lambda toks: float(toks[0]))
| integer.setParseAction(lambda toks: int(toks[0]))
| str_
- | bool_.setParseAction(lambda toks: toks[0] == "true")
+ | bool_.setParseAction(lambda toks: toks[0].lower() == "true")
| propertyPath.setParseAction(lambda toks: SubstituteVal(toks))
) # need to add support for alg expressions
@@ -104,7 +112,8 @@ def __repr__(self):
boolExpression = Forward()
boolCondition = Group(
- (Group(propertyVal)("lval") + binaryOp + Group(propertyVal)("rval")) | (lparen + boolExpression + rparen)
+ (Group(propertyVal)("lval") + binaryOp + Group(propertyVal)("rval"))
+ | (lparen + boolExpression + rparen)
)
boolExpression << boolCondition + ZeroOrMore((and_ | or_) + boolExpression)
@@ -198,14 +207,16 @@ def _compile(self):
return
try:
- self._tokens = boolExpression.parseString(self._query, parseAll=self.strict)
+ self._tokens = boolExpression.parseString(
+ self._query, parseAll=self.strict
+ )
except ParseException:
raise
self._compiled = True
def _expand_val(self, val, context):
- if type(val) == list:
+ if isinstance(val, list):
val = [self._expand_val(v, context) for v in val]
if isinstance(val, SubstituteVal):
diff --git a/pipeline/validators/connection.py b/pipeline/validators/connection.py
index 75b08bd15..c7b674a00 100644
--- a/pipeline/validators/connection.py
+++ b/pipeline/validators/connection.py
@@ -11,7 +11,7 @@
specific language governing permissions and limitations under the License.
"""
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.exceptions import ConnectionValidateError
from pipeline.utils.graph import Graph
@@ -35,16 +35,30 @@ def validate_graph_connection(data):
for j in nodes[i][PE.target]:
if nodes[j][PE.type] not in rule["allowed_out"]:
message += _("不能连接%s类型节点\n") % nodes[i][PE.type]
- if rule["min_in"] > len(nodes[i][PE.source]) or len(nodes[i][PE.source]) > rule["max_in"]:
- message += _("节点的入度最大为%s,最小为%s\n") % (rule["max_in"], rule["min_in"])
- if rule["min_out"] > len(nodes[i][PE.target]) or len(nodes[i][PE.target]) > rule["max_out"]:
- message += _("节点的出度最大为%s,最小为%s\n") % (rule["max_out"], rule["min_out"])
+ if (
+ rule["min_in"] > len(nodes[i][PE.source])
+ or len(nodes[i][PE.source]) > rule["max_in"]
+ ):
+ message += _("节点的入度最大为%s,最小为%s\n") % (
+ rule["max_in"],
+ rule["min_in"],
+ )
+ if (
+ rule["min_out"] > len(nodes[i][PE.target])
+ or len(nodes[i][PE.target]) > rule["max_out"]
+ ):
+ message += _("节点的出度最大为%s,最小为%s\n") % (
+ rule["max_out"],
+ rule["min_out"],
+ )
if message:
result["failed_nodes"].append(i)
result["message"][i] = message
if result["failed_nodes"]:
- raise ConnectionValidateError(failed_nodes=result["failed_nodes"], detail=result["message"])
+ raise ConnectionValidateError(
+ failed_nodes=result["failed_nodes"], detail=result["message"]
+ )
def validate_graph_without_circle(data):
@@ -60,8 +74,14 @@ def validate_graph_without_circle(data):
nodes = [data[PE.start_event][PE.id], data[PE.end_event][PE.id]]
nodes += list(data[PE.gateways].keys()) + list(data[PE.activities].keys())
- flows = [[flow[PE.source], flow[PE.target]] for _, flow in list(data[PE.flows].items())]
+ flows = [
+ [flow[PE.source], flow[PE.target]] for _, flow in list(data[PE.flows].items())
+ ]
cycle = Graph(nodes, flows).get_cycle()
if cycle:
- return {"result": False, "message": "pipeline graph has circle", "error_data": cycle}
+ return {
+ "result": False,
+ "message": "pipeline graph has circle",
+ "error_data": cycle,
+ }
return {"result": True, "data": []}
diff --git a/pipeline/validators/gateway.py b/pipeline/validators/gateway.py
index 28c6677ef..d091fad55 100644
--- a/pipeline/validators/gateway.py
+++ b/pipeline/validators/gateway.py
@@ -13,7 +13,7 @@
import queue
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline import exceptions
from pipeline.core.constants import PE
@@ -127,7 +127,10 @@ def match_converge(
target[i] = None
break
else:
- raise exceptions.ConvergeMatchError(cur_index, _("并行网关中的分支网关必须将所有分支汇聚到一个汇聚网关"))
+ raise exceptions.ConvergeMatchError(
+ cur_index,
+ _("并行网关中的分支网关必须将所有分支汇聚到一个汇聚网关"),
+ )
converge_id, shared = match_converge(
converges=converges,
@@ -152,7 +155,10 @@ def match_converge(
# can't find corresponding converge gateway, which means this gateway will reach end event directly
target[i] = end_event_id
- if target[i] in converges and dist_from_start[target[i]] < dist_from_start[cur_index]:
+ if (
+ target[i] in converges
+ and dist_from_start[target[i]] < dist_from_start[cur_index]
+ ):
# do not match previous converge
target[i] = None
@@ -180,7 +186,9 @@ def match_converge(
if not_in_parallel_gateway(stack):
converge_end = True
else:
- raise exceptions.ConvergeMatchError(cur_index, _("并行网关中的分支网关必须将所有分支汇聚到一个汇聚网关"))
+ raise exceptions.ConvergeMatchError(
+ cur_index, _("并行网关中的分支网关必须将所有分支汇聚到一个汇聚网关")
+ )
# exclusive gateway point back to self
elif is_exg and target[i] == current_gateway[PE.id]:
@@ -190,7 +198,9 @@ def match_converge(
# exclusive gateway converge at different converge gateway
elif is_exg and target[i] in converges and converge_id != target[i]:
- raise exceptions.ConvergeMatchError(cur_index, _("分支网关的所有分支第一个遇到的汇聚网关必须是同一个"))
+ raise exceptions.ConvergeMatchError(
+ cur_index, _("分支网关的所有分支第一个遇到的汇聚网关必须是同一个")
+ )
# meet previous node
elif is_exg and target[i] is None:
@@ -200,13 +210,18 @@ def match_converge(
# invalid cases
else:
- raise exceptions.ConvergeMatchError(cur_index, _("非法网关,请检查其分支是否符合规则"))
+ raise exceptions.ConvergeMatchError(
+ cur_index, _("非法网关,请检查其分支是否符合规则")
+ )
if is_exg:
if converge_id in converges:
# this converge is shared by multiple gateway
# only compare to the number of positive incoming
- shared = converge_in_len[converge_id] > cur_to_converge or converge_id in converged
+ shared = (
+ converge_in_len[converge_id] > cur_to_converge
+ or converge_id in converged
+ )
else:
# for parallel gateway
@@ -217,12 +232,16 @@ def match_converge(
for gateway_id in converged.get(converge_id, []):
# find another parallel gateway
if gateways[gateway_id][PE.type] in PARALLEL_GATEWAYS:
- raise exceptions.ConvergeMatchError(converge_id, _("汇聚网关只能汇聚来自同一个并行网关的分支"))
+ raise exceptions.ConvergeMatchError(
+ converge_id, _("汇聚网关只能汇聚来自同一个并行网关的分支")
+ )
shared = True
elif converge_incoming < gateway_outgoing:
- raise exceptions.ConvergeMatchError(converge_id, _("汇聚网关没有汇聚其对应的并行网关的所有分支"))
+ raise exceptions.ConvergeMatchError(
+ converge_id, _("汇聚网关没有汇聚其对应的并行网关的所有分支")
+ )
current_gateway["match"] = converge_id
current_gateway["share_converge"] = shared
@@ -264,7 +283,9 @@ def distance_from(origin, node, tree, marked, visited=None):
prev_node = get_node_for_sequence(incoming, tree, PE.source)
# get incoming node's distance recursively
- dist = distance_from(origin=origin, node=prev_node, tree=tree, marked=marked, visited=visited)
+ dist = distance_from(
+ origin=origin, node=prev_node, tree=tree, marked=marked, visited=visited
+ )
# if this incoming do not trace back to current node
if dist is not None:
@@ -295,8 +316,16 @@ def validate_gateways(tree):
# data preparation
for i, item in list(tree[PE.gateways].items()):
node = {
- PE.incoming: item[PE.incoming] if isinstance(item[PE.incoming], list) else [item[PE.incoming]],
- PE.outgoing: item[PE.outgoing] if isinstance(item[PE.outgoing], list) else [item[PE.outgoing]],
+ PE.incoming: (
+ item[PE.incoming]
+ if isinstance(item[PE.incoming], list)
+ else [item[PE.incoming]]
+ ),
+ PE.outgoing: (
+ item[PE.outgoing]
+ if isinstance(item[PE.outgoing], list)
+ else [item[PE.outgoing]]
+ ),
PE.type: item[PE.type],
PE.target: [],
PE.source: [],
@@ -309,14 +338,20 @@ def validate_gateways(tree):
for index in node[PE.outgoing]:
index = tree[PE.flows][index][PE.target]
while index in tree[PE.activities]:
- index = tree[PE.flows][tree[PE.activities][index][PE.outgoing]][PE.target]
+ index = tree[PE.flows][tree[PE.activities][index][PE.outgoing]][
+ PE.target
+ ]
# append this node's id to current gateway's target list
node[PE.target].append(index)
# get current node's distance from start event
- if not distance_from(node=node, origin=tree[PE.start_event], tree=tree, marked=distances):
- raise exceptions.ConvergeMatchError(node[PE.id], _("无法获取该网关距离开始节点的距离"))
+ if not distance_from(
+ node=node, origin=tree[PE.start_event], tree=tree, marked=distances
+ ):
+ raise exceptions.ConvergeMatchError(
+ node[PE.id], _("无法获取该网关距离开始节点的距离")
+ )
if item[PE.type] == PE.ConvergeGateway:
converges[i] = node
@@ -403,7 +438,9 @@ def blend(source, target, custom_stream=None):
return
if len(source[STREAM]) == 0:
- raise exceptions.InvalidOperationException("stream validation error, node(%s) stream is empty" % source[PE.id])
+ raise exceptions.InvalidOperationException(
+ "stream validation error, node(%s) stream is empty" % source[PE.id]
+ )
# blend
for s in source[STREAM]:
@@ -448,7 +485,9 @@ def flowing(where, to, parallel_converges):
if target_id in parallel_converges:
- is_valid_branch = where[STREAM].issubset(parallel_converges[target_id][P_STREAM])
+ is_valid_branch = where[STREAM].issubset(
+ parallel_converges[target_id][P_STREAM]
+ )
is_direct_connect = where.get(PE.converge_gateway_id) == target_id
if is_valid_branch or is_direct_connect:
@@ -483,7 +522,10 @@ def validate_stream(tree):
# set allow streams for parallel's converge
if node[PE.type] in PARALLEL_GATEWAYS:
- parallel_converges[node[PE.converge_gateway_id]] = {P_STREAM: streams_for_parallel(node), P: nid}
+ parallel_converges[node[PE.converge_gateway_id]] = {
+ P_STREAM: streams_for_parallel(node),
+ P: nid,
+ }
# build stream from start
node_queue = queue.Queue()
diff --git a/pipeline/variable_framework/models.py b/pipeline/variable_framework/models.py
index 3d78b8b9e..6898191ca 100644
--- a/pipeline/variable_framework/models.py
+++ b/pipeline/variable_framework/models.py
@@ -12,7 +12,7 @@
"""
from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from pipeline.core.data.library import VariableLibrary
diff --git a/readme.md b/readme.md
index 492e8dab6..4b1caecab 100644
--- a/readme.md
+++ b/readme.md
@@ -44,6 +44,35 @@ ITSM (IT 服务管理)是一套帮助企业对 IT 系统的规划、研发、实
- [第三方API接入说明](docs/install/custom_api.md)
- [ITSM 接入指引](docs/wiki/access.md)
+
+## Unit Testing
+- 在本地进行单元测试时建议使用如下环境变量配置,需要注意配置好MySQL和Redis
+- 建议使用PyCharm中集成的Django Test加载`.env`文件的方式去执行,target填写`itsm.tests`
+```.dotenv
+RUN_ENV=open
+APP_CODE=bk_itsm
+APP_ID=bk_itsm
+RUN_VER=open
+SECRET_KEY=12345678-1234-5678-1234-123456789012
+APP_TOKEN=12345678-1234-5678-1234-123456789012
+BK_PAAS_HOST=http://127.0.0.1
+BK_IAM_V3_INNER_HOST=127.0.0.1
+BK_IAM_INNER_HOST=http://127.0.0.1:8080
+BROKER_URL=redis://localhost:6379/0
+USE_IAM=false
+BKAPP_REDIS_HOST=localhost
+BKAPP_BK_IAM_SYSTEM_ID=itsm
+BKAPP_IAM_INITIAL_FILE=dev
+BKAPP_REDIS_PORT=6379
+BKAPP_REDIS_PASSWORD=
+BK_MYSQL_NAME=bk_itsm_ci
+BK_MYSQL_USER=root
+BK_MYSQL_PASSWORD=root
+BK_MYSQL_HOST=localhost
+BK_MYSQL_PORT=3306
+BK_MYSQL_TEST_NAME=bk_itsm_ci_test
+```
+
## Version plan
- [版本日志](docs/RELEASE.md)
[(English Documents Available)](docs/RELEASE_EN.md)
diff --git a/readme_en.md b/readme_en.md
index 3b3a4cc66..428336ec1 100644
--- a/readme_en.md
+++ b/readme_en.md
@@ -43,6 +43,34 @@ ITSM is an upper layer SaaS application based on the Tencent Blueking product sy
- [API_Request_Sandbox_Instructions](docs/install/api_sandbox_guide.md)
- [ITSM Access Guidelines](docs/wiki/access.md)
+## Unit Testing
+- When performing unit tests locally, it is recommended to use the following environment variable configuration. Ensure that MySQL and Redis are properly set up.
+- It is advisable to execute the tests using the integrated Django Test feature in PyCharm, which loads the `.env` file. Set the target to `itsm.tests`.
+```.dotenv
+RUN_ENV=open
+APP_CODE=bk_itsm
+APP_ID=bk_itsm
+RUN_VER=open
+SECRET_KEY=12345678-1234-5678-1234-123456789012
+APP_TOKEN=12345678-1234-5678-1234-123456789012
+BK_PAAS_HOST=http://127.0.0.1
+BK_IAM_V3_INNER_HOST=127.0.0.1
+BK_IAM_INNER_HOST=http://127.0.0.1:8080
+BROKER_URL=redis://localhost:6379/0
+USE_IAM=false
+BKAPP_REDIS_HOST=localhost
+BKAPP_BK_IAM_SYSTEM_ID=itsm
+BKAPP_IAM_INITIAL_FILE=dev
+BKAPP_REDIS_PORT=6379
+BKAPP_REDIS_PASSWORD=
+BK_MYSQL_NAME=bk_itsm_ci
+BK_MYSQL_USER=root
+BK_MYSQL_PASSWORD=root
+BK_MYSQL_HOST=localhost
+BK_MYSQL_PORT=3306
+BK_MYSQL_TEST_NAME=bk_itsm_ci_test
+```
+
## Version plan
- [RELEASE](docs/RELEASE_EN.md)
[(Chinese Documents Available)](docs/RELEASE.md)
diff --git a/requirements.txt b/requirements.txt
index 514fa4e1d..25dccf836 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,126 +4,127 @@
# 请确保指定的包和版本号,可通过pip安装
# blueapps requirement
-Django==3.2.25
-PyMySQL==1.0.2
-MarkupSafe==2.0.0
-Mako==1.1.6
-requests==2.27.1
-celery==4.4.6
-django-celery-beat==2.0.0
-django-celery-results==2.0.1
+Django==4.2
+PyMySQL==1.1.1
+MarkupSafe==2.1.5
+Mako==1.3.2
+requests==2.31.0
+celery==5.3.0
+django-celery-beat==2.7.0
+django-celery-results==2.5.1
# pi==3.3.1
-python-json-logger==0.1.7
-whitenoise==5.2.0
+python-json-logger==2.0.7
+whitenoise==6.8.2
six==1.16.0
-httplib2==0.9.1
-djangorestframework==3.12.4
-drf-extensions==0.7.0
-django-cors-headers==3.7.0
+httplib2==0.22.0
+djangorestframework==3.15.1
+drf-extensions==0.7.1
+django-cors-headers==4.2.0
jsonfield==3.1.0
-pypinyin==0.31.0
-django_extensions==2.1.0
+pypinyin==0.53.0
+django_extensions==3.2.3
django-filter==2.4.0
# django-autofixture==0.12.1
-django-revproxy==0.10.0
-pyinstrument_cext==0.2.2
-pyinstrument==3.0.3
-humanize==0.5.1
+django-revproxy==0.13.0
+pyinstrument_cext==0.2.4
+pyinstrument==5.0.0
+humanize==4.11.0
xlwt==1.3.0
-jsonschema==3.2.0
+jsonschema==4.23.0
# django-smart-autoregister==0.0.5
django-bulk-update==2.2.0
-django-redis==5.0.0
-RestrictedPython==5.0
+django-redis==5.4.0
+RestrictedPython==7.4
# wiki related
-bleach==1.5.0
-django-classy-tags==2.0.0
-django-mptt==0.12.0
-django-mptt-admin==2.1.0
-django-nyt==1.2
-django-sekizai==2.0.0
-Markdown==3.2.1
-Pillow==8.4.0
-sorl-thumbnail==12.7.0
-django-simplemde==0.1.2
-martor==1.2.8
-python-dateutil==2.7.5
-django-multiselectfield==0.1.12
+bleach==6.1.0
+django-classy-tags==4.1.0
+django-mptt==0.16.0
+django-mptt-admin==2.7.0
+django-nyt==1.4.1
+django-sekizai==4.1.0
+Markdown==3.5.2
+Pillow==10.2.0
+sorl-thumbnail==12.11.0
+django-simplemde==0.1.4
+martor==1.6.45
+python-dateutil==2.9.0
+django-multiselectfield==0.1.13
# monitor
statsd==3.3.0
# pipeline
-ujson==4.3.0
-pyparsing==2.2.0
-redis==4.3.6
-django-timezone-field==4.1.2
-factory_boy==2.11.1
-mistune==2.0.5
-eventlet==0.33.3
-gevent==22.10.2
+ujson==5.9.0
+pyparsing==3.2.0
+redis==5.0.3
+django-timezone-field==5.1
+factory_boy==3.3.1
+mistune==3.0.2
+eventlet==0.35.2
+gevent==24.2.1
# iam
-cachetools==3.1.1
-certifi==2023.7.22
-chardet==3.0.4
+cachetools==5.5.0
+certifi==2024.2.2
+chardet==5.2.0
curlify==2.2.1
-idna==2.8
-urllib3==1.26.18
+idna==3.10
+urllib3==2.2.1
# pycrypto==2.6.1
#others
-raven==6.1.0
+raven==6.10.0
ddtrace==0.14.1
-gunicorn==19.6.0
-bkstorages==1.0.1
-pytz==2019.3
-suds-jurko==0.6
-mock==3.0.5
+gunicorn==23.0.0
+bkstorages==2.0.0
+pytz==2024.2
+mock==5.1.0
# excel
-xlrd==1.2.0
-typing-extensions==3.7.4.3
+xlrd==2.0.1
+typing-extensions==4.12.2
-anyjson==0.3.3
#bkouth
-PyJWT==2.4.0
-cryptography==40.0.2
+PyJWT==2.8.0
+cryptography==42.0.5
# prometheus
-django-prometheus==2.1.0
+django-prometheus==2.3.1
# grpcio
-grpcio==1.48.2
+grpcio==1.63.2
-protobuf==3.19.6
+protobuf==5.26.0
# opentelemetry
-opentelemetry-api==1.6.2
-opentelemetry-sdk==1.6.2
-opentelemetry-exporter-otlp==1.6.2
-opentelemetry-exporter-jaeger==1.6.2
-opentelemetry-exporter-jaeger-proto-grpc==1.6.2
-opentelemetry-exporter-jaeger-thrift==1.6.2
-opentelemetry-instrumentation==0.25b2
-opentelemetry-instrumentation-celery==0.25b2
-opentelemetry-instrumentation-django==0.25b2
-opentelemetry-instrumentation-dbapi==0.25b2
-opentelemetry-instrumentation-redis==0.25b2
-opentelemetry-instrumentation-logging==0.25b2
-opentelemetry-instrumentation-requests==0.25b2
-
-pyCryptodome==3.14.1
-
-Jinja2==3.0.3
-jmespath==0.10.0
-requests_toolbelt==0.9.1
-
-apigw-manager[cryptography]==1.1.8
-blueapps[opentelemetry,bkcrypto]==4.14.0
-
-drf-yasg==1.20.0
-
-bk-notice-sdk==1.3.0
+opentelemetry-api==1.29.0
+opentelemetry-sdk==1.29.0
+opentelemetry-exporter-otlp==1.29.0
+opentelemetry-exporter-jaeger==1.21.0
+opentelemetry-exporter-jaeger-proto-grpc==1.21.0
+opentelemetry-exporter-jaeger-thrift==1.21.0
+opentelemetry-instrumentation==0.50b0
+opentelemetry-instrumentation-celery==0.50b0
+opentelemetry-instrumentation-django==0.50b0
+opentelemetry-instrumentation-dbapi==0.50b0
+opentelemetry-instrumentation-redis==0.50b0
+opentelemetry-instrumentation-logging==0.50b0
+opentelemetry-instrumentation-requests==0.50b0
+
+pyCryptodome==3.20.0
+
+Jinja2==3.1.3
+jmespath==1.0.1
+requests_toolbelt==1.0.0
+
+apigw-manager[cryptography]==4.0.0
+blueapps[opentelemetry,bkcrypto]==4.15.4
+
+drf-yasg==1.21.8
+
+bk-notice-sdk==1.3.2
+
+sqlparse==0.4.4
+werkzeug==3.0.1
\ No newline at end of file
diff --git a/runtime.txt b/runtime.txt
index 4252f1066..e34519556 100644
--- a/runtime.txt
+++ b/runtime.txt
@@ -1 +1 @@
-python-3.6.12
+python-3.11.10
diff --git a/scripts/workflows/install.sh b/scripts/workflows/install.sh
index 7ee2772b1..477e7b68b 100755
--- a/scripts/workflows/install.sh
+++ b/scripts/workflows/install.sh
@@ -16,6 +16,7 @@ pip install -r requirements.txt
pip install -r requirements.dev.txt
pip install -r requirements_open.txt
pip install black
+pip install coverage
# 删除遗留数据库,并新建一个空的本地数据库
CREATE_DB_SQL="
diff --git a/scripts/workflows/prepare_services.sh b/scripts/workflows/prepare_services.sh
index 28d6589e3..62ee85692 100755
--- a/scripts/workflows/prepare_services.sh
+++ b/scripts/workflows/prepare_services.sh
@@ -11,7 +11,7 @@ if [ "$CREATE_PYTHON_VENV" ]; then
pip install virtualenv
VENV_DIR="${APP_CODE}_venv"
virtualenv "$VENV_DIR"
- virtualenv -p /usr/local/bin/python3.6 "$VENV_DIR"
+ virtualenv -p /usr/local/bin/python3.11 "$VENV_DIR"
# 激活Python虚拟环境
source "${VENV_DIR}/bin/activate"
fi
diff --git a/sops_proxy/urls.py b/sops_proxy/urls.py
index 738faf4d2..4fe74726f 100644
--- a/sops_proxy/urls.py
+++ b/sops_proxy/urls.py
@@ -23,14 +23,14 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.conf.urls import url
+from django.urls import re_path
from sops_proxy.views import SopsProxy
urlpatterns = [
# 插件静态资源(jsonp)转发
- # url(r"^static/(?P.*)$", dispatch_static),
+ # re_path(r"^static/(?P.*)$", dispatch_static),
# 插件请求(ajax)转发
- # url(r"^(?P.*)$", dispatch_query),
- url(r"^(?P.*)$", SopsProxy.as_view()),
+ # re_path(r"^(?P.*)$", dispatch_query),
+ re_path(r"^(?P.*)$", SopsProxy.as_view()),
]
diff --git a/urls.py b/urls.py
index f0d9fccf5..6efd83e71 100644
--- a/urls.py
+++ b/urls.py
@@ -24,7 +24,7 @@
"""
from django.conf import settings
-from django.conf.urls import include, url
+from django.urls import include, re_path
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
@@ -33,22 +33,22 @@
# 公共URL配置
urlpatterns = [
# Django后台数据库管理®
- url(r"^admin/", admin.site.urls),
- url(r"^notice/", include("bk_notice_sdk.urls")),
+ re_path(r"^admin/", admin.site.urls),
+ re_path(r"^notice/", include("bk_notice_sdk.urls")),
# 用户登录鉴权
- # url(r'^account/', include('account.urls')),
- url(r"^account/", include("blueapps.account.urls")),
+ # re_path(r'^account/', include('account.urls')),
+ re_path(r"^account/", include("blueapps.account.urls")),
# 接口版本管理
- url(r"^api/", include("itsm.api.v1")),
+ re_path(r"^api/", include("itsm.api.v1")),
# 对外开放的接口
- url(r"^openapi/", include("itsm.api.open_v1")),
- url(r"^openapi/v2/", include("itsm.api.open_v2")),
+ re_path(r"^openapi/", include("itsm.api.open_v1")),
+ re_path(r"^openapi/v2/", include("itsm.api.open_v2")),
# 监控,普罗米修斯相关的接口
- url(r"^monitor/", include("itsm.monitor.urls")),
+ re_path(r"^monitor/", include("itsm.monitor.urls")),
# 各种入口:微信/wiki/首页等
- url(r"^", include("itsm.sites.urls")),
+ re_path(r"^", include("itsm.sites.urls")),
# eri admin
- url(r"^eri/admin/", include("pipeline.contrib.engine_admin.urls")),
+ re_path(r"^eri/admin/", include("pipeline.contrib.engine_admin.urls")),
]
handler404 = "error_pages.views.error_404"
@@ -62,5 +62,7 @@
# 全局生效:不推荐生产环境使用
urlpatterns += [
# wiki上传图片404也可以这样简单解决:路由层面不复用MEDIA_URL,后者只用来生成url,比如可以自定义prefix为SITE_URL
- url(r"^media/(?P.*)$", static.serve, {"document_root": settings.MEDIA_ROOT}),
+ re_path(
+ r"^media/(?P.*)$", static.serve, {"document_root": settings.MEDIA_ROOT}
+ ),
]
diff --git a/weixin/README.md b/weixin/README.md
index 788a1f6bb..a2937a858 100644
--- a/weixin/README.md
+++ b/weixin/README.md
@@ -35,8 +35,8 @@
* 修改urls.py文件
```python
# urlpatterns 添加
- url(r'^weixin/login/', include('weixin.core.urls')),
- url(r'^weixin/', include('weixin.urls')),
+ re_path(r'^weixin/login/', include('weixin.core.urls')),
+ re_path(r'^weixin/', include('weixin.urls')),
```
## 蓝鲸应用
* 部署蓝鲸应用
diff --git a/weixin/core/admin.py b/weixin/core/admin.py
index f526fb2dc..124e4ead2 100644
--- a/weixin/core/admin.py
+++ b/weixin/core/admin.py
@@ -24,22 +24,24 @@
"""
from django.contrib import admin
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from weixin.core.models import BkWeixinUser
class BkWeixinUserAdmin(admin.ModelAdmin):
fieldsets = (
- (None, {'fields': ('userid', 'openid', 'nickname')}),
- (_('Personal info'), {
- 'fields': ('gender', 'country', 'province', 'city', 'email', 'mobile')}),
- (_('Extra info'), {'fields': ('avatar_url', 'qr_code')}),
+ (None, {"fields": ("userid", "openid", "nickname")}),
+ (
+ _("Personal info"),
+ {"fields": ("gender", "country", "province", "city", "email", "mobile")},
+ ),
+ (_("Extra info"), {"fields": ("avatar_url", "qr_code")}),
)
- list_display = ('userid', 'openid', 'nickname', 'mobile', 'email')
- search_fields = ('userid', 'nickname')
- ordering = ('userid',)
+ list_display = ("userid", "openid", "nickname", "mobile", "email")
+ search_fields = ("userid", "nickname")
+ ordering = ("userid",)
admin.site.register(BkWeixinUser, BkWeixinUserAdmin)
diff --git a/weixin/core/urls.py b/weixin/core/urls.py
index 6993662e3..ac5bd34f7 100644
--- a/weixin/core/urls.py
+++ b/weixin/core/urls.py
@@ -23,11 +23,11 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.conf.urls import url
+from django.urls import re_path
from . import views
urlpatterns = [
- url(r'^$', views.login, name='weixin_login'),
+ re_path(r"^$", views.login, name="weixin_login"),
]
diff --git a/weixin/urls.py b/weixin/urls.py
index 812b3d849..24f0f8c11 100644
--- a/weixin/urls.py
+++ b/weixin/urls.py
@@ -23,7 +23,7 @@
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
-from django.conf.urls import include, url
+from django.urls import include, re_path
from rest_framework.routers import DefaultRouter
from itsm.misc.views import download, upload
@@ -66,9 +66,9 @@
routers.register(r"task/tasks", WXTaskViewSet, basename="wx_tasks")
urlpatterns = routers.urls + [
- url(r"^gateway/", include("itsm.gateway.urls")),
- url(r"^upload_file/$", upload),
- url(r"^download_file/$", download),
- url(r"^postman/rpc_api/$", WXRpcApiViewSet.as_view()),
- url(r"^c/compapi/v2/usermanage/fs_list_users/$", get_batch_users),
+ re_path(r"^gateway/", include("itsm.gateway.urls")),
+ re_path(r"^upload_file/$", upload),
+ re_path(r"^download_file/$", download),
+ re_path(r"^postman/rpc_api/$", WXRpcApiViewSet.as_view()),
+ re_path(r"^c/compapi/v2/usermanage/fs_list_users/$", get_batch_users),
]
diff --git a/weixin/validators.py b/weixin/validators.py
index 86a3cf877..e5acc61c9 100644
--- a/weixin/validators.py
+++ b/weixin/validators.py
@@ -26,7 +26,7 @@
from functools import wraps
from django.contrib.auth.models import AnonymousUser
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
import settings
from weixin.utils import FailResponse
@@ -39,13 +39,18 @@ def __wrapper(request, *args, **kwargs):
# 必须在微信中打开
if isinstance(request.weixin_user, AnonymousUser):
- return FailResponse(message=_('请在企业微信中打开链接'), code='1002').json()
+ return FailResponse(message=_("请在企业微信中打开链接"), code="1002").json()
# 必须绑定微信到蓝鲸
if isinstance(request.user, AnonymousUser):
return FailResponse(
- message=_('请先登录蓝鲸平台的个人中心({}/console/user_center/)绑定你的微信({}),并访问一次ITSM.').format(
- settings.BK_PAAS_HOST, getattr(request.weixin_user, 'nickname', '')), code='1003').json()
+ message=_(
+ "请先登录蓝鲸平台的个人中心({}/console/user_center/)绑定你的微信({}),并访问一次ITSM."
+ ).format(
+ settings.BK_PAAS_HOST, getattr(request.weixin_user, "nickname", "")
+ ),
+ code="1003",
+ ).json()
return view_func(request, *args, **kwargs)