refactor(orgs): 重构组织与用户关系接口

pull/4451/head
xinwen 2020-08-03 16:17:46 +08:00 committed by 老广
parent f0d564180c
commit c3c5801d2e
11 changed files with 100 additions and 57 deletions

View File

@ -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

View File

@ -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(')'))

View File

@ -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

View File

@ -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

View File

@ -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

16
apps/orgs/filters.py Normal file
View File

@ -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')

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -18,7 +18,7 @@ __all__ = [
'ChangeUserPasswordSerializer', 'ResetOTPSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
'UserProfileSerializer', 'UserOrgSerializer', 'UserProfileSerializer', 'UserOrgSerializer',
'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer', 'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer',
'UserRetrieveSerializer' 'UserRetrieveSerializer', 'MiniUserSerializer',
] ]