From 6ef5154d4db320d732375ec98e20de5cf994a3d5 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 9 Nov 2022 13:34:29 +0800 Subject: [PATCH 1/7] fix: swagger (#9031) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/automations/change_secret.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/automations/change_secret.py b/apps/assets/api/automations/change_secret.py index 944443914..112f0f93b 100644 --- a/apps/assets/api/automations/change_secret.py +++ b/apps/assets/api/automations/change_secret.py @@ -24,8 +24,8 @@ class ChangeSecretAutomationViewSet(OrgBulkModelViewSet): class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): serializer_class = serializers.ChangeSecretRecordSerializer - filter_fields = ['username', 'asset', 'reason', 'execution'] - search_fields = ['username', 'reason', 'asset__hostname'] + filter_fields = ['asset', 'execution_id'] + search_fields = ['asset__hostname'] def get_queryset(self): return ChangeSecretRecord.objects.all() From 3b4e388ed0991c6d855160bd756a0c783b011146 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 9 Nov 2022 15:42:21 +0800 Subject: [PATCH 2/7] perf: gather account api adjustment --- apps/assets/automations/base/manager.py | 1 + apps/assets/models/automations/base.py | 10 +++------- apps/assets/models/automations/gather_accounts.py | 4 ++++ apps/assets/serializers/automations/base.py | 13 +++++++++---- .../serializers/automations/gather_accounts.py | 8 ++++++-- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 512454d9f..6d1563258 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -232,5 +232,6 @@ class BasePlaybookManager: except Exception as e: self.on_runner_failed(runner, e) print('\n') + self.execution.status = 'success' self.execution.date_finished = timezone.now() self.execution.save() diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index e814d4128..fd9bc422b 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -15,12 +15,8 @@ from assets.const import AutomationTypes class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): accounts = models.JSONField(default=list, verbose_name=_("Accounts")) - nodes = models.ManyToManyField( - 'assets.Node', blank=True, verbose_name=_("Nodes") - ) - assets = models.ManyToManyField( - 'assets.Asset', blank=True, verbose_name=_("Assets") - ) + nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) + assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets")) type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type')) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -92,7 +88,7 @@ class AutomationExecution(OrgModelMixin): 'BaseAutomation', related_name='executions', on_delete=models.CASCADE, verbose_name=_('Automation task') ) - status = models.CharField(max_length=16, default='pending') + status = models.CharField(max_length=16, default='pending', verbose_name=_('Status')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) diff --git a/apps/assets/models/automations/gather_accounts.py b/apps/assets/models/automations/gather_accounts.py index 861031af4..a3aa42383 100644 --- a/apps/assets/models/automations/gather_accounts.py +++ b/apps/assets/models/automations/gather_accounts.py @@ -13,3 +13,7 @@ class GatherAccountsAutomation(BaseAutomation): class Meta: verbose_name = _("Gather asset accounts") + + @property + def executed_amount(self): + return self.executions.count() diff --git a/apps/assets/serializers/automations/base.py b/apps/assets/serializers/automations/base.py index 58c169c13..3ff3f6686 100644 --- a/apps/assets/serializers/automations/base.py +++ b/apps/assets/serializers/automations/base.py @@ -3,9 +3,10 @@ from rest_framework import serializers from ops.mixin import PeriodTaskSerializerMixin from assets.const import AutomationTypes -from assets.models import Asset, BaseAutomation, AutomationExecution +from assets.models import Asset, Node, BaseAutomation, AutomationExecution from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.utils import get_logger +from common.drf.fields import ObjectRelatedField logger = get_logger(__file__) @@ -16,6 +17,9 @@ __all__ = [ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer): + assets = ObjectRelatedField(many=True, required=False, queryset=Asset.objects, label=_('Assets')) + nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) + class Meta: read_only_fields = [ 'date_created', 'date_updated', 'created_by', 'periodic_display' @@ -26,6 +30,7 @@ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSe ] extra_kwargs = { 'name': {'required': True}, + 'type': {'read_only': True}, 'periodic_display': {'label': _('Periodic perform')}, } @@ -37,10 +42,10 @@ class AutomationExecutionSerializer(serializers.ModelSerializer): class Meta: model = AutomationExecution - fields = [ - 'id', 'automation', 'trigger', 'trigger_display', - 'date_start', 'date_finished', 'snapshot', 'type' + read_only_fields = [ + 'trigger_display', 'date_start', 'date_finished', 'snapshot', 'status' ] + fields = ['id', 'automation', 'trigger', 'type'] + read_only_fields @staticmethod def get_snapshot(obj): diff --git a/apps/assets/serializers/automations/gather_accounts.py b/apps/assets/serializers/automations/gather_accounts.py index 0f6308d49..6b86fd64c 100644 --- a/apps/assets/serializers/automations/gather_accounts.py +++ b/apps/assets/serializers/automations/gather_accounts.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from django.utils.translation import ugettext_lazy as _ from assets.models import GatherAccountsAutomation from common.utils import get_logger @@ -15,6 +16,9 @@ __all__ = [ class GatherAccountAutomationSerializer(BaseAutomationSerializer): class Meta: model = GatherAccountsAutomation - read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + ['executed_amount'] fields = BaseAutomationSerializer.Meta.fields + read_only_fields - extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs + + extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{ + 'executed_amount': {'label': _('Executed amount')} + }} From cf4744791a6e77c764177eb07ea9b44655a81eea Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 9 Nov 2022 15:55:48 +0800 Subject: [PATCH 3/7] fix: automation execution bug --- apps/assets/api/automations/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/automations/base.py b/apps/assets/api/automations/base.py index 1b551f412..845b721c8 100644 --- a/apps/assets/api/automations/base.py +++ b/apps/assets/api/automations/base.py @@ -111,8 +111,8 @@ class AutomationExecutionViewSet( serializer.is_valid(raise_exception=True) automation = serializer.validated_data.get('automation') tp = serializer.validated_data.get('type') - model = AutomationTypes.get_model(tp) + model = AutomationTypes.get_type_model(tp) task = execute_automation.delay( - pid=automation.ok, trigger=Trigger.manual, model=model + pid=automation.pk, trigger=Trigger.manual, model=model ) return Response({'task': task.id}, status=status.HTTP_201_CREATED) From 1b2eda51e33d14c0482ce8d9efd5f69c1853a54b Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 9 Nov 2022 16:14:25 +0800 Subject: [PATCH 4/7] perf: get host applet by name --- apps/terminal/api/applet/relation.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/terminal/api/applet/relation.py b/apps/terminal/api/applet/relation.py index 513be6aab..e31c6ba5c 100644 --- a/apps/terminal/api/applet/relation.py +++ b/apps/terminal/api/applet/relation.py @@ -8,6 +8,7 @@ from rest_framework.response import Response from common.drf.api import JMSModelViewSet from common.permissions import IsServiceAccount +from common.utils import is_uuid from orgs.utils import tmp_to_builtin_org from rbac.permissions import RBACPermission from terminal.models import AppletHost @@ -63,6 +64,13 @@ class AppletHostAppletViewSet(HostMixin, JMSModelViewSet): host: AppletHost serializer_class = AppletPublicationSerializer + def get_object(self): + pk = self.kwargs.get('pk') + if not is_uuid(pk): + return self.host.publications.get(applet__name=pk) + else: + return self.host.publications.get(pk=pk) + def get_queryset(self): queryset = self.host.publications.all() return queryset From a5cef743568b4bf4a889690a2ac8a86388edd123 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 9 Nov 2022 18:15:21 +0800 Subject: [PATCH 5/7] perf: serializer (#9034) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/serializers/account/account.py | 1 - apps/assets/serializers/account/backup.py | 15 +++---- apps/assets/serializers/account/base.py | 41 ++---------------- .../serializers/automations/change_secret.py | 42 ++++++++----------- apps/assets/serializers/base.py | 8 ++-- apps/assets/serializers/gathered_user.py | 6 ++- apps/tickets/serializers/flow.py | 15 ++++--- 7 files changed, 47 insertions(+), 81 deletions(-) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index c6fcd4496..cfd9a52f4 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -58,7 +58,6 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): required=False, queryset=Asset.objects, label=_('Asset'), attrs=('id', 'name', 'address', 'platform_id') ) - secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type')) class Meta(BaseAccountSerializer.Meta): model = Account diff --git a/apps/assets/serializers/account/backup.py b/apps/assets/serializers/account/backup.py index 8aa9aa8a7..06cf4e2f9 100644 --- a/apps/assets/serializers/account/backup.py +++ b/apps/assets/serializers/account/backup.py @@ -6,6 +6,8 @@ from rest_framework import serializers from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ops.mixin import PeriodTaskSerializerMixin from common.utils import get_logger +from common.const.choices import Trigger +from common.drf.fields import LabeledChoiceField from assets.models import AccountBackupPlan, AccountBackupPlanExecution @@ -32,17 +34,12 @@ class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceMode class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer): - trigger_display = serializers.ReadOnlyField( - source='get_trigger_display', label=_('Trigger mode') - ) + trigger = LabeledChoiceField(choices=Trigger.choices, label=_('Trigger mode')) class Meta: model = AccountBackupPlanExecution - fields = [ - 'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason', - 'is_success', 'plan', 'org_id', 'recipients', 'trigger_display' - ] - read_only_fields = ( + read_only_fields = [ 'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason', 'is_success', 'org_id', 'recipients' - ) + ] + fields = read_only_fields + ['plan'] diff --git a/apps/assets/serializers/account/base.py b/apps/assets/serializers/account/base.py index e086da02a..c0e3553e8 100644 --- a/apps/assets/serializers/account/base.py +++ b/apps/assets/serializers/account/base.py @@ -1,28 +1,19 @@ # -*- coding: utf-8 -*- -from io import StringIO - from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers -from common.utils import validate_ssh_private_key, ssh_private_key_gen -from common.drf.fields import EncryptedField -from orgs.mixins.serializers import BulkOrgResourceModelSerializer from assets.models import BaseAccount +from assets.serializers.base import AuthValidateMixin +from orgs.mixins.serializers import BulkOrgResourceModelSerializer __all__ = ['BaseAccountSerializer'] -class BaseAccountSerializer(BulkOrgResourceModelSerializer): - secret = EncryptedField( - label=_('Secret'), required=False, allow_blank=True, - allow_null=True, max_length=40960 - ) - +class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): class Meta: model = BaseAccount fields_mini = ['id', 'name', 'username'] fields_small = fields_mini + [ - 'secret_type', 'secret', 'has_secret', + 'secret_type', 'secret', 'has_secret', 'passphrase', 'privileged', 'is_active', 'specific', ] fields_other = ['created_by', 'date_created', 'date_updated', 'comment'] @@ -32,29 +23,5 @@ class BaseAccountSerializer(BulkOrgResourceModelSerializer): 'date_verified', 'created_by', 'date_created', ] extra_kwargs = { - 'secret': {'write_only': True}, - 'passphrase': {'write_only': True}, 'specific': {'label': _('Specific')}, } - - def validate_private_key(self, private_key): - if not private_key: - return '' - passphrase = self.initial_data.get('passphrase') - passphrase = passphrase if passphrase else None - valid = validate_ssh_private_key(private_key, password=passphrase) - if not valid: - raise serializers.ValidationError(_("private key invalid or passphrase error")) - - private_key = ssh_private_key_gen(private_key, password=passphrase) - string_io = StringIO() - private_key.write_private_key(string_io) - private_key = string_io.getvalue() - return private_key - - def validate_secret(self, value): - secret_type = self.initial_data.get('secret_type') - if secret_type == 'ssh_key': - value = self.validate_private_key(value) - return value - diff --git a/apps/assets/serializers/automations/change_secret.py b/apps/assets/serializers/automations/change_secret.py index 104a3837e..3b9137bc4 100644 --- a/apps/assets/serializers/automations/change_secret.py +++ b/apps/assets/serializers/automations/change_secret.py @@ -3,10 +3,11 @@ from django.utils.translation import ugettext as _ from rest_framework import serializers -from assets.serializers.base import AuthValidateMixin -from assets.models import ChangeSecretAutomation, ChangeSecretRecord -from assets.const import DEFAULT_PASSWORD_RULES, SecretType, SecretStrategy from common.utils import get_logger +from common.drf.fields import LabeledChoiceField, ObjectRelatedField +from assets.serializers.base import AuthValidateMixin +from assets.const import DEFAULT_PASSWORD_RULES, SecretType, SecretStrategy, SSHKeyStrategy +from assets.models import Asset, Account, ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution from .base import BaseAutomationSerializer @@ -20,19 +21,17 @@ __all__ = [ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializer): + secret_strategy = LabeledChoiceField( + choices=SecretStrategy.choices, required=True, label=_('Secret strategy') + ) + ssh_key_change_strategy = LabeledChoiceField( + choices=SSHKeyStrategy.choices, required=False, label=_('SSH Key strategy') + ) password_rules = serializers.DictField(default=DEFAULT_PASSWORD_RULES) - secret_strategy_display = serializers.ReadOnlyField( - source='get_secret_strategy_display', label=_('Secret strategy') - ) - ssh_key_change_strategy_display = serializers.ReadOnlyField( - source='get_ssh_key_strategy_display', label=_('SSH Key strategy') - ) class Meta: model = ChangeSecretAutomation - read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + [ - 'secret_strategy_display', 'ssh_key_change_strategy_display' - ] + read_only_fields = BaseAutomationSerializer.Meta.read_only_fields fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [ 'secret_type', 'secret_strategy', 'secret', 'password_rules', 'ssh_key_change_strategy', 'passphrase', 'recipients', @@ -84,26 +83,21 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ class ChangeSecretRecordSerializer(serializers.ModelSerializer): - asset_display = serializers.SerializerMethodField(label=_('Asset display')) - account_display = serializers.SerializerMethodField(label=_('Account display')) is_success = serializers.SerializerMethodField(label=_('Is success')) + asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset')) + account = ObjectRelatedField(queryset=Account.objects, label=_('Account')) + execution = ObjectRelatedField( + queryset=AutomationExecution.objects, label=_('Automation task execution') + ) class Meta: model = ChangeSecretRecord fields = [ - 'id', 'asset', 'account', 'date_started', 'date_finished', - 'is_success', 'error', 'execution', 'asset_display', 'account_display' + 'id', 'asset', 'account', 'date_started', + 'date_finished', 'is_success', 'error', 'execution', ] read_only_fields = fields - @staticmethod - def get_asset_display(instance): - return str(instance.asset) - - @staticmethod - def get_account_display(instance): - return str(instance.account) - @staticmethod def get_is_success(obj): if obj.status == 'success': diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 7b5b62a16..18432a7e5 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -3,15 +3,17 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.drf.fields import EncryptedField from assets.const import SecretType +from common.drf.fields import EncryptedField, LabeledChoiceField from .utils import validate_password_for_ansible, validate_ssh_key class AuthValidateMixin(serializers.Serializer): - secret_type = serializers.CharField(label=_('Secret type'), max_length=16, required=True) + secret_type = LabeledChoiceField( + choices=SecretType.choices, required=True, label=_('Secret type') + ) secret = EncryptedField( - label=_('Secret'), required=False, max_length=16384, allow_blank=True, + label=_('Secret'), required=False, max_length=40960, allow_blank=True, allow_null=True, write_only=True, ) passphrase = serializers.CharField( diff --git a/apps/assets/serializers/gathered_user.py b/apps/assets/serializers/gathered_user.py index 6cb90f46e..a0b58de45 100644 --- a/apps/assets/serializers/gathered_user.py +++ b/apps/assets/serializers/gathered_user.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- # - from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from ..models import GatheredUser +from common.drf.fields import ObjectRelatedField +from ..models import GatheredUser, Asset class GatheredUserSerializer(OrgResourceModelSerializerMixin): + asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset')) + class Meta: model = GatheredUser fields_mini = ['id'] diff --git a/apps/tickets/serializers/flow.py b/apps/tickets/serializers/flow.py index e8c066100..e949fa8d6 100644 --- a/apps/tickets/serializers/flow.py +++ b/apps/tickets/serializers/flow.py @@ -5,21 +5,24 @@ from rest_framework import serializers from orgs.models import Organization from orgs.utils import get_current_org_id from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from common.drf.fields import LabeledChoiceField from tickets.models import TicketFlow, ApprovalRule -from tickets.const import TicketApprovalStrategy +from tickets.const import TicketApprovalStrategy, TicketType __all__ = ['TicketFlowSerializer'] class TicketFlowApproveSerializer(serializers.ModelSerializer): - strategy_display = serializers.ReadOnlyField(source='get_strategy_display', label=_('Approve strategy')) + strategy = LabeledChoiceField( + choices=TicketApprovalStrategy.choices, required=True, label=_('Approve strategy') + ) assignees_read_only = serializers.SerializerMethodField(label=_('Assignees')) assignees_display = serializers.SerializerMethodField(label=_('Assignees display')) class Meta: model = ApprovalRule fields_small = [ - 'level', 'strategy', 'assignees_read_only', 'assignees_display', 'strategy_display' + 'level', 'strategy', 'assignees_read_only', 'assignees_display', ] fields_m2m = ['assignees', ] fields = fields_small + fields_m2m @@ -46,14 +49,16 @@ class TicketFlowApproveSerializer(serializers.ModelSerializer): class TicketFlowSerializer(OrgResourceModelSerializerMixin): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) + type = LabeledChoiceField( + choices=TicketType.choices, required=True, label=_('Type') + ) rules = TicketFlowApproveSerializer(many=True, required=True) class Meta: model = TicketFlow fields_mini = ['id', ] fields_small = fields_mini + [ - 'type', 'type_display', 'approval_level', 'created_by', 'date_created', 'date_updated', + 'type', 'approval_level', 'created_by', 'date_created', 'date_updated', 'org_id', 'org_name' ] fields = fields_small + ['rules', ] From becb10a453142c9e5b02d18ee5d424595a8db183 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 9 Nov 2022 18:31:35 +0800 Subject: [PATCH 6/7] perf: changr secret record api --- apps/assets/api/automations/change_secret.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/api/automations/change_secret.py b/apps/assets/api/automations/change_secret.py index 112f0f93b..291c56518 100644 --- a/apps/assets/api/automations/change_secret.py +++ b/apps/assets/api/automations/change_secret.py @@ -36,5 +36,5 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): execution = get_object_or_none(AutomationExecution, pk=eid) if execution: queryset = queryset.filter(execution=execution) - queryset = queryset.order_by('is_success', '-date_start') + queryset = queryset.order_by('-date_start') return queryset From 2f611009dc21ca6228e2d07016107eeecc1f1289 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 9 Nov 2022 20:51:43 +0800 Subject: [PATCH 7/7] perf: acl --- apps/acls/serializers/login_acl.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/acls/serializers/login_acl.py b/apps/acls/serializers/login_acl.py index a699ae1ea..536061082 100644 --- a/apps/acls/serializers/login_acl.py +++ b/apps/acls/serializers/login_acl.py @@ -2,7 +2,9 @@ from django.utils.translation import ugettext as _ from rest_framework import serializers from common.drf.serializers import BulkModelSerializer from common.drf.serializers import MethodSerializer +from common.drf.fields import ObjectRelatedField from jumpserver.utils import has_valid_xpack_license +from users.models import User from ..models import LoginACL from .rules import RuleSerializer @@ -12,8 +14,10 @@ common_help_text = _('Format for comma-delimited string, with * indicating a mat class LoginACLSerializer(BulkModelSerializer): - user_display = serializers.ReadOnlyField(source='user.username', label=_('Username')) - reviewers_display = serializers.SerializerMethodField(label=_('Reviewers')) + user = ObjectRelatedField(queryset=User.objects, label=_('User')) + reviewers = ObjectRelatedField( + queryset=User.objects, label=_('Reviewers'), many=True, required=False + ) action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count') rules = MethodSerializer() @@ -22,13 +26,11 @@ class LoginACLSerializer(BulkModelSerializer): model = LoginACL fields_mini = ['id', 'name'] fields_small = fields_mini + [ - 'priority', 'rules', 'action', 'action_display', - 'is_active', 'user', 'user_display', - 'date_created', 'date_updated', 'reviewers_amount', - 'comment', 'created_by' + 'priority', 'rules', 'action', 'action_display', 'is_active', 'user', + 'date_created', 'date_updated', 'reviewers_amount', 'comment', 'created_by', ] - fields_fk = ['user', 'user_display'] - fields_m2m = ['reviewers', 'reviewers_display'] + fields_fk = ['user'] + fields_m2m = ['reviewers'] fields = fields_small + fields_fk + fields_m2m extra_kwargs = { 'priority': {'default': 50},