diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py
index 83b1d490a..79beef8fc 100644
--- a/apps/applications/api/remote_app.py
+++ b/apps/applications/api/remote_app.py
@@ -1,10 +1,8 @@
# coding: utf-8
#
-
-from rest_framework import generics
-
from orgs.mixins.api import OrgBulkModelViewSet
+from orgs.mixins import generics
from ..hands import IsOrgAdmin, IsAppUser
from ..models import RemoteApp
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
@@ -16,14 +14,14 @@ __all__ = [
class RemoteAppViewSet(OrgBulkModelViewSet):
+ model = RemoteApp
filter_fields = ('name',)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
- queryset = RemoteApp.objects.all()
serializer_class = RemoteAppSerializer
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
- queryset = RemoteApp.objects.all()
+ model = RemoteApp
permission_classes = (IsAppUser, )
serializer_class = RemoteAppConnectionInfoSerializer
diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py
index a4404e290..e7126878d 100644
--- a/apps/assets/api/__init__.py
+++ b/apps/assets/api/__init__.py
@@ -7,3 +7,4 @@ from .domain import *
from .cmd_filter import *
from .asset_user import *
from .gathered_user import *
+from .favorite_asset import *
diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py
index fd10e6129..b91f10c65 100644
--- a/apps/assets/api/admin_user.py
+++ b/apps/assets/api/admin_user.py
@@ -15,11 +15,10 @@
from django.db import transaction
from django.shortcuts import get_object_or_404
-from rest_framework import generics
from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet
+from orgs.mixins import generics
-from common.mixins import CommonApiMixin
from common.utils import get_logger
from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
@@ -39,22 +38,21 @@ class AdminUserViewSet(OrgBulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""
-
+ model = AdminUser
filter_fields = ("name", "username")
search_fields = filter_fields
- queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,)
class AdminUserAuthApi(generics.UpdateAPIView):
- queryset = AdminUser.objects.all()
+ model = AdminUser
serializer_class = serializers.AdminUserAuthSerializer
permission_classes = (IsOrgAdmin,)
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
- queryset = AdminUser.objects.all()
+ model = AdminUser
serializer_class = serializers.ReplaceNodeAdminUserSerializer
permission_classes = (IsOrgAdmin,)
@@ -79,7 +77,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset admin user assets_connectivity
"""
- queryset = AdminUser.objects.all()
+ model = AdminUser
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py
index 39341328b..64fdc16dc 100644
--- a/apps/assets/api/asset.py
+++ b/apps/assets/api/asset.py
@@ -3,14 +3,14 @@
import random
-from rest_framework import generics
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins.api import OrgBulkModelViewSet
-from ..models import Asset, AdminUser, Node
+from orgs.mixins import generics
+from ..models import Asset, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectivity_manual
@@ -29,10 +29,10 @@ class AssetViewSet(OrgBulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
+ model = Asset
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
search_fields = ("hostname", "ip")
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
- queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsOrgAdminOrAppUser,)
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
@@ -57,7 +57,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
Refresh asset hardware info
"""
- queryset = Asset.objects.all()
+ model = Asset
serializer_class = serializers.AssetSerializer
permission_classes = (IsOrgAdmin,)
@@ -72,7 +72,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
"""
Test asset admin user assets_connectivity
"""
- queryset = Asset.objects.all()
+ model = Asset
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
@@ -84,9 +84,9 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
class AssetGatewayApi(generics.RetrieveAPIView):
- queryset = Asset.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GatewayWithAuthSerializer
+ model = Asset
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
@@ -98,4 +98,4 @@ class AssetGatewayApi(generics.RetrieveAPIView):
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
return Response(serializer.data)
else:
- return Response({"msg": "Not have gateway"}, status=404)
\ No newline at end of file
+ return Response({"msg": "Not have gateway"}, status=404)
diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py
index 846172548..b4ae67c72 100644
--- a/apps/assets/api/cmd_filter.py
+++ b/apps/assets/api/cmd_filter.py
@@ -13,14 +13,15 @@ __all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
class CommandFilterViewSet(OrgBulkModelViewSet):
+ model = CommandFilter
filter_fields = ("name",)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
- queryset = CommandFilter.objects.all()
serializer_class = serializers.CommandFilterSerializer
class CommandFilterRuleViewSet(OrgBulkModelViewSet):
+ model = CommandFilterRule
filter_fields = ("content",)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py
index 03c4c2307..13be62315 100644
--- a/apps/assets/api/domain.py
+++ b/apps/assets/api/domain.py
@@ -15,7 +15,7 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(OrgBulkModelViewSet):
- queryset = Domain.objects.all()
+ model = Domain
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.DomainSerializer
@@ -26,16 +26,15 @@ class DomainViewSet(OrgBulkModelViewSet):
class GatewayViewSet(OrgBulkModelViewSet):
+ model = Gateway
filter_fields = ("domain__name", "name", "username", "ip", "domain")
search_fields = filter_fields
- queryset = Gateway.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsOrgAdmin,)
- model = Gateway
object = None
def post(self, request, *args, **kwargs):
diff --git a/apps/assets/api/favorite_asset.py b/apps/assets/api/favorite_asset.py
new file mode 100644
index 000000000..174c77330
--- /dev/null
+++ b/apps/assets/api/favorite_asset.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+from rest_framework_bulk import BulkModelViewSet
+
+from common.permissions import IsValidUser
+from orgs.utils import tmp_to_root_org
+from ..models import FavoriteAsset
+from ..serializers import FavoriteAssetSerializer
+
+__all__ = ['FavoriteAssetViewSet']
+
+
+class FavoriteAssetViewSet(BulkModelViewSet):
+ serializer_class = FavoriteAssetSerializer
+ permission_classes = (IsValidUser,)
+ filter_fields = ['asset']
+
+ def dispatch(self, request, *args, **kwargs):
+ with tmp_to_root_org():
+ return super().dispatch(request, *args, **kwargs)
+
+ def get_queryset(self):
+ queryset = FavoriteAsset.objects.filter(user=self.request.user)
+ return queryset
+
+ def allow_bulk_destroy(self, qs, filtered):
+ return filtered.count() == 1
diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py
index 038d47208..b9f137648 100644
--- a/apps/assets/api/gathered_user.py
+++ b/apps/assets/api/gathered_user.py
@@ -13,12 +13,10 @@ __all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet):
- queryset = GatheredUser.objects.all()
+ model = GatheredUser
serializer_class = GatheredUserSerializer
permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filter_fields = ['asset', 'username', 'present']
search_fields = ['username', 'asset__ip', 'asset__hostname']
-
-
diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py
index c3b798959..fe298169a 100644
--- a/apps/assets/api/label.py
+++ b/apps/assets/api/label.py
@@ -27,6 +27,7 @@ __all__ = ['LabelViewSet']
class LabelViewSet(OrgBulkModelViewSet):
+ model = Label
filter_fields = ("name", "value")
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py
index a48d5f00d..1451650d2 100644
--- a/apps/assets/api/node.py
+++ b/apps/assets/api/node.py
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from rest_framework import generics, status
+from rest_framework import status
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework.response import Response
@@ -23,9 +23,12 @@ from django.shortcuts import get_object_or_404
from common.utils import get_logger, get_object_or_none
from common.tree import TreeNodeSerializer
from orgs.mixins.api import OrgModelViewSet
+from orgs.mixins import generics
from ..hands import IsOrgAdmin
from ..models import Node
-from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
+from ..tasks import (
+ update_assets_hardware_info_util, test_asset_connectivity_util
+)
from .. import serializers
@@ -40,9 +43,9 @@ __all__ = [
class NodeViewSet(OrgModelViewSet):
+ model = Node
filter_fields = ('value', 'key', 'id')
search_fields = ('value', )
- queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
@@ -79,6 +82,7 @@ class NodeListAsTreeApi(generics.ListAPIView):
}
]
"""
+ model = Node
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer
@@ -87,10 +91,6 @@ class NodeListAsTreeApi(generics.ListAPIView):
queryset = [node.as_tree_node() for node in queryset]
return queryset
- def get_queryset(self):
- queryset = Node.objects.all()
- return queryset
-
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.to_tree_queryset(queryset)
@@ -98,7 +98,6 @@ class NodeListAsTreeApi(generics.ListAPIView):
class NodeChildrenApi(generics.ListCreateAPIView):
- queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
instance = None
@@ -162,6 +161,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi):
]
"""
+ model = Node
serializer_class = TreeNodeSerializer
http_method_names = ['get']
@@ -204,7 +204,7 @@ class NodeAssetsApi(generics.ListAPIView):
class NodeAddChildrenApi(generics.UpdateAPIView):
- queryset = Node.objects.all()
+ model = Node
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer
instance = None
@@ -221,8 +221,8 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
class NodeAddAssetsApi(generics.UpdateAPIView):
+ model = Node
serializer_class = serializers.NodeAssetsSerializer
- queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
instance = None
@@ -233,8 +233,8 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
class NodeRemoveAssetsApi(generics.UpdateAPIView):
+ model = Node
serializer_class = serializers.NodeAssetsSerializer
- queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
instance = None
@@ -249,8 +249,8 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
class NodeReplaceAssetsApi(generics.UpdateAPIView):
+ model = Node
serializer_class = serializers.NodeAssetsSerializer
- queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
instance = None
@@ -262,8 +262,8 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView):
class RefreshNodeHardwareInfoApi(APIView):
- permission_classes = (IsOrgAdmin,)
model = Node
+ permission_classes = (IsOrgAdmin,)
def get(self, request, *args, **kwargs):
node_id = kwargs.get('pk')
diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py
index 9b87ed9aa..5bf38853d 100644
--- a/apps/assets/api/system_user.py
+++ b/apps/assets/api/system_user.py
@@ -14,13 +14,13 @@
# limitations under the License.
from django.shortcuts import get_object_or_404
-from rest_framework import generics
from rest_framework.response import Response
from common.serializers import CeleryTaskSerializer
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins.api import OrgBulkModelViewSet
+from orgs.mixins import generics
from ..models import SystemUser, Asset
from .. import serializers
from ..tasks import (
@@ -43,22 +43,18 @@ class SystemUserViewSet(OrgBulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
+ model = SystemUser
filter_fields = ("name", "username")
search_fields = filter_fields
- queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer
permission_classes = (IsOrgAdminOrAppUser,)
- def get_queryset(self):
- queryset = super().get_queryset().all()
- return queryset
-
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
"""
Get system user auth info
"""
- queryset = SystemUser.objects.all()
+ model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
@@ -72,7 +68,7 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
"""
Get system user with asset auth info
"""
- queryset = SystemUser.objects.all()
+ model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
@@ -88,7 +84,7 @@ class SystemUserPushApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
- queryset = SystemUser.objects.all()
+ model = SystemUser
permission_classes = (IsOrgAdmin,)
serializer_class = CeleryTaskSerializer
@@ -105,7 +101,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
- queryset = SystemUser.objects.all()
+ model = SystemUser
permission_classes = (IsOrgAdmin,)
serializer_class = CeleryTaskSerializer
@@ -132,7 +128,7 @@ class SystemUserAssetsListView(generics.ListAPIView):
class SystemUserPushToAssetApi(generics.RetrieveAPIView):
- queryset = SystemUser.objects.all()
+ model = SystemUser
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
@@ -145,7 +141,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView):
class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
- queryset = SystemUser.objects.all()
+ model = SystemUser
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py
index d3d64d602..b6e5d3a42 100644
--- a/apps/assets/forms/asset.py
+++ b/apps/assets/forms/asset.py
@@ -129,7 +129,7 @@ class AssetUpdateForm(OrgModelForm):
class AssetBulkUpdateForm(OrgModelForm):
assets = forms.ModelMultipleChoiceField(
required=True,
- label=_('Select assets'), queryset=Asset.objects.all(),
+ label=_('Select assets'), queryset=Asset.objects,
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
@@ -155,11 +155,18 @@ class AssetBulkUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ self.set_fields_queryset()
+
# 重写其他字段为不再required
for name, field in self.fields.items():
if name != 'assets':
field.required = False
+ def set_fields_queryset(self):
+ assets_field = self.fields['assets']
+ if hasattr(self, 'data'):
+ assets_field.queryset = Asset.objects.all()
+
def save(self, commit=True):
changed_fields = []
for field in self._meta.fields:
diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py
index 496147a99..71d9f6cfa 100644
--- a/apps/assets/forms/domain.py
+++ b/apps/assets/forms/domain.py
@@ -12,7 +12,7 @@ __all__ = ['DomainForm', 'GatewayForm']
class DomainForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField(
- queryset=Asset.objects.all(), label=_('Asset'), required=False,
+ queryset=Asset.objects, label=_('Asset'), required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
)
@@ -23,19 +23,23 @@ class DomainForm(forms.ModelForm):
fields = ['name', 'comment', 'assets']
def __init__(self, *args, **kwargs):
- if kwargs.get('instance', None):
- initial = kwargs.get('initial', {})
- initial['assets'] = kwargs['instance'].assets.all()
super().__init__(*args, **kwargs)
+ self.set_fields_queryset()
- # 前端渲染优化, 防止过多资产
+ def set_fields_queryset(self):
assets_field = self.fields.get('assets')
+
+ # 没有data代表是渲染表单, 有data代表是提交创建/更新表单
if not self.data:
- instance = kwargs.get('instance')
- if instance:
- assets_field.queryset = instance.assets.all()
+ # 有instance 代表渲染更新表单, 否则是创建表单
+ # 前端渲染优化, 防止过多资产, 设置assets queryset为none
+ if self.instance:
+ assets_field.initial = self.instance.assets.all()
+ assets_field.queryset = self.instance.assets.all()
else:
assets_field.queryset = Asset.objects.none()
+ else:
+ assets_field.queryset = Asset.objects.all()
def save(self, commit=True):
instance = super().save(commit=commit)
diff --git a/apps/assets/forms/label.py b/apps/assets/forms/label.py
index 8a5a54e4a..1f2f13987 100644
--- a/apps/assets/forms/label.py
+++ b/apps/assets/forms/label.py
@@ -10,7 +10,7 @@ __all__ = ['LabelForm']
class LabelForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField(
- queryset=Asset.objects.all(), label=_('Asset'), required=False,
+ queryset=Asset.objects.none(), label=_('Asset'), required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
)
@@ -21,19 +21,23 @@ class LabelForm(forms.ModelForm):
fields = ['name', 'value', 'assets']
def __init__(self, *args, **kwargs):
- if kwargs.get('instance', None):
- initial = kwargs.get('initial', {})
- initial['assets'] = kwargs['instance'].assets.all()
super().__init__(*args, **kwargs)
+ self.set_fields_queryset()
- # 前端渲染优化, 防止过多资产
+ def set_fields_queryset(self):
assets_field = self.fields.get('assets')
+
+ # 没有data代表是渲染表单, 有data代表是提交创建/更新表单
if not self.data:
- instance = kwargs.get('instance')
- if instance:
- assets_field.queryset = instance.assets.all()
+ # 有instance 代表渲染更新表单, 否则是创建表单
+ # 前端渲染优化, 防止过多资产, 设置assets queryset为none
+ if self.instance:
+ assets_field.initial = self.instance.assets.all()
+ assets_field.queryset = self.instance.assets.all()
else:
assets_field.queryset = Asset.objects.none()
+ else:
+ assets_field.queryset = Asset.objects.all()
def save(self, commit=True):
label = super().save(commit=commit)
diff --git a/apps/assets/migrations/0042_favoriteasset.py b/apps/assets/migrations/0042_favoriteasset.py
new file mode 100644
index 000000000..3baebee74
--- /dev/null
+++ b/apps/assets/migrations/0042_favoriteasset.py
@@ -0,0 +1,31 @@
+# Generated by Django 2.2.5 on 2019-10-16 08:38
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('assets', '0041_gathereduser'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FavoriteAsset',
+ fields=[
+ ('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')),
+ ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'unique_together': {('user', 'asset')},
+ },
+ ),
+ ]
diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py
index c69f19bf6..db9c54aed 100644
--- a/apps/assets/models/__init__.py
+++ b/apps/assets/models/__init__.py
@@ -10,3 +10,4 @@ from .authbook import *
from .utils import *
from .authbook import *
from .gathered_user import *
+from .favorite_asset import *
diff --git a/apps/assets/models/favorite_asset.py b/apps/assets/models/favorite_asset.py
new file mode 100644
index 000000000..3abc69c8c
--- /dev/null
+++ b/apps/assets/models/favorite_asset.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+#
+from django.db import models
+
+from common.mixins.models import CommonModelMixin
+
+
+__all__ = ['FavoriteAsset']
+
+
+class FavoriteAsset(CommonModelMixin):
+ user = models.ForeignKey('users.User', on_delete=models.CASCADE)
+ asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE)
+
+ class Meta:
+ unique_together = ('user', 'asset')
+
+ @classmethod
+ def get_user_favorite_assets_id(cls, user):
+ return cls.objects.filter(user=user).values_list('asset', flat=True)
diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py
index 71e1d4a24..dd1f706f2 100644
--- a/apps/assets/models/node.py
+++ b/apps/assets/models/node.py
@@ -324,6 +324,8 @@ class SomeNodesMixin:
ungrouped_value = _('ungrouped')
empty_key = '-11'
empty_value = _("empty")
+ favorite_key = '-12'
+ favorite_value = _("favorite")
def is_default_node(self):
return self.key == self.default_key
@@ -363,7 +365,7 @@ class SomeNodesMixin:
@classmethod
def ungrouped_node(cls):
with tmp_to_org(Organization.system()):
- defaults = {'value': cls.ungrouped_key}
+ defaults = {'value': cls.ungrouped_value}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.ungrouped_key
)
@@ -387,11 +389,21 @@ class SomeNodesMixin:
)
return obj
+ @classmethod
+ def favorite_node(cls):
+ with tmp_to_org(Organization.system()):
+ defaults = {'value': cls.favorite_value}
+ obj, created = cls.objects.get_or_create(
+ defaults=defaults, key=cls.favorite_key
+ )
+ return obj
+
@classmethod
def initial_some_nodes(cls):
cls.default_node()
cls.empty_node()
cls.ungrouped_node()
+ cls.favorite_node()
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
@@ -412,11 +424,11 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin
def __str__(self):
return self.value
- def __eq__(self, other):
- if not other:
- return False
- return self.id == other.id
-
+ # def __eq__(self, other):
+ # if not other:
+ # return False
+ # return self.id == other.id
+ #
def __gt__(self, other):
self_key = [int(k) for k in self.key.split(':')]
other_key = [int(k) for k in other.key.split(':')]
diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py
index 9c86ef407..2c3e9fbd4 100644
--- a/apps/assets/serializers/__init__.py
+++ b/apps/assets/serializers/__init__.py
@@ -10,3 +10,4 @@ from .domain import *
from .cmd_filter import *
from .asset_user import *
from .gathered_user import *
+from .favorite_asset import *
diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py
index 918f1d9bf..63aac8cc0 100644
--- a/apps/assets/serializers/admin_user.py
+++ b/apps/assets/serializers/admin_user.py
@@ -45,7 +45,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
管理用户更新关联到的集群
"""
nodes = serializers.PrimaryKeyRelatedField(
- many=True, queryset=Node.objects.all()
+ many=True, queryset=Node.objects
)
class Meta:
diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py
index a93d2b2c3..18b5ea982 100644
--- a/apps/assets/serializers/asset_user.py
+++ b/apps/assets/serializers/asset_user.py
@@ -79,7 +79,7 @@ class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class AssetUserPushSerializer(serializers.Serializer):
- asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset"))
+ asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, label=_("Asset"))
username = serializers.CharField(max_length=1024)
def create(self, validated_data):
diff --git a/apps/assets/serializers/favorite_asset.py b/apps/assets/serializers/favorite_asset.py
new file mode 100644
index 000000000..8429d959e
--- /dev/null
+++ b/apps/assets/serializers/favorite_asset.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+
+from rest_framework import serializers
+
+from orgs.utils import tmp_to_root_org
+from common.serializers import AdaptedBulkListSerializer
+from common.mixins import BulkSerializerMixin
+from ..models import FavoriteAsset
+
+
+__all__ = ['FavoriteAssetSerializer']
+
+
+class FavoriteAssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
+ user = serializers.HiddenField(
+ default=serializers.CurrentUserDefault()
+ )
+
+ class Meta:
+ list_serializer_class = AdaptedBulkListSerializer
+ model = FavoriteAsset
+ fields = ['user', 'asset']
diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py
index 10a7a52d1..79e07df13 100644
--- a/apps/assets/serializers/node.py
+++ b/apps/assets/serializers/node.py
@@ -38,8 +38,10 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
return data
-class NodeAssetsSerializer(serializers.ModelSerializer):
- assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
+class NodeAssetsSerializer(BulkOrgResourceModelSerializer):
+ assets = serializers.PrimaryKeyRelatedField(
+ many=True, queryset=Asset.objects
+ )
class Meta:
model = Node
diff --git a/apps/assets/templates/assets/asset_bulk_update.html b/apps/assets/templates/assets/asset_bulk_update.html
index 2dd0d8660..08df48e91 100644
--- a/apps/assets/templates/assets/asset_bulk_update.html
+++ b/apps/assets/templates/assets/asset_bulk_update.html
@@ -25,12 +25,20 @@
+{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
{% endblock %}
diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py
index 46ea16483..35651429b 100644
--- a/apps/assets/urls/api_urls.py
+++ b/apps/assets/urls/api_urls.py
@@ -22,6 +22,7 @@ router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter')
router.register(r'asset-users', api.AssetUserViewSet, 'asset-user')
router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
+router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
diff --git a/apps/audits/api.py b/apps/audits/api.py
index 626749b29..4d7165b4b 100644
--- a/apps/audits/api.py
+++ b/apps/audits/api.py
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
#
-from rest_framework import viewsets
-
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor
+from orgs.mixins.api import OrgModelViewSet
from .models import FTPLog
from .serializers import FTPLogSerializer
-class FTPLogViewSet(viewsets.ModelViewSet):
- queryset = FTPLog.objects.all()
+class FTPLogViewSet(OrgModelViewSet):
+ model = FTPLog
serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,)
+
diff --git a/apps/common/api.py b/apps/common/api.py
index 4c5d28254..d69540cfd 100644
--- a/apps/common/api.py
+++ b/apps/common/api.py
@@ -83,8 +83,6 @@ class LogTailApi(generics.RetrieveAPIView):
return Response({"data": data, 'end': end, 'mark': new_mark})
-
-
class ResourcesIDCacheApi(APIView):
def post(self, request, *args, **kwargs):
spm = str(uuid.uuid4())
diff --git a/apps/common/mixins/models.py b/apps/common/mixins/models.py
index d4af23896..df3d899e0 100644
--- a/apps/common/mixins/models.py
+++ b/apps/common/mixins/models.py
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
#
-
+import uuid
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
-__all__ = ["NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet"]
+__all__ = [
+ "NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet",
+ "CommonModelMixin"
+]
class NoDeleteQuerySet(models.query.QuerySet):
@@ -40,3 +43,13 @@ class NoDeleteModelMixin(models.Model):
self.is_discard = True
self.discard_time = timezone.now()
return self.save()
+
+
+class CommonModelMixin(models.Model):
+ id = models.UUIDField(default=uuid.uuid4, primary_key=True)
+ created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
+ date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
+ date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated'))
+
+ class Meta:
+ abstract = True
diff --git a/apps/common/signals_handlers.py b/apps/common/signals_handlers.py
index e7dd728c3..1fbfd536f 100644
--- a/apps/common/signals_handlers.py
+++ b/apps/common/signals_handlers.py
@@ -36,7 +36,7 @@ def on_request_finished_logging_db_query(sender, **kwargs):
queries = connection.queries
counters = defaultdict(Counter)
for query in queries:
- if not query['sql'].startswith('SELECT'):
+ if not query['sql'] or not query['sql'].startswith('SELECT'):
continue
tables = pattern.findall(query['sql'])
table_name = ''.join(tables)
diff --git a/apps/common/tree.py b/apps/common/tree.py
index e73b43aa6..a9da1482f 100644
--- a/apps/common/tree.py
+++ b/apps/common/tree.py
@@ -51,6 +51,8 @@ class TreeNode:
result = True
elif self.pId != other.pId:
result = self.pId > other.pId
+ elif self.id.startswith('-') and not other.id.startswith('-'):
+ result = False
else:
result = self.name > other.name
return result
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index a4ec9786d..62707d108 100644
Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 79c7d4faa..f282f0c22 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/zh/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-10-15 17:37+0800\n"
+"POT-Creation-Date: 2019-10-17 16:09+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler \n"
"Language-Team: Jumpserver team\n"
@@ -193,11 +193,11 @@ msgstr "参数"
#: assets/templates/assets/cmd_filter_detail.html:77
#: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100
-#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:16
-#: perms/models/base.py:54
+#: common/mixins/models.py:50 ops/templates/ops/adhoc_detail.html:86
+#: orgs/models.py:16 perms/models/base.py:54
#: perms/templates/perms/asset_permission_detail.html:98
#: perms/templates/perms/remote_app_permission_detail.html:90
-#: users/models/user.py:414 users/serializers/v1.py:141
+#: users/models/user.py:414 users/serializers/v1.py:143
#: users/templates/users/user_detail.html:111
#: xpack/plugins/change_auth_plan/models.py:108
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
@@ -216,7 +216,8 @@ msgstr "创建者"
#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/cmd_filter_detail.html:69
#: assets/templates/assets/domain_detail.html:68
-#: assets/templates/assets/system_user_detail.html:96 ops/models/adhoc.py:45
+#: assets/templates/assets/system_user_detail.html:96
+#: common/mixins/models.py:51 ops/models/adhoc.py:45
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64
#: orgs/models.py:17 perms/models/base.py:55
#: perms/templates/perms/asset_permission_detail.html:94
@@ -321,7 +322,6 @@ msgstr "远程应用"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:53
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:44
#: xpack/plugins/interface/templates/interface/interface.html:72
-#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
#: xpack/plugins/vault/templates/vault/vault_create.html:45
msgid "Reset"
msgstr "重置"
@@ -524,7 +524,7 @@ msgstr "创建远程应用"
#: settings/templates/settings/terminal_setting.html:107
#: terminal/templates/terminal/session_list.html:36
#: terminal/templates/terminal/terminal_list.html:36
-#: users/templates/users/_granted_assets.html:29
+#: users/templates/users/_granted_assets.html:34
#: users/templates/users/user_group_list.html:38
#: users/templates/users/user_list.html:41
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60
@@ -539,7 +539,6 @@ msgid "Action"
msgstr "动作"
#: applications/templates/applications/user_remote_app_list.html:52
-#: assets/templates/assets/user_asset_list.html:32
#: perms/models/asset_permission.py:32
msgid "Connect"
msgstr "连接"
@@ -566,11 +565,11 @@ msgstr "远程应用详情"
msgid "My RemoteApp"
msgstr "我的远程应用"
-#: assets/api/node.py:58
+#: assets/api/node.py:61
msgid "You can't update the root node name"
msgstr "不能修改根节点名称"
-#: assets/api/node.py:65
+#: assets/api/node.py:68
msgid "Deletion failed and the node contains children or assets"
msgstr "删除失败,节点包含子节点或资产"
@@ -628,13 +627,13 @@ msgstr "标签"
#: assets/forms/asset.py:65 assets/forms/asset.py:112
#: assets/models/asset.py:144 assets/models/domain.py:26
#: assets/models/domain.py:52 assets/templates/assets/asset_detail.html:78
-#: assets/templates/assets/user_asset_list.html:53
+#: assets/templates/assets/user_asset_list.html:80
#: xpack/plugins/orgs/templates/orgs/org_list.html:18
msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:69 assets/forms/asset.py:103 assets/forms/asset.py:116
-#: assets/forms/asset.py:152 assets/models/node.py:409
+#: assets/forms/asset.py:152 assets/models/node.py:421
#: assets/templates/assets/asset_create.html:42
#: perms/forms/asset_permission.py:83 perms/forms/asset_permission.py:90
#: perms/templates/perms/asset_permission_list.html:53
@@ -702,7 +701,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70
#: perms/templates/perms/asset_permission_user.html:55
#: perms/templates/perms/remote_app_permission_user.html:54
-#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:14
+#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13
#: users/models/user.py:371 users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:36
@@ -729,7 +728,7 @@ msgstr "密码或密钥密码"
#: authentication/forms.py:15
#: authentication/templates/authentication/login.html:68
#: authentication/templates/authentication/new_login.html:95
-#: settings/forms.py:114 users/forms.py:16 users/forms.py:28
+#: settings/forms.py:114 users/forms.py:15 users/forms.py:27
#: users/templates/users/reset_password.html:53
#: users/templates/users/user_password_authentication.html:18
#: users/templates/users/user_password_update.html:44
@@ -790,10 +789,10 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: assets/templates/assets/asset_detail.html:62
#: assets/templates/assets/asset_list.html:97
#: assets/templates/assets/domain_gateway_list.html:68
-#: assets/templates/assets/user_asset_list.html:49
+#: assets/templates/assets/user_asset_list.html:76
#: audits/templates/audits/login_log_list.html:60
#: perms/templates/perms/asset_permission_asset.html:58 settings/forms.py:144
-#: users/templates/users/_granted_assets.html:26
+#: users/templates/users/_granted_assets.html:31
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:54
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:73
msgid "IP"
@@ -807,10 +806,10 @@ msgstr "IP"
#: assets/templates/assets/_asset_user_list.html:19
#: assets/templates/assets/asset_detail.html:58
#: assets/templates/assets/asset_list.html:96
-#: assets/templates/assets/user_asset_list.html:48
+#: assets/templates/assets/user_asset_list.html:75
#: perms/templates/perms/asset_permission_asset.html:57
#: perms/templates/perms/asset_permission_list.html:73 settings/forms.py:143
-#: users/templates/users/_granted_assets.html:25
+#: users/templates/users/_granted_assets.html:30
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:53
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:72
msgid "Hostname"
@@ -822,18 +821,19 @@ msgstr "主机名"
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:53
#: terminal/templates/terminal/session_list.html:31
+#: terminal/templates/terminal/session_list.html:75
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:142 assets/serializers/asset.py:68
#: assets/templates/assets/asset_create.html:24
-#: assets/templates/assets/user_asset_list.html:50
+#: assets/templates/assets/user_asset_list.html:77
#: perms/serializers/user_permission.py:48
msgid "Protocols"
msgstr "协议组"
#: assets/models/asset.py:143 assets/templates/assets/asset_detail.html:102
-#: assets/templates/assets/user_asset_list.html:51
+#: assets/templates/assets/user_asset_list.html:78
msgid "Platform"
msgstr "系统平台"
@@ -940,7 +940,8 @@ msgid "SSH public key"
msgstr "ssh公钥"
#: assets/models/base.py:35 assets/models/gathered_user.py:21
-#: assets/templates/assets/cmd_filter_detail.html:73 ops/models/adhoc.py:46
+#: assets/templates/assets/cmd_filter_detail.html:73 common/mixins/models.py:52
+#: ops/models/adhoc.py:46
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:76
msgid "Date updated"
@@ -1107,9 +1108,9 @@ msgstr "默认资产组"
#: terminal/models.py:156 terminal/templates/terminal/command_list.html:29
#: terminal/templates/terminal/command_list.html:65
#: terminal/templates/terminal/session_list.html:27
-#: terminal/templates/terminal/session_list.html:71 users/forms.py:312
+#: terminal/templates/terminal/session_list.html:71 users/forms.py:319
#: users/models/user.py:127 users/models/user.py:143 users/models/user.py:500
-#: users/serializers/v1.py:130 users/templates/users/user_group_detail.html:78
+#: users/serializers/v1.py:132 users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:36 users/views/user.py:250
#: xpack/plugins/orgs/forms.py:28
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
@@ -1117,7 +1118,7 @@ msgstr "默认资产组"
msgid "User"
msgstr "用户"
-#: assets/models/label.py:19 assets/models/node.py:400
+#: assets/models/label.py:19 assets/models/node.py:412
#: assets/templates/assets/label_list.html:15 settings/models.py:30
msgid "Value"
msgstr "值"
@@ -1138,7 +1139,11 @@ msgstr "未分组"
msgid "empty"
msgstr "空"
-#: assets/models/node.py:399
+#: assets/models/node.py:328
+msgid "favorite"
+msgstr "收藏夹"
+
+#: assets/models/node.py:411
msgid "Key"
msgstr "键"
@@ -1192,7 +1197,7 @@ msgstr "Shell"
msgid "Login mode"
msgstr "登录模式"
-#: assets/models/user.py:162 assets/templates/assets/user_asset_list.html:52
+#: assets/models/user.py:162 assets/templates/assets/user_asset_list.html:79
#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:52
#: audits/templates/audits/ftp_log_list.html:75
#: perms/forms/asset_permission.py:86 perms/forms/remote_app_permission.py:43
@@ -1208,7 +1213,7 @@ msgstr "登录模式"
#: terminal/templates/terminal/command_list.html:67
#: terminal/templates/terminal/session_list.html:29
#: terminal/templates/terminal/session_list.html:73
-#: users/templates/users/_granted_assets.html:27
+#: users/templates/users/_granted_assets.html:32
#: xpack/plugins/orgs/templates/orgs/org_list.html:20
msgid "System user"
msgstr "系统用户"
@@ -1258,7 +1263,7 @@ msgstr "组织名称"
msgid "Backend"
msgstr "后端"
-#: assets/serializers/asset_user.py:67 users/forms.py:263
+#: assets/serializers/asset_user.py:67 users/forms.py:262
#: users/models/user.py:403 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:49
#: users/templates/users/user_profile.html:69
@@ -1872,6 +1877,7 @@ msgstr "删除选择资产"
#: users/templates/users/user_group_list.html:118
#: users/templates/users/user_list.html:254
#: xpack/plugins/interface/templates/interface/interface.html:101
+#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
msgid "Cancel"
msgstr "取消"
@@ -2257,7 +2263,7 @@ msgstr "Agent"
#: audits/models.py:85 audits/templates/audits/login_log_list.html:62
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
-#: users/forms.py:175 users/models/user.py:395
+#: users/forms.py:174 users/models/user.py:395
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
@@ -2480,7 +2486,7 @@ msgid ""
"after {} minutes)"
msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)"
-#: authentication/forms.py:66 users/forms.py:22
+#: authentication/forms.py:66 users/forms.py:21
msgid "MFA code"
msgstr "MFA 验证码"
@@ -2505,7 +2511,7 @@ msgid "Secret"
msgstr "密文"
#: authentication/templates/authentication/_access_key_modal.html:48
-#: users/templates/users/_granted_assets.html:75
+#: users/templates/users/_granted_assets.html:80
msgid "Show"
msgstr "显示"
@@ -2713,11 +2719,11 @@ msgstr ""
msgid "Encrypt field using Secret Key"
msgstr ""
-#: common/mixins/models.py:31
+#: common/mixins/models.py:34
msgid "is discard"
msgstr ""
-#: common/mixins/models.py:32
+#: common/mixins/models.py:35
msgid "discard time"
msgstr ""
@@ -3105,7 +3111,7 @@ msgstr "命令执行列表"
msgid "Command execution"
msgstr "命令执行"
-#: orgs/mixins/models.py:61 orgs/mixins/serializers.py:26 orgs/models.py:31
+#: orgs/mixins/models.py:58 orgs/mixins/serializers.py:26 orgs/models.py:31
msgid "Organization"
msgstr "组织"
@@ -3122,7 +3128,7 @@ msgstr "空"
#: perms/templates/perms/asset_permission_list.html:71
#: perms/templates/perms/asset_permission_list.html:118
#: perms/templates/perms/remote_app_permission_list.html:16
-#: templates/_nav.html:21 users/forms.py:286 users/models/group.py:26
+#: templates/_nav.html:21 users/forms.py:293 users/models/group.py:26
#: users/models/user.py:379 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:218
#: users/templates/users/user_list.html:38
@@ -3969,7 +3975,7 @@ msgid "Commercial support"
msgstr "商业支持"
#: templates/_header_bar.html:70 templates/_nav.html:30
-#: templates/_nav_user.html:32 users/forms.py:154
+#: templates/_nav_user.html:32 users/forms.py:153
#: users/templates/users/_user.html:43
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:40
@@ -4390,7 +4396,7 @@ msgstr "线程数"
msgid "Boot Time"
msgstr "运行时间"
-#: terminal/models.py:162 terminal/templates/terminal/session_list.html:136
+#: terminal/models.py:162 terminal/templates/terminal/session_list.html:137
msgid "Replay"
msgstr "回放"
@@ -4456,15 +4462,15 @@ msgstr "终断所选"
msgid "Confirm finished"
msgstr "确认已完成"
-#: terminal/templates/terminal/session_list.html:91
+#: terminal/templates/terminal/session_list.html:92
msgid "Terminate task send, waiting ..."
msgstr "终断任务已发送,请等待"
-#: terminal/templates/terminal/session_list.html:142
+#: terminal/templates/terminal/session_list.html:143
msgid "Terminate"
msgstr "终断"
-#: terminal/templates/terminal/session_list.html:173
+#: terminal/templates/terminal/session_list.html:174
msgid "Finish session success"
msgstr "标记会话完成成功"
@@ -4534,7 +4540,7 @@ msgstr "你可以使用ssh客户端工具连接终端"
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
-#: users/forms.py:33 users/models/user.py:383
+#: users/forms.py:32 users/models/user.py:383
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:37
@@ -4542,44 +4548,44 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
msgid "Role"
msgstr "角色"
-#: users/forms.py:36 users/forms.py:233
+#: users/forms.py:35 users/forms.py:232
#: users/templates/users/user_update.html:30
msgid "ssh public key"
msgstr "ssh公钥"
-#: users/forms.py:37 users/forms.py:234
+#: users/forms.py:36 users/forms.py:233
msgid "ssh-rsa AAAA..."
msgstr ""
-#: users/forms.py:38
+#: users/forms.py:37
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
-#: users/forms.py:52 users/templates/users/user_detail.html:226
+#: users/forms.py:51 users/templates/users/user_detail.html:226
msgid "Join user groups"
msgstr "添加到用户组"
-#: users/forms.py:87 users/forms.py:248
+#: users/forms.py:86 users/forms.py:247
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
-#: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:116
+#: users/forms.py:90 users/forms.py:251 users/serializers/v1.py:116
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
-#: users/forms.py:104 users/views/login.py:114 users/views/user.py:287
+#: users/forms.py:103 users/views/login.py:114 users/views/user.py:287
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
-#: users/forms.py:125
+#: users/forms.py:124
msgid "Reset link will be generated and sent to the user"
msgstr "生成重置密码链接,通过邮件发送给用户"
-#: users/forms.py:126
+#: users/forms.py:125
msgid "Set password"
msgstr "设置密码"
-#: users/forms.py:133 xpack/plugins/change_auth_plan/models.py:88
+#: users/forms.py:132 xpack/plugins/change_auth_plan/models.py:88
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
@@ -4587,7 +4593,7 @@ msgstr "设置密码"
msgid "Password strategy"
msgstr "密码策略"
-#: users/forms.py:160
+#: users/forms.py:159
msgid ""
"When enabled, you will enter the MFA binding process the next time you log "
"in. you can also directly bind in \"personal information -> quick "
@@ -4596,11 +4602,11 @@ msgstr ""
"启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修改->更"
"改MFA设置)中直接绑定!"
-#: users/forms.py:170
+#: users/forms.py:169
msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全。"
-#: users/forms.py:180
+#: users/forms.py:179
msgid ""
"In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex "
@@ -4609,41 +4615,41 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)"
-#: users/forms.py:187 users/templates/users/first_login.html:48
+#: users/forms.py:186 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:110
#: users/templates/users/first_login.html:139
msgid "Finish"
msgstr "完成"
-#: users/forms.py:193
+#: users/forms.py:192
msgid "Old password"
msgstr "原来密码"
-#: users/forms.py:198
+#: users/forms.py:197
msgid "New password"
msgstr "新密码"
-#: users/forms.py:203
+#: users/forms.py:202
msgid "Confirm password"
msgstr "确认密码"
-#: users/forms.py:213
+#: users/forms.py:212
msgid "Old password error"
msgstr "原来密码错误"
-#: users/forms.py:221
+#: users/forms.py:220
msgid "Password does not match"
msgstr "密码不一致"
-#: users/forms.py:231
+#: users/forms.py:230
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
-#: users/forms.py:235
+#: users/forms.py:234
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
-#: users/forms.py:269 users/forms.py:274 users/forms.py:316
+#: users/forms.py:268 users/forms.py:273 users/forms.py:323
#: xpack/plugins/orgs/forms.py:18
msgid "Select users"
msgstr "选择用户"
@@ -4736,7 +4742,7 @@ msgstr "角色只能为 {}"
msgid "Password does not match security rules"
msgstr "密码不满足安全规则"
-#: users/serializers/v1.py:147
+#: users/serializers/v1.py:157
msgid "Auditors cannot be join in the user group"
msgstr "审计员不能被加入到用户组"
@@ -5933,7 +5939,7 @@ msgstr "更新同步实例任务"
#: xpack/plugins/gathered_user/views.py:21
#: xpack/plugins/gathered_user/views.py:34
#: xpack/plugins/gathered_user/views.py:49
-#: xpack/plugins/gathered_user/views.py:66
+#: xpack/plugins/gathered_user/views.py:69
msgid "Gathered user"
msgstr "收集用户"
@@ -5968,7 +5974,7 @@ msgstr "创建任务"
msgid "Gathered user list"
msgstr "收集用户列表"
-#: xpack/plugins/gathered_user/views.py:67
+#: xpack/plugins/gathered_user/views.py:70
msgid "Update task"
msgstr "更新任务"
diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py
index 9de7f2c7d..8fb7f30dd 100644
--- a/apps/orgs/mixins/api.py
+++ b/apps/orgs/mixins/api.py
@@ -5,12 +5,12 @@ from rest_framework.viewsets import ModelViewSet
from rest_framework_bulk import BulkModelViewSet
from common.mixins import CommonApiMixin
-from ..utils import set_to_root_org
+from ..utils import set_to_root_org, filter_org_queryset
from ..models import Organization
__all__ = [
'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet',
- 'OrgBulkModelViewSet',
+ 'OrgBulkModelViewSet', 'OrgQuerySetMixin',
]
@@ -22,7 +22,15 @@ class RootOrgViewMixin:
class OrgQuerySetMixin:
def get_queryset(self):
- queryset = super().get_queryset().all()
+ if hasattr(self, 'model'):
+ queryset = self.model.objects.all()
+ else:
+ assert self.queryset is None, (
+ "'%s' should not include a `queryset` attribute"
+ % self.__class__.__name__
+ )
+ queryset = super().get_queryset()
+
if hasattr(self, 'swagger_fake_view'):
return queryset[:1]
if hasattr(self, 'action') and self.action == 'list' and \
diff --git a/apps/orgs/mixins/generics.py b/apps/orgs/mixins/generics.py
new file mode 100644
index 000000000..490ee1b87
--- /dev/null
+++ b/apps/orgs/mixins/generics.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+from rest_framework import generics
+
+from .api import OrgQuerySetMixin
+
+
+class ListAPIView(OrgQuerySetMixin, generics.ListAPIView):
+ pass
+
+
+class RetrieveAPIView(OrgQuerySetMixin, generics.RetrieveAPIView):
+ pass
+
+
+class CreateAPIView(OrgQuerySetMixin, generics.CreateAPIView):
+ pass
+
+
+class DestroyAPIView(OrgQuerySetMixin, generics.DestroyAPIView):
+ pass
+
+
+class ListCreateAPIView(OrgQuerySetMixin, generics.ListCreateAPIView):
+ pass
+
+
+class UpdateAPIView(OrgQuerySetMixin, generics.UpdateAPIView):
+ pass
+
+
+class RetrieveUpdateAPIView(OrgQuerySetMixin, generics.RetrieveUpdateAPIView):
+ pass
+
+
+class RetrieveDestroyAPIView(OrgQuerySetMixin, generics.RetrieveDestroyAPIView):
+ pass
+
+
+class RetrieveUpdateDestroyAPIView(OrgQuerySetMixin, generics.RetrieveUpdateDestroyAPIView):
+ pass
diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py
index 672b975dc..4ffa24c2a 100644
--- a/apps/orgs/mixins/models.py
+++ b/apps/orgs/mixins/models.py
@@ -9,6 +9,7 @@ from django.core.exceptions import ValidationError
from common.utils import get_logger
from ..utils import (
set_current_org, get_current_org, current_org,
+ get_org_filters
)
from ..models import Organization
@@ -19,42 +20,38 @@ __all__ = [
]
+class OrgQuerySet(models.QuerySet):
+ pass
+
+
class OrgManager(models.Manager):
-
def get_queryset(self):
- queryset = super(OrgManager, self).get_queryset()
- kwargs = {}
-
- _current_org = get_current_org()
- if _current_org is None:
- kwargs['id'] = None
- elif _current_org.is_real():
- kwargs['org_id'] = _current_org.id
- elif _current_org.is_default():
- queryset = queryset.filter(org_id="")
- #
- # lines = traceback.format_stack()
- # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
- # for line in lines[-10:-1]:
- # print(line)
- # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
-
- queryset = queryset.filter(**kwargs)
+ queryset = super().get_queryset()
+ kwargs = get_org_filters()
+ if kwargs:
+ return queryset.filter(**kwargs)
return queryset
- def all(self):
- if not current_org:
- msg = 'You can `objects.set_current_org(org).all()` then run it'
- return self
- else:
- return super(OrgManager, self).all()
-
def set_current_org(self, org):
if isinstance(org, str):
org = Organization.get_instance(org)
set_current_org(org)
return self
+ def all(self):
+ # print("Call all: {}".format(current_org))
+ #
+ # lines = traceback.format_stack()
+ # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
+ # for line in lines[-10:-1]:
+ # print(line)
+ # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
+ if not current_org:
+ msg = 'You can `objects.set_current_org(org).all()` then run it'
+ return self
+ else:
+ return super().all()
+
class OrgModelMixin(models.Model):
org_id = models.CharField(max_length=36, blank=True, default='',
@@ -65,9 +62,12 @@ class OrgModelMixin(models.Model):
def save(self, *args, **kwargs):
org = get_current_org()
- if org is not None and (org.is_real() or org.is_system()):
+ if org is None:
+ return super().save(*args, **kwargs)
+
+ if org.is_real() or org.is_system():
self.org_id = org.id
- elif org is not None and org.is_default():
+ elif org.is_default():
self.org_id = ''
return super().save(*args, **kwargs)
diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py
index 8fca26e35..2a7cfca6b 100644
--- a/apps/orgs/utils.py
+++ b/apps/orgs/utils.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
#
+import traceback
from werkzeug.local import LocalProxy
from contextlib import contextmanager
@@ -82,4 +83,31 @@ def tmp_to_org(org):
set_current_org(ori_org)
+def get_org_filters():
+ kwargs = {}
+
+ _current_org = get_current_org()
+ if _current_org is None:
+ return kwargs
+
+ if _current_org.is_real():
+ kwargs['org_id'] = _current_org.id
+ elif _current_org.is_default():
+ kwargs["org_id"] = ''
+ return kwargs
+
+
+def filter_org_queryset(queryset):
+ kwargs = get_org_filters()
+
+ #
+ # lines = traceback.format_stack()
+ # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
+ # for line in lines[-10:-1]:
+ # print(line)
+ # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
+ queryset = queryset.filter(**kwargs)
+ return queryset
+
+
current_org = LocalProxy(get_current_org)
diff --git a/apps/perms/api/asset_permission.py b/apps/perms/api/asset_permission.py
index 41c64c7f1..68f01d2e3 100644
--- a/apps/perms/api/asset_permission.py
+++ b/apps/perms/api/asset_permission.py
@@ -1,14 +1,13 @@
# -*- coding: utf-8 -*-
#
-from django.utils import timezone
from django.db.models import Q
from rest_framework.views import Response
from django.shortcuts import get_object_or_404
-from rest_framework.generics import RetrieveUpdateAPIView, ListAPIView
-from rest_framework import viewsets
from common.permissions import IsOrgAdmin
+from orgs.mixins.api import OrgModelViewSet
+from orgs.mixins import generics
from common.utils import get_object_or_none
from ..models import AssetPermission
from ..hands import (
@@ -24,15 +23,21 @@ __all__ = [
]
-class AssetPermissionViewSet(viewsets.ModelViewSet):
+class AssetPermissionViewSet(OrgModelViewSet):
"""
资产授权列表的增删改查api
"""
- queryset = AssetPermission.objects.all()
+ model = AssetPermission
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
filter_fields = ['name']
permission_classes = (IsOrgAdmin,)
+ def get_queryset(self):
+ queryset = super().get_queryset().prefetch_related(
+ "nodes", "assets", "users", "user_groups", "system_users"
+ )
+ return queryset
+
def get_serializer_class(self):
if self.action in ("list", 'retrieve') and \
self.request.query_params.get("display"):
@@ -160,19 +165,14 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
queryset = queryset.distinct()
return queryset
- def get_queryset(self):
- return self.queryset.all().prefetch_related(
- "nodes", "assets", "users", "user_groups", "system_users"
- )
-
-class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
+class AssetPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
"""
将用户从授权中移除,Detail页面会调用
"""
+ model = AssetPermission
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
- queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
@@ -187,10 +187,10 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
return Response({"error": serializer.errors})
-class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
+class AssetPermissionAddUserApi(generics.RetrieveUpdateAPIView):
+ model = AssetPermission
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
- queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
@@ -205,13 +205,13 @@ class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
return Response({"error": serializer.errors})
-class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
+class AssetPermissionRemoveAssetApi(generics.RetrieveUpdateAPIView):
"""
将用户从授权中移除,Detail页面会调用
"""
+ model = AssetPermission
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
- queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
@@ -226,10 +226,10 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
return Response({"error": serializer.errors})
-class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
+class AssetPermissionAddAssetApi(generics.RetrieveUpdateAPIView):
+ model = AssetPermission
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
- queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
@@ -244,7 +244,7 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
return Response({"error": serializer.errors})
-class AssetPermissionAssetsApi(ListAPIView):
+class AssetPermissionAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionAssetsSerializer
filter_fields = ("hostname", "ip")
diff --git a/apps/perms/api/mixin.py b/apps/perms/api/mixin.py
index 85db529a0..86e1a8c0a 100644
--- a/apps/perms/api/mixin.py
+++ b/apps/perms/api/mixin.py
@@ -4,7 +4,7 @@
from rest_framework.generics import get_object_or_404
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.utils import get_logger
-from orgs.utils import set_to_root_org
+from orgs.utils import set_to_root_org, get_current_org, set_current_org, tmp_to_root_org
from ..hands import User, UserGroup
@@ -17,15 +17,24 @@ __all__ = [
class UserPermissionMixin:
permission_classes = (IsOrgAdminOrAppUser,)
+ current_org = None
obj = None
def initial(self, *args, **kwargs):
super().initial(*args, *kwargs)
+ self.current_org = get_current_org()
+ set_to_root_org()
self.obj = self.get_obj()
- def get(self, request, *args, **kwargs):
- set_to_root_org()
- return super().get(request, *args, **kwargs)
+ # def dispatch(self, request, *args, **kwargs):
+ # """不能这么做,校验权限时拿不到组织了"""
+ # with tmp_to_root_org():
+ # return super().dispatch(request, *args, **kwargs)
+
+ # def get(self, request, *args, **kwargs):
+ # """有的api重写了get方法"""
+ # with tmp_to_root_org():
+ # return super().get(request, *args, **kwargs)
def get_obj(self):
user_id = self.kwargs.get('pk', '')
@@ -40,6 +49,13 @@ class UserPermissionMixin:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
+ def finalize_response(self, request, response, *args, **kwargs):
+ response = super().finalize_response(request, response, *args, **kwargs)
+ org = getattr(self, 'current_org', None)
+ if org:
+ set_current_org(org)
+ return response
+
class UserGroupPermissionMixin:
obj = None
diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py
index 12b1ebfb6..6ced7f0ae 100644
--- a/apps/perms/api/remote_app_permission.py
+++ b/apps/perms/api/remote_app_permission.py
@@ -1,10 +1,11 @@
# coding: utf-8
#
-from rest_framework import viewsets, generics
from rest_framework.views import Response
from common.permissions import IsOrgAdmin
+from orgs.mixins.api import OrgModelViewSet
+from orgs.mixins import generics
from ..models import RemoteAppPermission
from ..serializers import (
RemoteAppPermissionSerializer,
@@ -20,18 +21,18 @@ __all__ = [
]
-class RemoteAppPermissionViewSet(viewsets.ModelViewSet):
+class RemoteAppPermissionViewSet(OrgModelViewSet):
+ model = RemoteAppPermission
filter_fields = ('name', )
search_fields = filter_fields
- queryset = RemoteAppPermission.objects.all()
serializer_class = RemoteAppPermissionSerializer
permission_classes = (IsOrgAdmin,)
class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView):
+ model = RemoteAppPermission
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateUserSerializer
- queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
@@ -46,9 +47,9 @@ class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView):
class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
+ model = RemoteAppPermission
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateUserSerializer
- queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
@@ -63,9 +64,9 @@ class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView):
+ model = RemoteAppPermission
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer
- queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
@@ -80,9 +81,9 @@ class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView):
class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
+ model = RemoteAppPermission
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer
- queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py
index 069f08937..4a004e0d1 100644
--- a/apps/perms/api/user_permission/mixin.py
+++ b/apps/perms/api/user_permission/mixin.py
@@ -34,7 +34,7 @@ class UserNodeTreeMixin:
for node in nodes:
assets_amount = self.tree.valid_assets_amount(node.key)
- if assets_amount == 0 and node.key != Node.empty_key:
+ if assets_amount == 0 and not node.key.startswith('-'):
continue
node.assets_amount = assets_amount
data = ParserNode.parse_node_to_tree_node(node)
diff --git a/apps/perms/api/user_remote_app_permission.py b/apps/perms/api/user_remote_app_permission.py
index 030e760b5..868c92015 100644
--- a/apps/perms/api/user_remote_app_permission.py
+++ b/apps/perms/api/user_remote_app_permission.py
@@ -3,12 +3,10 @@
import uuid
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response
-from rest_framework.generics import (
- ListAPIView, get_object_or_404,
-)
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer
+from orgs.mixins import generics
from ..utils import (
RemoteAppPermissionUtil, construct_remote_apps_tree_root,
parse_remote_app_to_tree_node,
@@ -25,7 +23,7 @@ __all__ = [
]
-class UserGrantedRemoteAppsApi(ListAPIView):
+class UserGrantedRemoteAppsApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = RemoteAppSerializer
filter_fields = ['name', 'id']
@@ -68,7 +66,7 @@ class UserGrantedRemoteAppsAsTreeApi(UserGrantedRemoteAppsApi):
return super().get_serializer(data, many=True)
-class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, ListAPIView):
+class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.RemoteAppSystemUserSerializer
only_fields = serializers.RemoteAppSystemUserSerializer.Meta.only_fields
@@ -110,7 +108,7 @@ class ValidateUserRemoteAppPermissionApi(APIView):
# RemoteApp permission
-class UserGroupGrantedRemoteAppsApi(ListAPIView):
+class UserGroupGrantedRemoteAppsApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser, )
serializer_class = RemoteAppSerializer
diff --git a/apps/perms/hands.py b/apps/perms/hands.py
index aef0f4875..ab9e3f494 100644
--- a/apps/perms/hands.py
+++ b/apps/perms/hands.py
@@ -2,10 +2,15 @@
#
from users.models import User, UserGroup
-from assets.models import Asset, SystemUser, Node, Label
+from assets.models import Asset, SystemUser, Node, Label, FavoriteAsset
from assets.serializers import NodeSerializer
from applications.serializers import RemoteAppSerializer
from applications.models import RemoteApp
-
+__all__ = [
+ 'User', 'UserGroup',
+ 'Asset', 'SystemUser', 'Node', 'Label', 'FavoriteAsset',
+ 'NodeSerializer', 'RemoteAppSerializer',
+ 'RemoteApp'
+]
diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py
index bbc64bfa9..6754511d0 100644
--- a/apps/perms/utils/asset_permission.py
+++ b/apps/perms/utils/asset_permission.py
@@ -13,7 +13,7 @@ from common.utils import get_logger, timeit, lazyproperty
from common.tree import TreeNode
from assets.utils import TreeService
from ..models import AssetPermission
-from ..hands import Node, Asset, SystemUser
+from ..hands import Node, Asset, SystemUser, User, FavoriteAsset
logger = get_logger(__file__)
@@ -293,6 +293,20 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
parent=user_tree.root,
)
+ def add_favorite_node_if_need(self, user_tree):
+ if not isinstance(self.object, User):
+ return
+ node_key = Node.favorite_key
+ node_value = Node.favorite_value
+ user_tree.create_node(
+ identifier=node_key, tag=node_value,
+ parent=user_tree.root,
+ )
+ assets_id = FavoriteAsset.get_user_favorite_assets_id(self.object)
+ all_valid_assets = user_tree.all_valid_assets(user_tree.root)
+ valid_assets_id = set(assets_id) & all_valid_assets
+ user_tree.set_assets(node_key, valid_assets_id)
+
def set_user_tree_to_local(self, user_tree):
self._user_tree = user_tree
self._user_tree_filter_id = self._filter_id
@@ -323,6 +337,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self.add_single_assets_node_to_user_tree(user_tree)
self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree)
+ self.add_favorite_node_if_need(user_tree)
self.set_user_tree_to_cache_if_need(user_tree)
self.set_user_tree_to_local(user_tree)
return user_tree
diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js
index d028743be..4f55cfb4f 100644
--- a/apps/static/js/jumpserver.js
+++ b/apps/static/js/jumpserver.js
@@ -267,7 +267,7 @@ function requestApi(props) {
$.ajax({
url: props.url,
type: props.method || "PATCH",
- data: props.body,
+ data: props.body || props.data,
contentType: props.content_type || "application/json; charset=utf-8",
dataType: props.data_type || "json"
}).done(function (data, textStatue, jqXHR) {
@@ -579,6 +579,9 @@ jumpserver.initServerSideDataTable = function (options) {
ajax: {
url: options.ajax_url,
error: function (jqXHR, textStatus, errorThrown) {
+ if (jqXHR.responseText && jqXHR.responseText.indexOf("%(value)s") !== -1 ) {
+ return
+ }
var msg = gettext("Unknown error occur");
if (jqXHR.responseJSON) {
if (jqXHR.responseJSON.error) {
@@ -953,8 +956,13 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
function rootNodeAddDom(ztree, callback) {
var refreshIcon = "";
var rootNode = ztree.getNodes()[0];
- var $rootNodeRef = $("#" + rootNode.tId + "_a");
- $rootNodeRef.after(refreshIcon);
+ if (rootNode) {
+ var $rootNodeRef = $("#" + rootNode.tId + "_a");
+ $rootNodeRef.after(refreshIcon);
+ } else {
+ $rootNodeRef = $('#' + ztree.setting.treeId);
+ $rootNodeRef.html(refreshIcon);
+ }
var refreshIconRef = $('#tree-refresh');
refreshIconRef.bind('click', function () {
ztree.destroy();
diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py
index 7071ddd33..264502959 100644
--- a/apps/terminal/api/session.py
+++ b/apps/terminal/api/session.py
@@ -24,10 +24,10 @@ logger = get_logger(__name__)
class SessionViewSet(OrgBulkModelViewSet):
- queryset = Session.objects.all()
+ model = Session
serializer_class = serializers.SessionSerializer
permission_classes = (IsOrgAdminOrAppUser, )
- filter_fields = [
+ filterset_fields = [
"user", "asset", "system_user", "remote_addr",
"protocol", "terminal", "is_finished",
]
diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html
index d23d9be02..1393bd241 100644
--- a/apps/terminal/templates/terminal/session_list.html
+++ b/apps/terminal/templates/terminal/session_list.html
@@ -72,6 +72,7 @@
{% trans 'Asset' %}
{% trans 'System user' %}
{% trans 'Remote addr' %}
+ {% trans 'Protocol' %}
{# {% trans 'Protocol' %}#}
{% endblock %}
diff --git a/apps/users/api/group.py b/apps/users/api/group.py
index e5c68004b..eb00ea220 100644
--- a/apps/users/api/group.py
+++ b/apps/users/api/group.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
#
-from rest_framework import generics
-
from ..serializers import (
UserGroupSerializer,
UserGroupListSerializer,
@@ -10,6 +8,7 @@ from ..serializers import (
)
from ..models import UserGroup
from orgs.mixins.api import OrgBulkModelViewSet
+from orgs.mixins import generics
from common.permissions import IsOrgAdmin
@@ -17,9 +16,9 @@ __all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi']
class UserGroupViewSet(OrgBulkModelViewSet):
+ model = UserGroup
filter_fields = ("name",)
search_fields = filter_fields
- queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer
permission_classes = (IsOrgAdmin,)
@@ -31,6 +30,6 @@ class UserGroupViewSet(OrgBulkModelViewSet):
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
- queryset = UserGroup.objects.all()
+ model = UserGroup
serializer_class = UserGroupUpdateMemberSerializer
permission_classes = (IsOrgAdmin,)
diff --git a/apps/users/api/user.py b/apps/users/api/user.py
index 77406c896..2a033915c 100644
--- a/apps/users/api/user.py
+++ b/apps/users/api/user.py
@@ -17,7 +17,7 @@ from common.permissions import (
from common.mixins import CommonApiMixin
from common.utils import get_logger
from orgs.utils import current_org
-from .. import serializers
+from .. import serializers, utils
from ..models import User
from ..signals import post_user_create
@@ -30,13 +30,21 @@ __all__ = [
]
-class UserViewSet(CommonApiMixin, BulkModelViewSet):
+class UserQuerysetMixin:
+ def get_queryset(self):
+ queryset = utils.get_current_org_members()
+ return queryset
+
+
+class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
filter_fields = ('username', 'email', 'name', 'id')
search_fields = filter_fields
- queryset = User.objects.exclude(role=User.ROLE_APP)
serializer_class = serializers.UserSerializer
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
+ def get_queryset(self):
+ return super().get_queryset().prefetch_related('groups')
+
def send_created_signal(self, users):
if not isinstance(users, list):
users = [users]
@@ -51,11 +59,6 @@ class UserViewSet(CommonApiMixin, BulkModelViewSet):
current_org.users.add(*users)
self.send_created_signal(users)
- def get_queryset(self):
- queryset = current_org.get_org_members()\
- .prefetch_related('groups')
- return queryset
-
def get_permissions(self):
if self.action in ["retrieve", "list"]:
self.permission_classes = (IsOrgAdminOrAppUser,)
@@ -79,9 +82,8 @@ class UserViewSet(CommonApiMixin, BulkModelViewSet):
return super().perform_bulk_update(serializer)
-class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
+class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,)
- queryset = User.objects.all()
serializer_class = serializers.ChangeUserPasswordSerializer
def perform_update(self, serializer):
@@ -90,13 +92,12 @@ class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
user.save()
-class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
- queryset = User.objects.all()
+class UserUpdateGroupApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
serializer_class = serializers.UserUpdateGroupSerializer
permission_classes = (IsOrgAdmin,)
-class UserResetPasswordApi(generics.UpdateAPIView):
+class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = serializers.UserSerializer
permission_classes = (IsAuthenticated,)
@@ -111,8 +112,7 @@ class UserResetPasswordApi(generics.UpdateAPIView):
send_reset_password_mail(user)
-class UserResetPKApi(generics.UpdateAPIView):
- queryset = User.objects.all()
+class UserResetPKApi(UserQuerysetMixin, generics.UpdateAPIView):
serializer_class = serializers.UserSerializer
permission_classes = (IsAuthenticated,)
@@ -125,8 +125,7 @@ class UserResetPKApi(generics.UpdateAPIView):
# 废弃
-class UserUpdatePKApi(generics.UpdateAPIView):
- queryset = User.objects.all()
+class UserUpdatePKApi(UserQuerysetMixin, generics.UpdateAPIView):
serializer_class = serializers.UserPKUpdateSerializer
permission_classes = (IsCurrentUserOrReadOnly,)
@@ -136,8 +135,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user.save()
-class UserUnblockPKApi(generics.UpdateAPIView):
- queryset = User.objects.all()
+class UserUnblockPKApi(UserQuerysetMixin, generics.UpdateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
@@ -165,8 +163,7 @@ class UserProfileApi(generics.RetrieveAPIView):
return super().retrieve(request, *args, **kwargs)
-class UserResetOTPApi(generics.RetrieveAPIView):
- queryset = User.objects.all()
+class UserResetOTPApi(UserQuerysetMixin, generics.RetrieveAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.ResetOTPSerializer
diff --git a/apps/users/forms.py b/apps/users/forms.py
index 3a8228b49..98d7c9e09 100644
--- a/apps/users/forms.py
+++ b/apps/users/forms.py
@@ -5,9 +5,8 @@ from django.utils.translation import gettext_lazy as _
from common.utils import validate_ssh_public_key
from orgs.mixins.forms import OrgModelForm
-from orgs.utils import current_org
from .models import User, UserGroup
-from .utils import check_password_rules
+from .utils import check_password_rules, get_current_org_members
class UserCheckPasswordForm(forms.Form):
@@ -267,15 +266,23 @@ class UserBulkUpdateForm(OrgModelForm):
users = forms.ModelMultipleChoiceField(
required=True,
label=_('Select users'),
- queryset=User.objects.all(),
+ queryset=User.objects.none(),
widget=forms.SelectMultiple(
attrs={
- 'class': 'select2',
+ 'class': 'users-select2',
'data-placeholder': _('Select users')
}
)
)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.set_fields_queryset()
+
+ def set_fields_queryset(self):
+ users_field = self.fields['users']
+ users_field.queryset = get_current_org_members()
+
class Meta:
model = User
fields = ['users', 'groups', 'date_expired']
@@ -320,25 +327,19 @@ class UserGroupForm(OrgModelForm):
)
def __init__(self, **kwargs):
- instance = kwargs.get('instance')
- if instance:
- initial = kwargs.get('initial', {})
- initial.update({'users': instance.users.all()})
- kwargs['initial'] = initial
super().__init__(**kwargs)
- if 'initial' not in kwargs:
- return
+ self.set_fields_queryset()
+
+ def set_fields_queryset(self):
users_field = self.fields.get('users')
- if instance:
- users_field.queryset = instance.users.all()
+ if self.instance:
+ users_field.initial = self.instance.users.all()
+ users_field.queryset = self.instance.users.all()
else:
users_field.queryset = User.objects.none()
def save(self, commit=True):
- group = super().save(commit=commit)
- users = self.cleaned_data['users']
- group.users.set(users)
- return group
+ raise Exception("Save by restful api")
class Meta:
model = UserGroup
diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py
index 59890afea..847afe885 100644
--- a/apps/users/serializers/v1.py
+++ b/apps/users/serializers/v1.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
#
-import copy
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
@@ -12,6 +11,7 @@ from common.serializers import AdaptedBulkListSerializer
from common.permissions import CanUpdateDeleteUser
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import User, UserGroup
+from .. import utils
__all__ = [
@@ -118,7 +118,9 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
class UserUpdateGroupSerializer(serializers.ModelSerializer):
- groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
+ groups = serializers.PrimaryKeyRelatedField(
+ many=True, queryset=UserGroup.objects
+ )
class Meta:
model = User
@@ -127,7 +129,7 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer):
class UserGroupSerializer(BulkOrgResourceModelSerializer):
users = serializers.PrimaryKeyRelatedField(
- required=False, many=True, queryset=User.objects.all(), label=_('User')
+ required=False, many=True, queryset=User.objects, label=_('User')
)
class Meta:
@@ -141,6 +143,14 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
'created_by': {'label': _('Created by'), 'read_only': True}
}
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.set_fields_queryset()
+
+ def set_fields_queryset(self):
+ users_field = self.fields['users']
+ users_field.child_relation.queryset = utils.get_current_org_members()
+
def validate_users(self, users):
for user in users:
if user.is_super_auditor:
@@ -154,12 +164,20 @@ class UserGroupListSerializer(UserGroupSerializer):
class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
- users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all())
+ users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects)
class Meta:
model = UserGroup
fields = ['id', 'users']
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.set_fields_queryset()
+
+ def set_fields_queryset(self):
+ users_field = self.fields['users']
+ users_field.child_relation.queryset = utils.get_current_org_members()
+
class ChangeUserPasswordSerializer(serializers.ModelSerializer):
diff --git a/apps/users/templates/users/_granted_assets.html b/apps/users/templates/users/_granted_assets.html
index 8e6401a71..ce6bf3cd4 100644
--- a/apps/users/templates/users/_granted_assets.html
+++ b/apps/users/templates/users/_granted_assets.html
@@ -1,5 +1,5 @@
{% load i18n %}
-