perf: 修改 account (#10088)

* perf: 优化账号创建策略

* perf: 修改账号

* perf: 修改 account

* perf: 修改 account

* perf: 修改批量创建

* perf: 修改账号批量创建

* perf: 继续优化账号批量添加

* perf: 优化创建 accounts 的结果

* perf: 优化账号批量返回的格式

* perf: 优化账号

---------

Co-authored-by: ibuler <ibuler@qq.com>
pull/10125/head
fit2bot 2023-04-03 18:18:31 +08:00 committed by GitHub
parent 4601bb9e58
commit c5340b5adc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 378 additions and 237 deletions

View File

@ -1,6 +1,6 @@
from django.shortcuts import get_object_or_404
from rest_framework.decorators import action
from rest_framework.generics import ListAPIView
from rest_framework.generics import ListAPIView, CreateAPIView
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK
@ -15,7 +15,7 @@ from rbac.permissions import RBACPermission
__all__ = [
'AccountViewSet', 'AccountSecretsViewSet',
'AccountHistoriesSecretAPI'
'AccountHistoriesSecretAPI', 'AssetAccountBulkCreateApi',
]
@ -97,6 +97,20 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
}
class AssetAccountBulkCreateApi(CreateAPIView):
serializer_class = serializers.AssetAccountBulkSerializer
rbac_perms = {
'POST': 'accounts.add_account',
}
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.create(serializer.validated_data)
serializer = serializers.AssetAccountBulkSerializerResultSerializer(data, many=True)
return Response(data=serializer.data, status=HTTP_200_OK)
class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView):
model = Account.history.model
serializer_class = serializers.AccountHistorySerializer

View File

@ -20,7 +20,7 @@ class Source(TextChoices):
COLLECTED = 'collected', _('Collected')
class BulkCreateStrategy(TextChoices):
class AccountInvalidPolicy(TextChoices):
SKIP = 'skip', _('Skip')
UPDATE = 'update', _('Update')
ERROR = 'error', _('Failed')

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-03-23 07:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0009_account_usernames_to_ids'),
]
operations = [
migrations.AddField(
model_name='account',
name='source_id',
field=models.CharField(max_length=128, null=True, blank=True, verbose_name='Source ID'),
),
]

View File

@ -53,6 +53,7 @@ class Account(AbsConnectivity, BaseAccount):
version = models.IntegerField(default=0, verbose_name=_('Version'))
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
class Meta:
verbose_name = _('Account')

View File

@ -1,15 +1,18 @@
import uuid
from collections import defaultdict
from django.db import IntegrityError
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.generics import get_object_or_404
from rest_framework.validators import UniqueTogetherValidator
from accounts import validator
from accounts.const import SecretType, Source, BulkCreateStrategy
from accounts.const import SecretType, Source, AccountInvalidPolicy
from accounts.models import Account, AccountTemplate
from accounts.tasks import push_accounts_to_assets_task
from assets.const import Category, AllTypes
from assets.const import Category, AllTypes, Protocol
from assets.models import Asset
from common.serializers import SecretReadableMixin, BulkModelSerializer
from common.serializers import SecretReadableMixin
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
from common.utils import get_logger
from .base import BaseAccountSerializer
@ -17,74 +20,134 @@ from .base import BaseAccountSerializer
logger = get_logger(__name__)
class AccountSerializerCreateValidateMixin:
from_id: str
template: bool
push_now: bool
replace_attrs: callable
class AccountCreateUpdateSerializerMixin(serializers.Serializer):
template = serializers.PrimaryKeyRelatedField(
queryset=AccountTemplate.objects,
required=False, label=_("Template"), write_only=True
)
push_now = serializers.BooleanField(
default=False, label=_("Push now"), write_only=True
)
on_invalid = LabeledChoiceField(
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
write_only=True, label=_('Exist policy')
)
class Meta:
fields = ['template', 'push_now', 'on_invalid']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_initial_value()
def set_initial_value(self):
if not getattr(self, 'initial_data', None):
return
if isinstance(self.initial_data, dict):
initial_data = [self.initial_data]
else:
initial_data = self.initial_data
for data in initial_data:
if not data.get('asset') and not self.instance:
raise serializers.ValidationError({'asset': 'Asset is required'})
asset = data.get('asset') or self.instance.asset
self.from_template_if_need(data)
self.set_uniq_name_if_need(data, asset)
def to_internal_value(self, data):
from_id = data.pop('id', None)
ret = super().to_internal_value(data)
self.from_id = from_id
return ret
@staticmethod
def related_template_values(template: AccountTemplate, attrs):
ignore_fields = ['id', 'date_created', 'date_updated', 'org_id']
def set_uniq_name_if_need(initial_data, asset):
name = initial_data.get('name')
if not name:
name = initial_data.get('username')
if Account.objects.filter(name=name, asset=asset).exists():
name = name + '_' + uuid.uuid4().hex[:4]
initial_data['name'] = name
@staticmethod
def from_template_if_need(initial_data):
template_id = initial_data.pop('template', None)
if not template_id:
return
if isinstance(template_id, (str, uuid.UUID)):
template = AccountTemplate.objects.filter(id=template_id).first()
else:
template = template_id
if not template:
raise serializers.ValidationError({'template': 'Template not found'})
# Set initial data from template
ignore_fields = ['id', 'name', 'date_created', 'date_updated', 'org_id']
field_names = [
field.name for field in template._meta.fields
if field.name not in ignore_fields
]
attrs = {'source': 'template', 'source_id': template.id}
for name in field_names:
attrs[name] = attrs.get(name) or getattr(template, name)
def set_secret(self, attrs):
_id = self.from_id
template = attrs.pop('template', None)
if _id and template:
account_template = get_object_or_404(AccountTemplate, id=_id)
self.related_template_values(account_template, attrs)
elif _id and not template:
account = get_object_or_404(Account, id=_id)
attrs['secret'] = account.secret
return attrs
def validate(self, attrs):
attrs = super().validate(attrs)
return self.set_secret(attrs)
value = getattr(template, name, None)
if value is None:
continue
attrs[name] = value
initial_data.update(attrs)
@staticmethod
def push_account(instance, push_now):
if not push_now:
def push_account_if_need(instance, push_now, stat):
if not push_now or stat != 'created':
return
push_accounts_to_assets_task.delay([str(instance.id)])
def get_validators(self):
_validators = super().get_validators()
if getattr(self, 'initial_data', None) is None:
return _validators
on_invalid = self.initial_data.get('on_invalid')
if on_invalid == AccountInvalidPolicy.ERROR:
return _validators
_validators = [v for v in _validators if not isinstance(v, UniqueTogetherValidator)]
return _validators
@staticmethod
def do_create(vd):
on_invalid = vd.pop('on_invalid', None)
q = Q()
if vd.get('name'):
q |= Q(name=vd['name'])
if vd.get('username'):
q |= Q(username=vd['username'], secret_type=vd.get('secret_type'))
instance = Account.objects.filter(asset=vd['asset']).filter(q).first()
# 不存在这个资产,不用关系策略
if not instance:
instance = Account.objects.create(**vd)
return instance, 'created'
if on_invalid == AccountInvalidPolicy.SKIP:
return instance, 'skipped'
elif on_invalid == AccountInvalidPolicy.UPDATE:
for k, v in vd.items():
setattr(instance, k, v)
instance.save()
return instance, 'updated'
else:
raise serializers.ValidationError('Account already exists')
def create(self, validated_data):
push_now = validated_data.pop('push_now', None)
instance = super().create(validated_data)
self.push_account(instance, push_now)
instance, stat = self.do_create(validated_data)
self.push_account_if_need(instance, push_now, stat)
return instance
def update(self, instance, validated_data):
# account cannot be modified
validated_data.pop('username', None)
validated_data.pop('on_invalid', None)
push_now = validated_data.pop('push_now', None)
instance = super().update(instance, validated_data)
self.push_account(instance, push_now)
self.push_account_if_need(instance, push_now, 'updated')
return instance
class AccountSerializerCreateMixin(AccountSerializerCreateValidateMixin, BulkModelSerializer):
template = serializers.BooleanField(
default=False, label=_("Template"), write_only=True
)
push_now = serializers.BooleanField(
default=False, label=_("Push now"), write_only=True
)
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
class AccountAssetSerializer(serializers.ModelSerializer):
platform = ObjectRelatedField(read_only=True)
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
@ -106,62 +169,207 @@ class AccountAssetSerializer(serializers.ModelSerializer):
raise serializers.ValidationError(_('Asset not found'))
class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer):
asset = AccountAssetSerializer(label=_('Asset'))
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
su_from = ObjectRelatedField(
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
label=_('Su from'), attrs=('id', 'name', 'username')
)
strategy = LabeledChoiceField(
choices=BulkCreateStrategy.choices, default=BulkCreateStrategy.SKIP,
write_only=True, label=_('Account policy')
)
class Meta(BaseAccountSerializer.Meta):
model = Account
fields = BaseAccountSerializer.Meta.fields + [
'su_from', 'asset', 'template', 'version',
'push_now', 'source', 'connectivity', 'strategy'
'su_from', 'asset', 'version',
'source', 'source_id', 'connectivity',
] + AccountCreateUpdateSerializerMixin.Meta.fields
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
'source', 'source_id', 'connectivity'
]
extra_kwargs = {
**BaseAccountSerializer.Meta.extra_kwargs,
'name': {'required': False, 'allow_null': True},
'name': {'required': False},
}
def validate_name(self, value):
if not value:
value = self.initial_data.get('username')
return value
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset \
.prefetch_related('asset', 'asset__platform', 'asset__platform__automation')
queryset = queryset.prefetch_related(
'asset', 'asset__platform',
'asset__platform__automation'
)
return queryset
def get_validators(self):
ignore = False
validators = [validator.AccountSecretTypeValidator(fields=('secret_type',))]
view = self.context.get('view')
request = self.context.get('request')
if request and view:
data = request.data
action = view.action
ignore = action == 'create' and isinstance(data, list)
_validators = super().get_validators()
for v in _validators:
if ignore and isinstance(v, UniqueTogetherValidator):
v = validator.AccountUniqueTogetherValidator(v.queryset, v.fields)
validators.append(v)
return validators
class AssetAccountBulkSerializerResultSerializer(serializers.Serializer):
asset = serializers.CharField(read_only=True, label=_('Asset'))
state = serializers.CharField(read_only=True, label=_('State'))
error = serializers.CharField(read_only=True, label=_('Error'))
changed = serializers.BooleanField(read_only=True, label=_('Changed'))
def validate(self, attrs):
attrs = super().validate(attrs)
attrs.pop('strategy', None)
return attrs
class AssetAccountBulkSerializer(AccountCreateUpdateSerializerMixin, serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, many=True, label=_('Assets'))
class Meta:
model = Account
fields = [
'name', 'username', 'secret', 'secret_type',
'privileged', 'is_active', 'comment', 'template',
'on_invalid', 'push_now', 'assets',
]
extra_kwargs = {
'name': {'required': False},
'secret_type': {'required': False},
}
def set_initial_value(self):
if not getattr(self, 'initial_data', None):
return
initial_data = self.initial_data
self.from_template_if_need(initial_data)
@staticmethod
def _get_valid_secret_type_assets(assets, secret_type):
if isinstance(assets, list):
asset_ids = [a.id for a in assets]
assets = Asset.objects.filter(id__in=asset_ids)
asset_protocol = assets.prefetch_related('protocols').values_list('id', 'protocols__name')
protocol_secret_types_map = Protocol.protocol_secret_types()
asset_secret_types_mapp = defaultdict(set)
for asset_id, protocol in asset_protocol:
secret_types = set(protocol_secret_types_map.get(protocol, []))
asset_secret_types_mapp[asset_id].update(secret_types)
return [
asset for asset in assets
if secret_type in asset_secret_types_mapp.get(asset.id, [])
]
@staticmethod
def get_filter_lookup(vd):
return {
'username': vd['username'],
'secret_type': vd['secret_type'],
'asset': vd['asset'],
}
@staticmethod
def get_uniq_name(vd):
return vd['name'] + '-' + uuid.uuid4().hex[:4]
@staticmethod
def _handle_update_create(vd, lookup):
ori = Account.objects.filter(**lookup).first()
if ori and ori.secret == vd['secret']:
return ori, False, 'skipped'
instance, value = Account.objects.update_or_create(defaults=vd, **lookup)
state = 'created' if value else 'updated'
return instance, True, state
@staticmethod
def _handle_skip_create(vd, lookup):
instance, value = Account.objects.get_or_create(defaults=vd, **lookup)
state = 'created' if value else 'skipped'
return instance, value, state
@staticmethod
def _handle_err_create(vd, lookup):
instance, value = Account.objects.get_or_create(defaults=vd, **lookup)
if not value:
raise serializers.ValidationError(_('Account already exists'))
return instance, True, 'created'
def perform_create(self, vd, handler):
lookup = self.get_filter_lookup(vd)
try:
instance, changed, state = handler(vd, lookup)
except IntegrityError:
vd['name'] = self.get_uniq_name(vd)
instance, changed, state = handler(vd, lookup)
return instance, changed, state
def get_create_handler(self, on_invalid):
if on_invalid == 'update':
handler = self._handle_update_create
elif on_invalid == 'skip':
handler = self._handle_skip_create
else:
handler = self._handle_err_create
return handler
def perform_bulk_create(self, vd):
assets = vd.pop('assets')
on_invalid = vd.pop('on_invalid', 'skip')
secret_type = vd.get('secret_type', 'password')
if not vd.get('name'):
vd['name'] = vd.get('username')
create_handler = self.get_create_handler(on_invalid)
secret_type_supports = self._get_valid_secret_type_assets(assets, secret_type)
_results = {}
for asset in assets:
if asset not in secret_type_supports:
_results[asset] = {
'error': _('Asset does not support this secret type: %s') % secret_type,
'state': 'error',
}
continue
vd = vd.copy()
vd['asset'] = asset
try:
instance, changed, state = self.perform_create(vd, create_handler)
_results[asset] = {
'changed': changed, 'instance': instance.id, 'state': state
}
except serializers.ValidationError as e:
_results[asset] = {'error': e.detail[0], 'state': 'error'}
except Exception as e:
logger.exception(e)
_results[asset] = {'error': str(e), 'state': 'error'}
results = [{'asset': asset, **result} for asset, result in _results.items()]
state_score = {'created': 3, 'updated': 2, 'skipped': 1, 'error': 0}
results = sorted(results, key=lambda x: state_score.get(x['state'], 4))
if on_invalid != 'error':
return results
errors = []
errors.extend([result for result in results if result['state'] == 'error'])
for result in results:
if result['state'] != 'skipped':
continue
errors.append({
'error': _('Account has exist'),
'state': 'error',
'asset': str(result['asset'])
})
if errors:
raise serializers.ValidationError(errors)
return results
@staticmethod
def push_accounts_if_need(results, push_now):
if not push_now:
return
accounts = [str(v['instance']) for v in results if v.get('instance')]
push_accounts_to_assets_task.delay(accounts)
def create(self, validated_data):
push_now = validated_data.pop('push_now', False)
results = self.perform_bulk_create(validated_data)
self.push_accounts_if_need(results, push_now)
for res in results:
res['asset'] = str(res['asset'])
return results
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
@ -177,8 +385,8 @@ class AccountHistorySerializer(serializers.ModelSerializer):
class Meta:
model = Account.history.model
fields = [
'id', 'secret', 'secret_type', 'version', 'history_date',
'history_user'
'id', 'secret', 'secret_type', 'version',
'history_date', 'history_user'
]
read_only_fields = fields
extra_kwargs = {

View File

@ -13,7 +13,7 @@ __all__ = ['AuthValidateMixin', 'BaseAccountSerializer']
class AuthValidateMixin(serializers.Serializer):
secret_type = LabeledChoiceField(
choices=SecretType.choices, required=True, label=_('Secret type')
choices=SecretType.choices, label=_('Secret type'), default='password'
)
secret = EncryptedField(
label=_('Secret'), required=False, max_length=40960, allow_blank=True,
@ -77,6 +77,5 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
'date_verified', 'created_by', 'date_created',
]
extra_kwargs = {
'name': {'required': True},
'spec_info': {'label': _('Spec info')},
}

View File

@ -8,8 +8,8 @@ logger = get_logger(__name__)
@receiver(pre_save, sender=Account)
def on_account_pre_save(sender, instance, created=False, **kwargs):
if created:
def on_account_pre_save(sender, instance, **kwargs):
if instance.version == 0:
instance.version = 1
else:
instance.version = instance.history.count()

View File

@ -25,6 +25,7 @@ router.register(r'push-account-executions', api.PushAccountExecutionViewSet, 'pu
router.register(r'push-account-records', api.PushAccountRecordViewSet, 'push-account-record')
urlpatterns = [
path('accounts/bulk/', api.AssetAccountBulkCreateApi.as_view(), name='account-bulk-create'),
path('accounts/tasks/', api.AccountsTaskCreateAPI.as_view(), name='account-task-create'),
path('account-secrets/<uuid:pk>/histories/', api.AccountHistoriesSecretAPI.as_view(),
name='account-secret-history'),

View File

@ -1,101 +0,0 @@
from functools import reduce
from django.utils.translation import ugettext_lazy as _
from rest_framework.validators import (
UniqueTogetherValidator, ValidationError
)
from accounts.const import BulkCreateStrategy
from accounts.models import Account
from assets.const import Protocol
__all__ = ['AccountUniqueTogetherValidator', 'AccountSecretTypeValidator']
class ValidatorStrategyMixin:
@staticmethod
def get_strategy(attrs):
return attrs.get('strategy', BulkCreateStrategy.SKIP)
def __call__(self, attrs, serializer):
message = None
try:
super().__call__(attrs, serializer)
except ValidationError as e:
message = e.detail[0]
strategy = self.get_strategy(attrs)
if not message:
return
if strategy == BulkCreateStrategy.ERROR:
raise ValidationError(message, code='error')
elif strategy in [BulkCreateStrategy.SKIP, BulkCreateStrategy.UPDATE]:
raise ValidationError({})
else:
return
class SecretTypeValidator:
requires_context = True
protocol_settings = Protocol.settings()
message = _('{field_name} not a legal option')
def __init__(self, fields):
self.fields = fields
def __call__(self, attrs, serializer):
secret_types = set()
if serializer.instance:
asset = serializer.instance.asset
else:
asset = attrs['asset']
secret_type = attrs['secret_type']
platform_protocols_dict = {
name: self.protocol_settings.get(name, {}).get('secret_types', [])
for name in asset.platform.protocols.values_list('name', flat=True)
}
for name in asset.protocols.values_list('name', flat=True):
if name in platform_protocols_dict:
secret_types |= set(platform_protocols_dict[name])
if secret_type not in secret_types:
message = self.message.format(field_name=secret_type)
raise ValidationError(message, code='error')
class UpdateAccountMixin:
fields: tuple
get_strategy: callable
def update(self, attrs):
unique_together = Account._meta.unique_together
unique_together_fields = reduce(lambda x, y: set(x) | set(y), unique_together)
query = {field_name: attrs[field_name] for field_name in unique_together_fields}
account = Account.objects.filter(**query).first()
if not account:
query = {field_name: attrs[field_name] for field_name in self.fields}
account = Account.objects.filter(**query).first()
for k, v in attrs.items():
setattr(account, k, v)
account.save()
def __call__(self, attrs, serializer):
try:
super().__call__(attrs, serializer)
except ValidationError as e:
strategy = self.get_strategy(attrs)
if strategy == BulkCreateStrategy.UPDATE:
self.update(attrs)
message = e.detail[0]
raise ValidationError(message, code='unique')
class AccountUniqueTogetherValidator(
ValidatorStrategyMixin, UpdateAccountMixin, UniqueTogetherValidator
):
pass
class AccountSecretTypeValidator(ValidatorStrategyMixin, SecretTypeValidator):
pass

View File

@ -128,3 +128,11 @@ class Protocol(ChoicesMixin, models.TextChoices):
**cls.database_protocols(),
**cls.cloud_protocols()
}
@classmethod
def protocol_secret_types(cls):
settings = cls.settings()
return {
protocol: settings[protocol]['secret_types']
for protocol in cls.settings()
}

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-03-24 03:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0111_auto_20230321_1633'),
]
operations = [
migrations.AddField(
model_name='platformprotocol',
name='public',
field=models.BooleanField(default=True, verbose_name='Public'),
),
]

View File

@ -15,6 +15,7 @@ class PlatformProtocol(models.Model):
primary = models.BooleanField(default=False, verbose_name=_('Primary'))
required = models.BooleanField(default=False, verbose_name=_('Required'))
default = models.BooleanField(default=False, verbose_name=_('Default'))
public = models.BooleanField(default=True, verbose_name=_('Public'))
setting = models.JSONField(verbose_name=_('Setting'), default=dict)
platform = models.ForeignKey('Platform', on_delete=models.CASCADE, related_name='protocols')

View File

@ -6,9 +6,8 @@ from django.db.transaction import atomic
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from accounts.const import SecretType
from accounts.models import Account
from accounts.serializers import AuthValidateMixin, AccountSerializerCreateValidateMixin
from accounts.serializers import AccountSerializer
from common.serializers import WritableNestedModelSerializer, SecretReadableMixin, CommonModelSerializer
from common.serializers.fields import LabeledChoiceField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
@ -59,49 +58,19 @@ class AssetPlatformSerializer(serializers.ModelSerializer):
}
class AssetAccountSerializer(
AuthValidateMixin,
AccountSerializerCreateValidateMixin,
CommonModelSerializer
):
class AssetAccountSerializer(AccountSerializer):
add_org_fields = False
push_now = serializers.BooleanField(
default=False, label=_("Push now"), write_only=True
)
template = serializers.BooleanField(
default=False, label=_("Template"), write_only=True
)
name = serializers.CharField(max_length=128, required=False, label=_("Name"))
secret_type = LabeledChoiceField(
choices=SecretType.choices, default=SecretType.PASSWORD,
required=False, label=_('Secret type')
)
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, required=False, write_only=True)
class Meta:
model = Account
fields_mini = [
'id', 'name', 'username', 'privileged',
'is_active', 'version', 'secret_type',
class Meta(AccountSerializer.Meta):
fields = [
f for f in AccountSerializer.Meta.fields
if f not in ['spec_info']
]
fields_write_only = [
'secret', 'passphrase', 'push_now', 'template'
]
fields = fields_mini + fields_write_only
extra_kwargs = {
'secret': {'write_only': True},
**AccountSerializer.Meta.extra_kwargs,
}
def validate_push_now(self, value):
request = self.context['request']
if not request.user.has_perms('accounts.push_account'):
return False
return value
def validate_name(self, value):
if not value:
value = self.initial_data.get('username')
return value
class AccountSecretSerializer(SecretReadableMixin, CommonModelSerializer):
class Meta:
@ -132,7 +101,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type'))
labels = AssetLabelSerializer(many=True, required=False, label=_('Label'))
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=())
accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Account'))
accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, label=_('Account'))
nodes_display = serializers.ListField(read_only=False, required=False, label=_("Node path"))
class Meta:
@ -280,8 +249,11 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
if not accounts_data:
return
for data in accounts_data:
data['asset'] = asset
AssetAccountSerializer().create(data)
data['asset'] = asset.id
s = AssetAccountSerializer(data=accounts_data, many=True)
s.is_valid(raise_exception=True)
s.save()
@atomic
def create(self, validated_data):

View File

@ -112,8 +112,10 @@ class Applet(JMSBaseModel):
def select_host_account(self):
# 选择激活的发布机
hosts = [item for item in self.hosts.filter(is_active=True).all()
if item.load != 'offline']
hosts = [
host for host in self.hosts.filter(is_active=True)
if host.load != 'offline'
]
if not hosts:
return None