From d7d9fe271857e9b0d25694b3d0d394ab10323f9b Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 1 Sep 2022 14:46:31 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0098_auto_20220430_2126.py | 2 +- .../migrations/0108_auto_20220901_1034.py | 37 +++ .../migrations/0109_auto_20220901_1431.py | 30 +++ .../models/{authbook.py => _authbook.py} | 0 apps/assets/models/_user.py | 3 +- apps/assets/models/account.py | 3 - apps/assets/models/asset/common.py | 3 +- apps/assets/models/asset/database.py | 1 + apps/assets/models/asset/host.py | 53 ---- apps/assets/models/base.py | 2 +- apps/assets/models/domain.py | 4 +- apps/assets/models/platform.py | 11 +- apps/assets/serializers/account/account.py | 3 +- apps/assets/serializers/asset.py | 226 ------------------ apps/assets/serializers/asset/__init__.py | 6 +- apps/assets/serializers/asset/category.py | 46 +--- apps/assets/serializers/asset/cloud.py | 11 + apps/assets/serializers/asset/common.py | 36 +-- apps/assets/serializers/asset/database.py | 11 + apps/assets/serializers/asset/host.py | 37 +++ apps/assets/serializers/asset/networking.py | 10 + apps/assets/serializers/asset/web.py | 11 + apps/assets/serializers/platform.py | 10 +- apps/common/drf/fields.py | 37 ++- apps/common/drf/metadata.py | 6 +- 25 files changed, 216 insertions(+), 383 deletions(-) create mode 100644 apps/assets/migrations/0108_auto_20220901_1034.py create mode 100644 apps/assets/migrations/0109_auto_20220901_1431.py rename apps/assets/models/{authbook.py => _authbook.py} (100%) delete mode 100644 apps/assets/serializers/asset.py create mode 100644 apps/assets/serializers/asset/cloud.py create mode 100644 apps/assets/serializers/asset/database.py create mode 100644 apps/assets/serializers/asset/host.py create mode 100644 apps/assets/serializers/asset/networking.py create mode 100644 apps/assets/serializers/asset/web.py diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index 943e8241f..fd3e27ba0 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -77,7 +77,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='su_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='SU method'), + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='SU method'), ), migrations.AddField( model_name='platform', diff --git a/apps/assets/migrations/0108_auto_20220901_1034.py b/apps/assets/migrations/0108_auto_20220901_1034.py new file mode 100644 index 000000000..d19749cf7 --- /dev/null +++ b/apps/assets/migrations/0108_auto_20220901_1034.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.14 on 2022-09-01 02:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0107_alter_accountbackupplan_types'), + ] + + operations = [ + migrations.RemoveField( + model_name='platform', + name='create_account_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='create_account_method', + ), + migrations.RemoveField( + model_name='platform', + name='domain_default', + ), + migrations.RemoveField( + model_name='platform', + name='domain_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='ping_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='ping_method', + ), + ] diff --git a/apps/assets/migrations/0109_auto_20220901_1431.py b/apps/assets/migrations/0109_auto_20220901_1431.py new file mode 100644 index 000000000..07fad818e --- /dev/null +++ b/apps/assets/migrations/0109_auto_20220901_1431.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.14 on 2022-09-01 06:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0108_auto_20220901_1034'), + ] + + operations = [ + migrations.RemoveField( + model_name='host', + name='device_info', + ), + migrations.AddField( + model_name='asset', + name='info', + field=models.JSONField(blank=True, default=dict, verbose_name='Info'), + ), + migrations.AddField( + model_name='database', + name='version', + field=models.CharField(blank=True, max_length=16, verbose_name='Version'), + ), + migrations.DeleteModel( + name='DeviceInfo', + ), + ] diff --git a/apps/assets/models/authbook.py b/apps/assets/models/_authbook.py similarity index 100% rename from apps/assets/models/authbook.py rename to apps/assets/models/_authbook.py diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py index 83aaec2c0..ab593b016 100644 --- a/apps/assets/models/_user.py +++ b/apps/assets/models/_user.py @@ -16,7 +16,7 @@ __all__ = ['SystemUser'] logger = logging.getLogger(__name__) -class SystemUser(ProtocolMixin, BaseAccount): +class SystemUser(BaseAccount, ProtocolMixin): LOGIN_AUTO = 'auto' LOGIN_MANUAL = 'manual' LOGIN_MODE_CHOICES = ( @@ -44,6 +44,7 @@ class SystemUser(ProtocolMixin, BaseAccount): # linux su 命令 (switch user) su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")) + privileged = None class Meta: ordering = ['name'] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 585307324..fc76e86ee 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -2,14 +2,12 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords -from common.db import fields from .base import BaseAccount, AbsConnectivity __all__ = ['Account', 'AccountTemplate'] class Account(BaseAccount, AbsConnectivity): - token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=0, verbose_name=_('Version')) @@ -30,7 +28,6 @@ class Account(BaseAccount, AbsConnectivity): class AccountTemplate(BaseAccount, AbsConnectivity): - token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) class Meta: diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 1842f5f1b..0a995d8cc 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -11,7 +11,6 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty from orgs.mixins.models import OrgManager, JMSOrgBaseModel -from ...const import Category from ..platform import Platform from ..base import AbsConnectivity @@ -82,7 +81,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) - + info = models.JSONField(verbose_name='Info', default=dict, blank=True) objects = AssetManager.from_queryset(AssetQuerySet)() def __str__(self): diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index cb97c95cd..9d5ee1325 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -6,6 +6,7 @@ from .common import Asset class Database(Asset): db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) + version = models.CharField(max_length=16, verbose_name=_("Version"), blank=True) def __str__(self): return '{}({}://{}/{})'.format(self.name, self.type, self.ip, self.db_name) diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 79df6eb58..3f29fbe8f 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,61 +1,8 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - -from common.mixins.models import CommonModelMixin from assets.const import Category from .common import Asset class Host(Asset): - device_info = models.OneToOneField('DeviceInfo', null=True, on_delete=models.SET_NULL, verbose_name=_("Host")) - def save(self, *args, **kwargs): self.category = Category.HOST return super().save(*args, **kwargs) - - -class DeviceInfo(CommonModelMixin): - # Collect - vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) - model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) - sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) - - cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) - cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) - cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) - cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) - memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) - disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) - disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) - - os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) - os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) - os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) - hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) - number = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Asset number')) - - @property - def cpu_info(self): - info = "" - if self.cpu_model: - info += self.cpu_model - if self.cpu_count and self.cpu_cores: - info += "{}*{}".format(self.cpu_count, self.cpu_cores) - return info - - @property - def hardware_info(self): - if self.cpu_count: - return '{} Core {} {}'.format( - self.cpu_vcpus or self.cpu_count * self.cpu_cores, - self.memory, self.disk_total - ) - else: - return '' - - def __str__(self): - return '{} of {}'.format(self.hardware_info, self.host.name) - - class Meta: - verbose_name = _("DeviceInfo") - diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index c32de09e1..9cdac9e1f 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -176,7 +176,7 @@ class BaseAccount(OrgModelMixin, AuthMixin): password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) - # token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) + token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index a4ab3888f..72a5c748c 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -22,8 +22,7 @@ class Domain(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) comment = models.TextField(blank=True, verbose_name=_('Comment')) - date_created = models.DateTimeField(auto_now_add=True, null=True, - verbose_name=_('Date created')) + date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) class Meta: verbose_name = _("Domain") @@ -64,6 +63,7 @@ class Gateway(BaseAccount): domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) + token = None def __str__(self): return self.name diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 06bdc4de3..71ebf769f 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -1,7 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from assets.const import Category, AllTypes +from assets.const import AllTypes from common.db.fields import JsonDictTextField @@ -30,23 +30,14 @@ class Platform(models.Model): meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) internal = models.BooleanField(default=False, verbose_name=_("Internal")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) - domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) - domain_default = models.ForeignKey( - 'assets.Domain', null=True, on_delete=models.SET_NULL, - verbose_name=_("Domain default") - ) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) # Accounts # 这应该和账号有关 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) - ping_enabled = models.BooleanField(default=False) - ping_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) - create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled")) - create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method")) change_password_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) change_password_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index e43445251..0e73f6a1b 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -11,7 +11,8 @@ from .common import BaseAccountSerializer class AccountSerializer( - AccountTemplateSerializerMixin, AuthSerializerMixin, + AccountTemplateSerializerMixin, + AuthSerializerMixin, BulkOrgResourceModelSerializer ): ip = serializers.ReadOnlyField(label=_("IP")) diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py deleted file mode 100644 index 5211cfef6..000000000 --- a/apps/assets/serializers/asset.py +++ /dev/null @@ -1,226 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework import serializers -from django.core.validators import RegexValidator -from django.utils.translation import ugettext_lazy as _ - -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from ..models import Asset, Node, Platform, SystemUser - -__all__ = [ - 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', - 'ProtocolsField', 'PlatformSerializer', - 'AssetTaskSerializer', 'AssetsTaskSerializer', 'ProtocolsField', -] - - -class ProtocolField(serializers.RegexField): - protocols = '|'.join(dict(Asset.Protocol.choices).keys()) - default_error_messages = { - 'invalid': _('Protocol format should {}/{}').format(protocols, '1-65535') - } - regex = r'^(%s)/(\d{1,5})$' % protocols - - def __init__(self, *args, **kwargs): - super().__init__(self.regex, **kwargs) - - -def validate_duplicate_protocols(values): - errors = [] - names = [] - - for value in values: - if not value or '/' not in value: - continue - name = value.split('/')[0] - if name in names: - errors.append(_("Protocol duplicate: {}").format(name)) - names.append(name) - errors.append('') - if any(errors): - raise serializers.ValidationError(errors) - - -class ProtocolsField(serializers.ListField): - default_validators = [validate_duplicate_protocols] - - def __init__(self, *args, **kwargs): - kwargs['child'] = ProtocolField() - kwargs['allow_null'] = True - kwargs['allow_empty'] = True - kwargs['min_length'] = 1 - kwargs['max_length'] = 4 - super().__init__(*args, **kwargs) - - def to_representation(self, value): - if not value: - return [] - return value.split(' ') - - -class AssetSerializer(BulkOrgResourceModelSerializer): - platform = serializers.SlugRelatedField( - slug_field='name', queryset=Platform.objects.all(), label=_("Platform") - ) - protocols = ProtocolsField(label=_('Protocols'), required=False, default=['ssh/22']) - domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name')) - nodes_display = serializers.ListField( - child=serializers.CharField(), label=_('Nodes name'), required=False - ) - labels_display = serializers.ListField( - child=serializers.CharField(), label=_('Labels name'), required=False, read_only=True - ) - - """ - 资产的数据结构 - """ - - class Meta: - model = Asset - fields_mini = ['id', 'hostname', 'ip', 'platform', 'protocols'] - fields_small = fields_mini + [ - 'protocol', 'port', 'protocols', 'is_active', - 'public_ip', 'number', 'comment', - ] - fields_hardware = [ - 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', - 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', - 'os', 'os_version', 'os_arch', 'hostname_raw', - 'cpu_info', 'hardware_info', - ] - fields_fk = [ - 'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display' - ] - fields_m2m = [ - 'nodes', 'nodes_display', 'labels', 'labels_display', - ] - read_only_fields = [ - 'connectivity', 'date_verified', 'cpu_info', 'hardware_info', - 'created_by', 'date_created', - ] - fields = fields_small + fields_hardware + fields_fk + fields_m2m + read_only_fields - extra_kwargs = { - 'protocol': {'write_only': True}, - 'port': {'write_only': True}, - 'hardware_info': {'label': _('Hardware info'), 'read_only': True}, - 'admin_user_display': {'label': _('Admin user display'), 'read_only': True}, - 'cpu_info': {'label': _('CPU info')}, - } - - def get_fields(self): - fields = super().get_fields() - - admin_user_field = fields.get('admin_user') - # 因为 mixin 中对 fields 有处理,可能不需要返回 admin_user - if admin_user_field: - admin_user_field.queryset = SystemUser.objects.filter(type=SystemUser.Type.admin) - return fields - - @classmethod - def setup_eager_loading(cls, queryset): - """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('domain', 'platform', 'admin_user') - queryset = queryset.prefetch_related('nodes', 'labels') - return queryset - - def compatible_with_old_protocol(self, validated_data): - protocols_data = validated_data.pop("protocols", []) - - # 兼容老的api - name = validated_data.get("protocol") - port = validated_data.get("port") - if not protocols_data and name and port: - protocols_data.insert(0, '/'.join([name, str(port)])) - elif not name and not port and protocols_data: - protocol = protocols_data[0].split('/') - validated_data["protocol"] = protocol[0] - validated_data["port"] = int(protocol[1]) - if protocols_data: - validated_data["protocols"] = ' '.join(protocols_data) - - def perform_nodes_display_create(self, instance, nodes_display): - if not nodes_display: - return - nodes_to_set = [] - for full_value in nodes_display: - node = Node.objects.filter(full_value=full_value).first() - if node: - nodes_to_set.append(node) - else: - node = Node.create_node_by_full_value(full_value) - nodes_to_set.append(node) - instance.nodes.set(nodes_to_set) - - def create(self, validated_data): - self.compatible_with_old_protocol(validated_data) - nodes_display = validated_data.pop('nodes_display', '') - instance = super().create(validated_data) - self.perform_nodes_display_create(instance, nodes_display) - return instance - - def update(self, instance, validated_data): - nodes_display = validated_data.pop('nodes_display', '') - self.compatible_with_old_protocol(validated_data) - instance = super().update(instance, validated_data) - self.perform_nodes_display_create(instance, nodes_display) - return instance - - -class MiniAssetSerializer(serializers.ModelSerializer): - class Meta: - model = Asset - fields = AssetSerializer.Meta.fields_mini - - -class PlatformSerializer(serializers.ModelSerializer): - meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # TODO 修复 drf SlugField RegexValidator bug,之后记得删除 - validators = self.fields['name'].validators - if isinstance(validators[-1], RegexValidator): - validators.pop() - - class Meta: - model = Platform - fields = [ - 'id', 'name', 'base', 'charset', - 'internal', 'meta', 'comment' - ] - extra_kwargs = { - 'internal': {'read_only': True}, - } - - -class AssetSimpleSerializer(serializers.ModelSerializer): - class Meta: - model = Asset - fields = ['id', 'hostname', 'ip', 'port', 'connectivity', 'date_verified'] - - -class AssetsTaskSerializer(serializers.Serializer): - ACTION_CHOICES = ( - ('refresh', 'refresh'), - ('test', 'test'), - ) - task = serializers.CharField(read_only=True) - action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True) - assets = serializers.PrimaryKeyRelatedField( - queryset=Asset.objects, required=False, allow_empty=True, many=True - ) - - -class AssetTaskSerializer(AssetsTaskSerializer): - ACTION_CHOICES = tuple(list(AssetsTaskSerializer.ACTION_CHOICES) + [ - ('push_system_user', 'push_system_user'), - ('test_system_user', 'test_system_user') - ]) - action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True) - asset = serializers.PrimaryKeyRelatedField( - queryset=Asset.objects, required=False, allow_empty=True, many=False - ) - system_users = serializers.PrimaryKeyRelatedField( - queryset=SystemUser.objects, required=False, allow_empty=True, many=True - ) diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py index c6e5732be..93d35b736 100644 --- a/apps/assets/serializers/asset/__init__.py +++ b/apps/assets/serializers/asset/__init__.py @@ -1,2 +1,6 @@ from .common import * -from .category import * +from .host import * +from .database import * +from .networking import * +from .cloud import * +from .web import * diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index e4129bb1a..1b69dc79d 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -1,49 +1,7 @@ -from rest_framework import serializers - -from assets.models import DeviceInfo, Host, Database, Networking, Cloud, Web +from assets.models import Networking from .common import AssetSerializer -__all__ = [ - 'DeviceSerializer', 'HostSerializer', 'DatabaseSerializer', - 'NetworkingSerializer', 'CloudSerializer', 'WebSerializer', -] - - -class DeviceSerializer(serializers.ModelSerializer): - class Meta: - model = DeviceInfo - fields = [ - 'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', - 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', - 'os', 'os_version', 'os_arch', 'hostname_raw', 'number', - 'cpu_info', 'hardware_info', 'date_updated' - ] - - -class HostSerializer(AssetSerializer): - device_info = DeviceSerializer(read_only=True, allow_null=True) - - class Meta(AssetSerializer.Meta): - model = Host - fields = AssetSerializer.Meta.fields + ['device_info'] - - -class DatabaseSerializer(AssetSerializer): - class Meta(AssetSerializer.Meta): - model = Database - fields = AssetSerializer.Meta.fields + ['db_name'] - - -class WebSerializer(AssetSerializer): - class Meta(AssetSerializer.Meta): - model = Web - fields = AssetSerializer.Meta.fields + ['url'] - - -class CloudSerializer(AssetSerializer): - class Meta(AssetSerializer.Meta): - model = Cloud - fields = AssetSerializer.Meta.fields + ['cluster'] +__all__ = ['NetworkingSerializer'] class NetworkingSerializer(AssetSerializer): diff --git a/apps/assets/serializers/asset/cloud.py b/apps/assets/serializers/asset/cloud.py new file mode 100644 index 000000000..38e95bc2c --- /dev/null +++ b/apps/assets/serializers/asset/cloud.py @@ -0,0 +1,11 @@ +from assets.models import Cloud +from .common import AssetSerializer + +__all__ = ['CloudSerializer'] + + +class CloudSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Cloud + fields = AssetSerializer.Meta.fields + ['cluster'] + diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 4a043595e..11c800224 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -6,7 +6,7 @@ from django.db.transaction import atomic from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer -from common.drf.fields import ChoiceDisplayField +from common.drf.fields import LabeledChoiceField, ObjectedRelatedField from ..account import AccountSerializer from ...models import Asset, Node, Platform, Protocol, Label, Domain from ...const import Category, AllTypes @@ -42,33 +42,15 @@ class AssetPlatformSerializer(serializers.ModelSerializer): } -class AssetDomainSerializer(serializers.ModelSerializer): - class Meta: - model = Domain - fields = ['id', 'name'] - extra_kwargs = { - 'name': {'required': False} - } - - -class AssetNodesSerializer(serializers.ModelSerializer): - class Meta: - model = Node - fields = ['id', 'value'] - extra_kwargs = { - 'value': {'required': False} - } - - class AssetSerializer(JMSWritableNestedModelSerializer): - category = ChoiceDisplayField(choices=Category.choices, read_only=True, label=_('Category')) - type = ChoiceDisplayField(choices=AllTypes.choices, read_only=True, label=_('Type')) - domain = AssetDomainSerializer(required=False) - platform = AssetPlatformSerializer(required=False) - labels = AssetLabelSerializer(many=True, required=False) - nodes = AssetNodesSerializer(many=True, required=False) - accounts = AccountSerializer(many=True, required=False) - protocols = AssetProtocolsSerializer(many=True, required=False) + category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) + type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type')) + domain = ObjectedRelatedField(required=False, queryset=Domain.objects, label=_('Domain')) + platform = ObjectedRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) + nodes = ObjectedRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) + labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) + accounts = AccountSerializer(many=True, required=False, label=_('Accounts')) + protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) """ 资产的数据结构 diff --git a/apps/assets/serializers/asset/database.py b/apps/assets/serializers/asset/database.py new file mode 100644 index 000000000..d168d2ffe --- /dev/null +++ b/apps/assets/serializers/asset/database.py @@ -0,0 +1,11 @@ + +from assets.models import Database +from .common import AssetSerializer + +__all__ = ['DatabaseSerializer'] + + +class DatabaseSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Database + fields = AssetSerializer.Meta.fields + ['db_name'] diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py new file mode 100644 index 000000000..62638035d --- /dev/null +++ b/apps/assets/serializers/asset/host.py @@ -0,0 +1,37 @@ +from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ + +from assets.models import Host +from .common import AssetSerializer + + +__all__ = ['HostInfoSerializer', 'HostSerializer'] + + +class HostInfoSerializer(serializers.Serializer): + vendor = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Vendor')) + model = serializers.CharField(max_length=54, required=False, allow_blank=True, label=_('Model')) + sn = serializers.CharField(max_length=128, required=False, allow_blank=True, label=_('Serial number')) + + cpu_model = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU model')) + cpu_count = serializers.IntegerField(required=False, label=_('CPU count')) + cpu_cores = serializers.IntegerField(required=False, label=_('CPU cores')) + cpu_vcpus = serializers.IntegerField(required=False, label=_('CPU vcpus')) + memory = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('Memory')) + disk_total = serializers.CharField(max_length=1024, allow_blank=True, required=False, label=_('Disk total')) + disk_info = serializers.CharField(max_length=1024, allow_blank=True, required=False, label=_('Disk info')) + + os = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('OS')) + os_version = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS version')) + os_arch = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS arch')) + hostname_raw = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Hostname raw')) + number = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Asset number')) + + +class HostSerializer(AssetSerializer): + info = HostInfoSerializer(allow_null=True) + + class Meta(AssetSerializer.Meta): + model = Host + fields = AssetSerializer.Meta.fields + ['info'] + diff --git a/apps/assets/serializers/asset/networking.py b/apps/assets/serializers/asset/networking.py new file mode 100644 index 000000000..aff838bd5 --- /dev/null +++ b/apps/assets/serializers/asset/networking.py @@ -0,0 +1,10 @@ + +from assets.models import Networking +from .common import AssetSerializer + +__all__ = ['NetworkingSerializer'] + + +class NetworkingSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Networking diff --git a/apps/assets/serializers/asset/web.py b/apps/assets/serializers/asset/web.py new file mode 100644 index 000000000..fc726c68e --- /dev/null +++ b/apps/assets/serializers/asset/web.py @@ -0,0 +1,11 @@ + +from assets.models import Web +from .common import AssetSerializer + +__all__ = ['WebSerializer'] + + +class WebSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Web + fields = AssetSerializer.Meta.fields + ['url'] diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 75f24897e..c346a49ec 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -1,7 +1,7 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ -from common.drf.fields import ChoiceDisplayField +from common.drf.fields import LabeledChoiceField from common.drf.serializers import JMSWritableNestedModelSerializer from ..models import Platform, PlatformProtocol from ..const import Category, AllTypes @@ -30,11 +30,11 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer): class PlatformSerializer(JMSWritableNestedModelSerializer): - type = ChoiceDisplayField(choices=AllTypes.choices, label=_("Type")) - category = ChoiceDisplayField(choices=Category.choices, label=_("Category")) + type = LabeledChoiceField(choices=AllTypes.choices, label=_("Type")) + category = LabeledChoiceField(choices=Category.choices, label=_("Category")) protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) type_constraints = serializers.ReadOnlyField(required=False, read_only=True) - su_method = ChoiceDisplayField( + su_method = LabeledChoiceField( choices=[('sudo', 'sudo su -'), ('su', 'su - ')], label='切换方式', required=False, default='sudo' ) @@ -54,6 +54,8 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): ] extra_kwargs = { 'su_enabled': {'label': '启用切换账号'}, + 'domain_enabled': {'label': "启用网域"}, + 'domain_default': {'label': "默认网域"}, 'verify_account_enabled': {'label': '启用校验账号'}, 'verify_account_method': {'label': '校验账号方式'}, 'create_account_enabled': {'label': '启用创建账号'}, diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 30fd6ce05..a61a59b04 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -4,11 +4,13 @@ import six from rest_framework.fields import ChoiceField from rest_framework import serializers +from django.core.exceptions import ObjectDoesNotExist from common.utils import decrypt_password __all__ = [ - 'ReadableHiddenField', 'EncryptedField', 'ChoiceDisplayField' + 'ReadableHiddenField', 'EncryptedField', 'LabeledChoiceField', + 'ObjectedRelatedField', ] @@ -40,9 +42,9 @@ class EncryptedField(serializers.CharField): return decrypt_password(value) -class ChoiceDisplayField(ChoiceField): +class LabeledChoiceField(ChoiceField): def __init__(self, *args, **kwargs): - super(ChoiceDisplayField, self).__init__(*args, **kwargs) + super(LabeledChoiceField, self).__init__(*args, **kwargs) self.choice_mapper = { six.text_type(key): value for key, value in self.choices.items() } @@ -58,4 +60,31 @@ class ChoiceDisplayField(ChoiceField): def to_internal_value(self, data): if isinstance(data, dict): return data.get('value') - return super(ChoiceDisplayField, self).to_internal_value(data) + return super(LabeledChoiceField, self).to_internal_value(data) + + +class ObjectedRelatedField(serializers.RelatedField): + def __init__(self, **kwargs): + self.attrs = kwargs.pop('attrs', None) or ('id', 'name') + super().__init__(**kwargs) + + def to_representation(self, value): + data = {} + for attr in self.attrs: + data[attr] = getattr(value, attr) + return data + + def to_internal_value(self, data): + if isinstance(data, dict): + pk = data.get(self.attrs[0]) + else: + pk = data + queryset = self.get_queryset() + try: + if isinstance(data, bool): + raise TypeError + return queryset.get(pk=pk) + except ObjectDoesNotExist: + self.fail('does_not_exist', pk_value=pk) + except (TypeError, ValueError): + self.fail('incorrect_type', data_type=type(pk).__name__) diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index d1c771ffc..e84731321 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -85,13 +85,13 @@ class SimpleMetadataWithFilters(SimpleMetadata): field_info['choices'] = [ { 'value': choice_value, - 'display_name': force_text(choice_name, strings_only=True) + 'label': force_text(choice_name, strings_only=True) } for choice_value, choice_name in dict(field.choices).items() ] - if field.__class__.__name__ == 'ChoiceDisplayField': - field_info['type'] = 'display_choice' + if field.__class__.__name__ == 'LabeledChoiceField': + field_info['type'] = 'labeled_choice' return field_info def get_filters_fields(self, request, view):