perf: Approval process role selection supports multiple strategies

pull/13832/head
feng 2024-07-26 16:23:38 +08:00 committed by Bryan
parent 920cfdac5c
commit 41b2ce06a8
4 changed files with 103 additions and 87 deletions

View File

@ -50,13 +50,6 @@ class TicketLevel(IntegerChoices):
two = 2, _("Two level") 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): class TicketApplyAssetScope(TextChoices):
all = 'all', _("All assets") all = 'all', _("All assets")
permed = 'permed', _("Permed assets") permed = 'permed', _("Permed assets")

View File

@ -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',
),
]

View File

@ -3,31 +3,24 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.db.fields import JSONManyToManyField, RelatedManager
from common.db.models import JMSBaseModel from common.db.models import JMSBaseModel
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from orgs.models import Organization 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 users.models import User
from ..const import TicketType, TicketLevel, TicketApprovalStrategy from ..const import TicketType, TicketLevel
__all__ = ['TicketFlow', 'ApprovalRule'] __all__ = ['TicketFlow', 'ApprovalRule']
class ApprovalRule(JMSBaseModel): class ApprovalRule(JMSBaseModel):
level = models.SmallIntegerField( level = models.SmallIntegerField(
default=TicketLevel.one, choices=TicketLevel.choices, default=TicketLevel.one,
choices=TicketLevel.choices,
verbose_name=_('Approve level') verbose_name=_('Approve level')
) )
strategy = models.CharField( users = JSONManyToManyField('users.User', default=dict, verbose_name=_('Users'))
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")
)
class Meta: class Meta:
verbose_name = _('Ticket flow approval rule') verbose_name = _('Ticket flow approval rule')
@ -36,17 +29,10 @@ class ApprovalRule(JMSBaseModel):
return '{}({})'.format(self.id, self.level) return '{}({})'.format(self.id, self.level)
def get_assignees(self, org_id=None): def get_assignees(self, org_id=None):
assignees = [] org = Organization.get_instance(org_id, default=current_org)
org_id = org_id if org_id else get_current_org_id() user_qs = User.get_org_users(org=org)
with tmp_to_org(org_id): query = RelatedManager.get_to_filter_qs(self.users.value, user_qs.model)
if self.strategy == TicketApprovalStrategy.super_admin: assignees = user_qs.filter(*query).distinct()
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()
return assignees return assignees

View File

@ -1,51 +1,23 @@
from django.db.transaction import atomic
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers 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.mixins.serializers import OrgResourceModelSerializerMixin
from orgs.models import Organization from orgs.models import Organization
from orgs.utils import get_current_org_id 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 from tickets.models import TicketFlow, ApprovalRule
__all__ = ['TicketFlowSerializer'] __all__ = ['TicketFlowSerializer']
class TicketFlowApproveSerializer(serializers.ModelSerializer): class TicketFlowApproveSerializer(serializers.ModelSerializer):
strategy = LabeledChoiceField( users = JSONManyToManyField(label=_('User'))
choices=TicketApprovalStrategy.choices, required=True, label=_('Approve strategy')
)
assignees_read_only = serializers.SerializerMethodField(label=_('Assignees'))
assignees_display = serializers.SerializerMethodField(label=_('Assignees display'))
class Meta: class Meta:
model = ApprovalRule model = ApprovalRule
fields_small = [ fields = ['level', 'users']
'level', 'strategy', 'assignees_read_only', 'assignees_display', read_only_fields = ['level']
]
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)
class TicketFlowSerializer(OrgResourceModelSerializerMixin): class TicketFlowSerializer(OrgResourceModelSerializerMixin):
@ -56,15 +28,14 @@ class TicketFlowSerializer(OrgResourceModelSerializerMixin):
class Meta: class Meta:
model = TicketFlow model = TicketFlow
fields_mini = ['id', ] fields_mini = ['id', 'type']
fields_small = fields_mini + [ fields_small = fields_mini + [
'type', 'approval_level', 'created_by', 'date_created', 'date_updated', 'approval_level', 'created_by', 'date_created',
'org_id', 'org_name' 'date_updated', 'org_id', 'org_name'
] ]
fields = fields_small + ['rules', ] fields = fields_small + ['rules']
read_only_fields = ['created_by', 'org_id', 'date_created', 'date_updated'] read_only_fields = ['created_by', 'date_created', 'date_updated']
extra_kwargs = { extra_kwargs = {
'type': {'required': True},
'approval_level': {'required': True} 'approval_level': {'required': True}
} }
@ -76,31 +47,23 @@ class TicketFlowSerializer(OrgResourceModelSerializerMixin):
return value return value
def create_or_update(self, action, validated_data, instance=None): def create_or_update(self, action, validated_data, instance=None):
related = 'rules' children = validated_data.pop('rules', [])
assignees = 'assignees' if instance is None:
childs = validated_data.pop(related, [])
if not instance:
instance = getattr(super(), action)(validated_data) instance = getattr(super(), action)(validated_data)
else: else:
instance = getattr(super(), action)(instance, validated_data) instance = getattr(super(), action)(instance, validated_data)
getattr(instance, related).all().delete() instance.rules.all().delete()
instance_related = getattr(instance, related)
child_instances = [] child_instances = [
related_model = instance_related.model instance.rules.model.objects.create(**data, level=level)
# Todo: 这个权限的判断 for level, data in enumerate(children, 1)
for level, data in enumerate(childs, 1): ]
data_m2m = data.pop(assignees, None) instance.rules.set(child_instances)
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)
return instance return instance
@atomic
def create(self, validated_data): def create(self, validated_data):
return self.create_or_update('create', validated_data) return self.create_or_update('create', validated_data)
@atomic
def update(self, instance, validated_data): def update(self, instance, validated_data):
current_org_id = get_current_org_id() current_org_id = get_current_org_id()
root_org_id = Organization.ROOT_ID root_org_id = Organization.ROOT_ID