diff --git a/apps/tickets/const.py b/apps/tickets/const.py index 45424d883..99e308a87 100644 --- a/apps/tickets/const.py +++ b/apps/tickets/const.py @@ -50,13 +50,6 @@ class TicketLevel(IntegerChoices): two = 2, _("Two level") -class TicketApprovalStrategy(TextChoices): - org_admin = 'org_admin', _("Org admin") - custom_user = 'custom_user', _("Custom user") - super_admin = 'super_admin', _("Super admin") - super_org_admin = 'super_org_admin', _("Super admin and org admin") - - class TicketApplyAssetScope(TextChoices): all = 'all', _("All assets") permed = 'permed', _("Permed assets") diff --git a/apps/tickets/migrations/0004_replace_assignees_to_users.py b/apps/tickets/migrations/0004_replace_assignees_to_users.py new file mode 100644 index 000000000..3c08bbad5 --- /dev/null +++ b/apps/tickets/migrations/0004_replace_assignees_to_users.py @@ -0,0 +1,74 @@ +# Generated by Django 4.1.13 on 2024-07-26 06:08 + +from django.db import migrations + +import common.db.fields + + +def generate_user_attrs(name, match, value): + return { + "type": "attrs", + "attrs": [ + { + "name": name, + "match": match, + "value": value + } + ] + } + + +def migrate_assignees_to_users(apps, schema_editor): + rule_model = apps.get_model('tickets', 'ApprovalRule') + rules = rule_model.objects.all() + objs = [] + + for rule in rules: + strategy = rule.strategy + if strategy == 'super_admin': + rule.users = generate_user_attrs( + "system_roles", "m2m", ["00000000-0000-0000-0000-000000000001"] + ) + elif strategy == 'org_admin': + rule.users = generate_user_attrs( + "org_roles", "m2m", ["00000000-0000-0000-0000-000000000005"] + ) + elif strategy == 'super_org_admin': + rule.users = { + "type": "attrs", + "attrs": [ + {"name": "org_roles", "match": "m2m", "value": ["00000000-0000-0000-0000-000000000005"]}, + {"name": "system_roles", "match": "m2m", "value": ["00000000-0000-0000-0000-000000000001"]} + ] + } + elif strategy == 'custom_user': + user_ids = [str(user_id) for user_id in rule.assignees.values_list('id', flat=True)] + rule.users = {"type": "ids", "ids": user_ids} + else: + continue + objs.append(rule) + + rule_model.objects.bulk_update(objs, ['users']) + + +class Migration(migrations.Migration): + dependencies = [ + ('tickets', '0003_initial_ticket_flow_data'), + ] + + operations = [ + migrations.AddField( + model_name='approvalrule', + name='users', + field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'), + ), + migrations.RunPython(migrate_assignees_to_users), + migrations.RemoveField( + model_name='approvalrule', + name='assignees', + ), + migrations.RemoveField( + model_name='approvalrule', + name='strategy', + ), + ] diff --git a/apps/tickets/models/flow.py b/apps/tickets/models/flow.py index c339e4685..aed3fa1d7 100644 --- a/apps/tickets/models/flow.py +++ b/apps/tickets/models/flow.py @@ -3,31 +3,24 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from common.db.fields import JSONManyToManyField, RelatedManager from common.db.models import JMSBaseModel from orgs.mixins.models import OrgModelMixin from orgs.models import Organization -from orgs.utils import tmp_to_org, get_current_org_id +from orgs.utils import tmp_to_org, current_org from users.models import User -from ..const import TicketType, TicketLevel, TicketApprovalStrategy +from ..const import TicketType, TicketLevel __all__ = ['TicketFlow', 'ApprovalRule'] class ApprovalRule(JMSBaseModel): level = models.SmallIntegerField( - default=TicketLevel.one, choices=TicketLevel.choices, + default=TicketLevel.one, + choices=TicketLevel.choices, verbose_name=_('Approve level') ) - strategy = models.CharField( - max_length=64, default=TicketApprovalStrategy.super_admin, - choices=TicketApprovalStrategy.choices, - verbose_name=_('Approve strategy') - ) - # 受理人列表 - assignees = models.ManyToManyField( - 'users.User', related_name='assigned_ticket_flow_approval_rule', - verbose_name=_("Assignees") - ) + users = JSONManyToManyField('users.User', default=dict, verbose_name=_('Users')) class Meta: verbose_name = _('Ticket flow approval rule') @@ -36,17 +29,10 @@ class ApprovalRule(JMSBaseModel): return '{}({})'.format(self.id, self.level) def get_assignees(self, org_id=None): - assignees = [] - org_id = org_id if org_id else get_current_org_id() - with tmp_to_org(org_id): - if self.strategy == TicketApprovalStrategy.super_admin: - assignees = User.get_super_admins() - elif self.strategy == TicketApprovalStrategy.org_admin: - assignees = User.get_org_admins() - elif self.strategy == TicketApprovalStrategy.super_org_admin: - assignees = User.get_super_and_org_admins() - elif self.strategy == TicketApprovalStrategy.custom_user: - assignees = self.assignees.all() + org = Organization.get_instance(org_id, default=current_org) + user_qs = User.get_org_users(org=org) + query = RelatedManager.get_to_filter_qs(self.users.value, user_qs.model) + assignees = user_qs.filter(*query).distinct() return assignees diff --git a/apps/tickets/serializers/flow.py b/apps/tickets/serializers/flow.py index 01951c1ae..29955f26d 100644 --- a/apps/tickets/serializers/flow.py +++ b/apps/tickets/serializers/flow.py @@ -1,51 +1,23 @@ -from django.db.transaction import atomic from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from common.serializers.fields import LabeledChoiceField +from common.serializers.fields import LabeledChoiceField, JSONManyToManyField from orgs.mixins.serializers import OrgResourceModelSerializerMixin from orgs.models import Organization from orgs.utils import get_current_org_id -from tickets.const import TicketApprovalStrategy, TicketType +from tickets.const import TicketType from tickets.models import TicketFlow, ApprovalRule __all__ = ['TicketFlowSerializer'] class TicketFlowApproveSerializer(serializers.ModelSerializer): - strategy = LabeledChoiceField( - choices=TicketApprovalStrategy.choices, required=True, label=_('Approve strategy') - ) - assignees_read_only = serializers.SerializerMethodField(label=_('Assignees')) - assignees_display = serializers.SerializerMethodField(label=_('Assignees display')) + users = JSONManyToManyField(label=_('User')) class Meta: model = ApprovalRule - fields_small = [ - 'level', 'strategy', 'assignees_read_only', 'assignees_display', - ] - fields_m2m = ['assignees', ] - fields = fields_small + fields_m2m - read_only_fields = ['level', 'assignees_display'] - extra_kwargs = { - 'assignees': {'write_only': True, 'allow_empty': True, 'required': False} - } - - @staticmethod - def get_assignees_display(instance): - return [str(assignee) for assignee in instance.get_assignees()] - - @staticmethod - def get_assignees_read_only(instance): - if instance.strategy == TicketApprovalStrategy.custom_user: - return instance.assignees.values_list('id', flat=True) - return [] - - def validate(self, attrs): - if attrs['strategy'] == TicketApprovalStrategy.custom_user and not attrs.get('assignees'): - error = _('Please select the Assignees') - raise serializers.ValidationError({'assignees': error}) - return super().validate(attrs) + fields = ['level', 'users'] + read_only_fields = ['level'] class TicketFlowSerializer(OrgResourceModelSerializerMixin): @@ -56,15 +28,14 @@ class TicketFlowSerializer(OrgResourceModelSerializerMixin): class Meta: model = TicketFlow - fields_mini = ['id', ] + fields_mini = ['id', 'type'] fields_small = fields_mini + [ - 'type', 'approval_level', 'created_by', 'date_created', 'date_updated', - 'org_id', 'org_name' + 'approval_level', 'created_by', 'date_created', + 'date_updated', 'org_id', 'org_name' ] - fields = fields_small + ['rules', ] - read_only_fields = ['created_by', 'org_id', 'date_created', 'date_updated'] + fields = fields_small + ['rules'] + read_only_fields = ['created_by', 'date_created', 'date_updated'] extra_kwargs = { - 'type': {'required': True}, 'approval_level': {'required': True} } @@ -76,31 +47,23 @@ class TicketFlowSerializer(OrgResourceModelSerializerMixin): return value def create_or_update(self, action, validated_data, instance=None): - related = 'rules' - assignees = 'assignees' - childs = validated_data.pop(related, []) - if not instance: + children = validated_data.pop('rules', []) + if instance is None: instance = getattr(super(), action)(validated_data) else: instance = getattr(super(), action)(instance, validated_data) - getattr(instance, related).all().delete() - instance_related = getattr(instance, related) - child_instances = [] - related_model = instance_related.model - # Todo: 这个权限的判断 - for level, data in enumerate(childs, 1): - data_m2m = data.pop(assignees, None) - child_instance = related_model.objects.create(**data, level=level) - getattr(child_instance, assignees).set(data_m2m) - child_instances.append(child_instance) - instance_related.set(child_instances) + instance.rules.all().delete() + + child_instances = [ + instance.rules.model.objects.create(**data, level=level) + for level, data in enumerate(children, 1) + ] + instance.rules.set(child_instances) return instance - @atomic def create(self, validated_data): return self.create_or_update('create', validated_data) - @atomic def update(self, instance, validated_data): current_org_id = get_current_org_id() root_org_id = Organization.ROOT_ID