From 3de881fa19d05acd63bf43cdf8ac9777b307a54d Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 2 Apr 2022 18:35:46 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=89=93=E7=AE=97=E9=87=8D=E6=9E=84=20?= =?UTF-8?q?asset=20application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/models/account.py | 7 + apps/applications/models/application.py | 212 +----------------- apps/applications/models/database.py | 13 ++ apps/applications/models/remote_app.py | 0 apps/applications/models/tree.py | 208 +++++++++++++++++ apps/assets/api/__init__.py | 1 + apps/assets/api/asset.py | 32 +-- apps/assets/api/platform.py | 21 ++ .../migrations/0003_auto_20180109_2331.py | 2 +- .../migrations/0007_auto_20180225_1815.py | 2 +- .../migrations/0045_auto_20191206_1607.py | 2 +- apps/assets/migrations/0090_add_host.py | 20 ++ .../migrations/0091_auto_20220401_1558.py | 40 ++++ apps/assets/migrations/0092_hardware.py | 42 ++++ .../migrations/0093_auto_20220403_1627.py | 51 +++++ .../migrations/0094_auto_20220402_1736.py | 69 ++++++ apps/assets/models/__init__.py | 1 + apps/assets/models/asset/__init__.py | 2 + apps/assets/models/asset/cloud.py | 0 .../models/{asset.py => asset/common.py} | 120 ++-------- apps/assets/models/asset/database.py | 9 + apps/assets/models/asset/host.py | 56 +++++ apps/assets/models/asset/network.py | 0 apps/assets/models/asset/remote_app.py | 0 apps/assets/models/platform.py | 56 +++++ apps/assets/serializers/asset/__init__.py | 1 + .../serializers/{asset.py => asset/common.py} | 15 +- apps/assets/serializers/asset/host.py | 19 ++ apps/common/drf/api.py | 14 +- apps/common/mixins/api/common.py | 13 +- apps/xpack.bak | 1 + 31 files changed, 667 insertions(+), 362 deletions(-) create mode 100644 apps/applications/models/database.py create mode 100644 apps/applications/models/remote_app.py create mode 100644 apps/applications/models/tree.py create mode 100644 apps/assets/api/platform.py create mode 100644 apps/assets/migrations/0090_add_host.py create mode 100644 apps/assets/migrations/0091_auto_20220401_1558.py create mode 100644 apps/assets/migrations/0092_hardware.py create mode 100644 apps/assets/migrations/0093_auto_20220403_1627.py create mode 100644 apps/assets/migrations/0094_auto_20220402_1736.py create mode 100644 apps/assets/models/asset/__init__.py create mode 100644 apps/assets/models/asset/cloud.py rename apps/assets/models/{asset.py => asset/common.py} (69%) create mode 100644 apps/assets/models/asset/database.py create mode 100644 apps/assets/models/asset/host.py create mode 100644 apps/assets/models/asset/network.py create mode 100644 apps/assets/models/asset/remote_app.py create mode 100644 apps/assets/models/platform.py create mode 100644 apps/assets/serializers/asset/__init__.py rename apps/assets/serializers/{asset.py => asset/common.py} (91%) create mode 100644 apps/assets/serializers/asset/host.py create mode 160000 apps/xpack.bak diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py index 627dec91a..5d82db36b 100644 --- a/apps/applications/models/account.py +++ b/apps/applications/models/account.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty from assets.models.base import BaseUser +from assets.models import SystemUser class Account(BaseUser): @@ -108,3 +109,9 @@ class Account(BaseUser): def __str__(self): return self.smart_name + + +class ApplicationUser(SystemUser): + class Meta: + proxy = True + verbose_name = _('Application user') diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index aa2d56f70..d1859747c 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -1,216 +1,13 @@ -from collections import defaultdict -from urllib.parse import urlencode, parse_qsl - from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.conf import settings from orgs.mixins.models import OrgModelMixin from common.mixins import CommonModelMixin -from common.tree import TreeNode from common.utils import is_uuid -from assets.models import Asset, SystemUser +from assets.models import Asset -from ..utils import KubernetesTree from .. import const - - -class ApplicationTreeNodeMixin: - id: str - name: str - type: str - category: str - attrs: dict - - @staticmethod - def create_tree_id(pid, type, v): - i = dict(parse_qsl(pid)) - i[type] = v - tree_id = urlencode(i) - return tree_id - - @classmethod - def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None, - show_empty=True, show_count=True): - count = counts.get(c.value, 0) - if count == 0 and not show_empty: - return None - label = c.label - if count is not None and show_count: - label = '{} ({})'.format(label, count) - data = { - 'id': id_, - 'name': label, - 'title': label, - 'pId': pid, - 'isParent': bool(count), - 'open': opened, - 'iconSkin': '', - 'meta': { - 'type': tp, - 'data': { - 'name': c.name, - 'value': c.value - } - } - } - return TreeNode(**data) - - @classmethod - def create_root_tree_node(cls, queryset, show_count=True): - count = queryset.count() if show_count else None - root_id = 'applications' - root_name = _('Applications') - if count is not None and show_count: - root_name = '{} ({})'.format(root_name, count) - node = TreeNode(**{ - 'id': root_id, - 'name': root_name, - 'title': root_name, - 'pId': '', - 'isParent': True, - 'open': True, - 'iconSkin': '', - 'meta': { - 'type': 'applications_root', - } - }) - return node - - @classmethod - def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True): - nodes = [] - categories = const.AppType.category_types_mapper().keys() - for category in categories: - if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category): - continue - i = cls.create_tree_id(pid, 'category', category.value) - node = cls.create_choice_node( - category, i, pid=pid, tp='category', - counts=counts, opened=False, show_empty=show_empty, - show_count=show_count - ) - if not node: - continue - nodes.append(node) - return nodes - - @classmethod - def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True): - nodes = [] - temp_pid = pid - type_category_mapper = const.AppType.type_category_mapper() - types = const.AppType.type_category_mapper().keys() - - for tp in types: - if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp): - continue - category = type_category_mapper.get(tp) - pid = cls.create_tree_id(pid, 'category', category.value) - i = cls.create_tree_id(pid, 'type', tp.value) - node = cls.create_choice_node( - tp, i, pid, tp='type', counts=counts, opened=False, - show_empty=show_empty, show_count=show_count - ) - pid = temp_pid - if not node: - continue - nodes.append(node) - return nodes - - @staticmethod - def get_tree_node_counts(queryset): - counts = defaultdict(int) - values = queryset.values_list('type', 'category') - for i in values: - tp = i[0] - category = i[1] - counts[tp] += 1 - counts[category] += 1 - return counts - - @classmethod - def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True): - counts = cls.get_tree_node_counts(queryset) - tree_nodes = [] - - # 类别的节点 - tree_nodes += cls.create_category_tree_nodes( - pid, counts, show_empty=show_empty, - show_count=show_count - ) - - # 类型的节点 - tree_nodes += cls.create_types_tree_nodes( - pid, counts, show_empty=show_empty, - show_count=show_count - ) - return tree_nodes - - @classmethod - def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True): - tree_nodes = [] - - # 根节点有可能是组织名称 - if root_node is None: - root_node = cls.create_root_tree_node(queryset, show_count=show_count) - tree_nodes.append(root_node) - - tree_nodes += cls.create_category_type_tree_nodes( - queryset, root_node.id, show_empty=show_empty, show_count=show_count - ) - - # 应用的节点 - for app in queryset: - if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type): - continue - node = app.as_tree_node(root_node.id) - tree_nodes.append(node) - return tree_nodes - - def create_app_tree_pid(self, root_id): - pid = self.create_tree_id(root_id, 'category', self.category) - pid = self.create_tree_id(pid, 'type', self.type) - return pid - - def as_tree_node(self, pid, k8s_as_tree=False): - if self.type == const.AppType.k8s and k8s_as_tree: - node = KubernetesTree(pid).as_tree_node(self) - else: - node = self._as_tree_node(pid) - return node - - def _attrs_to_tree(self): - if self.category == const.AppCategory.db: - return self.attrs - return {} - - def _as_tree_node(self, pid): - icon_skin_category_mapper = { - 'remote_app': 'chrome', - 'db': 'database', - 'cloud': 'cloud' - } - icon_skin = icon_skin_category_mapper.get(self.category, 'file') - pid = self.create_app_tree_pid(pid) - node = TreeNode(**{ - 'id': str(self.id), - 'name': self.name, - 'title': self.name, - 'pId': pid, - 'isParent': False, - 'open': False, - 'iconSkin': icon_skin, - 'meta': { - 'type': 'application', - 'data': { - 'category': self.category, - 'type': self.type, - 'attrs': self._attrs_to_tree() - } - } - }) - return node +from .tree import ApplicationTreeNodeMixin class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): @@ -279,8 +76,3 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): if raise_exception: raise ValueError("Remote App not has asset attr") - -class ApplicationUser(SystemUser): - class Meta: - proxy = True - verbose_name = _('Application user') diff --git a/apps/applications/models/database.py b/apps/applications/models/database.py new file mode 100644 index 000000000..f964dd737 --- /dev/null +++ b/apps/applications/models/database.py @@ -0,0 +1,13 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from .application import Application + + +class Database(Application): + host = models.CharField(max_length=1024, verbose_name=_('Host')) + port = models.IntegerField(verbose_name=_("Port")) + database = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_("Database")) + + class Meta: + verbose_name = _("Database") diff --git a/apps/applications/models/remote_app.py b/apps/applications/models/remote_app.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/models/tree.py b/apps/applications/models/tree.py new file mode 100644 index 000000000..1b459d660 --- /dev/null +++ b/apps/applications/models/tree.py @@ -0,0 +1,208 @@ +from collections import defaultdict +from urllib.parse import urlencode, parse_qsl + +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings + +from common.tree import TreeNode +from ..utils import KubernetesTree +from .. import const + + +class ApplicationTreeNodeMixin: + id: str + name: str + type: str + category: str + attrs: dict + + @staticmethod + def create_tree_id(pid, type, v): + i = dict(parse_qsl(pid)) + i[type] = v + tree_id = urlencode(i) + return tree_id + + @classmethod + def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None, + show_empty=True, show_count=True): + count = counts.get(c.value, 0) + if count == 0 and not show_empty: + return None + label = c.label + if count is not None and show_count: + label = '{} ({})'.format(label, count) + data = { + 'id': id_, + 'name': label, + 'title': label, + 'pId': pid, + 'isParent': bool(count), + 'open': opened, + 'iconSkin': '', + 'meta': { + 'type': tp, + 'data': { + 'name': c.name, + 'value': c.value + } + } + } + return TreeNode(**data) + + @classmethod + def create_root_tree_node(cls, queryset, show_count=True): + count = queryset.count() if show_count else None + root_id = 'applications' + root_name = _('Applications') + if count is not None and show_count: + root_name = '{} ({})'.format(root_name, count) + node = TreeNode(**{ + 'id': root_id, + 'name': root_name, + 'title': root_name, + 'pId': '', + 'isParent': True, + 'open': True, + 'iconSkin': '', + 'meta': { + 'type': 'applications_root', + } + }) + return node + + @classmethod + def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True): + nodes = [] + categories = const.AppType.category_types_mapper().keys() + for category in categories: + if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category): + continue + i = cls.create_tree_id(pid, 'category', category.value) + node = cls.create_choice_node( + category, i, pid=pid, tp='category', + counts=counts, opened=False, show_empty=show_empty, + show_count=show_count + ) + if not node: + continue + nodes.append(node) + return nodes + + @classmethod + def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True): + nodes = [] + temp_pid = pid + type_category_mapper = const.AppType.type_category_mapper() + types = const.AppType.type_category_mapper().keys() + + for tp in types: + if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp): + continue + category = type_category_mapper.get(tp) + pid = cls.create_tree_id(pid, 'category', category.value) + i = cls.create_tree_id(pid, 'type', tp.value) + node = cls.create_choice_node( + tp, i, pid, tp='type', counts=counts, opened=False, + show_empty=show_empty, show_count=show_count + ) + pid = temp_pid + if not node: + continue + nodes.append(node) + return nodes + + @staticmethod + def get_tree_node_counts(queryset): + counts = defaultdict(int) + values = queryset.values_list('type', 'category') + for i in values: + tp = i[0] + category = i[1] + counts[tp] += 1 + counts[category] += 1 + return counts + + @classmethod + def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True): + counts = cls.get_tree_node_counts(queryset) + tree_nodes = [] + + # 类别的节点 + tree_nodes += cls.create_category_tree_nodes( + pid, counts, show_empty=show_empty, + show_count=show_count + ) + + # 类型的节点 + tree_nodes += cls.create_types_tree_nodes( + pid, counts, show_empty=show_empty, + show_count=show_count + ) + return tree_nodes + + @classmethod + def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True): + tree_nodes = [] + + # 根节点有可能是组织名称 + if root_node is None: + root_node = cls.create_root_tree_node(queryset, show_count=show_count) + tree_nodes.append(root_node) + + tree_nodes += cls.create_category_type_tree_nodes( + queryset, root_node.id, show_empty=show_empty, show_count=show_count + ) + + # 应用的节点 + for app in queryset: + if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type): + continue + node = app.as_tree_node(root_node.id) + tree_nodes.append(node) + return tree_nodes + + def create_app_tree_pid(self, root_id): + pid = self.create_tree_id(root_id, 'category', self.category) + pid = self.create_tree_id(pid, 'type', self.type) + return pid + + def as_tree_node(self, pid, k8s_as_tree=False): + if self.type == const.AppType.k8s and k8s_as_tree: + node = KubernetesTree(pid).as_tree_node(self) + else: + node = self._as_tree_node(pid) + return node + + def _attrs_to_tree(self): + if self.category == const.AppCategory.db: + return self.attrs + return {} + + def _as_tree_node(self, pid): + icon_skin_category_mapper = { + 'remote_app': 'chrome', + 'db': 'database', + 'cloud': 'cloud' + } + icon_skin = icon_skin_category_mapper.get(self.category, 'file') + pid = self.create_app_tree_pid(pid) + node = TreeNode(**{ + 'id': str(self.id), + 'name': self.name, + 'title': self.name, + 'pId': pid, + 'isParent': False, + 'open': False, + 'iconSkin': icon_skin, + 'meta': { + 'type': 'application', + 'data': { + 'category': self.category, + 'type': self.type, + 'attrs': self._attrs_to_tree() + } + } + }) + return node + diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 7a8ce9a60..0fe3f480b 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -1,4 +1,5 @@ from .mixin import * +from .platform import * from .admin_user import * from .asset import * from .label import * diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 3f4b7a209..b3bfd0047 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from rest_framework.viewsets import ModelViewSet from rest_framework.generics import RetrieveAPIView, ListAPIView +from rest_framework.decorators import action from django.shortcuts import get_object_or_404 from django.db.models import Q @@ -27,8 +27,7 @@ from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFi logger = get_logger(__file__) __all__ = [ 'AssetViewSet', 'AssetPlatformRetrieveApi', - 'AssetGatewayListApi', 'AssetPlatformViewSet', - 'AssetTaskCreateApi', 'AssetsTaskCreateApi', + 'AssetGatewayListApi', 'AssetTaskCreateApi', 'AssetsTaskCreateApi', 'AssetPermUserListApi', 'AssetPermUserPermissionsListApi', 'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi', ] @@ -52,7 +51,8 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) ordering = ('hostname', ) serializer_classes = { 'default': serializers.AssetSerializer, - 'suggestion': serializers.MiniAssetSerializer + 'suggestion': serializers.MiniAssetSerializer, + 'platform': serializers.PlatformSerializer } rbac_perms = { 'match': 'assets.match_asset' @@ -74,6 +74,10 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) assets = serializer.save() self.set_assets_node(assets) + @action(methods='GET', detail=True, url_path='platform') + def platform(self, request, *args, **kwargs): + pass + class AssetPlatformRetrieveApi(RetrieveAPIView): queryset = Platform.objects.all() @@ -88,20 +92,6 @@ class AssetPlatformRetrieveApi(RetrieveAPIView): return asset.platform -class AssetPlatformViewSet(ModelViewSet): - queryset = Platform.objects.all() - serializer_class = serializers.PlatformSerializer - filterset_fields = ['name', 'base'] - search_fields = ['name'] - - def check_object_permissions(self, request, obj): - if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: - self.permission_denied( - request, message={"detail": "Internal platform"} - ) - return super().check_object_permissions(request, obj) - - class AssetsTaskMixin: def perform_assets_task(self, serializer): @@ -246,7 +236,7 @@ class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi): return user_groups -class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView): +class BasePermedAssetListApi(generics.ListAPIView): model = AssetPermission serializer_class = AssetPermissionSerializer filterset_class = AssetPermissionFilter @@ -272,7 +262,7 @@ class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView): return queryset -class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin): +class AssetPermUserPermissionsListApi(BasePermedAssetListApi): def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) queryset = self.filter_user_related(queryset) @@ -291,7 +281,7 @@ class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsLis return user -class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin): +class AssetPermUserGroupPermissionsListApi(BasePermedAssetListApi): def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) queryset = self.filter_user_group_related(queryset) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py new file mode 100644 index 000000000..a7cb798ae --- /dev/null +++ b/apps/assets/api/platform.py @@ -0,0 +1,21 @@ +from rest_framework.viewsets import ModelViewSet + +from assets.models import Platform +from assets.serializers import PlatformSerializer + + +__all__ = ['AssetPlatformViewSet'] + + +class AssetPlatformViewSet(ModelViewSet): + queryset = Platform.objects.all() + serializer_class = PlatformSerializer + filterset_fields = ['name', 'base'] + search_fields = ['name'] + + def check_object_permissions(self, request, obj): + if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: + self.permission_denied( + request, message={"detail": "Internal platform"} + ) + return super().check_object_permissions(request, obj) diff --git a/apps/assets/migrations/0003_auto_20180109_2331.py b/apps/assets/migrations/0003_auto_20180109_2331.py index 254de6236..960032331 100644 --- a/apps/assets/migrations/0003_auto_20180109_2331.py +++ b/apps/assets/migrations/0003_auto_20180109_2331.py @@ -17,6 +17,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='asset', name='cluster', - field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'), + field=models.ForeignKey(default=assets.models.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'), ), ] diff --git a/apps/assets/migrations/0007_auto_20180225_1815.py b/apps/assets/migrations/0007_auto_20180225_1815.py index 009381bcb..4ce2b1e05 100644 --- a/apps/assets/migrations/0007_auto_20180225_1815.py +++ b/apps/assets/migrations/0007_auto_20180225_1815.py @@ -50,7 +50,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='asset', name='nodes', - field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'), + field=models.ManyToManyField(default=assets.models.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'), ), migrations.AddField( model_name='systemuser', diff --git a/apps/assets/migrations/0045_auto_20191206_1607.py b/apps/assets/migrations/0045_auto_20191206_1607.py index f51839289..bf04ad773 100644 --- a/apps/assets/migrations/0045_auto_20191206_1607.py +++ b/apps/assets/migrations/0045_auto_20191206_1607.py @@ -34,7 +34,7 @@ class Migration(migrations.Migration): model_name='asset', name='platform', field=models.ForeignKey( - default=assets.models.asset.Platform.default, + default=assets.models.Platform.default, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.Platform', verbose_name='Platform'), diff --git a/apps/assets/migrations/0090_add_host.py b/apps/assets/migrations/0090_add_host.py new file mode 100644 index 000000000..4ffeb6553 --- /dev/null +++ b/apps/assets/migrations/0090_add_host.py @@ -0,0 +1,20 @@ +# Generated by Django 3.1.14 on 2022-03-30 10:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0089_auto_20220310_0616'), + ] + + operations = [ + migrations.CreateModel( + name='Host', + fields=[ + ('asset_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assets.asset')), + ], + ), + ] diff --git a/apps/assets/migrations/0091_auto_20220401_1558.py b/apps/assets/migrations/0091_auto_20220401_1558.py new file mode 100644 index 000000000..e3275d4d3 --- /dev/null +++ b/apps/assets/migrations/0091_auto_20220401_1558.py @@ -0,0 +1,40 @@ +# Generated by Django 3.1.14 on 2022-04-01 07:58 + +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_to_host(apps, schema_editor): + asset_model = apps.get_model("assets", "Asset") + host_model = apps.get_model("assets", 'Host') + db_alias = schema_editor.connection.alias + + created = 0 + batch_size = 1000 + + while True: + start = created + end = created + batch_size + assets = asset_model.objects.using(db_alias).all()[start:end] + if not assets: + break + + hosts = [host_model(asset_ptr=asset) for asset in assets] + host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True) + created += len(hosts) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0090_add_host'), + ] + + operations = [ + migrations.AlterField( + model_name='host', + name='asset_ptr', + field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset'), + ), + migrations.RunPython(migrate_to_host) + ] diff --git a/apps/assets/migrations/0092_hardware.py b/apps/assets/migrations/0092_hardware.py new file mode 100644 index 000000000..b158363ef --- /dev/null +++ b/apps/assets/migrations/0092_hardware.py @@ -0,0 +1,42 @@ +# Generated by Django 3.1.14 on 2022-04-02 09:09 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0091_auto_20220401_1558'), + ] + + operations = [ + migrations.CreateModel( + name='HostInfo', + fields=[ + ('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')), + ('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')), + ('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')), + ('cpu_model', models.CharField(blank=True, max_length=64, null=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(blank=True, max_length=64, null=True, verbose_name='Memory')), + ('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')), + ('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')), + ('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')), + ('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')), + ('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')), + ('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='info', verbose_name='Host')), + ], + options={ + 'verbose_name': 'HostInfo', + }, + ), + ] diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py new file mode 100644 index 000000000..e01febe9b --- /dev/null +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -0,0 +1,51 @@ +# Generated by Django 3.1.14 on 2022-04-02 08:27 + +from django.utils import timezone +from django.db import migrations + + +def migrate_hardware(apps, *args): + host_model = apps.get_model('assets', 'Host') + asset_model = apps.get_model('assets', 'Asset') + hardware_model = apps.get_model('assets', 'HostInfo') + + created = 0 + batch_size = 1000 + + excludes = ['id', 'host', 'date_updated'] + fields = [f.name for f in hardware_model._meta.fields] + fields = [name for name in fields if name not in excludes] + + while True: + start = created + end = created + batch_size + hosts = host_model.objects.all()[start:end] + asset_ids = [h.asset_ptr_id for h in hosts] + assets = asset_model.objects.filter(id__in=asset_ids) + asset_mapper = {a.id: a for a in assets} + if not hosts: + break + + hardware_list = [] + for host in hosts: + hardware = hardware_model() + asset = asset_mapper[host.asset_ptr_id] + hardware.host = host + hardware.date_updated = timezone.now() + for name in fields: + setattr(hardware, name, getattr(asset, name)) + hardware_list.append(hardware) + + hardware_model.objects.bulk_create(hardware_list, ignore_conflicts=True) + created += len(hardware_list) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0092_hardware'), + ] + + operations = [ + migrations.RunPython(migrate_hardware) + ] diff --git a/apps/assets/migrations/0094_auto_20220402_1736.py b/apps/assets/migrations/0094_auto_20220402_1736.py new file mode 100644 index 000000000..daf53270b --- /dev/null +++ b/apps/assets/migrations/0094_auto_20220402_1736.py @@ -0,0 +1,69 @@ +# Generated by Django 3.1.14 on 2022-04-02 09:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0093_auto_20220403_1627'), + ] + + operations = [ + migrations.RemoveField( + model_name='asset', + name='cpu_cores', + ), + migrations.RemoveField( + model_name='asset', + name='cpu_count', + ), + migrations.RemoveField( + model_name='asset', + name='cpu_model', + ), + migrations.RemoveField( + model_name='asset', + name='cpu_vcpus', + ), + migrations.RemoveField( + model_name='asset', + name='disk_info', + ), + migrations.RemoveField( + model_name='asset', + name='disk_total', + ), + migrations.RemoveField( + model_name='asset', + name='hostname_raw', + ), + migrations.RemoveField( + model_name='asset', + name='memory', + ), + migrations.RemoveField( + model_name='asset', + name='model', + ), + migrations.RemoveField( + model_name='asset', + name='os', + ), + migrations.RemoveField( + model_name='asset', + name='os_arch', + ), + migrations.RemoveField( + model_name='asset', + name='os_version', + ), + migrations.RemoveField( + model_name='asset', + name='sn', + ), + migrations.RemoveField( + model_name='asset', + name='vendor', + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index d2dd03885..b4ea60bb1 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -1,4 +1,5 @@ from .base import * +from .platform import * from .asset import * from .label import Label from .user import * diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py new file mode 100644 index 000000000..51640e7cf --- /dev/null +++ b/apps/assets/models/asset/__init__.py @@ -0,0 +1,2 @@ +from .common import * +from .host import * diff --git a/apps/assets/models/asset/cloud.py b/apps/assets/models/asset/cloud.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset/common.py similarity index 69% rename from apps/assets/models/asset.py rename to apps/assets/models/asset/common.py index c4ecf9cfe..349765283 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset/common.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# +# import uuid import logging @@ -11,18 +11,17 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import ValidationError -from common.fields.model import JsonDictTextField from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, OrgManager +from ..platform import Platform +from ..base import AbsConnectivity -from .base import AbsConnectivity - -__all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet'] +__all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster'] logger = logging.getLogger(__name__) def default_cluster(): - from .cluster import Cluster + from assets.models import Cluster name = "Default" defaults = {"name": name} cluster, created = Cluster.objects.get_or_create( @@ -33,7 +32,7 @@ def default_cluster(): def default_node(): try: - from .node import Node + from assets.models import Node root = Node.org_root() return Node.objects.filter(id=root.id) except: @@ -106,7 +105,7 @@ class NodesRelationMixin: _all_nodes_keys = None def get_nodes(self): - from .node import Node + from assets.models import Node nodes = self.nodes.all() if not nodes: nodes = Node.objects.filter(id=Node.org_root().id) @@ -122,104 +121,25 @@ class NodesRelationMixin: return nodes -class Platform(models.Model): - CHARSET_CHOICES = ( - ('utf8', 'UTF-8'), - ('gbk', 'GBK'), - ) - BASE_CHOICES = ( - ('Linux', 'Linux'), - ('Unix', 'Unix'), - ('MacOS', 'MacOS'), - ('BSD', 'BSD'), - ('Windows', 'Windows'), - ('Other', 'Other'), - ) - name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) - base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base")) - charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) - 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")) - - @classmethod - def default(cls): - linux, created = cls.objects.get_or_create( - defaults={'name': 'Linux'}, name='Linux' - ) - return linux.id - - def is_windows(self): - return self.base.lower() in ('windows',) - - def is_unixlike(self): - return self.base.lower() in ("linux", "unix", "macos", "bsd") - - def __str__(self): - return self.name - - class Meta: - verbose_name = _("Platform") - # ordering = ('name',) - - -class AbsHardwareInfo(models.Model): - # 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')) - - class Meta: - abstract = True - - @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 '' - - -class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): +class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) + ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh, choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols")) - platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') - domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) - nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) + platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, + verbose_name=_("Platform"), related_name='assets') + domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', + verbose_name=_("Domain"), on_delete=models.SET_NULL) + nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', + verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) # Auth - admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, verbose_name=_("Admin user"), related_name='admin_assets') + admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, + verbose_name=_("Admin user"), related_name='admin_assets') # Some information public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP')) @@ -236,7 +156,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin return '{0.hostname}({0.ip})'.format(self) def set_admin_user_relation(self): - from .authbook import AuthBook + from assets.models import AuthBook if not self.admin_user: return if self.admin_user.type != 'admin': @@ -333,7 +253,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin return names def as_node(self): - from .node import Node + from assets.models import Node fake_node = Node() fake_node.id = self.id fake_node.key = self.id @@ -372,7 +292,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin return tree_node def get_all_system_users(self): - from .user import SystemUser + from assets.models import SystemUser system_user_ids = SystemUser.assets.through.objects.filter(asset=self)\ .values_list('systemuser_id', flat=True) system_users = SystemUser.objects.filter(id__in=system_user_ids) diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py new file mode 100644 index 000000000..ff0be89a7 --- /dev/null +++ b/apps/assets/models/asset/database.py @@ -0,0 +1,9 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from .common import Asset + + +class Database(Asset): + database = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) + diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py new file mode 100644 index 000000000..f7533d5fd --- /dev/null +++ b/apps/assets/models/asset/host.py @@ -0,0 +1,56 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from common.mixins.models import CommonModelMixin +from .common import Asset + + +class Host(Asset): + pass + + +class HostInfo(CommonModelMixin): + host = models.OneToOneField(Host, related_name='info', on_delete=models.CASCADE, + verbose_name=_("Host"), unique=True) + # 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')) + + @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.hostname) + + class Meta: + verbose_name = _("HostInfo") diff --git a/apps/assets/models/asset/network.py b/apps/assets/models/asset/network.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/models/asset/remote_app.py b/apps/assets/models/asset/remote_app.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py new file mode 100644 index 000000000..5b8aafe7e --- /dev/null +++ b/apps/assets/models/platform.py @@ -0,0 +1,56 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from common.fields.model import JsonDictTextField + +__all__ = ['Platform'] + + +class Category(models.TextChoices): + Host = 'host', _('Host') + Network = 'network', _('Network device') + Database = 'database', _('Database') + RemoteApp = 'remote_app', _('Microsoft remote app') + Cloud = 'cloud', _("Cloud") + + +class Platform(models.Model): + CHARSET_CHOICES = ( + ('utf8', 'UTF-8'), + ('gbk', 'GBK'), + ) + BASE_CHOICES = ( + ('Linux', 'Linux'), + ('Unix', 'Unix'), + ('MacOS', 'MacOS'), + ('BSD', 'BSD'), + ('Windows', 'Windows'), + ('Other', 'Other'), + ) + name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) + base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base")) + charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) + 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")) + + @classmethod + def default(cls): + linux, created = cls.objects.get_or_create( + defaults={'name': 'Linux'}, name='Linux' + ) + return linux.id + + def is_windows(self): + return self.base.lower() in ('windows',) + + def is_unixlike(self): + return self.base.lower() in ("linux", "unix", "macos", "bsd") + + def __str__(self): + return self.name + + class Meta: + verbose_name = _("Platform") + # ordering = ('name',) + diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py new file mode 100644 index 000000000..55e5f844b --- /dev/null +++ b/apps/assets/serializers/asset/__init__.py @@ -0,0 +1 @@ +from .common import * diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset/common.py similarity index 91% rename from apps/assets/serializers/asset.py rename to apps/assets/serializers/asset/common.py index 427d0e470..ead6ca1f6 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset/common.py @@ -5,7 +5,7 @@ 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 +from ...models import Asset, Node, Platform, SystemUser __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', @@ -82,12 +82,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer): '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' ] @@ -95,16 +89,13 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'nodes', 'nodes_display', 'labels', 'labels_display', ] read_only_fields = [ - 'connectivity', 'date_verified', 'cpu_info', 'hardware_info', - 'created_by', 'date_created', + 'connectivity', 'date_verified', 'created_by', 'date_created', ] - fields = fields_small + fields_hardware + fields_fk + fields_m2m + read_only_fields + fields = fields_small + 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): diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py new file mode 100644 index 000000000..1b40426cc --- /dev/null +++ b/apps/assets/serializers/asset/host.py @@ -0,0 +1,19 @@ +from rest_framework import serializers + +from .common import AssetSerializer +from assets.models import HostInfo + + +class HardwareSerializer(serializers.ModelSerializer): + class Meta: + model = HostInfo + 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', + 'cpu_info', 'hardware_info', 'date_updated' + ] + + +class HostSerializer(AssetSerializer): + hardware_info = HardwareSerializer(read_only=True) diff --git a/apps/common/drf/api.py b/apps/common/drf/api.py index 073e9fd7e..23567aa32 100644 --- a/apps/common/drf/api.py +++ b/apps/common/drf/api.py @@ -2,31 +2,31 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelV from rest_framework_bulk import BulkModelViewSet from ..mixins.api import ( - RelationMixin, AllowBulkDestroyMixin, CommonMixin + RelationMixin, AllowBulkDestroyMixin, CommonApiMixin ) -class JMSGenericViewSet(CommonMixin, GenericViewSet): +class JMSGenericViewSet(CommonApiMixin, GenericViewSet): pass -class JMSViewSet(CommonMixin, ViewSet): +class JMSViewSet(CommonApiMixin, ViewSet): pass -class JMSModelViewSet(CommonMixin, ModelViewSet): +class JMSModelViewSet(CommonApiMixin, ModelViewSet): pass -class JMSReadOnlyModelViewSet(CommonMixin, ReadOnlyModelViewSet): +class JMSReadOnlyModelViewSet(CommonApiMixin, ReadOnlyModelViewSet): pass -class JMSBulkModelViewSet(CommonMixin, AllowBulkDestroyMixin, BulkModelViewSet): +class JMSBulkModelViewSet(CommonApiMixin, AllowBulkDestroyMixin, BulkModelViewSet): pass -class JMSBulkRelationModelViewSet(CommonMixin, +class JMSBulkRelationModelViewSet(CommonApiMixin, RelationMixin, AllowBulkDestroyMixin, BulkModelViewSet): diff --git a/apps/common/mixins/api/common.py b/apps/common/mixins/api/common.py index 8dbf4fb1e..3c59739ad 100644 --- a/apps/common/mixins/api/common.py +++ b/apps/common/mixins/api/common.py @@ -13,7 +13,7 @@ from .queryset import QuerySetMixin __all__ = [ - 'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', 'CommonMixin' + 'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', ] @@ -82,15 +82,10 @@ class RelationMixin: self.send_m2m_changed_signal(instance, 'post_remove') -class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, RenderToJsonMixin): - pass - - -class CommonMixin(SerializerMixin, - QuerySetMixin, - ExtraFilterFieldsMixin, - RenderToJsonMixin): +class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, + QuerySetMixin, RenderToJsonMixin): pass + diff --git a/apps/xpack.bak b/apps/xpack.bak new file mode 160000 index 000000000..244ace5a9 --- /dev/null +++ b/apps/xpack.bak @@ -0,0 +1 @@ +Subproject commit 244ace5a95503ffaf41b73037692e1121f7c066f