mirror of https://github.com/jumpserver/jumpserver
refactor(orgs): 重构组织与用户关系接口
parent
f0d564180c
commit
c3c5801d2e
|
@ -1,7 +1,6 @@
|
||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from rest_framework_nested import routers
|
from rest_framework_nested import routers
|
||||||
# from rest_framework.routers import DefaultRouter
|
|
||||||
from rest_framework_bulk.routes import BulkRouter
|
from rest_framework_bulk.routes import BulkRouter
|
||||||
|
|
||||||
from common import api as capi
|
from common import api as capi
|
||||||
|
|
|
@ -12,11 +12,12 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db.models import *
|
from django.db.models import *
|
||||||
|
from django.db.models.functions import Concat
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class Choice(str):
|
class Choice(str):
|
||||||
def __new__(cls, value, label):
|
def __new__(cls, value, label=''): # `deepcopy` 的时候不会传 `label`
|
||||||
self = super().__new__(cls, value)
|
self = super().__new__(cls, value)
|
||||||
self.label = label
|
self.label = label
|
||||||
return self
|
return self
|
||||||
|
@ -77,3 +78,7 @@ class JMSModel(JMSBaseModel):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
def concated_display(name1, name2):
|
||||||
|
return Concat(F(name1), Value('('), F(name2), Value(')'))
|
||||||
|
|
|
@ -2,7 +2,8 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
|
||||||
from ..mixins.api import (
|
from ..mixins.api import (
|
||||||
SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin
|
SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin,
|
||||||
|
RelationMixin, AllowBulkDestoryMixin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,5 +27,16 @@ class JMSBulkModelViewSet(SerializerMixin2,
|
||||||
QuerySetMixin,
|
QuerySetMixin,
|
||||||
ExtraFilterFieldsMixin,
|
ExtraFilterFieldsMixin,
|
||||||
PaginatedResponseMixin,
|
PaginatedResponseMixin,
|
||||||
|
AllowBulkDestoryMixin,
|
||||||
|
BulkModelViewSet):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JMSBulkRelationModelViewSet(SerializerMixin2,
|
||||||
|
QuerySetMixin,
|
||||||
|
ExtraFilterFieldsMixin,
|
||||||
|
PaginatedResponseMixin,
|
||||||
|
RelationMixin,
|
||||||
|
AllowBulkDestoryMixin,
|
||||||
BulkModelViewSet):
|
BulkModelViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -11,6 +11,8 @@ from django.core.cache import cache
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework_bulk.drf3.mixins import BulkDestroyModelMixin
|
||||||
|
|
||||||
from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
|
from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
|
||||||
from ..utils import lazyproperty
|
from ..utils import lazyproperty
|
||||||
|
@ -223,10 +225,11 @@ class RelationMixin:
|
||||||
self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through
|
self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
# 注意,此处拦截了 `get_queryset` 没有 `super`
|
||||||
queryset = self.through.objects.all()
|
queryset = self.through.objects.all()
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def send_post_add_signal(self, instances):
|
def send_m2m_changed_signal(self, instances, action):
|
||||||
if not isinstance(instances, list):
|
if not isinstance(instances, list):
|
||||||
instances = [instances]
|
instances = [instances]
|
||||||
|
|
||||||
|
@ -239,13 +242,17 @@ class RelationMixin:
|
||||||
|
|
||||||
for from_obj, to_ids in from_to_mapper.items():
|
for from_obj, to_ids in from_to_mapper.items():
|
||||||
m2m_changed.send(
|
m2m_changed.send(
|
||||||
sender=self.through, instance=from_obj, action='post_add',
|
sender=self.through, instance=from_obj, action=action,
|
||||||
reverse=False, model=self.to_model, pk_set=to_ids
|
reverse=False, model=self.to_model, pk_set=to_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
instance = serializer.save()
|
instance = serializer.save()
|
||||||
self.send_post_add_signal(instance)
|
self.send_m2m_changed_signal(instance, 'post_add')
|
||||||
|
|
||||||
|
def perform_destroy(self, instance):
|
||||||
|
instance.delete()
|
||||||
|
self.send_m2m_changed_signal(instance, 'post_remove')
|
||||||
|
|
||||||
|
|
||||||
class SerializerMixin2:
|
class SerializerMixin2:
|
||||||
|
@ -275,3 +282,12 @@ class QuerySetMixin:
|
||||||
queryset = serializer_class.setup_eager_loading(queryset)
|
queryset = serializer_class.setup_eager_loading(queryset)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class AllowBulkDestoryMixin:
|
||||||
|
def allow_bulk_destroy(self, qs, filtered):
|
||||||
|
"""
|
||||||
|
我们规定,批量删除的情况必须用 `id` 指定要删除的数据。
|
||||||
|
"""
|
||||||
|
query = str(filtered.query)
|
||||||
|
return '`id` IN (' in query or '`id` =' in query
|
||||||
|
|
|
@ -7,14 +7,18 @@ from rest_framework.views import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
|
||||||
from common.permissions import IsSuperUserOrAppUser
|
from common.permissions import IsSuperUserOrAppUser
|
||||||
|
from common.drf.api import JMSBulkRelationModelViewSet
|
||||||
from .models import Organization, ROLE
|
from .models import Organization, ROLE
|
||||||
from .serializers import OrgSerializer, OrgReadSerializer, \
|
from .serializers import (
|
||||||
OrgAllUserSerializer, OrgRetrieveSerializer
|
OrgSerializer, OrgReadSerializer,
|
||||||
|
OrgRetrieveSerializer, OrgMemberSerializer
|
||||||
|
)
|
||||||
from users.models import User, UserGroup
|
from users.models import User, UserGroup
|
||||||
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
|
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
|
||||||
from perms.models import AssetPermission
|
from perms.models import AssetPermission
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
from .filters import OrgMemberRelationFilterSet
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
@ -61,15 +65,13 @@ class OrgViewSet(BulkModelViewSet):
|
||||||
return Response({'msg': True}, status=status.HTTP_200_OK)
|
return Response({'msg': True}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class OrgAllUserListApi(generics.ListAPIView):
|
class OrgMemberRelationBulkViewSet(JMSBulkRelationModelViewSet):
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
serializer_class = OrgAllUserSerializer
|
m2m_field = Organization.members.field
|
||||||
filter_fields = ("username", "name")
|
serializer_class = OrgMemberSerializer
|
||||||
search_fields = filter_fields
|
filterset_class = OrgMemberRelationFilterSet
|
||||||
|
|
||||||
def get_queryset(self):
|
def perform_bulk_destroy(self, queryset):
|
||||||
pk = self.kwargs.get("pk")
|
objs = list(queryset.all().prefetch_related('user', 'org'))
|
||||||
users = User.objects.filter(
|
queryset.delete()
|
||||||
orgs=pk, m2m_org_members__role=ROLE.USER
|
self.send_m2m_changed_signal(objs, action='post_remove')
|
||||||
).only(*self.serializer_class.Meta.only_fields)
|
|
||||||
return users
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
from django_filters.rest_framework import filterset
|
||||||
|
from django_filters.rest_framework import filters
|
||||||
|
|
||||||
|
from .models import OrganizationMember
|
||||||
|
|
||||||
|
|
||||||
|
class UUIDInFilter(filters.BaseInFilter, filters.UUIDFilter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OrgMemberRelationFilterSet(filterset.FilterSet):
|
||||||
|
id = UUIDInFilter(field_name='id', lookup_expr='in')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OrganizationMember
|
||||||
|
fields = ('org_id', 'user_id', 'role', 'id')
|
|
@ -11,8 +11,7 @@ from ..utils import get_current_org_id_for_serializer
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"OrgResourceSerializerMixin", "BulkOrgResourceSerializerMixin",
|
"OrgResourceSerializerMixin", "BulkOrgResourceSerializerMixin",
|
||||||
"BulkOrgResourceModelSerializer", "OrgMembershipSerializerMixin",
|
"BulkOrgResourceModelSerializer", "OrgResourceModelSerializerMixin",
|
||||||
"OrgResourceModelSerializerMixin",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,9 +52,3 @@ class BulkOrgResourceSerializerMixin(BulkSerializerMixin, OrgResourceSerializerM
|
||||||
|
|
||||||
class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer):
|
class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OrgMembershipSerializerMixin:
|
|
||||||
def run_validation(self, initial_data=None):
|
|
||||||
initial_data['organization'] = str(self.context['org'].id)
|
|
||||||
return super().run_validation(initial_data)
|
|
||||||
|
|
|
@ -149,6 +149,13 @@ class Organization(models.Model):
|
||||||
m2m_org_members__user_id=user.id
|
m2m_org_members__user_id=user.id
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_all_orgs(cls, user):
|
||||||
|
return [
|
||||||
|
*cls.objects.filter(members=user).distinct(),
|
||||||
|
cls.default()
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_admin_orgs(cls, user):
|
def get_user_admin_orgs(cls, user):
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
|
@ -161,7 +168,10 @@ class Organization(models.Model):
|
||||||
def get_user_user_orgs(cls, user):
|
def get_user_user_orgs(cls, user):
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return cls.objects.none()
|
return cls.objects.none()
|
||||||
return cls.get_user_orgs_by_role(user, ROLE.USER)
|
return [
|
||||||
|
*cls.get_user_orgs_by_role(user, ROLE.USER),
|
||||||
|
cls.default()
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_audit_orgs(cls, user):
|
def get_user_audit_orgs(cls, user):
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
from django.db.models import F
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from users.models.user import User
|
from users.models.user import User
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
|
from common.drf.serializers import BulkModelSerializer
|
||||||
|
from common.db.models import concated_display as display
|
||||||
from .models import Organization, OrganizationMember
|
from .models import Organization, OrganizationMember
|
||||||
from .mixins.serializers import OrgMembershipSerializerMixin
|
|
||||||
|
|
||||||
|
|
||||||
class OrgSerializer(ModelSerializer):
|
class OrgSerializer(ModelSerializer):
|
||||||
|
@ -50,30 +51,20 @@ class OrgReadSerializer(OrgSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer):
|
class OrgMemberSerializer(BulkModelSerializer):
|
||||||
|
org_display = serializers.CharField()
|
||||||
|
user_display = serializers.CharField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Organization.members.through
|
model = Organization.members.through
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
fields = ('id', 'org', 'user', 'role', 'org_display', 'user_display')
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer):
|
def setup_eager_loading(cls, queryset):
|
||||||
class Meta:
|
return queryset.annotate(
|
||||||
model = Organization.members.through
|
org_display=F('org__name'),
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
user_display=display('user__name', 'user__username')
|
||||||
fields = '__all__'
|
).distinct()
|
||||||
|
|
||||||
|
|
||||||
class OrgAllUserSerializer(serializers.Serializer):
|
|
||||||
user = serializers.UUIDField(read_only=True, source='id')
|
|
||||||
user_display = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
only_fields = ['id', 'username', 'name']
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_user_display(obj):
|
|
||||||
return str(obj)
|
|
||||||
|
|
||||||
|
|
||||||
class OrgRetrieveSerializer(OrgReadSerializer):
|
class OrgRetrieveSerializer(OrgReadSerializer):
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path, path
|
from django.urls import re_path
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from rest_framework_bulk.routes import BulkRouter
|
||||||
|
|
||||||
from common import api as capi
|
from common import api as capi
|
||||||
from .. import api
|
from .. import api
|
||||||
|
@ -10,15 +11,13 @@ from .. import api
|
||||||
|
|
||||||
app_name = 'orgs'
|
app_name = 'orgs'
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
|
bulk_router = BulkRouter()
|
||||||
|
|
||||||
router.register(r'orgs', api.OrgViewSet, 'org')
|
router.register(r'orgs', api.OrgViewSet, 'org')
|
||||||
|
bulk_router.register(r'org-memeber-relation', api.OrgMemberRelationBulkViewSet, 'org-memeber-relation')
|
||||||
|
|
||||||
old_version_urlpatterns = [
|
old_version_urlpatterns = [
|
||||||
re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api)
|
re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api)
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = router.urls + bulk_router.urls + old_version_urlpatterns
|
||||||
path('<uuid:pk>/users/all/', api.OrgAllUserListApi.as_view(), name='org-all-users'),
|
|
||||||
]
|
|
||||||
|
|
||||||
urlpatterns += router.urls + old_version_urlpatterns
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ __all__ = [
|
||||||
'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
|
'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
|
||||||
'UserProfileSerializer', 'UserOrgSerializer',
|
'UserProfileSerializer', 'UserOrgSerializer',
|
||||||
'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer',
|
'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer',
|
||||||
'UserRetrieveSerializer'
|
'UserRetrieveSerializer', 'MiniUserSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue