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/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/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/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/workflow.py b/itsm/workflow/models/workflow.py index 90fa062af..e16d1e70d 100644 --- a/itsm/workflow/models/workflow.py +++ b/itsm/workflow/models/workflow.py @@ -96,7 +96,7 @@ class WorkflowBase(ObjectManagerMixin, Model): ) is_draft = models.BooleanField(_("是否为草稿"), default=True) is_builtin = models.BooleanField(_("是否为系统内置"), default=False) - is_task_needed = models.NullBooleanField( + is_task_needed = models.BooleanField( _("是否需要关联子任务"), default=False, null=True ) owners = models.CharField(_("负责人"), max_length=LEN_XX_LONG, default=EMPTY_STRING)